diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 034cae45..d1ac7eef 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,10 +2,10 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-format": { - "version": "4.1.131201", + "csharpier": { + "version": "0.21.0", "commands": [ - "dotnet-format" + "dotnet-csharpier" ] } } diff --git a/.editorconfig b/.editorconfig index 28ede258..1903a97c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -195,8 +195,6 @@ csharp_prefer_static_local_function = true:warning csharp_prefer_simple_using_statement = true:suggestion dotnet_diagnostic.IDE0063.severity = suggestion -csharp_style_namespace_declarations = file_scoped - ########################################## # .NET Formatting Conventions # https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions @@ -251,6 +249,8 @@ csharp_space_between_square_brackets = false csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true +csharp_style_namespace_declarations = file_scoped + ########################################## # .NET Naming Conventions # https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions @@ -260,11 +260,13 @@ csharp_preserve_single_line_blocks = true dotnet_diagnostic.CA1000.severity = suggestion dotnet_diagnostic.CA1001.severity = error dotnet_diagnostic.CA1018.severity = error +dotnet_diagnostic.CA1036.severity = silent dotnet_diagnostic.CA1051.severity = suggestion dotnet_diagnostic.CA1068.severity = error dotnet_diagnostic.CA1069.severity = error dotnet_diagnostic.CA1304.severity = error dotnet_diagnostic.CA1305.severity = suggestion +dotnet_diagnostic.CA1307.severity = suggestion dotnet_diagnostic.CA1309.severity = suggestion dotnet_diagnostic.CA1310.severity = error dotnet_diagnostic.CA1707.severity = suggestion @@ -283,6 +285,7 @@ dotnet_diagnostic.CA1829.severity = suggestion dotnet_diagnostic.CA1834.severity = error dotnet_diagnostic.CA1845.severity = suggestion dotnet_diagnostic.CA1848.severity = suggestion +dotnet_diagnostic.CA1852.severity = suggestion dotnet_diagnostic.CA2016.severity = suggestion dotnet_diagnostic.CA2201.severity = error dotnet_diagnostic.CA2206.severity = error @@ -295,6 +298,7 @@ dotnet_diagnostic.CA2254.severity = suggestion dotnet_diagnostic.CS0169.severity = error dotnet_diagnostic.CS0219.severity = error +dotnet_diagnostic.CS0649.severity = suggestion dotnet_diagnostic.CS1998.severity = error dotnet_diagnostic.CS8602.severity = error dotnet_diagnostic.CS8604.severity = error @@ -305,43 +309,53 @@ dotnet_diagnostic.CS4014.severity = error dotnet_diagnostic.CS8600.severity = error dotnet_diagnostic.CS8603.severity = error dotnet_diagnostic.CS8625.severity = error +dotnet_diagnostic.CS8981.severity = suggestion dotnet_diagnostic.BL0005.severity = suggestion dotnet_diagnostic.MVC1000.severity = suggestion -dotnet_diagnostic.IDE0055.severity = suggestion # Fix formatting -dotnet_diagnostic.IDE0023.severity = suggestion # use expression body for operators -dotnet_diagnostic.IDE0025.severity = suggestion # use expression body for properties -dotnet_diagnostic.IDE1006.severity = suggestion # Naming rule violation: These words cannot contain lower case characters -dotnet_diagnostic.IDE0072.severity = suggestion # Populate switch - forces population of all cases even when default specified -dotnet_diagnostic.IDE0027.severity = suggestion # Use expression body for accessors -dotnet_diagnostic.IDE0032.severity = suggestion # Use auto property -dotnet_diagnostic.IDE0007.severity = error # Use var -dotnet_diagnostic.IDE0160.severity = suggestion # Use block scoped -dotnet_diagnostic.IDE0011.severity = error # Use braces on if statements -dotnet_diagnostic.IDE0057.severity = suggestion # substring can be simplified +dotnet_diagnostic.RZ10012.severity = error + dotnet_diagnostic.IDE0004.severity = error # redundant cast +dotnet_diagnostic.IDE0005.severity = error +dotnet_diagnostic.IDE0007.severity = error # Use var +dotnet_diagnostic.IDE0011.severity = error # Use braces on if statements dotnet_diagnostic.IDE0010.severity = silent # populate switch +dotnet_diagnostic.IDE0017.severity = suggestion # initialization can be simplified dotnet_diagnostic.IDE0021.severity = silent # expression body for constructors dotnet_diagnostic.IDE0022.severity = silent # expression body for methods +dotnet_diagnostic.IDE0023.severity = suggestion # use expression body for operators dotnet_diagnostic.IDE0024.severity = silent # expression body for operators +dotnet_diagnostic.IDE0025.severity = suggestion # use expression body for properties +dotnet_diagnostic.IDE0027.severity = suggestion # Use expression body for accessors dotnet_diagnostic.IDE0028.severity = silent +dotnet_diagnostic.IDE0032.severity = suggestion # Use auto property dotnet_diagnostic.IDE0033.severity = error # prefer tuple name +dotnet_diagnostic.IDE0037.severity = suggestion # simplify anonymous type dotnet_diagnostic.IDE0040.severity = error # modifiers required dotnet_diagnostic.IDE0041.severity = error # simplify null dotnet_diagnostic.IDE0042.severity = error # deconstruct variable -dotnet_diagnostic.IDE0044.severity = error # make field only when possible +dotnet_diagnostic.IDE0044.severity = suggestion # make field only when possible dotnet_diagnostic.IDE0047.severity = suggestion # paratemeter name dotnet_diagnostic.IDE0051.severity = error # unused field dotnet_diagnostic.IDE0052.severity = error # unused member +dotnet_diagnostic.IDE0053.severity = suggestion # lambda not needed +dotnet_diagnostic.IDE0055.severity = suggestion # Fix formatting +dotnet_diagnostic.IDE0057.severity = suggestion # substring can be simplified dotnet_diagnostic.IDE0060.severity = suggestion # unused parameters +dotnet_diagnostic.IDE0061.severity = suggestion # local expression body +dotnet_diagnostic.IDE0062.severity = suggestion # local to static +dotnet_diagnostic.IDE0063.severity = error # simplify using dotnet_diagnostic.IDE0066.severity = suggestion # switch expression +dotnet_diagnostic.IDE0072.severity = suggestion # Populate switch - forces population of all cases even when default specified dotnet_diagnostic.IDE0078.severity = suggestion # use pattern matching dotnet_diagnostic.IDE0090.severity = suggestion # new can be simplified -dotnet_diagnostic.IDE0130.severity = suggestion # namespace folder structure +dotnet_diagnostic.IDE0130.severity = error # namespace folder structure dotnet_diagnostic.IDE0160.severity = silent # Use block namespaces ARE NOT required -dotnet_diagnostic.IDE0161.severity = suggestion # Please use file namespaces +dotnet_diagnostic.IDE0161.severity = error # Please use file namespaces +dotnet_diagnostic.IDE0200.severity = suggestion # lambda not needed +dotnet_diagnostic.IDE1006.severity = suggestion # Naming rule violation: These words cannot contain lower case characters ########################################## # Styles diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index b29f37e7..530ec766 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -1,5 +1,10 @@ name: SharpCompress -on: [push, pull_request] +on: + push: + branches: + - 'master' + pull_request: + types: [ opened, synchronize, reopened, ready_for_review ] jobs: build: @@ -12,8 +17,8 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x - - run: dotnet run -p build/build.csproj + dotnet-version: 7.0.x + - run: dotnet run --project build/build.csproj - uses: actions/upload-artifact@v3 with: name: ${{ matrix.os }}-sharpcompress.nupkg diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..4b343bf8 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + latest + enable + Recommended + true + true + true + true + true + False + False + + diff --git a/build/Program.cs b/build/Program.cs index f5c91cbe..000051dd 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -7,76 +7,75 @@ using static Bullseye.Targets; using static SimpleExec.Command; const string Clean = "clean"; -const string Format = "format"; const string Build = "build"; const string Test = "test"; const string Publish = "publish"; -Target(Clean, - ForEach("**/bin", "**/obj"), - dir => - { - IEnumerable GetDirectories(string d) - { - return Glob.Directories(".", d); - } +Target( + Clean, + ForEach("**/bin", "**/obj"), + dir => + { + IEnumerable GetDirectories(string d) + { + return Glob.Directories(".", d); + } - void RemoveDirectory(string d) - { - if (Directory.Exists(d)) - { - Console.WriteLine(d); - Directory.Delete(d, true); - } - } + void RemoveDirectory(string d) + { + if (Directory.Exists(d)) + { + Console.WriteLine(d); + Directory.Delete(d, true); + } + } - foreach (var d in GetDirectories(dir)) - { - RemoveDirectory(d); - } - }); + foreach (var d in GetDirectories(dir)) + { + RemoveDirectory(d); + } + } +); -Target(Format, - () => - { - Run("dotnet", "tool restore"); - Run("dotnet", "format --check"); - }); +Target( + Build, + () => + { + Run("dotnet", "build src/SharpCompress/SharpCompress.csproj -c Release"); + } +); -Target(Build, - DependsOn(Format), - framework => - { - Run("dotnet", "build src/SharpCompress/SharpCompress.csproj -c Release"); - }); +Target( + Test, + DependsOn(Build), + ForEach("net7.0", "net462"), + framework => + { + IEnumerable GetFiles(string d) + { + return Glob.Files(".", d); + } -Target(Test, - DependsOn(Build), - ForEach("net6.0", "net461"), - framework => - { - IEnumerable GetFiles(string d) - { - return Glob.Files(".", d); - } + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net462") + { + return; + } - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net461") - { - return; - } + foreach (var file in GetFiles("**/*.Test.csproj")) + { + Run("dotnet", $"test {file} -c Release -f {framework}"); + } + } +); - foreach (var file in GetFiles("**/*.Test.csproj")) - { - Run("dotnet", $"test {file} -c Release -f {framework}"); - } - }); - -Target(Publish, - DependsOn(Test), - () => - { - Run("dotnet", "pack src/SharpCompress/SharpCompress.csproj -c Release -o artifacts/"); - }); +Target( + Publish, + DependsOn(Test), + () => + { + Run("dotnet", "pack src/SharpCompress/SharpCompress.csproj -c Release -o artifacts/"); + } +); Target("default", DependsOn(Publish), () => Console.WriteLine("Done!")); diff --git a/build/build.csproj b/build/build.csproj index 792624f2..66d55099 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net7.0 - + - + diff --git a/global.json b/global.json index a3bb3f5e..e6e67e4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.200", + "version": "7.0.101", "rollForward": "latestFeature" } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Algorithms/Adler32.cs b/src/SharpCompress/Algorithms/Adler32.cs index e98c864e..1f094c13 100644 --- a/src/SharpCompress/Algorithms/Adler32.cs +++ b/src/SharpCompress/Algorithms/Adler32.cs @@ -15,305 +15,256 @@ using System.Runtime.Intrinsics.X86; #endif #pragma warning disable IDE0007 // Use implicit type -namespace SharpCompress.Algorithms +namespace SharpCompress.Algorithms; + +/// +/// Calculates the 32 bit Adler checksum of a given buffer according to +/// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) +/// +internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Compression/Zlib/Adler32.cs { /// - /// Calculates the 32 bit Adler checksum of a given buffer according to - /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) + /// Global inlining options. Helps temporarily disable inlining for better profiler output. /// - internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Compression/Zlib/Adler32.cs + private static class InliningOptions // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/InliningOptions.cs { /// - /// Global inlining options. Helps temporarily disable inlining for better profiler output. + /// regardless of the build conditions. /// - private static class InliningOptions // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/InliningOptions.cs - { - /// - /// regardless of the build conditions. - /// - public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining; #if PROFILING - public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; - public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; + public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; + public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else #if SUPPORTS_HOTPATH - public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; #else - public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining; #endif - public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif - public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; - } + public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; + } #if SUPPORTS_RUNTIME_INTRINSICS + /// + /// Provides optimized static methods for trigonometric, logarithmic, + /// and other common mathematical functions. + /// + private static class Numerics // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/Numerics.cs + { /// - /// Provides optimized static methods for trigonometric, logarithmic, - /// and other common mathematical functions. + /// Reduces elements of the vector into one sum. /// - private static class Numerics // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/Numerics.cs + /// The accumulator to reduce. + /// The sum of all elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReduceSum(Vector256 accumulator) { - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReduceSum(Vector256 accumulator) - { - // Add upper lane to lower lane. - Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); + // Add upper lane to lower lane. + Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); - // Add odd to even. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); + // Add odd to even. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); - // Add high to low. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); + // Add high to low. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); - return Sse2.ConvertToInt32(vsum); - } - - /// - /// Reduces even elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of even elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EvenReduceSum(Vector256 accumulator) - { - Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low - - // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882 - return Sse2.ConvertToInt32(vsum); - } + return Sse2.ConvertToInt32(vsum); } + + /// + /// Reduces even elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of even elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int EvenReduceSum(Vector256 accumulator) + { + Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low + + // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882 + return Sse2.ConvertToInt32(vsum); + } + } #endif - /// - /// The default initial seed value of a Adler32 checksum calculation. - /// - public const uint SeedValue = 1U; + /// + /// The default initial seed value of a Adler32 checksum calculation. + /// + public const uint SeedValue = 1U; - // Largest prime smaller than 65536 - private const uint BASE = 65521; + // Largest prime smaller than 65536 + private const uint BASE = 65521; - // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 - private const uint NMAX = 5552; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private const uint NMAX = 5552; #if SUPPORTS_RUNTIME_INTRINSICS - private const int MinBufferSize = 64; + private const int MinBufferSize = 64; - private const int BlockSize = 1 << 5; + private const int BlockSize = 1 << 5; - // The C# compiler emits this as a compile-time constant embedded in the PE file. - private static ReadOnlySpan Tap1Tap2 => new byte[] + // The C# compiler emits this as a compile-time constant embedded in the PE file. + private static ReadOnlySpan Tap1Tap2 => + new byte[] { - 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1 - 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2 + 32, + 31, + 30, + 29, + 28, + 27, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, // tap1 + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1 // tap2 }; #endif - /// - /// Calculates the Adler32 checksum with the bytes taken from the span. - /// - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Calculate(ReadOnlySpan buffer) - => Calculate(SeedValue, buffer); + /// + /// Calculates the Adler32 checksum with the bytes taken from the span. + /// + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) => Calculate(SeedValue, buffer); - /// - /// Calculates the Adler32 checksum with the bytes taken from the span and seed. - /// - /// The input Adler32 value. - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static uint Calculate(uint adler, ReadOnlySpan buffer) + /// + /// Calculates the Adler32 checksum with the bytes taken from the span and seed. + /// + /// The input Adler32 value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static uint Calculate(uint adler, ReadOnlySpan buffer) + { + if (buffer.IsEmpty) { - if (buffer.IsEmpty) - { - return adler; - } + return adler; + } #if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateAvx2(adler, buffer); - } + if (Avx2.IsSupported && buffer.Length >= MinBufferSize) + { + return CalculateAvx2(adler, buffer); + } - if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateSse(adler, buffer); - } + if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) + { + return CalculateSse(adler, buffer); + } - return CalculateScalar(adler, buffer); + return CalculateScalar(adler, buffer); #else - return CalculateScalar(adler, buffer); + return CalculateScalar(adler, buffer); #endif - } + } - // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c #if SUPPORTS_RUNTIME_INTRINSICS - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + + // Process the data in blocks. + uint length = (uint)buffer.Length; + uint blocks = length / BlockSize; + length -= blocks * BlockSize; + + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - - // Process the data in blocks. - uint length = (uint)buffer.Length; - uint blocks = length / BlockSize; - length -= blocks * BlockSize; - - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) - { - byte* localBufferPtr = bufferPtr; - - // _mm_setr_epi8 on x86 - Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); - Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); - Vector128 zero = Vector128.Zero; - var ones = Vector128.Create((short)1); - - while (blocks > 0) - { - uint n = NMAX / BlockSize; /* The NMAX constraint. */ - if (n > blocks) - { - n = blocks; - } - - blocks -= n; - - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - Vector128 v_ps = Vector128.CreateScalar(s1 * n); - Vector128 v_s2 = Vector128.CreateScalar(s2); - Vector128 v_s1 = Vector128.Zero; - - do - { - // Load 32 input bytes. - Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); - Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); - - // Add previous block byte sum to v_ps. - v_ps = Sse2.Add(v_ps, v_s1); - - // Horizontally add the bytes for s1, multiply-adds the - // bytes by [ 32, 31, 30, ... ] for s2. - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); - Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); - - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); - Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); - - localBufferPtr += BlockSize; - } - while (--n > 0); - - v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); - - // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). - const byte S2301 = 0b1011_0001; // A B C D -> B A D C - const byte S1032 = 0b0100_1110; // A B C D -> C D A B - - v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032)); - - s1 += v_s1.ToScalar(); - - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301)); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032)); - - s2 = v_s2.ToScalar(); - - // Reduce. - s1 %= BASE; - s2 %= BASE; - } - - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } - - return s1 | (s2 << 16); - } - } - } - - // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) - { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint length = (uint)buffer.Length; - - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) + fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) { byte* localBufferPtr = bufferPtr; - Vector256 zero = Vector256.Zero; - var dot3v = Vector256.Create((short)1); - var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + // _mm_setr_epi8 on x86 + Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); + Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); + Vector128 zero = Vector128.Zero; + var ones = Vector128.Create((short)1); - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - var vs1 = Vector256.CreateScalar(s1); - var vs2 = Vector256.CreateScalar(s2); - - while (length >= 32) + while (blocks > 0) { - int k = length < NMAX ? (int)length : (int)NMAX; - k -= k % 32; - length -= (uint)k; - - Vector256 vs10 = vs1; - Vector256 vs3 = Vector256.Zero; - - while (k >= 32) + uint n = NMAX / BlockSize; /* The NMAX constraint. */ + if (n > blocks) { - // Load 32 input bytes. - Vector256 block = Avx.LoadVector256(localBufferPtr); - - // Sum of abs diff, resulting in 2 x int32's - Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); - - vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); - vs3 = Avx2.Add(vs3, vs10); - - // sum 32 uint8s to 16 shorts. - Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); - - // sum 16 shorts to 8 uint32s. - Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); - - vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); - vs10 = vs1; - - localBufferPtr += BlockSize; - k -= 32; + n = blocks; } - // Defer the multiplication with 32 to outside of the loop. - vs3 = Avx2.ShiftLeftLogical(vs3, 5); - vs2 = Avx2.Add(vs2, vs3); + blocks -= n; - s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); - s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); + // Process n blocks of data. At most NMAX data bytes can be + // processed before s2 must be reduced modulo BASE. + Vector128 v_ps = Vector128.CreateScalar(s1 * n); + Vector128 v_s2 = Vector128.CreateScalar(s2); + Vector128 v_s1 = Vector128.Zero; + do + { + // Load 32 input bytes. + Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); + Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); + + // Add previous block byte sum to v_ps. + v_ps = Sse2.Add(v_ps, v_s1); + + // Horizontally add the bytes for s1, multiply-adds the + // bytes by [ 32, 31, 30, ... ] for s2. + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); + Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); + + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); + Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); + + localBufferPtr += BlockSize; + } while (--n > 0); + + v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); + + // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). + const byte S2301 = 0b1011_0001; // A B C D -> B A D C + const byte S1032 = 0b0100_1110; // A B C D -> C D A B + + v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032)); + + s1 += v_s1.ToScalar(); + + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301)); + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032)); + + s2 = v_s2.ToScalar(); + + // Reduce. s1 %= BASE; s2 %= BASE; - - vs1 = Vector256.CreateScalar(s1); - vs2 = Vector256.CreateScalar(s2); } if (length > 0) @@ -324,97 +275,212 @@ namespace SharpCompress.Algorithms return s1 | (s2 << 16); } } + } - private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2) + // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint length = (uint)buffer.Length; + + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) { - if (length >= 16) + byte* localBufferPtr = bufferPtr; + + Vector256 zero = Vector256.Zero; + var dot3v = Vector256.Create((short)1); + var dot2v = Vector256.Create( + 32, + 31, + 30, + 29, + 28, + 27, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1 + ); + + // Process n blocks of data. At most NMAX data bytes can be + // processed before s2 must be reduced modulo BASE. + var vs1 = Vector256.CreateScalar(s1); + var vs2 = Vector256.CreateScalar(s2); + + while (length >= 32) { - s2 += s1 += localBufferPtr[0]; - s2 += s1 += localBufferPtr[1]; - s2 += s1 += localBufferPtr[2]; - s2 += s1 += localBufferPtr[3]; - s2 += s1 += localBufferPtr[4]; - s2 += s1 += localBufferPtr[5]; - s2 += s1 += localBufferPtr[6]; - s2 += s1 += localBufferPtr[7]; - s2 += s1 += localBufferPtr[8]; - s2 += s1 += localBufferPtr[9]; - s2 += s1 += localBufferPtr[10]; - s2 += s1 += localBufferPtr[11]; - s2 += s1 += localBufferPtr[12]; - s2 += s1 += localBufferPtr[13]; - s2 += s1 += localBufferPtr[14]; - s2 += s1 += localBufferPtr[15]; + int k = length < NMAX ? (int)length : (int)NMAX; + k -= k % 32; + length -= (uint)k; - localBufferPtr += 16; - length -= 16; - } + Vector256 vs10 = vs1; + Vector256 vs3 = Vector256.Zero; - while (length-- > 0) - { - s2 += s1 += *localBufferPtr++; - } - - if (s1 >= BASE) - { - s1 -= BASE; - } - - s2 %= BASE; - } -#endif - - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) - { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint k; - - fixed (byte* bufferPtr = buffer) - { - var localBufferPtr = bufferPtr; - uint length = (uint)buffer.Length; - - while (length > 0) + while (k >= 32) { - k = length < NMAX ? length : NMAX; - length -= k; + // Load 32 input bytes. + Vector256 block = Avx.LoadVector256(localBufferPtr); - while (k >= 16) - { - s2 += s1 += localBufferPtr[0]; - s2 += s1 += localBufferPtr[1]; - s2 += s1 += localBufferPtr[2]; - s2 += s1 += localBufferPtr[3]; - s2 += s1 += localBufferPtr[4]; - s2 += s1 += localBufferPtr[5]; - s2 += s1 += localBufferPtr[6]; - s2 += s1 += localBufferPtr[7]; - s2 += s1 += localBufferPtr[8]; - s2 += s1 += localBufferPtr[9]; - s2 += s1 += localBufferPtr[10]; - s2 += s1 += localBufferPtr[11]; - s2 += s1 += localBufferPtr[12]; - s2 += s1 += localBufferPtr[13]; - s2 += s1 += localBufferPtr[14]; - s2 += s1 += localBufferPtr[15]; + // Sum of abs diff, resulting in 2 x int32's + Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); - localBufferPtr += 16; - k -= 16; - } + vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); + vs3 = Avx2.Add(vs3, vs10); - while (k-- > 0) - { - s2 += s1 += *localBufferPtr++; - } + // sum 32 uint8s to 16 shorts. + Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); - s1 %= BASE; - s2 %= BASE; + // sum 16 shorts to 8 uint32s. + Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); + + vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); + vs10 = vs1; + + localBufferPtr += BlockSize; + k -= 32; } - return (s2 << 16) | s1; + // Defer the multiplication with 32 to outside of the loop. + vs3 = Avx2.ShiftLeftLogical(vs3, 5); + vs2 = Avx2.Add(vs2, vs3); + + s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); + s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); + + s1 %= BASE; + s2 %= BASE; + + vs1 = Vector256.CreateScalar(s1); + vs2 = Vector256.CreateScalar(s2); } + + if (length > 0) + { + HandleLeftOver(localBufferPtr, length, ref s1, ref s2); + } + + return s1 | (s2 << 16); + } + } + + private static unsafe void HandleLeftOver( + byte* localBufferPtr, + uint length, + ref uint s1, + ref uint s2 + ) + { + if (length >= 16) + { + s2 += s1 += localBufferPtr[0]; + s2 += s1 += localBufferPtr[1]; + s2 += s1 += localBufferPtr[2]; + s2 += s1 += localBufferPtr[3]; + s2 += s1 += localBufferPtr[4]; + s2 += s1 += localBufferPtr[5]; + s2 += s1 += localBufferPtr[6]; + s2 += s1 += localBufferPtr[7]; + s2 += s1 += localBufferPtr[8]; + s2 += s1 += localBufferPtr[9]; + s2 += s1 += localBufferPtr[10]; + s2 += s1 += localBufferPtr[11]; + s2 += s1 += localBufferPtr[12]; + s2 += s1 += localBufferPtr[13]; + s2 += s1 += localBufferPtr[14]; + s2 += s1 += localBufferPtr[15]; + + localBufferPtr += 16; + length -= 16; + } + + while (length-- > 0) + { + s2 += s1 += *localBufferPtr++; + } + + if (s1 >= BASE) + { + s1 -= BASE; + } + + s2 %= BASE; + } +#endif + + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint k; + + fixed (byte* bufferPtr = buffer) + { + var localBufferPtr = bufferPtr; + uint length = (uint)buffer.Length; + + while (length > 0) + { + k = length < NMAX ? length : NMAX; + length -= k; + + while (k >= 16) + { + s2 += s1 += localBufferPtr[0]; + s2 += s1 += localBufferPtr[1]; + s2 += s1 += localBufferPtr[2]; + s2 += s1 += localBufferPtr[3]; + s2 += s1 += localBufferPtr[4]; + s2 += s1 += localBufferPtr[5]; + s2 += s1 += localBufferPtr[6]; + s2 += s1 += localBufferPtr[7]; + s2 += s1 += localBufferPtr[8]; + s2 += s1 += localBufferPtr[9]; + s2 += s1 += localBufferPtr[10]; + s2 += s1 += localBufferPtr[11]; + s2 += s1 += localBufferPtr[12]; + s2 += s1 += localBufferPtr[13]; + s2 += s1 += localBufferPtr[14]; + s2 += s1 += localBufferPtr[15]; + + localBufferPtr += 16; + k -= 16; + } + + while (k-- > 0) + { + s2 += s1 += *localBufferPtr++; + } + + s1 %= BASE; + s2 %= BASE; + } + + return (s2 << 16) | s1; } } } diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index b6c0ef76..cfac55f2 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -6,161 +6,168 @@ using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public abstract class AbstractArchive : IArchive, IArchiveExtractionListener + where TEntry : IArchiveEntry + where TVolume : IVolume { - public abstract class AbstractArchive : IArchive, IArchiveExtractionListener - where TEntry : IArchiveEntry - where TVolume : IVolume + private readonly LazyReadOnlyCollection lazyVolumes; + private readonly LazyReadOnlyCollection lazyEntries; + + public event EventHandler>? EntryExtractionBegin; + public event EventHandler>? EntryExtractionEnd; + + public event EventHandler? CompressedBytesRead; + public event EventHandler? FilePartExtractionBegin; + + protected ReaderOptions ReaderOptions { get; } + + private bool disposed; + protected SourceStream SrcStream; + + internal AbstractArchive(ArchiveType type, SourceStream srcStream) { - private readonly LazyReadOnlyCollection lazyVolumes; - private readonly LazyReadOnlyCollection lazyEntries; - - public event EventHandler>? EntryExtractionBegin; - public event EventHandler>? EntryExtractionEnd; - - public event EventHandler? CompressedBytesRead; - public event EventHandler? FilePartExtractionBegin; - - protected ReaderOptions ReaderOptions { get; } - - private bool disposed; - protected SourceStream SrcStream; - - internal AbstractArchive(ArchiveType type, SourceStream srcStream) - { - Type = type; - ReaderOptions = srcStream.ReaderOptions; - SrcStream = srcStream; - lazyVolumes = new LazyReadOnlyCollection(LoadVolumes(SrcStream)); - lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes)); - } + Type = type; + ReaderOptions = srcStream.ReaderOptions; + SrcStream = srcStream; + lazyVolumes = new LazyReadOnlyCollection(LoadVolumes(SrcStream)); + lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes)); + } #nullable disable - internal AbstractArchive(ArchiveType type) - { - Type = type; - lazyVolumes = new LazyReadOnlyCollection(Enumerable.Empty()); - lazyEntries = new LazyReadOnlyCollection(Enumerable.Empty()); - } + internal AbstractArchive(ArchiveType type) + { + Type = type; + lazyVolumes = new LazyReadOnlyCollection(Enumerable.Empty()); + lazyEntries = new LazyReadOnlyCollection(Enumerable.Empty()); + } + #nullable enable - public ArchiveType Type { get; } + public ArchiveType Type { get; } - void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) + void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) => + EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs(entry)); + + void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry) => + EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs(entry)); + + private static Stream CheckStreams(Stream stream) + { + if (!stream.CanSeek || !stream.CanRead) { - EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs(entry)); + throw new ArgumentException("Archive streams must be Readable and Seekable"); } + return stream; + } - void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry) + /// + /// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive. + /// + public virtual ICollection Entries => lazyEntries; + + /// + /// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive. + /// + public ICollection Volumes => lazyVolumes; + + /// + /// The total size of the files compressed in the archive. + /// + public virtual long TotalSize => + Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize); + + /// + /// The total size of the files as uncompressed in the archive. + /// + public virtual long TotalUncompressSize => + Entries.Aggregate(0L, (total, cf) => total + cf.Size); + + protected abstract IEnumerable LoadVolumes(SourceStream srcStream); + protected abstract IEnumerable LoadEntries(IEnumerable volumes); + + IEnumerable IArchive.Entries => Entries.Cast(); + + IEnumerable IArchive.Volumes => lazyVolumes.Cast(); + + public virtual void Dispose() + { + if (!disposed) { - EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs(entry)); + lazyVolumes.ForEach(v => v.Dispose()); + lazyEntries.GetLoaded().Cast().ForEach(x => x.Close()); + SrcStream?.Dispose(); + + disposed = true; } + } - private static Stream CheckStreams(Stream stream) - { - if (!stream.CanSeek || !stream.CanRead) - { - throw new ArgumentException("Archive streams must be Readable and Seekable"); - } - return stream; - } + void IArchiveExtractionListener.EnsureEntriesLoaded() + { + lazyEntries.EnsureFullyLoaded(); + lazyVolumes.EnsureFullyLoaded(); + } - /// - /// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive. - /// - public virtual ICollection Entries => lazyEntries; - - /// - /// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive. - /// - public ICollection Volumes => lazyVolumes; - - /// - /// The total size of the files compressed in the archive. - /// - public virtual long TotalSize => Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize); - - /// - /// The total size of the files as uncompressed in the archive. - /// - public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size); - - protected abstract IEnumerable LoadVolumes(SourceStream srcStream); - protected abstract IEnumerable LoadEntries(IEnumerable volumes); - - IEnumerable IArchive.Entries => Entries.Cast(); - - IEnumerable IArchive.Volumes => lazyVolumes.Cast(); - - public virtual void Dispose() - { - if (!disposed) - { - lazyVolumes.ForEach(v => v.Dispose()); - lazyEntries.GetLoaded().Cast().ForEach(x => x.Close()); - if (SrcStream != null) - SrcStream.Dispose(); - disposed = true; - } - } - - void IArchiveExtractionListener.EnsureEntriesLoaded() - { - lazyEntries.EnsureFullyLoaded(); - lazyVolumes.EnsureFullyLoaded(); - } - - void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes) - { - CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs( + void IExtractionListener.FireCompressedBytesRead( + long currentPartCompressedBytes, + long compressedReadBytes + ) => + CompressedBytesRead?.Invoke( + this, + new CompressedBytesReadEventArgs( currentFilePartCompressedBytesRead: currentPartCompressedBytes, compressedBytesRead: compressedReadBytes - )); - } + ) + ); - void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize) - { - FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs( + void IExtractionListener.FireFilePartExtractionBegin( + string name, + long size, + long compressedSize + ) => + FilePartExtractionBegin?.Invoke( + this, + new FilePartExtractionBeginEventArgs( compressedSize: compressedSize, size: size, name: name - )); - } + ) + ); - /// - /// Use this method to extract all entries in an archive in order. - /// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be - /// extracted sequentially for the best performance. - /// - /// This method will load all entry information from the archive. - /// - /// WARNING: this will reuse the underlying stream for the archive. Errors may - /// occur if this is used at the same time as other extraction methods on this instance. - /// - /// - public IReader ExtractAllEntries() + /// + /// Use this method to extract all entries in an archive in order. + /// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be + /// extracted sequentially for the best performance. + /// + /// This method will load all entry information from the archive. + /// + /// WARNING: this will reuse the underlying stream for the archive. Errors may + /// occur if this is used at the same time as other extraction methods on this instance. + /// + /// + public IReader ExtractAllEntries() + { + ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); + return CreateReaderForSolidExtraction(); + } + + protected abstract IReader CreateReaderForSolidExtraction(); + + /// + /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). + /// + public virtual bool IsSolid => false; + + /// + /// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive. + /// + public bool IsComplete + { + get { ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); - return CreateReaderForSolidExtraction(); - } - - protected abstract IReader CreateReaderForSolidExtraction(); - - /// - /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). - /// - public virtual bool IsSolid => false; - - /// - /// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive. - /// - public bool IsComplete - { - get - { - ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); - return Entries.All(x => x.IsComplete); - } + return Entries.All(x => x.IsComplete); } } } diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs index 42a8213e..cf97aff8 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs @@ -4,163 +4,175 @@ using System.IO; using System.Linq; using SharpCompress.Common; using SharpCompress.IO; -using SharpCompress.Readers; using SharpCompress.Writers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public abstract class AbstractWritableArchive + : AbstractArchive, + IWritableArchive + where TEntry : IArchiveEntry + where TVolume : IVolume { - public abstract class AbstractWritableArchive : AbstractArchive, IWritableArchive - where TEntry : IArchiveEntry - where TVolume : IVolume + private class RebuildPauseDisposable : IDisposable { - private class RebuildPauseDisposable : IDisposable - { - private readonly AbstractWritableArchive archive; - - public RebuildPauseDisposable(AbstractWritableArchive archive) - { - this.archive = archive; - archive.pauseRebuilding = true; - } - - public void Dispose() - { - archive.pauseRebuilding = false; - archive.RebuildModifiedCollection(); - } - } - private readonly List newEntries = new List(); - private readonly List removedEntries = new List(); - - private readonly List modifiedEntries = new List(); - private bool hasModifications; - private bool pauseRebuilding; - - internal AbstractWritableArchive(ArchiveType type) - : base(type) + private readonly AbstractWritableArchive archive; + + public RebuildPauseDisposable(AbstractWritableArchive archive) { + this.archive = archive; + archive.pauseRebuilding = true; } - internal AbstractWritableArchive(ArchiveType type, SourceStream srcStream) - : base(type, srcStream) + public void Dispose() { - } - - public override ICollection Entries - { - get - { - if (hasModifications) - { - return modifiedEntries; - } - return base.Entries; - } - } - - public IDisposable PauseEntryRebuilding() - { - return new RebuildPauseDisposable(this); - } - - private void RebuildModifiedCollection() - { - if (pauseRebuilding) - { - return; - } - hasModifications = true; - newEntries.RemoveAll(v => removedEntries.Contains(v)); - modifiedEntries.Clear(); - modifiedEntries.AddRange(OldEntries.Concat(newEntries)); - } - - private IEnumerable OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } } - - public void RemoveEntry(TEntry entry) - { - if (!removedEntries.Contains(entry)) - { - removedEntries.Add(entry); - RebuildModifiedCollection(); - } - } - - void IWritableArchive.RemoveEntry(IArchiveEntry entry) - { - RemoveEntry((TEntry)entry); - } - - public TEntry AddEntry(string key, Stream source, - long size = 0, DateTime? modified = null) - { - return AddEntry(key, source, false, size, modified); - } - - IArchiveEntry IWritableArchive.AddEntry(string key, Stream source, bool closeStream, long size, DateTime? modified) - { - return AddEntry(key, source, closeStream, size, modified); - } - - public TEntry AddEntry(string key, Stream source, bool closeStream, - long size = 0, DateTime? modified = null) - { - if (key.Length > 0 && key[0] is '/' or '\\') - { - key = key.Substring(1); - } - if (DoesKeyMatchExisting(key)) - { - throw new ArchiveException("Cannot add entry with duplicate key: " + key); - } - var entry = CreateEntry(key, source, size, modified, closeStream); - newEntries.Add(entry); - RebuildModifiedCollection(); - return entry; - } - - private bool DoesKeyMatchExisting(string key) - { - foreach (var path in Entries.Select(x => x.Key)) - { - var p = path.Replace('/', '\\'); - if (p.Length > 0 && p[0] == '\\') - { - p = p.Substring(1); - } - return string.Equals(p, key, StringComparison.OrdinalIgnoreCase); - } - return false; - } - - public void SaveTo(Stream stream, WriterOptions options) - { - //reset streams of new entries - newEntries.Cast().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin)); - SaveTo(stream, options, OldEntries, newEntries); - } - - protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified, - bool closeStream) - { - if (!source.CanRead || !source.CanSeek) - { - throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API"); - } - return CreateEntryInternal(key, source, size, modified, closeStream); - } - - protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified, - bool closeStream); - - protected abstract void SaveTo(Stream stream, WriterOptions options, IEnumerable oldEntries, IEnumerable newEntries); - - public override void Dispose() - { - base.Dispose(); - newEntries.Cast().ForEach(x => x.Close()); - removedEntries.Cast().ForEach(x => x.Close()); - modifiedEntries.Cast().ForEach(x => x.Close()); + archive.pauseRebuilding = false; + archive.RebuildModifiedCollection(); } } + + private readonly List newEntries = new List(); + private readonly List removedEntries = new List(); + + private readonly List modifiedEntries = new List(); + private bool hasModifications; + private bool pauseRebuilding; + + internal AbstractWritableArchive(ArchiveType type) : base(type) { } + + internal AbstractWritableArchive(ArchiveType type, SourceStream srcStream) + : base(type, srcStream) { } + + public override ICollection Entries + { + get + { + if (hasModifications) + { + return modifiedEntries; + } + return base.Entries; + } + } + + public IDisposable PauseEntryRebuilding() => new RebuildPauseDisposable(this); + + private void RebuildModifiedCollection() + { + if (pauseRebuilding) + { + return; + } + hasModifications = true; + newEntries.RemoveAll(v => removedEntries.Contains(v)); + modifiedEntries.Clear(); + modifiedEntries.AddRange(OldEntries.Concat(newEntries)); + } + + private IEnumerable OldEntries => base.Entries.Where(x => !removedEntries.Contains(x)); + + public void RemoveEntry(TEntry entry) + { + if (!removedEntries.Contains(entry)) + { + removedEntries.Add(entry); + RebuildModifiedCollection(); + } + } + + void IWritableArchive.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry); + + public TEntry AddEntry(string key, Stream source, long size = 0, DateTime? modified = null) => + AddEntry(key, source, false, size, modified); + + IArchiveEntry IWritableArchive.AddEntry( + string key, + Stream source, + bool closeStream, + long size, + DateTime? modified + ) => AddEntry(key, source, closeStream, size, modified); + + public TEntry AddEntry( + string key, + Stream source, + bool closeStream, + long size = 0, + DateTime? modified = null + ) + { + if (key.Length > 0 && key[0] is '/' or '\\') + { + key = key.Substring(1); + } + if (DoesKeyMatchExisting(key)) + { + throw new ArchiveException("Cannot add entry with duplicate key: " + key); + } + var entry = CreateEntry(key, source, size, modified, closeStream); + newEntries.Add(entry); + RebuildModifiedCollection(); + return entry; + } + + private bool DoesKeyMatchExisting(string key) + { + foreach (var path in Entries.Select(x => x.Key)) + { + var p = path.Replace('/', '\\'); + if (p.Length > 0 && p[0] == '\\') + { + p = p.Substring(1); + } + return string.Equals(p, key, StringComparison.OrdinalIgnoreCase); + } + return false; + } + + public void SaveTo(Stream stream, WriterOptions options) + { + //reset streams of new entries + newEntries.Cast().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin)); + SaveTo(stream, options, OldEntries, newEntries); + } + + protected TEntry CreateEntry( + string key, + Stream source, + long size, + DateTime? modified, + bool closeStream + ) + { + if (!source.CanRead || !source.CanSeek) + { + throw new ArgumentException( + "Streams must be readable and seekable to use the Writing Archive API" + ); + } + return CreateEntryInternal(key, source, size, modified, closeStream); + } + + protected abstract TEntry CreateEntryInternal( + string key, + Stream source, + long size, + DateTime? modified, + bool closeStream + ); + + protected abstract void SaveTo( + Stream stream, + WriterOptions options, + IEnumerable oldEntries, + IEnumerable newEntries + ); + + public override void Dispose() + { + base.Dispose(); + newEntries.Cast().ForEach(x => x.Close()); + removedEntries.Cast().ForEach(x => x.Close()); + modifiedEntries.Cast().ForEach(x => x.Close()); + } } diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 71c0358e..6b489fa1 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -2,232 +2,238 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using SharpCompress.Archives.GZip; -using SharpCompress.Archives.Rar; -using SharpCompress.Archives.SevenZip; -using SharpCompress.Archives.Tar; -using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Factories; -using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public static class ArchiveFactory { - public static class ArchiveFactory + /// + /// Opens an Archive for random access + /// + /// + /// + /// + public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) { - /// - /// Opens an Archive for random access - /// - /// - /// - /// - public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - readerOptions ??= new ReaderOptions(); + readerOptions ??= new ReaderOptions(); - return FindFactory(stream).Open(stream, readerOptions); + return FindFactory(stream).Open(stream, readerOptions); + } + + public static IWritableArchive Create(ArchiveType type) + { + var factory = Factory.Factories + .OfType() + .FirstOrDefault(item => item.KnownArchiveType == type); + + if (factory != null) + { + return factory.CreateWriteableArchive(); } - public static IWritableArchive Create(ArchiveType type) + throw new NotSupportedException("Cannot create Archives of type: " + type); + } + + /// + /// Constructor expects a filepath to an existing file. + /// + /// + /// + public static IArchive Open(string filePath, ReaderOptions? options = null) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), options); + } + + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + return FindFactory(fileInfo).Open(fileInfo, options); + } + + /// + /// Constructor with IEnumerable FileInfo objects, multi and split support. + /// + /// + /// + public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var filesArray = fileInfos.ToArray(); + if (filesArray.Length == 0) { - var factory = Factories.Factory - .Factories - .OfType() - .Where(item => item.KnownArchiveType == type) - .FirstOrDefault(); - - if (factory != null) - return factory.CreateWriteableArchive(); - - throw new NotSupportedException("Cannot create Archives of type: " + type); + throw new InvalidOperationException("No files to open"); } - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IArchive Open(string filePath, ReaderOptions? options = null) + var fileInfo = filesArray[0]; + if (filesArray.Length == 1) { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), options); + return Open(fileInfo, options); } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - options ??= new ReaderOptions { LeaveStreamOpen = false }; + fileInfo.CheckNotNull(nameof(fileInfo)); + options ??= new ReaderOptions { LeaveStreamOpen = false }; - return FindFactory(fileInfo).Open(fileInfo, options); + return FindFactory(fileInfo).Open(filesArray, options); + } + + /// + /// Constructor with IEnumerable FileInfo objects, multi and split support. + /// + /// + /// + public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) + { + streams.CheckNotNull(nameof(streams)); + var streamsArray = streams.ToArray(); + if (streamsArray.Length == 0) + { + throw new InvalidOperationException("No streams"); } - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) + var firstStream = streamsArray[0]; + if (streamsArray.Length == 1) { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] filesArray = fileInfos.ToArray(); - if (filesArray.Length == 0) - throw new InvalidOperationException("No files to open"); - FileInfo fileInfo = filesArray[0]; - if (filesArray.Length == 1) - return Open(fileInfo, options); - - fileInfo.CheckNotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - return FindFactory(fileInfo).Open(filesArray, options); + return Open(firstStream, options); } - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) + firstStream.CheckNotNull(nameof(firstStream)); + options ??= new ReaderOptions(); + + return FindFactory(firstStream).Open(streamsArray, options); + } + + /// + /// Extract to specific directory, retaining filename + /// + public static void WriteToDirectory( + string sourceArchive, + string destinationDirectory, + ExtractionOptions? options = null + ) + { + using var archive = Open(sourceArchive); + foreach (var entry in archive.Entries) { - streams.CheckNotNull(nameof(streams)); - Stream[] streamsArray = streams.ToArray(); - if (streamsArray.Length == 0) - throw new InvalidOperationException("No streams"); - Stream firstStream = streamsArray[0]; - if (streamsArray.Length == 1) - return Open(firstStream, options); + entry.WriteToDirectory(destinationDirectory, options); + } + } - firstStream.CheckNotNull(nameof(firstStream)); - options ??= new ReaderOptions(); + private static T FindFactory(FileInfo finfo) where T : IFactory + { + finfo.CheckNotNull(nameof(finfo)); + using Stream stream = finfo.OpenRead(); + return FindFactory(stream); + } - return FindFactory(firstStream).Open(streamsArray, options); - } - - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteToDirectory(string sourceArchive, string destinationDirectory, - ExtractionOptions? options = null) + private static T FindFactory(Stream stream) where T : IFactory + { + stream.CheckNotNull(nameof(stream)); + if (!stream.CanRead || !stream.CanSeek) { - using IArchive archive = Open(sourceArchive); - foreach (IArchiveEntry entry in archive.Entries) - { - entry.WriteToDirectory(destinationDirectory, options); - } + throw new ArgumentException("Stream should be readable and seekable"); } - private static T FindFactory(FileInfo finfo) - where T : IFactory + var factories = Factory.Factories.OfType(); + + var startPosition = stream.Position; + + foreach (var factory in factories) { - finfo.CheckNotNull(nameof(finfo)); - using (Stream stream = finfo.OpenRead()) - { - return FindFactory(stream); - } - } + stream.Seek(startPosition, SeekOrigin.Begin); - private static T FindFactory(Stream stream) - where T: IFactory - { - stream.CheckNotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - - var factories = Factories.Factory.Factories.OfType(); - - long startPosition = stream.Position; - - foreach (var factory in factories) + if (factory.IsArchive(stream)) { stream.Seek(startPosition, SeekOrigin.Begin); - if (factory.IsArchive(stream)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - return factory; - } + return factory; } - - var extensions = string.Join(", ", factories.Select(item => item.Name)); - - throw new InvalidOperationException($"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"); } - public static bool IsArchive(string filePath, out ArchiveType? type) + var extensions = string.Join(", ", factories.Select(item => item.Name)); + + throw new InvalidOperationException( + $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" + ); + } + + public static bool IsArchive(string filePath, out ArchiveType? type) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + using Stream s = File.OpenRead(filePath); + return IsArchive(s, out type); + } + + private static bool IsArchive(Stream stream, out ArchiveType? type) + { + type = null; + stream.CheckNotNull(nameof(stream)); + + if (!stream.CanRead || !stream.CanSeek) { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - using (Stream s = File.OpenRead(filePath)) - return IsArchive(s, out type); + throw new ArgumentException("Stream should be readable and seekable"); } - private static bool IsArchive(Stream stream, out ArchiveType? type) - { - type = null; - stream.CheckNotNull(nameof(stream)); + var startPosition = stream.Position; - if (!stream.CanRead || !stream.CanSeek) + foreach (var factory in Factory.Factories) + { + stream.Position = startPosition; + + if (factory.IsArchive(stream, null)) { - throw new ArgumentException("Stream should be readable and seekable"); + type = factory.KnownArchiveType; + return true; } - - var startPosition = stream.Position; - - foreach(var factory in Factories.Factory.Factories) - { - stream.Position = startPosition; - - if (factory.IsArchive(stream, null)) - { - type = factory.KnownArchiveType; - return true; - } - } - - return false; } - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// - public static IEnumerable GetFileParts(string part1) - { - part1.CheckNotNullOrEmpty(nameof(part1)); - return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); - } + return false; + } - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// - public static IEnumerable GetFileParts(FileInfo part1) - { - part1.CheckNotNull(nameof(part1)); - yield return part1; + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// + /// + public static IEnumerable GetFileParts(string part1) + { + part1.CheckNotNullOrEmpty(nameof(part1)); + return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); + } - foreach(var factory in Factory.Factories.OfType()) + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// + /// + public static IEnumerable GetFileParts(FileInfo part1) + { + part1.CheckNotNull(nameof(part1)); + yield return part1; + + foreach (var factory in Factory.Factories.OfType()) + { + var i = 1; + var part = factory.GetFilePart(i++, part1); + + if (part != null) { - int i = 1; - FileInfo? part = factory.GetFilePart(i++, part1); - - if (part != null) + yield return part; + while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too { yield return part; - while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too - yield return part; - - yield break; } + + yield break; } } } diff --git a/src/SharpCompress/Archives/ArchiveVolumeFactory.cs b/src/SharpCompress/Archives/ArchiveVolumeFactory.cs index 09315b40..d8f5535f 100644 --- a/src/SharpCompress/Archives/ArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/ArchiveVolumeFactory.cs @@ -1,29 +1,30 @@ using System; -using System.Collections.Generic; using System.IO; -using SharpCompress.Readers; -using System.Linq; -using System.Text; -using SharpCompress.Common.Rar.Headers; using System.Text.RegularExpressions; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +internal abstract class ArchiveVolumeFactory { - internal abstract class ArchiveVolumeFactory + internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part { - internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part - { - FileInfo? item = null; + FileInfo? item = null; - //split 001, 002 ... - Match m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase); - if (m.Success) - item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0')))); - - if (item != null && item.Exists) - return item; - return null; - } + //split 001, 002 ... + Match m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase); + if (m.Success) + item = new FileInfo( + Path.Combine( + part1.DirectoryName!, + String.Concat( + m.Groups[1].Value, + (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0') + ) + ) + ); + if (item != null && item.Exists) + return item; + return null; } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 845e05fb..eea56db0 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -10,183 +10,195 @@ using SharpCompress.Readers.GZip; using SharpCompress.Writers; using SharpCompress.Writers.GZip; -namespace SharpCompress.Archives.GZip +namespace SharpCompress.Archives.GZip; + +public class GZipArchive : AbstractWritableArchive { - public class GZipArchive : AbstractWritableArchive + /// + /// Constructor expects a filepath to an existing file. + /// + /// + /// + public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null) { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null) + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.CheckNotNull(nameof(fileInfo)); + return new GZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static GZipArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new GZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static GZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.CheckNotNull(nameof(streams)); + var strms = streams.ToArray(); + return new GZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Takes a seekable Stream as a source + /// + /// + /// + public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.CheckNotNull(nameof(stream)); + return new GZipArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static GZipArchive Create() => new GZipArchive(); + + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal GZipArchive(SourceStream srcStream) : base(ArchiveType.Tar, srcStream) { } + + protected override IEnumerable LoadVolumes(SourceStream srcStream) + { + srcStream.LoadAllParts(); + var idx = 0; + return srcStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, idx++)); + } + + public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); + + public static bool IsGZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + return false; } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + using Stream stream = fileInfo.OpenRead(); + return IsGZipFile(stream); + } + + public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath)); + + public void SaveTo(FileInfo fileInfo) + { + using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); + SaveTo(stream, new WriterOptions(CompressionType.GZip)); + } + + public static bool IsGZipFile(Stream stream) + { + // read the header on the first read + Span header = stackalloc byte[10]; + + // workitem 8501: handle edge case (decompress empty stream) + if (!stream.ReadFully(header)) { - fileInfo.CheckNotNull(nameof(fileInfo)); - return new GZipArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions())); + return false; } - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static GZipArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - return new GZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions())); + return false; } - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static GZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + return true; + } + + internal GZipArchive() : base(ArchiveType.GZip) { } + + protected override GZipArchiveEntry CreateEntryInternal( + string filePath, + Stream source, + long size, + DateTime? modified, + bool closeStream + ) + { + if (Entries.Any()) { - streams.CheckNotNull(nameof(streams)); - Stream[] strms = streams.ToArray(); - return new GZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions())); + throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); } + return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); + } - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) + protected override void SaveTo( + Stream stream, + WriterOptions options, + IEnumerable oldEntries, + IEnumerable newEntries + ) + { + if (Entries.Count > 1) { - stream.CheckNotNull(nameof(stream)); - return new GZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())); + throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); } - - public static GZipArchive Create() + using var writer = new GZipWriter(stream, new GZipWriterOptions(options)); + foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory)) { - return new GZipArchive(); - } - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// - internal GZipArchive(SourceStream srcStream) - : base(ArchiveType.Tar, srcStream) - { - } - - protected override IEnumerable LoadVolumes(SourceStream srcStream) - { - srcStream.LoadAllParts(); - int idx = 0; - return srcStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, idx++)); - } - public static bool IsGZipFile(string filePath) - { - return IsGZipFile(new FileInfo(filePath)); - } - - public static bool IsGZipFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - - using Stream stream = fileInfo.OpenRead(); - return IsGZipFile(stream); - } - - public void SaveTo(string filePath) - { - SaveTo(new FileInfo(filePath)); - } - - public void SaveTo(FileInfo fileInfo) - { - using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write)) - { - SaveTo(stream, new WriterOptions(CompressionType.GZip)); - } - } - - public static bool IsGZipFile(Stream stream) - { - // read the header on the first read - Span header = stackalloc byte[10]; - - // workitem 8501: handle edge case (decompress empty stream) - if (!stream.ReadFully(header)) - { - return false; - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - return false; - } - - return true; - } - - internal GZipArchive() - : base(ArchiveType.GZip) - { - } - - protected override GZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, - bool closeStream) - { - if (Entries.Any()) - { - throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); - } - return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); - } - - protected override void SaveTo(Stream stream, WriterOptions options, - IEnumerable oldEntries, - IEnumerable newEntries) - { - if (Entries.Count > 1) - { - throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); - } - using (var writer = new GZipWriter(stream, new GZipWriterOptions(options))) - { - foreach (var entry in oldEntries.Concat(newEntries) - .Where(x => !x.IsDirectory)) - { - using (var entryStream = entry.OpenEntryStream()) - { - writer.Write(entry.Key, entryStream, entry.LastModifiedTime); - } - } - } - } - - protected override IEnumerable LoadEntries(IEnumerable volumes) - { - Stream stream = volumes.Single().Stream; - yield return new GZipArchiveEntry(this, new GZipFilePart(stream, ReaderOptions.ArchiveEncoding)); - } - - protected override IReader CreateReaderForSolidExtraction() - { - var stream = Volumes.Single().Stream; - stream.Position = 0; - return GZipReader.Open(stream); + using var entryStream = entry.OpenEntryStream(); + writer.Write(entry.Key, entryStream, entry.LastModifiedTime); } } + + protected override IEnumerable LoadEntries(IEnumerable volumes) + { + var stream = volumes.Single().Stream; + yield return new GZipArchiveEntry( + this, + new GZipFilePart(stream, ReaderOptions.ArchiveEncoding) + ); + } + + protected override IReader CreateReaderForSolidExtraction() + { + var stream = Volumes.Single().Stream; + stream.Position = 0; + return GZipReader.Open(stream); + } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs index 237a5247..0109ad98 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs @@ -2,33 +2,29 @@ using System.Linq; using SharpCompress.Common.GZip; -namespace SharpCompress.Archives.GZip +namespace SharpCompress.Archives.GZip; + +public class GZipArchiveEntry : GZipEntry, IArchiveEntry { - public class GZipArchiveEntry : GZipEntry, IArchiveEntry + internal GZipArchiveEntry(GZipArchive archive, GZipFilePart part) : base(part) => + Archive = archive; + + public virtual Stream OpenEntryStream() { - internal GZipArchiveEntry(GZipArchive archive, GZipFilePart part) - : base(part) + //this is to reset the stream to be read multiple times + var part = (GZipFilePart)Parts.Single(); + if (part.GetRawStream().Position != part.EntryStartPosition) { - Archive = archive; + part.GetRawStream().Position = part.EntryStartPosition; } - - public virtual Stream OpenEntryStream() - { - //this is to reset the stream to be read multiple times - var part = (GZipFilePart)Parts.Single(); - if (part.GetRawStream().Position != part.EntryStartPosition) - { - part.GetRawStream().Position = part.EntryStartPosition; - } - return Parts.Single().GetCompressedStream(); - } - - #region IArchiveEntry Members - - public IArchive Archive { get; } - - public bool IsComplete => true; - - #endregion + return Parts.Single().GetCompressedStream(); } -} \ No newline at end of file + + #region IArchiveEntry Members + + public IArchive Archive { get; } + + public bool IsComplete => true; + + #endregion +} diff --git a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs index 4a315571..7685ceb1 100644 --- a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs @@ -6,63 +6,67 @@ using System.IO; using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Archives.GZip +namespace SharpCompress.Archives.GZip; + +internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArchiveEntry { - internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArchiveEntry + private readonly bool closeStream; + private readonly Stream stream; + + internal GZipWritableArchiveEntry( + GZipArchive archive, + Stream stream, + string path, + long size, + DateTime? lastModified, + bool closeStream + ) : base(archive, null) { - private readonly bool closeStream; - private readonly Stream stream; + this.stream = stream; + Key = path; + Size = size; + LastModifiedTime = lastModified; + this.closeStream = closeStream; + } - internal GZipWritableArchiveEntry(GZipArchive archive, Stream stream, - string path, long size, DateTime? lastModified, bool closeStream) - : base(archive, null) + public override long Crc => 0; + + public override string Key { get; } + + public override long CompressedSize => 0; + + public override long Size { get; } + + public override DateTime? LastModifiedTime { get; } + + 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 Parts => throw new NotImplementedException(); + + Stream IWritableArchiveEntry.Stream => stream; + + public override Stream OpenEntryStream() + { + //ensure new stream is at the start, this could be reset + stream.Seek(0, SeekOrigin.Begin); + return NonDisposingStream.Create(stream); + } + + internal override void Close() + { + if (closeStream) { - this.stream = stream; - Key = path; - Size = size; - LastModifiedTime = lastModified; - this.closeStream = closeStream; - } - - public override long Crc => 0; - - public override string Key { get; } - - public override long CompressedSize => 0; - - public override long Size { get; } - - public override DateTime? LastModifiedTime { get; } - - 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 Parts => throw new NotImplementedException(); - - Stream IWritableArchiveEntry.Stream => stream; - - public override Stream OpenEntryStream() - { - //ensure new stream is at the start, this could be reset - stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); - } - - internal override void Close() - { - if (closeStream) - { - stream.Dispose(); - } + stream.Dispose(); } } } diff --git a/src/SharpCompress/Archives/IArchive.cs b/src/SharpCompress/Archives/IArchive.cs index 2ba84a39..154529bf 100644 --- a/src/SharpCompress/Archives/IArchive.cs +++ b/src/SharpCompress/Archives/IArchive.cs @@ -1,49 +1,48 @@ -using System; +using System; using System.Collections.Generic; using SharpCompress.Common; using SharpCompress.Readers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public interface IArchive : IDisposable { - public interface IArchive : IDisposable - { - event EventHandler> EntryExtractionBegin; - event EventHandler> EntryExtractionEnd; + event EventHandler> EntryExtractionBegin; + event EventHandler> EntryExtractionEnd; - event EventHandler CompressedBytesRead; - event EventHandler FilePartExtractionBegin; + event EventHandler CompressedBytesRead; + event EventHandler FilePartExtractionBegin; - IEnumerable Entries { get; } - IEnumerable Volumes { get; } + IEnumerable Entries { get; } + IEnumerable Volumes { get; } - ArchiveType Type { get; } + ArchiveType Type { get; } - /// - /// Use this method to extract all entries in an archive in order. - /// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be - /// extracted sequentially for the best performance. - /// - IReader ExtractAllEntries(); + /// + /// Use this method to extract all entries in an archive in order. + /// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be + /// extracted sequentially for the best performance. + /// + IReader ExtractAllEntries(); - /// - /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). - /// Rar Archives can be SOLID while all 7Zip archives are considered SOLID. - /// - bool IsSolid { get; } + /// + /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). + /// Rar Archives can be SOLID while all 7Zip archives are considered SOLID. + /// + bool IsSolid { get; } - /// - /// This checks to see if all the known entries have IsComplete = true - /// - bool IsComplete { get; } + /// + /// This checks to see if all the known entries have IsComplete = true + /// + bool IsComplete { get; } - /// - /// The total size of the files compressed in the archive. - /// - long TotalSize { get; } + /// + /// The total size of the files compressed in the archive. + /// + long TotalSize { get; } - /// - /// The total size of the files as uncompressed in the archive. - /// - long TotalUncompressSize { get; } - } -} \ No newline at end of file + /// + /// The total size of the files as uncompressed in the archive. + /// + long TotalUncompressSize { get; } +} diff --git a/src/SharpCompress/Archives/IArchiveEntry.cs b/src/SharpCompress/Archives/IArchiveEntry.cs index 43e681b4..708753cb 100644 --- a/src/SharpCompress/Archives/IArchiveEntry.cs +++ b/src/SharpCompress/Archives/IArchiveEntry.cs @@ -1,24 +1,23 @@ -using System.IO; +using System.IO; using SharpCompress.Common; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public interface IArchiveEntry : IEntry { - public interface IArchiveEntry : IEntry - { - /// - /// Opens the current entry as a stream that will decompress as it is read. - /// Read the entire stream or use SkipEntry on EntryStream. - /// - Stream OpenEntryStream(); + /// + /// Opens the current entry as a stream that will decompress as it is read. + /// Read the entire stream or use SkipEntry on EntryStream. + /// + Stream OpenEntryStream(); - /// - /// The archive can find all the parts of the archive needed to extract this entry. - /// - bool IsComplete { get; } + /// + /// The archive can find all the parts of the archive needed to extract this entry. + /// + bool IsComplete { get; } - /// - /// The archive instance this entry belongs to - /// - IArchive Archive { get; } - } -} \ No newline at end of file + /// + /// The archive instance this entry belongs to + /// + IArchive Archive { get; } +} diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index 829b8bbd..0992f152 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -1,63 +1,70 @@ -using System.IO; +using System.IO; using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public static class IArchiveEntryExtensions { - public static class IArchiveEntryExtensions + public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo) { - public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo) + if (archiveEntry.IsDirectory) { - if (archiveEntry.IsDirectory) - { - throw new ExtractionException("Entry is a file directory and cannot be extracted."); - } - - var streamListener = (IArchiveExtractionListener)archiveEntry.Archive; - streamListener.EnsureEntriesLoaded(); - streamListener.FireEntryExtractionBegin(archiveEntry); - streamListener.FireFilePartExtractionBegin(archiveEntry.Key, archiveEntry.Size, archiveEntry.CompressedSize); - var entryStream = archiveEntry.OpenEntryStream(); - if (entryStream is null) - { - return; - } - using (entryStream) - { - using (Stream s = new ListeningStream(streamListener, entryStream)) - { - s.TransferTo(streamToWriteTo); - } - } - streamListener.FireEntryExtractionEnd(archiveEntry); + throw new ExtractionException("Entry is a file directory and cannot be extracted."); } - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteToDirectory(this IArchiveEntry entry, string destinationDirectory, - ExtractionOptions? options = null) + var streamListener = (IArchiveExtractionListener)archiveEntry.Archive; + streamListener.EnsureEntriesLoaded(); + streamListener.FireEntryExtractionBegin(archiveEntry); + streamListener.FireFilePartExtractionBegin( + archiveEntry.Key, + archiveEntry.Size, + archiveEntry.CompressedSize + ); + var entryStream = archiveEntry.OpenEntryStream(); + if (entryStream is null) { - ExtractionMethods.WriteEntryToDirectory(entry, destinationDirectory, options, - entry.WriteToFile); + return; } - - /// - /// Extract to specific file - /// - public static void WriteToFile(this IArchiveEntry entry, - string destinationFileName, - ExtractionOptions? options = null) + using (entryStream) { - - ExtractionMethods.WriteEntryToFile(entry, destinationFileName, options, - (x, fm) => - { - using (FileStream fs = File.Open(destinationFileName, fm)) - { - entry.WriteTo(fs); - } - }); + using Stream s = new ListeningStream(streamListener, entryStream); + s.TransferTo(streamToWriteTo); } + streamListener.FireEntryExtractionEnd(archiveEntry); } -} \ No newline at end of file + + /// + /// Extract to specific directory, retaining filename + /// + public static void WriteToDirectory( + this IArchiveEntry entry, + string destinationDirectory, + ExtractionOptions? options = null + ) => + ExtractionMethods.WriteEntryToDirectory( + entry, + destinationDirectory, + options, + entry.WriteToFile + ); + + /// + /// Extract to specific file + /// + public static void WriteToFile( + this IArchiveEntry entry, + string destinationFileName, + ExtractionOptions? options = null + ) => + ExtractionMethods.WriteEntryToFile( + entry, + destinationFileName, + options, + (x, fm) => + { + using var fs = File.Open(destinationFileName, fm); + entry.WriteTo(fs); + } + ); +} diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index 23b8a27e..14a48dbd 100644 --- a/src/SharpCompress/Archives/IArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveExtensions.cs @@ -1,20 +1,22 @@ -using System.Linq; +using System.Linq; using SharpCompress.Common; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public static class IArchiveExtensions { - public static class IArchiveExtensions + /// + /// Extract to specific directory, retaining filename + /// + public static void WriteToDirectory( + this IArchive archive, + string destinationDirectory, + ExtractionOptions? options = null + ) { - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteToDirectory(this IArchive archive, string destinationDirectory, - ExtractionOptions? options = null) + foreach (var entry in archive.Entries.Where(x => !x.IsDirectory)) { - foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory)) - { - entry.WriteToDirectory(destinationDirectory, options); - } + entry.WriteToDirectory(destinationDirectory, options); } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Archives/IArchiveExtractionListener.cs b/src/SharpCompress/Archives/IArchiveExtractionListener.cs index 9ce07e8a..7bc2ef34 100644 --- a/src/SharpCompress/Archives/IArchiveExtractionListener.cs +++ b/src/SharpCompress/Archives/IArchiveExtractionListener.cs @@ -1,11 +1,10 @@ -using SharpCompress.Common; +using SharpCompress.Common; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +internal interface IArchiveExtractionListener : IExtractionListener { - internal interface IArchiveExtractionListener : IExtractionListener - { - void EnsureEntriesLoaded(); - void FireEntryExtractionBegin(IArchiveEntry entry); - void FireEntryExtractionEnd(IArchiveEntry entry); - } -} \ No newline at end of file + void EnsureEntriesLoaded(); + void FireEntryExtractionBegin(IArchiveEntry entry); + void FireEntryExtractionEnd(IArchiveEntry entry); +} diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs index 98014688..370e5c9f 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -1,40 +1,35 @@ -using System; -using System.Collections.Generic; using System.IO; - -using SharpCompress.Common; using SharpCompress.Factories; using SharpCompress.Readers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +/// +/// Represents a factory used to identify and open archives. +/// +/// +/// Currently implemented by:
+/// +/// +/// +/// +/// +/// +/// +///
+public interface IArchiveFactory : IFactory { /// - /// Represents a factory used to identify and open archives. + /// Opens an Archive for random access. /// - /// - /// Currently implemented by:
- /// - /// - /// - /// - /// - /// - /// - ///
- public interface IArchiveFactory : IFactory - { - /// - /// Opens an Archive for random access. - /// - /// An open, readable and seekable stream. - /// reading options. - IArchive Open(Stream stream, ReaderOptions? readerOptions = null); + /// An open, readable and seekable stream. + /// reading options. + IArchive Open(Stream stream, ReaderOptions? readerOptions = null); - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// the file to open. - /// reading options. - IArchive Open(System.IO.FileInfo fileInfo, ReaderOptions? readerOptions = null); - } + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// the file to open. + /// reading options. + IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null); } diff --git a/src/SharpCompress/Archives/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs index 6ba39280..c26b649f 100644 --- a/src/SharpCompress/Archives/IMultiArchiveFactory.cs +++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs @@ -1,40 +1,36 @@ -using System; using System.Collections.Generic; using System.IO; - -using SharpCompress.Common; using SharpCompress.Factories; using SharpCompress.Readers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +/// +/// Represents a factory used to identify and open archives. +/// +/// +/// Implemented by:
+/// +/// +/// +/// +/// +/// +/// +///
+public interface IMultiArchiveFactory : IFactory { /// - /// Represents a factory used to identify and open archives. + /// Constructor with IEnumerable FileInfo objects, multi and split support. /// - /// - /// Implemented by:
- /// - /// - /// - /// - /// - /// - /// - ///
- public interface IMultiArchiveFactory : IFactory - { - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// reading options. - IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null); + /// + /// reading options. + IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null); - /// - /// Constructor with IEnumerable Stream objects, multi and split support. - /// - /// - /// reading options. - IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null); - } + /// + /// Constructor with IEnumerable Stream objects, multi and split support. + /// + /// + /// reading options. + IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null); } diff --git a/src/SharpCompress/Archives/IWritableArchive.cs b/src/SharpCompress/Archives/IWritableArchive.cs index 3b1b0328..37b84aa0 100644 --- a/src/SharpCompress/Archives/IWritableArchive.cs +++ b/src/SharpCompress/Archives/IWritableArchive.cs @@ -2,20 +2,25 @@ using System; using System.IO; using SharpCompress.Writers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public interface IWritableArchive : IArchive { - public interface IWritableArchive : IArchive - { - void RemoveEntry(IArchiveEntry entry); + void RemoveEntry(IArchiveEntry entry); - IArchiveEntry AddEntry(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null); + IArchiveEntry AddEntry( + string key, + Stream source, + bool closeStream, + long size = 0, + DateTime? modified = null + ); - void SaveTo(Stream stream, WriterOptions options); + void SaveTo(Stream stream, WriterOptions options); - /// - /// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended. - /// - /// IDisposeable to resume entry rebuilding - IDisposable PauseEntryRebuilding(); - } -} \ No newline at end of file + /// + /// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended. + /// + /// IDisposeable to resume entry rebuilding + IDisposable PauseEntryRebuilding(); +} diff --git a/src/SharpCompress/Archives/IWritableArchiveEntry.cs b/src/SharpCompress/Archives/IWritableArchiveEntry.cs index 044eb1c7..b4ed22c1 100644 --- a/src/SharpCompress/Archives/IWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/IWritableArchiveEntry.cs @@ -1,9 +1,8 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +internal interface IWritableArchiveEntry { - internal interface IWritableArchiveEntry - { - Stream Stream { get; } - } -} \ No newline at end of file + Stream Stream { get; } +} diff --git a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs index 468622a7..8f531d41 100644 --- a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs @@ -2,56 +2,85 @@ using System.IO; using SharpCompress.Writers; -namespace SharpCompress.Archives +namespace SharpCompress.Archives; + +public static class IWritableArchiveExtensions { - public static class IWritableArchiveExtensions + public static void AddEntry( + this IWritableArchive writableArchive, + string entryPath, + string filePath + ) { - public static void AddEntry(this IWritableArchive writableArchive, - string entryPath, string filePath) + var fileInfo = new FileInfo(filePath); + if (!fileInfo.Exists) { - var fileInfo = new FileInfo(filePath); - if (!fileInfo.Exists) - { - throw new FileNotFoundException("Could not AddEntry: " + filePath); - } - writableArchive.AddEntry(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length, - fileInfo.LastWriteTime); + throw new FileNotFoundException("Could not AddEntry: " + filePath); } + writableArchive.AddEntry( + entryPath, + new FileInfo(filePath).OpenRead(), + true, + fileInfo.Length, + fileInfo.LastWriteTime + ); + } - public static void SaveTo(this IWritableArchive writableArchive, string filePath, WriterOptions options) - { - writableArchive.SaveTo(new FileInfo(filePath), options); - } + public static void SaveTo( + this IWritableArchive writableArchive, + string filePath, + WriterOptions options + ) => writableArchive.SaveTo(new FileInfo(filePath), options); - public static void SaveTo(this IWritableArchive writableArchive, FileInfo fileInfo, WriterOptions options) - { - using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write)) - { - writableArchive.SaveTo(stream, options); - } - } + public static void SaveTo( + this IWritableArchive writableArchive, + FileInfo fileInfo, + WriterOptions options + ) + { + using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); + writableArchive.SaveTo(stream, options); + } - public static void AddAllFromDirectory( - this IWritableArchive writableArchive, - string filePath, string searchPattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories) + public static void AddAllFromDirectory( + this IWritableArchive writableArchive, + string filePath, + string searchPattern = "*.*", + SearchOption searchOption = SearchOption.AllDirectories + ) + { + using (writableArchive.PauseEntryRebuilding()) { - using (writableArchive.PauseEntryRebuilding()) + foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)) { - foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)) - { - var fileInfo = new FileInfo(path); - writableArchive.AddEntry(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length, - fileInfo.LastWriteTime); - } + var fileInfo = new FileInfo(path); + writableArchive.AddEntry( + path.Substring(filePath.Length), + fileInfo.OpenRead(), + true, + fileInfo.Length, + fileInfo.LastWriteTime + ); } } - public static IArchiveEntry AddEntry(this IWritableArchive writableArchive, string key, FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - throw new ArgumentException("FileInfo does not exist."); - } - return writableArchive.AddEntry(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime); - } } -} \ No newline at end of file + + public static IArchiveEntry AddEntry( + this IWritableArchive writableArchive, + string key, + FileInfo fileInfo + ) + { + if (!fileInfo.Exists) + { + throw new ArgumentException("FileInfo does not exist."); + } + return writableArchive.AddEntry( + key, + fileInfo.OpenRead(), + true, + fileInfo.Length, + fileInfo.LastWriteTime + ); + } +} diff --git a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs index 59c58134..4fae9f55 100644 --- a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs +++ b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs @@ -1,27 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace SharpCompress.Archives; -namespace SharpCompress.Archives +/// +/// Decorator for used to declare an archive format as able to create writeable archives +/// +/// +/// Implemented by:
+/// +/// +/// +/// +/// +public interface IWriteableArchiveFactory : Factories.IFactory { /// - /// Decorator for used to declare an archive format as able to create writeable archives + /// Creates a new, empty archive, ready to be written. /// - /// - /// Implemented by:
- /// - /// - /// - /// - /// - public interface IWriteableArchiveFactory : Factories.IFactory - { - /// - /// Creates a new, empty archive, ready to be written. - /// - /// - IWritableArchive CreateWriteableArchive(); - } + /// + IWritableArchive CreateWriteableArchive(); } diff --git a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs index 1afba647..3e576db3 100644 --- a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs +++ b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs @@ -6,39 +6,33 @@ using SharpCompress.Common.Rar.Headers; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +/// +/// A rar part based on a FileInfo object +/// +internal class FileInfoRarArchiveVolume : RarVolume { - /// - /// A rar part based on a FileInfo object - /// - internal class FileInfoRarArchiveVolume : RarVolume + internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index = 0) + : base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index) { - internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index = 0) - : base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index) - { - FileInfo = fileInfo; - FileParts = GetVolumeFileParts().ToArray().ToReadOnly(); - } - - private static ReaderOptions FixOptions(ReaderOptions options) - { - //make sure we're closing streams with fileinfo - options.LeaveStreamOpen = false; - return options; - } - - internal ReadOnlyCollection FileParts { get; } - - internal FileInfo FileInfo { get; } - - internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) - { - return new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo); - } - - internal override IEnumerable ReadFileParts() - { - return FileParts; - } + FileInfo = fileInfo; + FileParts = GetVolumeFileParts().ToArray().ToReadOnly(); } + + private static ReaderOptions FixOptions(ReaderOptions options) + { + //make sure we're closing streams with fileinfo + options.LeaveStreamOpen = false; + return options; + } + + internal ReadOnlyCollection FileParts { get; } + + internal FileInfo FileInfo { get; } + + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) => + new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo); + + internal override IEnumerable ReadFileParts() => FileParts; } diff --git a/src/SharpCompress/Archives/Rar/FileInfoRarFilePart.cs b/src/SharpCompress/Archives/Rar/FileInfoRarFilePart.cs index b2134e6d..b5416f9e 100644 --- a/src/SharpCompress/Archives/Rar/FileInfoRarFilePart.cs +++ b/src/SharpCompress/Archives/Rar/FileInfoRarFilePart.cs @@ -1,25 +1,20 @@ using System.IO; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +internal sealed class FileInfoRarFilePart : SeekableFilePart { - internal sealed class FileInfoRarFilePart : SeekableFilePart - { - internal FileInfoRarFilePart(FileInfoRarArchiveVolume volume, string? password, MarkHeader mh, FileHeader fh, FileInfo fi) - : base(mh, fh, volume.Index, volume.Stream, password) - { - FileInfo = fi; - } + internal FileInfoRarFilePart( + FileInfoRarArchiveVolume volume, + string? password, + MarkHeader mh, + FileHeader fh, + FileInfo fi + ) : base(mh, fh, volume.Index, volume.Stream, password) => FileInfo = fi; - internal FileInfo FileInfo { get; } + internal FileInfo FileInfo { get; } - internal override string FilePartName - { - get - { - return "Rar File: " + FileInfo.FullName - + " File Entry: " + FileHeader.FileName; - } - } - } + internal override string FilePartName => + "Rar File: " + FileInfo.FullName + " File Entry: " + FileHeader.FileName; } diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs b/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs index 7eefef9c..bf6f0cd4 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs @@ -1,23 +1,18 @@ using System.Linq; -namespace SharpCompress.Archives.Rar -{ - public static class RarArchiveExtensions - { - /// - /// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing. - /// - public static bool IsFirstVolume(this RarArchive archive) - { - return archive.Volumes.First().IsFirstVolume; - } +namespace SharpCompress.Archives.Rar; - /// - /// RarArchive is part of a multi-part archive. - /// - public static bool IsMultipartVolume(this RarArchive archive) - { - return archive.Volumes.First().IsMultiVolume; - } - } -} \ No newline at end of file +public static class RarArchiveExtensions +{ + /// + /// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing. + /// + public static bool IsFirstVolume(this RarArchive archive) => + archive.Volumes.First().IsFirstVolume; + + /// + /// RarArchive is part of a multi-part archive. + /// + public static bool IsMultipartVolume(this RarArchive archive) => + archive.Volumes.First().IsMultiVolume; +} diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index 6d43a46b..48f4ef45 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -9,149 +9,168 @@ using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Rar; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +public class RarArchive : AbstractArchive { - public class - RarArchive : AbstractArchive + internal Lazy UnpackV2017 { get; } = + new Lazy(() => new Compressors.Rar.UnpackV2017.Unpack()); + internal Lazy UnpackV1 { get; } = + new Lazy(() => new Compressors.Rar.UnpackV1.Unpack()); + + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal RarArchive(SourceStream srcStream) : base(ArchiveType.Rar, srcStream) { } + + protected override IEnumerable LoadEntries(IEnumerable volumes) => + RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions); + + protected override IEnumerable LoadVolumes(SourceStream srcStream) { - internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); - internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); - - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// - internal RarArchive(SourceStream srcStream) - : base(ArchiveType.Rar, srcStream) + SrcStream.LoadAllParts(); //request all streams + var streams = SrcStream.Streams.ToArray(); + var idx = 0; + if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split { - } + SrcStream.IsVolumes = true; + streams[1].Position = 0; + SrcStream.Position = 0; - protected override IEnumerable LoadEntries(IEnumerable volumes) + return srcStream.Streams.Select( + a => new StreamRarArchiveVolume(a, ReaderOptions, idx++) + ); + } + else //split mode or single file { - return RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions); + return new StreamRarArchiveVolume(SrcStream, ReaderOptions, idx++).AsEnumerable(); } - - protected override IEnumerable LoadVolumes(SourceStream srcStream) - { - base.SrcStream.LoadAllParts(); //request all streams - Stream[] streams = base.SrcStream.Streams.ToArray(); - int idx = 0; - if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split - { - base.SrcStream.IsVolumes = true; - streams[1].Position = 0; - base.SrcStream.Position = 0; - - return srcStream.Streams.Select(a => new StreamRarArchiveVolume(a, ReaderOptions, idx++)); - } - else //split mode or single file - return new StreamRarArchiveVolume(base.SrcStream, ReaderOptions, idx++).AsEnumerable(); - } - - protected override IReader CreateReaderForSolidExtraction() - { - var stream = Volumes.First().Stream; - stream.Position = 0; - return RarReader.Open(stream, ReaderOptions); - } - - public override bool IsSolid => Volumes.First().IsSolidArchive; - - public virtual int MinVersion => Volumes.First().MinVersion; - public virtual int MaxVersion => Volumes.First().MaxVersion; - - #region Creation - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static RarArchive Open(string filePath, ReaderOptions? options = null) - { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - FileInfo fileInfo = new FileInfo(filePath); - return new RarArchive(new SourceStream(fileInfo, i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), options ?? new ReaderOptions())); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - fileInfo.CheckNotNull(nameof(fileInfo)); - return new RarArchive(new SourceStream(fileInfo, i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), options ?? new ReaderOptions())); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static RarArchive Open(Stream stream, ReaderOptions? options = null) - { - stream.CheckNotNull(nameof(stream)); - return new RarArchive(new SourceStream(stream, i => null, options ?? new ReaderOptions())); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static RarArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) - { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - return new RarArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions())); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static RarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.CheckNotNull(nameof(streams)); - Stream[] strms = streams.ToArray(); - return new RarArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions())); - } - - - public static bool IsRarFile(string filePath) - { - return IsRarFile(new FileInfo(filePath)); - } - - public static bool IsRarFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using (Stream stream = fileInfo.OpenRead()) - { - return IsRarFile(stream); - } - } - - public static bool IsRarFile(Stream stream, ReaderOptions? options = null) - { - try - { - MarkHeader.Read(stream, true, false); - return true; - } - catch - { - return false; - } - } - - #endregion } + + protected override IReader CreateReaderForSolidExtraction() + { + var stream = Volumes.First().Stream; + stream.Position = 0; + return RarReader.Open(stream, ReaderOptions); + } + + public override bool IsSolid => Volumes.First().IsSolidArchive; + + public virtual int MinVersion => Volumes.First().MinVersion; + public virtual int MaxVersion => Volumes.First().MaxVersion; + + #region Creation + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static RarArchive Open(string filePath, ReaderOptions? options = null) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + var fileInfo = new FileInfo(filePath); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null) + { + fileInfo.CheckNotNull(nameof(fileInfo)); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + /// + /// Takes a seekable Stream as a source + /// + /// + /// + public static RarArchive Open(Stream stream, ReaderOptions? options = null) + { + stream.CheckNotNull(nameof(stream)); + return new RarArchive(new SourceStream(stream, i => null, options ?? new ReaderOptions())); + } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static RarArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new RarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static RarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.CheckNotNull(nameof(streams)); + var strms = streams.ToArray(); + return new RarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); + + public static bool IsRarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsRarFile(stream); + } + + public static bool IsRarFile(Stream stream, ReaderOptions? options = null) + { + try + { + MarkHeader.Read(stream, true, false); + return true; + } + catch + { + return false; + } + } + + #endregion } diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index fb2ba0f8..5885210a 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,82 +7,95 @@ using SharpCompress.Common.Rar.Headers; using SharpCompress.Compressors.Rar; using SharpCompress.Readers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +public class RarArchiveEntry : RarEntry, IArchiveEntry { - public class RarArchiveEntry : RarEntry, IArchiveEntry + private readonly ICollection parts; + private readonly RarArchive archive; + private readonly ReaderOptions readerOptions; + + internal RarArchiveEntry( + RarArchive archive, + IEnumerable parts, + ReaderOptions readerOptions + ) { - private readonly ICollection parts; - private readonly RarArchive archive; - private readonly ReaderOptions readerOptions; + this.parts = parts.ToList(); + this.archive = archive; + this.readerOptions = readerOptions; + IsSolid = FileHeader.IsSolid; + } - internal RarArchiveEntry(RarArchive archive, IEnumerable parts, ReaderOptions readerOptions) + public override CompressionType CompressionType => CompressionType.Rar; + + public IArchive Archive => archive; + + internal override IEnumerable Parts => parts.Cast(); + + internal override FileHeader FileHeader => parts.First().FileHeader; + + public override long Crc + { + get { - this.parts = parts.ToList(); - this.archive = archive; - this.readerOptions = readerOptions; - this.IsSolid = this.FileHeader.IsSolid; + CheckIncomplete(); + return parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc; + } + } + + public override long Size + { + get + { + CheckIncomplete(); + return parts.First().FileHeader.UncompressedSize; + } + } + + public override long CompressedSize + { + get + { + CheckIncomplete(); + return parts.Aggregate(0L, (total, fp) => total + fp.FileHeader.CompressedSize); + } + } + + public Stream OpenEntryStream() + { + if (IsRarV3) + { + return new RarStream( + archive.UnpackV1.Value, + FileHeader, + new MultiVolumeReadOnlyStream(Parts.Cast(), archive) + ); } - public override CompressionType CompressionType => CompressionType.Rar; + return new RarStream( + archive.UnpackV2017.Value, + FileHeader, + new MultiVolumeReadOnlyStream(Parts.Cast(), archive) + ); + } - public IArchive Archive => archive; - - internal override IEnumerable Parts => parts.Cast(); - - internal override FileHeader FileHeader => parts.First().FileHeader; - - public override long Crc + public bool IsComplete + { + get { - get - { - CheckIncomplete(); - return parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc; - } + var headers = parts.Select(x => x.FileHeader); + return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter; } + } - public override long Size + private void CheckIncomplete() + { + if (!readerOptions.DisableCheckIncomplete && !IsComplete) { - get - { - CheckIncomplete(); - return parts.First().FileHeader.UncompressedSize; - } - } - - public override long CompressedSize - { - get - { - CheckIncomplete(); - return parts.Aggregate(0L, (total, fp) => total + fp.FileHeader.CompressedSize); - } - } - - public Stream OpenEntryStream() - { - if (IsRarV3) - { - return new RarStream(archive.UnpackV1.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); - } - - return new RarStream(archive.UnpackV2017.Value, FileHeader, new MultiVolumeReadOnlyStream(Parts.Cast(), archive)); - } - - public bool IsComplete - { - get - { - var headers = parts.Select(x => x.FileHeader); - return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter; - } - } - - private void CheckIncomplete() - { - if (!readerOptions.DisableCheckIncomplete && !IsComplete) - { - throw new IncompleteArchiveException("ArchiveEntry is incomplete and cannot perform this operation."); - } + throw new IncompleteArchiveException( + "ArchiveEntry is incomplete and cannot perform this operation." + ); } } } diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs index 2d471c18..ec4ace7c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntryFactory.cs @@ -1,49 +1,52 @@ -using System.Collections.Generic; +using System.Collections.Generic; using SharpCompress.Common.Rar; using SharpCompress.Readers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +internal static class RarArchiveEntryFactory { - internal static class RarArchiveEntryFactory + private static IEnumerable GetFileParts(IEnumerable parts) { - private static IEnumerable GetFileParts(IEnumerable parts) + foreach (var rarPart in parts) { - foreach (RarVolume rarPart in parts) + foreach (var fp in rarPart.ReadFileParts()) { - foreach (RarFilePart fp in rarPart.ReadFileParts()) - { - yield return fp; - } - } - } - - private static IEnumerable> GetMatchedFileParts(IEnumerable parts) - { - var groupedParts = new List(); - foreach (RarFilePart fp in GetFileParts(parts)) - { - groupedParts.Add(fp); - - if (!fp.FileHeader.IsSplitAfter) - { - yield return groupedParts; - groupedParts = new List(); - } - } - if (groupedParts.Count > 0) - { - yield return groupedParts; - } - } - - internal static IEnumerable GetEntries(RarArchive archive, - IEnumerable rarParts, - ReaderOptions readerOptions) - { - foreach (var groupedParts in GetMatchedFileParts(rarParts)) - { - yield return new RarArchiveEntry(archive, groupedParts, readerOptions); + yield return fp; } } } -} \ No newline at end of file + + private static IEnumerable> GetMatchedFileParts( + IEnumerable parts + ) + { + var groupedParts = new List(); + foreach (var fp in GetFileParts(parts)) + { + groupedParts.Add(fp); + + if (!fp.FileHeader.IsSplitAfter) + { + yield return groupedParts; + groupedParts = new List(); + } + } + if (groupedParts.Count > 0) + { + yield return groupedParts; + } + } + + internal static IEnumerable GetEntries( + RarArchive archive, + IEnumerable rarParts, + ReaderOptions readerOptions + ) + { + foreach (var groupedParts in GetMatchedFileParts(rarParts)) + { + yield return new RarArchiveEntry(archive, groupedParts, readerOptions); + } + } +} diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index abbd73b3..6903bc5f 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -1,40 +1,51 @@ using System; -using System.Collections.Generic; using System.IO; -using SharpCompress.Common.Rar; -using SharpCompress.Readers; -using System.Linq; -using System.Text; -using SharpCompress.Common.Rar.Headers; using System.Text.RegularExpressions; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +internal static class RarArchiveVolumeFactory { - internal static class RarArchiveVolumeFactory + internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part { - internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part + FileInfo? item = null; + + //new style rar - ..part1 | /part01 | part001 .... + Match m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase); + if (m.Success) + item = new FileInfo( + Path.Combine( + part1.DirectoryName!, + String.Concat( + m.Groups[1].Value, + (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'), + m.Groups[3].Value + ) + ) + ); + else { - FileInfo? item = null; - - //new style rar - ..part1 | /part01 | part001 .... - Match m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase); + //old style - ...rar, .r00, .r01 ... + m = Regex.Match(part1.Name, @"^(.*\.r)(ar|[0-9]+)$", RegexOptions.IgnoreCase); if (m.Success) - item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, (index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'), m.Groups[3].Value))); - else - { - //old style - ...rar, .r00, .r01 ... - m = Regex.Match(part1.Name, @"^(.*\.r)(ar|[0-9]+)$", RegexOptions.IgnoreCase); - if (m.Success) - item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, index == 0 ? "ar" : (index - 1).ToString().PadLeft(m.Groups[2].Value.Length, '0')))); - else //split .001, .002 .... - return ArchiveVolumeFactory.GetFilePart(index, part1); - } - - if (item != null && item.Exists) - return item; - - return null; //no more items + item = new FileInfo( + Path.Combine( + part1.DirectoryName!, + String.Concat( + m.Groups[1].Value, + index == 0 + ? "ar" + : (index - 1).ToString().PadLeft(m.Groups[2].Value.Length, '0') + ) + ) + ); + else //split .001, .002 .... + return ArchiveVolumeFactory.GetFilePart(index, part1); } + if (item != null && item.Exists) + return item; + + return null; //no more items } } diff --git a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs index 1e4758ad..e7247eae 100644 --- a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs +++ b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs @@ -2,30 +2,34 @@ using System.IO; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +internal class SeekableFilePart : RarFilePart { - internal class SeekableFilePart : RarFilePart + private readonly Stream stream; + private readonly string? password; + + internal SeekableFilePart( + MarkHeader mh, + FileHeader fh, + int index, + Stream stream, + string? password + ) : base(mh, fh, index) { - private readonly Stream stream; - private readonly string? password; - - internal SeekableFilePart(MarkHeader mh, FileHeader fh, int index, Stream stream, string? password) - : base(mh, fh, index) - { - this.stream = stream; - this.password = password; - } - - internal override Stream GetCompressedStream() - { - stream.Position = FileHeader.DataStartPosition; - if (FileHeader.R4Salt != null) - { - return new RarCryptoWrapper(stream, password!, FileHeader.R4Salt); - } - return stream; - } - - internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName; + this.stream = stream; + this.password = password; } + + internal override Stream GetCompressedStream() + { + stream.Position = FileHeader.DataStartPosition; + if (FileHeader.R4Salt != null) + { + return new RarCryptoWrapper(stream, password!, FileHeader.R4Salt); + } + return stream; + } + + internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName; } diff --git a/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs b/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs index 68def3f7..3eb5095c 100644 --- a/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs +++ b/src/SharpCompress/Archives/Rar/StreamRarArchiveVolume.cs @@ -5,23 +5,15 @@ using SharpCompress.Common.Rar.Headers; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Archives.Rar +namespace SharpCompress.Archives.Rar; + +internal class StreamRarArchiveVolume : RarVolume { - internal class StreamRarArchiveVolume : RarVolume - { - internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index = 0) - : base(StreamingMode.Seekable, stream, options, index) - { - } + internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index = 0) + : base(StreamingMode.Seekable, stream, options, index) { } - internal override IEnumerable ReadFileParts() - { - return GetVolumeFileParts(); - } + internal override IEnumerable ReadFileParts() => GetVolumeFileParts(); - internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) - { - return new SeekableFilePart(markHeader, fileHeader, this.Index, Stream, ReaderOptions.Password); - } - } + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) => + new SeekableFilePart(markHeader, fileHeader, Index, Stream, ReaderOptions.Password); } diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 276f1399..8bb782f0 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -10,239 +10,254 @@ using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Archives.SevenZip +namespace SharpCompress.Archives.SevenZip; + +public class SevenZipArchive : AbstractArchive { - public class SevenZipArchive : AbstractArchive + private ArchiveDatabase database; + + /// + /// Constructor expects a filepath to an existing file. + /// + /// + /// + public static SevenZipArchive Open(string filePath, ReaderOptions readerOptions = null) { - private ArchiveDatabase database; - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static SevenZipArchive Open(string filePath, ReaderOptions readerOptions = null) - { - filePath.CheckNotNullOrEmpty("filePath"); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } + filePath.CheckNotNullOrEmpty("filePath"); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions readerOptions = null) - { - fileInfo.CheckNotNull("fileInfo"); - return new SevenZipArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions readerOptions = null) + { + fileInfo.CheckNotNull("fileInfo"); + return new SevenZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static SevenZipArchive Open(IEnumerable fileInfos, ReaderOptions readerOptions = null) - { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - return new SevenZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static SevenZipArchive Open( + IEnumerable fileInfos, + ReaderOptions readerOptions = null + ) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new SevenZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static SevenZipArchive Open(IEnumerable streams, ReaderOptions readerOptions = null) - { - streams.CheckNotNull(nameof(streams)); - Stream[] strms = streams.ToArray(); - return new SevenZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static SevenZipArchive Open( + IEnumerable streams, + ReaderOptions readerOptions = null + ) + { + streams.CheckNotNull(nameof(streams)); + var strms = streams.ToArray(); + return new SevenZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static SevenZipArchive Open(Stream stream, ReaderOptions readerOptions = null) - { - stream.CheckNotNull("stream"); - return new SevenZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())); - } + /// + /// Takes a seekable Stream as a source + /// + /// + /// + public static SevenZipArchive Open(Stream stream, ReaderOptions readerOptions = null) + { + stream.CheckNotNull("stream"); + return new SevenZipArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// - internal SevenZipArchive(SourceStream srcStream) - : base(ArchiveType.SevenZip, srcStream) - { - } + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal SevenZipArchive(SourceStream srcStream) : base(ArchiveType.SevenZip, srcStream) { } - protected override IEnumerable LoadVolumes(SourceStream srcStream) - { - base.SrcStream.LoadAllParts(); //request all streams - int idx = 0; - return new SevenZipVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported - } + protected override IEnumerable LoadVolumes(SourceStream srcStream) + { + SrcStream.LoadAllParts(); //request all streams + var idx = 0; + return new SevenZipVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported + } - public static bool IsSevenZipFile(string filePath) - { - return IsSevenZipFile(new FileInfo(filePath)); - } + public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); - public static bool IsSevenZipFile(FileInfo fileInfo) + public static bool IsSevenZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) { - if (!fileInfo.Exists) + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsSevenZipFile(stream); + } + + internal SevenZipArchive() : base(ArchiveType.SevenZip) { } + + protected override IEnumerable LoadEntries( + IEnumerable volumes + ) + { + var stream = volumes.Single().Stream; + LoadFactory(stream); + var entries = new SevenZipArchiveEntry[database._files.Count]; + for (var i = 0; i < database._files.Count; i++) + { + var file = database._files[i]; + entries[i] = new SevenZipArchiveEntry( + this, + new SevenZipFilePart(stream, database, i, file, ReaderOptions.ArchiveEncoding) + ); + } + foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder)) + { + var isSolid = false; + foreach (var entry in group) { - return false; - } - using (Stream stream = fileInfo.OpenRead()) - { - return IsSevenZipFile(stream); + entry.IsSolid = isSolid; + isSolid = true; //mark others in this group as solid - same as rar behaviour. } } - internal SevenZipArchive() - : base(ArchiveType.SevenZip) + return entries; + } + + private void LoadFactory(Stream stream) + { + if (database is null) { - } - - protected override IEnumerable LoadEntries(IEnumerable volumes) - { - var stream = volumes.Single().Stream; - LoadFactory(stream); - var entries = new SevenZipArchiveEntry[database._files.Count]; - for (int i = 0; i < database._files.Count; i++) - { - var file = database._files[i]; - entries[i] = new SevenZipArchiveEntry(this, new SevenZipFilePart(stream, database, i, file, ReaderOptions.ArchiveEncoding)); - } - foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder)) - { - var isSolid = false; - foreach (var entry in group) - { - entry.IsSolid = isSolid; - isSolid = true; //mark others in this group as solid - same as rar behaviour. - } - } - - return entries; - } - - private void LoadFactory(Stream stream) - { - if (database is null) - { - stream.Position = 0; - var reader = new ArchiveReader(); - reader.Open(stream); - database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password)); - } - } - - public static bool IsSevenZipFile(Stream stream) - { - try - { - return SignatureMatch(stream); - } - catch - { - return false; - } - } - - private static ReadOnlySpan SIGNATURE => new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; - - private static bool SignatureMatch(Stream stream) - { - BinaryReader reader = new BinaryReader(stream); - ReadOnlySpan signatureBytes = reader.ReadBytes(6); - return signatureBytes.SequenceEqual(SIGNATURE); - } - - protected override IReader CreateReaderForSolidExtraction() - { - return new SevenZipReader(ReaderOptions, this); - } - - public override bool IsSolid { get { return Entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder).Count() > 1; } } - - public override long TotalSize - { - get - { - int i = Entries.Count; - return database._packSizes.Aggregate(0L, (total, packSize) => total + packSize); - } - } - - private sealed class SevenZipReader : AbstractReader - { - private readonly SevenZipArchive archive; - private CFolder currentFolder; - private Stream currentStream; - private CFileItem currentItem; - - internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive) - : base(readerOptions, ArchiveType.SevenZip) - { - this.archive = archive; - } - - public override SevenZipVolume Volume => archive.Volumes.Single(); - - protected override IEnumerable GetEntries(Stream stream) - { - List entries = archive.Entries.ToList(); - stream.Position = 0; - foreach (var dir in entries.Where(x => x.IsDirectory)) - { - yield return dir; - } - foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder)) - { - currentFolder = group.Key; - if (group.Key is null) - { - currentStream = Stream.Null; - } - else - { - currentStream = archive.database.GetFolderStream(stream, currentFolder, new PasswordProvider(Options.Password)); - } - foreach (var entry in group) - { - currentItem = entry.FilePart.Header; - yield return entry; - } - } - } - - protected override EntryStream GetEntryStream() - { - return CreateEntryStream(new ReadOnlySubStream(currentStream, currentItem.Size)); - } - } - - private class PasswordProvider : IPasswordProvider - { - private readonly string _password; - - public PasswordProvider(string password) - { - _password = password; - } - - public string CryptoGetTextPassword() - { - return _password; - } + stream.Position = 0; + var reader = new ArchiveReader(); + reader.Open(stream); + database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password)); } } + + public static bool IsSevenZipFile(Stream stream) + { + try + { + return SignatureMatch(stream); + } + catch + { + return false; + } + } + + private static ReadOnlySpan SIGNATURE => + new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; + + private static bool SignatureMatch(Stream stream) + { + var reader = new BinaryReader(stream); + ReadOnlySpan signatureBytes = reader.ReadBytes(6); + return signatureBytes.SequenceEqual(SIGNATURE); + } + + protected override IReader CreateReaderForSolidExtraction() => + new SevenZipReader(ReaderOptions, this); + + public override bool IsSolid => + Entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder).Count() > 1; + + public override long TotalSize + { + get + { + var i = Entries.Count; + return database._packSizes.Aggregate(0L, (total, packSize) => total + packSize); + } + } + + private sealed class SevenZipReader : AbstractReader + { + private readonly SevenZipArchive archive; + private CFolder currentFolder; + private Stream currentStream; + private CFileItem currentItem; + + internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive) + : base(readerOptions, ArchiveType.SevenZip) => this.archive = archive; + + public override SevenZipVolume Volume => archive.Volumes.Single(); + + protected override IEnumerable GetEntries(Stream stream) + { + var entries = archive.Entries.ToList(); + stream.Position = 0; + foreach (var dir in entries.Where(x => x.IsDirectory)) + { + yield return dir; + } + foreach ( + var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder) + ) + { + currentFolder = group.Key; + if (group.Key is null) + { + currentStream = Stream.Null; + } + else + { + currentStream = archive.database.GetFolderStream( + stream, + currentFolder, + new PasswordProvider(Options.Password) + ); + } + foreach (var entry in group) + { + currentItem = entry.FilePart.Header; + yield return entry; + } + } + } + + protected override EntryStream GetEntryStream() => + CreateEntryStream(new ReadOnlySubStream(currentStream, currentItem.Size)); + } + + private class PasswordProvider : IPasswordProvider + { + private readonly string _password; + + public PasswordProvider(string password) => _password = password; + + public string CryptoGetTextPassword() => _password; + } } diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs index ea80b5cb..4e30f18e 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs @@ -1,28 +1,21 @@ using System.IO; using SharpCompress.Common.SevenZip; -namespace SharpCompress.Archives.SevenZip +namespace SharpCompress.Archives.SevenZip; + +public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry { - public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry - { - internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part) - : base(part) - { - Archive = archive; - } + internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part) : base(part) => + Archive = archive; - public Stream OpenEntryStream() - { - return FilePart.GetCompressedStream(); - } + public Stream OpenEntryStream() => FilePart.GetCompressedStream(); - public IArchive Archive { get; } + public IArchive Archive { get; } - public bool IsComplete => true; + public bool IsComplete => true; - /// - /// This is a 7Zip Anti item - /// - public bool IsAnti => FilePart.Header.IsAnti; - } -} \ No newline at end of file + /// + /// This is a 7Zip Anti item + /// + public bool IsAnti => FilePart.Header.IsAnti; +} diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index 51bdbc1f..218eda42 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -11,198 +11,226 @@ using SharpCompress.Readers.Tar; using SharpCompress.Writers; using SharpCompress.Writers.Tar; -namespace SharpCompress.Archives.Tar +namespace SharpCompress.Archives.Tar; + +public class TarArchive : AbstractWritableArchive { - public class TarArchive : AbstractWritableArchive + /// + /// Constructor expects a filepath to an existing file. + /// + /// + /// + public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null) { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.CheckNotNull(nameof(fileInfo)); - return new TarArchive(new SourceStream(fileInfo, i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.CheckNotNull(nameof(fileInfo)); + return new TarArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static TarArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) - { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - return new TarArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static TarArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new TarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static TarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.CheckNotNull(nameof(streams)); - Stream[] strms = streams.ToArray(); - return new TarArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions())); - } + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static TarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.CheckNotNull(nameof(streams)); + var strms = streams.ToArray(); + return new TarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.CheckNotNull(nameof(stream)); - return new TarArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())); - } + /// + /// Takes a seekable Stream as a source + /// + /// + /// + public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.CheckNotNull(nameof(stream)); + return new TarArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } - public static bool IsTarFile(string filePath) - { - return IsTarFile(new FileInfo(filePath)); - } + public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); - public static bool IsTarFile(FileInfo fileInfo) + public static bool IsTarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) { - if (!fileInfo.Exists) - { - return false; - } - using (Stream stream = fileInfo.OpenRead()) - { - return IsTarFile(stream); - } - } - - public static bool IsTarFile(Stream stream) - { - try - { - TarHeader tarHeader = new TarHeader(new ArchiveEncoding()); - bool readSucceeded = tarHeader.Read(new BinaryReader(stream)); - bool isEmptyArchive = tarHeader.Name.Length == 0 && tarHeader.Size == 0 && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); - return readSucceeded || isEmptyArchive; - } - catch - { - } return false; } + using Stream stream = fileInfo.OpenRead(); + return IsTarFile(stream); + } - protected override IEnumerable LoadVolumes(SourceStream srcStream) + public static bool IsTarFile(Stream stream) + { + try { - base.SrcStream.LoadAllParts(); //request all streams - int idx = 0; - return new TarVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported + var tarHeader = new TarHeader(new ArchiveEncoding()); + var readSucceeded = tarHeader.Read(new BinaryReader(stream)); + var isEmptyArchive = + tarHeader.Name.Length == 0 + && tarHeader.Size == 0 + && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); + return readSucceeded || isEmptyArchive; } + catch { } + return false; + } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// - internal TarArchive(SourceStream srcStream) - : base(ArchiveType.Tar, srcStream) - { - } + protected override IEnumerable LoadVolumes(SourceStream srcStream) + { + SrcStream.LoadAllParts(); //request all streams + var idx = 0; + return new TarVolume(srcStream, ReaderOptions, idx++).AsEnumerable(); //simple single volume or split, multivolume not supported + } - internal TarArchive() - : base(ArchiveType.Tar) - { - } + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal TarArchive(SourceStream srcStream) : base(ArchiveType.Tar, srcStream) { } - protected override IEnumerable LoadEntries(IEnumerable volumes) + internal TarArchive() : base(ArchiveType.Tar) { } + + protected override IEnumerable LoadEntries(IEnumerable volumes) + { + var stream = volumes.Single().Stream; + TarHeader? previousHeader = null; + foreach ( + var header in TarHeaderFactory.ReadHeader( + StreamingMode.Seekable, + stream, + ReaderOptions.ArchiveEncoding + ) + ) { - Stream stream = volumes.Single().Stream; - TarHeader? previousHeader = null; - foreach (TarHeader? header in TarHeaderFactory.ReadHeader(StreamingMode.Seekable, stream, ReaderOptions.ArchiveEncoding)) + if (header != null) { - if (header != null) + if (header.EntryType == EntryType.LongName) { - if (header.EntryType == EntryType.LongName) + previousHeader = header; + } + else + { + if (previousHeader != null) { - previousHeader = header; - } - else - { - if (previousHeader != null) + var entry = new TarArchiveEntry( + this, + new TarFilePart(previousHeader, stream), + CompressionType.None + ); + + var oldStreamPos = stream.Position; + + using (var entryStream = entry.OpenEntryStream()) { - var entry = new TarArchiveEntry(this, new TarFilePart(previousHeader, stream), - CompressionType.None); + using var memoryStream = new MemoryStream(); + entryStream.TransferTo(memoryStream); + memoryStream.Position = 0; + var bytes = memoryStream.ToArray(); - var oldStreamPos = stream.Position; - - using (var entryStream = entry.OpenEntryStream()) - { - using (var memoryStream = new MemoryStream()) - { - entryStream.TransferTo(memoryStream); - memoryStream.Position = 0; - var bytes = memoryStream.ToArray(); - - header.Name = ReaderOptions.ArchiveEncoding.Decode(bytes).TrimNulls(); - } - } - - stream.Position = oldStreamPos; - - previousHeader = null; + header.Name = ReaderOptions.ArchiveEncoding.Decode(bytes).TrimNulls(); } - yield return new TarArchiveEntry(this, new TarFilePart(header, stream), CompressionType.None); + + stream.Position = oldStreamPos; + + previousHeader = null; } + yield return new TarArchiveEntry( + this, + new TarFilePart(header, stream), + CompressionType.None + ); } } } - - public static TarArchive Create() - { - return new TarArchive(); - } - - protected override TarArchiveEntry CreateEntryInternal(string filePath, Stream source, - long size, DateTime? modified, bool closeStream) - { - return new TarWritableArchiveEntry(this, source, CompressionType.Unknown, filePath, size, modified, - closeStream); - } - - protected override void SaveTo(Stream stream, WriterOptions options, - IEnumerable oldEntries, - IEnumerable newEntries) - { - using (var writer = new TarWriter(stream, new TarWriterOptions(options))) - { - foreach (var entry in oldEntries.Concat(newEntries) - .Where(x => !x.IsDirectory)) - { - using (var entryStream = entry.OpenEntryStream()) - { - writer.Write(entry.Key, entryStream, entry.LastModifiedTime, entry.Size); - } - } - } - } - - protected override IReader CreateReaderForSolidExtraction() - { - var stream = Volumes.Single().Stream; - stream.Position = 0; - return TarReader.Open(stream); - } + } + + public static TarArchive Create() => new TarArchive(); + + protected override TarArchiveEntry CreateEntryInternal( + string filePath, + Stream source, + long size, + DateTime? modified, + bool closeStream + ) => + new TarWritableArchiveEntry( + this, + source, + CompressionType.Unknown, + filePath, + size, + modified, + closeStream + ); + + protected override void SaveTo( + Stream stream, + WriterOptions options, + IEnumerable oldEntries, + IEnumerable newEntries + ) + { + using var writer = new TarWriter(stream, new TarWriterOptions(options)); + foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory)) + { + using var entryStream = entry.OpenEntryStream(); + writer.Write(entry.Key, entryStream, entry.LastModifiedTime, entry.Size); + } + } + + protected override IReader CreateReaderForSolidExtraction() + { + var stream = Volumes.Single().Stream; + stream.Position = 0; + return TarReader.Open(stream); } } diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs index 51a0a49b..2da84d71 100644 --- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs @@ -3,27 +3,20 @@ using System.Linq; using SharpCompress.Common; using SharpCompress.Common.Tar; -namespace SharpCompress.Archives.Tar +namespace SharpCompress.Archives.Tar; + +public class TarArchiveEntry : TarEntry, IArchiveEntry { - public class TarArchiveEntry : TarEntry, IArchiveEntry - { - internal TarArchiveEntry(TarArchive archive, TarFilePart part, CompressionType compressionType) - : base(part, compressionType) - { - Archive = archive; - } + internal TarArchiveEntry(TarArchive archive, TarFilePart part, CompressionType compressionType) + : base(part, compressionType) => Archive = archive; - public virtual Stream OpenEntryStream() - { - return Parts.Single().GetCompressedStream(); - } + public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream(); - #region IArchiveEntry Members + #region IArchiveEntry Members - public IArchive Archive { get; } + public IArchive Archive { get; } - public bool IsComplete => true; + public bool IsComplete => true; - #endregion - } -} \ No newline at end of file + #endregion +} diff --git a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs index 1f96ca3d..e110987b 100644 --- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs @@ -6,62 +6,67 @@ using System.IO; using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Archives.Tar +namespace SharpCompress.Archives.Tar; + +internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry { - internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry + private readonly bool closeStream; + private readonly Stream stream; + + internal TarWritableArchiveEntry( + TarArchive archive, + Stream stream, + CompressionType compressionType, + string path, + long size, + DateTime? lastModified, + bool closeStream + ) : base(archive, null, compressionType) { - private readonly bool closeStream; - private readonly Stream stream; + this.stream = stream; + Key = path; + Size = size; + LastModifiedTime = lastModified; + this.closeStream = closeStream; + } - internal TarWritableArchiveEntry(TarArchive archive, Stream stream, CompressionType compressionType, - string path, long size, DateTime? lastModified, bool closeStream) - : base(archive, null, compressionType) + public override long Crc => 0; + + public override string Key { get; } + + public override long CompressedSize => 0; + + public override long Size { get; } + + public override DateTime? LastModifiedTime { get; } + + 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 Parts => throw new NotImplementedException(); + Stream IWritableArchiveEntry.Stream => stream; + + public override Stream OpenEntryStream() + { + //ensure new stream is at the start, this could be reset + stream.Seek(0, SeekOrigin.Begin); + return NonDisposingStream.Create(stream); + } + + internal override void Close() + { + if (closeStream) { - this.stream = stream; - Key = path; - Size = size; - LastModifiedTime = lastModified; - this.closeStream = closeStream; - } - - public override long Crc => 0; - - public override string Key { get; } - - public override long CompressedSize => 0; - - public override long Size { get; } - - public override DateTime? LastModifiedTime { get; } - - 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 Parts => throw new NotImplementedException(); - Stream IWritableArchiveEntry.Stream => stream; - - public override Stream OpenEntryStream() - { - //ensure new stream is at the start, this could be reset - stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); - } - - internal override void Close() - { - if (closeStream) - { - stream.Dispose(); - } + stream.Dispose(); } } } diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 97f83e02..99a66161 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -12,262 +12,292 @@ using SharpCompress.Readers.Zip; using SharpCompress.Writers; using SharpCompress.Writers.Zip; -namespace SharpCompress.Archives.Zip +namespace SharpCompress.Archives.Zip; + +public class ZipArchive : AbstractWritableArchive { - public class ZipArchive : AbstractWritableArchive - { #nullable disable - private readonly SeekableZipHeaderFactory headerFactory; + private readonly SeekableZipHeaderFactory headerFactory; + #nullable enable - /// - /// Gets or sets the compression level applied to files added to the archive, - /// if the compression method is set to deflate - /// - public CompressionLevel DeflateCompressionLevel { get; set; } + /// + /// Gets or sets the compression level applied to files added to the archive, + /// if the compression method is set to deflate + /// + public CompressionLevel DeflateCompressionLevel { get; set; } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// - internal ZipArchive(SourceStream srcStream) - : base(ArchiveType.Zip, srcStream) + /// + /// Constructor with a SourceStream able to handle FileInfo and Streams. + /// + /// + /// + internal ZipArchive(SourceStream srcStream) : base(ArchiveType.Zip, srcStream) => + headerFactory = new SeekableZipHeaderFactory( + srcStream.ReaderOptions.Password, + srcStream.ReaderOptions.ArchiveEncoding + ); + + /// + /// Constructor expects a filepath to an existing file. + /// + /// + /// + public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null) + { + filePath.CheckNotNullOrEmpty(nameof(filePath)); + return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + /// + /// Constructor with a FileInfo object to an existing file. + /// + /// + /// + public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.CheckNotNull(nameof(fileInfo)); + return new ZipArchive( + new SourceStream( + fileInfo, + i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with all file parts passed in + /// + /// + /// + public static ZipArchive Open( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.CheckNotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new ZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Constructor with all stream parts passed in + /// + /// + /// + public static ZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) + { + streams.CheckNotNull(nameof(streams)); + var strms = streams.ToArray(); + return new ZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + /// + /// Takes a seekable Stream as a source + /// + /// + /// + public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) + { + stream.CheckNotNull(nameof(stream)); + return new ZipArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static bool IsZipFile(string filePath, string? password = null) => + IsZipFile(new FileInfo(filePath), password); + + public static bool IsZipFile(FileInfo fileInfo, string? password = null) + { + if (!fileInfo.Exists) { - headerFactory = new SeekableZipHeaderFactory(srcStream.ReaderOptions.Password, srcStream.ReaderOptions.ArchiveEncoding); + return false; } + using Stream stream = fileInfo.OpenRead(); + return IsZipFile(stream, password); + } - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null) + public static bool IsZipFile(Stream stream, string? password = null) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding()); + try { - filePath.CheckNotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.CheckNotNull(nameof(fileInfo)); - return new ZipArchive(new SourceStream(fileInfo, i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), readerOptions ?? new ReaderOptions())); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static ZipArchive Open(IEnumerable fileInfos, ReaderOptions? readerOptions = null) - { - fileInfos.CheckNotNull(nameof(fileInfos)); - FileInfo[] files = fileInfos.ToArray(); - return new ZipArchive(new SourceStream(files[0], i => i < files.Length ? files[i] : null, readerOptions ?? new ReaderOptions())); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static ZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.CheckNotNull(nameof(streams)); - Stream[] strms = streams.ToArray(); - return new ZipArchive(new SourceStream(strms[0], i => i < strms.Length ? strms[i] : null, readerOptions ?? new ReaderOptions())); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.CheckNotNull(nameof(stream)); - return new ZipArchive(new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())); - } - - public static bool IsZipFile(string filePath, string? password = null) - { - return IsZipFile(new FileInfo(filePath), password); - } - - public static bool IsZipFile(FileInfo fileInfo, string? password = null) - { - if (!fileInfo.Exists) + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) { return false; } - using (Stream stream = fileInfo.OpenRead()) - { - return IsZipFile(stream, password); - } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); } - - public static bool IsZipFile(Stream stream, string? password = null) + catch (CryptographicException) { - StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding()); - try + return true; + } + catch + { + return false; + } + } + + public static bool IsZipMulti(Stream stream, string? password = null) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding()); + try + { + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) { - ZipHeader? header = headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) + if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe + { + var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); + var x = z.ReadSeekableHeader(stream).FirstOrDefault(); + return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; + } + else { return false; } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); } - - public static bool IsZipMulti(Stream stream, string? password = null) + catch (CryptographicException) { - StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding()); - try - { - ZipHeader? header = headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe - { - SeekableZipHeaderFactory z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); - var x = z.ReadSeekableHeader(stream).FirstOrDefault(); - return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; - } - else - return false; - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } + return true; } - - protected override IEnumerable LoadVolumes(SourceStream srcStream) + catch { - base.SrcStream.LoadAllParts(); //request all streams - base.SrcStream.Position = 0; - - List streams = base.SrcStream.Streams.ToList(); - int idx = 0; - if (streams.Count > 1) //test part 2 - true = multipart not split - { - streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception - bool isZip = IsZipFile(streams[1], ReaderOptions.Password); - streams[1].Position -= 4; - if (isZip) - { - base.SrcStream.IsVolumes = true; - - var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end - streams.RemoveAt(0); - streams.Add(tmp); - - //streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception - return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++)); - } - } - - //split mode or single file - return new ZipVolume(base.SrcStream, ReaderOptions, idx++).AsEnumerable(); - } - - internal ZipArchive() - : base(ArchiveType.Zip) - { - } - - protected override IEnumerable LoadEntries(IEnumerable volumes) - { - var vols = volumes.ToArray(); - foreach (ZipHeader h in headerFactory.ReadSeekableHeader(vols.Last().Stream)) - { - if (h != null) - { - switch (h.ZipHeaderType) - { - case ZipHeaderType.DirectoryEntry: - { - DirectoryEntryHeader deh = (DirectoryEntryHeader)h; - Stream s; - if (deh.RelativeOffsetOfEntryHeader + deh.CompressedSize > vols[deh.DiskNumberStart].Stream.Length) - { - var v = vols.Skip(deh.DiskNumberStart).ToArray(); - s = new SourceStream(v[0].Stream, i => i < v.Length ? v[i].Stream : null, new ReaderOptions() { LeaveStreamOpen = true }); - } - else - s = vols[deh.DiskNumberStart].Stream; - yield return new ZipArchiveEntry(this, new SeekableZipFilePart(headerFactory, deh, s)); - } - break; - case ZipHeaderType.DirectoryEnd: - { - byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty(); - volumes.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes); - yield break; - } - } - } - } - } - - public void SaveTo(Stream stream) - { - SaveTo(stream, new WriterOptions(CompressionType.Deflate)); - } - - protected override void SaveTo(Stream stream, WriterOptions options, - IEnumerable oldEntries, - IEnumerable newEntries) - { - using (var writer = new ZipWriter(stream, new ZipWriterOptions(options))) - { - foreach (var entry in oldEntries.Concat(newEntries) - .Where(x => !x.IsDirectory)) - { - using (var entryStream = entry.OpenEntryStream()) - { - writer.Write(entry.Key, entryStream, entry.LastModifiedTime); - } - } - } - } - - protected override ZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, - bool closeStream) - { - return new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); - } - - public static ZipArchive Create() - { - return new ZipArchive(); - } - - protected override IReader CreateReaderForSolidExtraction() - { - var stream = Volumes.Single().Stream; - stream.Position = 0; - return ZipReader.Open(stream, ReaderOptions); + return false; } } + + protected override IEnumerable LoadVolumes(SourceStream srcStream) + { + SrcStream.LoadAllParts(); //request all streams + SrcStream.Position = 0; + + var streams = SrcStream.Streams.ToList(); + var idx = 0; + if (streams.Count > 1) //test part 2 - true = multipart not split + { + streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception + var isZip = IsZipFile(streams[1], ReaderOptions.Password); + streams[1].Position -= 4; + if (isZip) + { + SrcStream.IsVolumes = true; + + var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end + streams.RemoveAt(0); + streams.Add(tmp); + + //streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception + return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++)); + } + } + + //split mode or single file + return new ZipVolume(SrcStream, ReaderOptions, idx++).AsEnumerable(); + } + + internal ZipArchive() : base(ArchiveType.Zip) { } + + protected override IEnumerable LoadEntries(IEnumerable volumes) + { + var vols = volumes.ToArray(); + foreach (var h in headerFactory.ReadSeekableHeader(vols.Last().Stream)) + { + if (h != null) + { + switch (h.ZipHeaderType) + { + case ZipHeaderType.DirectoryEntry: + + { + var deh = (DirectoryEntryHeader)h; + Stream s; + if ( + deh.RelativeOffsetOfEntryHeader + deh.CompressedSize + > vols[deh.DiskNumberStart].Stream.Length + ) + { + var v = vols.Skip(deh.DiskNumberStart).ToArray(); + s = new SourceStream( + v[0].Stream, + i => i < v.Length ? v[i].Stream : null, + new ReaderOptions() { LeaveStreamOpen = true } + ); + } + else + { + s = vols[deh.DiskNumberStart].Stream; + } + + yield return new ZipArchiveEntry( + this, + new SeekableZipFilePart(headerFactory, deh, s) + ); + } + break; + case ZipHeaderType.DirectoryEnd: + { + var bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty(); + volumes.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes); + yield break; + } + } + } + } + } + + public void SaveTo(Stream stream) => SaveTo(stream, new WriterOptions(CompressionType.Deflate)); + + protected override void SaveTo( + Stream stream, + WriterOptions options, + IEnumerable oldEntries, + IEnumerable newEntries + ) + { + using var writer = new ZipWriter(stream, new ZipWriterOptions(options)); + foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory)) + { + using var entryStream = entry.OpenEntryStream(); + writer.Write(entry.Key, entryStream, entry.LastModifiedTime); + } + } + + protected override ZipArchiveEntry CreateEntryInternal( + string filePath, + Stream source, + long size, + DateTime? modified, + bool closeStream + ) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); + + public static ZipArchive Create() => new ZipArchive(); + + protected override IReader CreateReaderForSolidExtraction() + { + var stream = Volumes.Single().Stream; + stream.Position = 0; + return ZipReader.Open(stream, ReaderOptions); + } } diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs index 2aad807b..59e30fd8 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs @@ -2,29 +2,22 @@ using System.Linq; using SharpCompress.Common.Zip; -namespace SharpCompress.Archives.Zip +namespace SharpCompress.Archives.Zip; + +public class ZipArchiveEntry : ZipEntry, IArchiveEntry { - public class ZipArchiveEntry : ZipEntry, IArchiveEntry - { - internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part) - : base(part) - { - Archive = archive; - } + internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part) : base(part) => + Archive = archive; - public virtual Stream OpenEntryStream() - { - return Parts.Single().GetCompressedStream(); - } + public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream(); - #region IArchiveEntry Members + #region IArchiveEntry Members - public IArchive Archive { get; } + public IArchive Archive { get; } - public bool IsComplete => true; + public bool IsComplete => true; - #endregion + #endregion - public string? Comment => ((SeekableZipFilePart)Parts.Single()).Comment; - } -} \ No newline at end of file + public string? Comment => ((SeekableZipFilePart)Parts.Single()).Comment; +} diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs index 439e5a70..fdaa85b9 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs @@ -1,34 +1,35 @@ using System; -using System.Collections.Generic; using System.IO; -using SharpCompress.Common.Rar; -using SharpCompress.Readers; -using System.Linq; -using System.Text; -using SharpCompress.Common.Rar.Headers; using System.Text.RegularExpressions; -namespace SharpCompress.Archives.Zip +namespace SharpCompress.Archives.Zip; + +internal static class ZipArchiveVolumeFactory { - internal static class ZipArchiveVolumeFactory + internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part { - internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part - { - FileInfo? item = null; + FileInfo? item = null; - //load files with zip/zipx first. Swapped to end once loaded in ZipArchive - //new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc - Match m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase); - if (m.Success) - item = new FileInfo(Path.Combine(part1.DirectoryName!, String.Concat(m.Groups[1].Value, Regex.Replace(m.Groups[2].Value, @"[^xz]", ""), index.ToString().PadLeft(2, '0')))); - else //split - 001, 002 ... - return ArchiveVolumeFactory.GetFilePart(index, part1); + //load files with zip/zipx first. Swapped to end once loaded in ZipArchive + //new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc + Match m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase); + if (m.Success) + item = new FileInfo( + Path.Combine( + part1.DirectoryName!, + String.Concat( + m.Groups[1].Value, + Regex.Replace(m.Groups[2].Value, @"[^xz]", ""), + index.ToString().PadLeft(2, '0') + ) + ) + ); + else //split - 001, 002 ... + return ArchiveVolumeFactory.GetFilePart(index, part1); - if (item != null && item.Exists) - return item; - - return null; //no more items - } + if (item != null && item.Exists) + return item; + return null; //no more items } } diff --git a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs index 7a91993a..a13a3dbb 100644 --- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs @@ -4,65 +4,69 @@ using System.IO; using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Archives.Zip +namespace SharpCompress.Archives.Zip; + +internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry { - internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry + private readonly bool closeStream; + private readonly Stream stream; + private bool isDisposed; + + internal ZipWritableArchiveEntry( + ZipArchive archive, + Stream stream, + string path, + long size, + DateTime? lastModified, + bool closeStream + ) : base(archive, null) { - private readonly bool closeStream; - private readonly Stream stream; - private bool isDisposed; + this.stream = stream; + Key = path; + Size = size; + LastModifiedTime = lastModified; + this.closeStream = closeStream; + } - internal ZipWritableArchiveEntry(ZipArchive archive, Stream stream, string path, long size, - DateTime? lastModified, bool closeStream) - : base(archive, null) + public override long Crc => 0; + + public override string Key { get; } + + public override long CompressedSize => 0; + + public override long Size { get; } + + public override DateTime? LastModifiedTime { get; } + + 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 Parts => throw new NotImplementedException(); + + Stream IWritableArchiveEntry.Stream => stream; + + public override Stream OpenEntryStream() + { + //ensure new stream is at the start, this could be reset + stream.Seek(0, SeekOrigin.Begin); + return NonDisposingStream.Create(stream); + } + + internal override void Close() + { + if (closeStream && !isDisposed) { - this.stream = stream; - Key = path; - Size = size; - LastModifiedTime = lastModified; - this.closeStream = closeStream; - } - - public override long Crc => 0; - - public override string Key { get; } - - public override long CompressedSize => 0; - - public override long Size { get; } - - public override DateTime? LastModifiedTime { get; } - - 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 Parts => throw new NotImplementedException(); - - Stream IWritableArchiveEntry.Stream => stream; - - public override Stream OpenEntryStream() - { - //ensure new stream is at the start, this could be reset - stream.Seek(0, SeekOrigin.Begin); - return NonDisposingStream.Create(stream); - } - - internal override void Close() - { - if (closeStream && !isDisposed) - { - stream.Dispose(); - isDisposed = true; - } + stream.Dispose(); + isDisposed = true; } } } diff --git a/src/SharpCompress/AssemblyInfo.cs b/src/SharpCompress/AssemblyInfo.cs index 086b9adc..0270020d 100644 --- a/src/SharpCompress/AssemblyInfo.cs +++ b/src/SharpCompress/AssemblyInfo.cs @@ -1,24 +1,3 @@ -using System; -using System.Reflection; -using System.Runtime.CompilerServices; +using System; -[assembly: AssemblyTitle("SharpCompress")] -[assembly: AssemblyProduct("SharpCompress")] -[assembly: InternalsVisibleTo("SharpCompress.Test" + SharpCompress.AssemblyInfo.PublicKeySuffix)] [assembly: CLSCompliant(true)] - -namespace SharpCompress -{ - /// - /// Just a static class to house the public key, to avoid repetition. - /// - internal static class AssemblyInfo - { - internal const string PublicKeySuffix = - ",PublicKey=002400000480000094000000060200000024000052534131000400000100010059acfa17d26c44" + - "7a4d03f16eaa72c9187c04f16e6569dd168b080e39a6f5c9fd00f28c768cd8e9a089d5a0e1b34c" + - "cd971488e7afe030ce5ce8df2053cf12ec89f6d38065c434c09ee6af3ee284c5dc08f44774b679" + - "bf39298e57efe30d4b00aecf9e4f6f8448b2cb0146d8956dfcab606cc64a0ac38c60a7d78b0d65" + - "d3b98dc0"; - } -} diff --git a/src/SharpCompress/Common/ArchiveEncoding.cs b/src/SharpCompress/Common/ArchiveEncoding.cs index 81e81099..53848ef7 100644 --- a/src/SharpCompress/Common/ArchiveEncoding.cs +++ b/src/SharpCompress/Common/ArchiveEncoding.cs @@ -1,76 +1,54 @@ -using System; +using System; using System.Text; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class ArchiveEncoding { - public class ArchiveEncoding + /// + /// Default encoding to use when archive format doesn't specify one. + /// + public Encoding Default { get; set; } + + /// + /// ArchiveEncoding used by encryption schemes which don't comply with RFC 2898. + /// + public Encoding Password { get; set; } + + /// + /// Set this encoding when you want to force it for all encoding operations. + /// + public Encoding? Forced { get; set; } + + /// + /// Set this when you want to use a custom method for all decoding operations. + /// + /// string Func(bytes, index, length) + public Func? CustomDecoder { get; set; } + + public ArchiveEncoding() : this(Encoding.Default, Encoding.Default) { } + + public ArchiveEncoding(Encoding def, Encoding password) { - /// - /// Default encoding to use when archive format doesn't specify one. - /// - public Encoding Default { get; set; } - - /// - /// ArchiveEncoding used by encryption schemes which don't comply with RFC 2898. - /// - public Encoding Password { get; set; } - - /// - /// Set this encoding when you want to force it for all encoding operations. - /// - public Encoding? Forced { get; set; } - - /// - /// Set this when you want to use a custom method for all decoding operations. - /// - /// string Func(bytes, index, length) - public Func? CustomDecoder { get; set; } - - public ArchiveEncoding() - : this(Encoding.Default, Encoding.Default) - { - } - public ArchiveEncoding(Encoding def, Encoding password) - { - Default = def; - Password = password; - } + Default = def; + Password = password; + } #if !NETFRAMEWORK - static ArchiveEncoding() - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } + static ArchiveEncoding() => Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); #endif - public string Decode(byte[] bytes) - { - return Decode(bytes, 0, bytes.Length); - } + public string Decode(byte[] bytes) => Decode(bytes, 0, bytes.Length); - public string Decode(byte[] bytes, int start, int length) - { - return GetDecoder().Invoke(bytes, start, length); - } + public string Decode(byte[] bytes, int start, int length) => + GetDecoder().Invoke(bytes, start, length); - public string DecodeUTF8(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } + public string DecodeUTF8(byte[] bytes) => Encoding.UTF8.GetString(bytes, 0, bytes.Length); - public byte[] Encode(string str) - { - return GetEncoding().GetBytes(str); - } + public byte[] Encode(string str) => GetEncoding().GetBytes(str); - public Encoding GetEncoding() - { - return Forced ?? Default ?? Encoding.UTF8; - } + public Encoding GetEncoding() => Forced ?? Default ?? Encoding.UTF8; - public Func GetDecoder() - { - return CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count)); - } - } + public Func GetDecoder() => + CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count)); } diff --git a/src/SharpCompress/Common/ArchiveException.cs b/src/SharpCompress/Common/ArchiveException.cs index 18207c64..0fc98baa 100644 --- a/src/SharpCompress/Common/ArchiveException.cs +++ b/src/SharpCompress/Common/ArchiveException.cs @@ -1,12 +1,8 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class ArchiveException : Exception { - public class ArchiveException : Exception - { - public ArchiveException(string message) - : base(message) - { - } - } -} \ No newline at end of file + public ArchiveException(string message) : base(message) { } +} diff --git a/src/SharpCompress/Common/ArchiveExtractionEventArgs.cs b/src/SharpCompress/Common/ArchiveExtractionEventArgs.cs index b1c9fc75..80817748 100644 --- a/src/SharpCompress/Common/ArchiveExtractionEventArgs.cs +++ b/src/SharpCompress/Common/ArchiveExtractionEventArgs.cs @@ -1,14 +1,10 @@ using System; -namespace SharpCompress.Common -{ - public class ArchiveExtractionEventArgs : EventArgs - { - internal ArchiveExtractionEventArgs(T entry) - { - Item = entry; - } +namespace SharpCompress.Common; - public T Item { get; } - } -} \ No newline at end of file +public class ArchiveExtractionEventArgs : EventArgs +{ + internal ArchiveExtractionEventArgs(T entry) => Item = entry; + + public T Item { get; } +} diff --git a/src/SharpCompress/Common/ArchiveType.cs b/src/SharpCompress/Common/ArchiveType.cs index a0d3097a..49b35387 100644 --- a/src/SharpCompress/Common/ArchiveType.cs +++ b/src/SharpCompress/Common/ArchiveType.cs @@ -1,11 +1,10 @@ -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public enum ArchiveType { - public enum ArchiveType - { - Rar, - Zip, - Tar, - SevenZip, - GZip - } -} \ No newline at end of file + Rar, + Zip, + Tar, + SevenZip, + GZip +} diff --git a/src/SharpCompress/Common/CompressedBytesReadEventArgs.cs b/src/SharpCompress/Common/CompressedBytesReadEventArgs.cs index 322a3c64..34ca461f 100644 --- a/src/SharpCompress/Common/CompressedBytesReadEventArgs.cs +++ b/src/SharpCompress/Common/CompressedBytesReadEventArgs.cs @@ -1,23 +1,25 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public sealed class CompressedBytesReadEventArgs : EventArgs { - public sealed class CompressedBytesReadEventArgs : EventArgs + public CompressedBytesReadEventArgs( + long compressedBytesRead, + long currentFilePartCompressedBytesRead + ) { - public CompressedBytesReadEventArgs(long compressedBytesRead, long currentFilePartCompressedBytesRead) - { - CompressedBytesRead = compressedBytesRead; - CurrentFilePartCompressedBytesRead = currentFilePartCompressedBytesRead; - } - - /// - /// Compressed bytes read for the current entry - /// - public long CompressedBytesRead { get; } - - /// - /// Current file part read for Multipart files (e.g. Rar) - /// - public long CurrentFilePartCompressedBytesRead { get; } + CompressedBytesRead = compressedBytesRead; + CurrentFilePartCompressedBytesRead = currentFilePartCompressedBytesRead; } -} \ No newline at end of file + + /// + /// Compressed bytes read for the current entry + /// + public long CompressedBytesRead { get; } + + /// + /// Current file part read for Multipart files (e.g. Rar) + /// + public long CurrentFilePartCompressedBytesRead { get; } +} diff --git a/src/SharpCompress/Common/CompressionType.cs b/src/SharpCompress/Common/CompressionType.cs index 23ed354f..11263245 100644 --- a/src/SharpCompress/Common/CompressionType.cs +++ b/src/SharpCompress/Common/CompressionType.cs @@ -1,19 +1,18 @@ -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public enum CompressionType { - public enum CompressionType - { - None, - GZip, - BZip2, - PPMd, - Deflate, - Rar, - LZMA, - BCJ, - BCJ2, - LZip, - Xz, - Unknown, - Deflate64 - } -} \ No newline at end of file + None, + GZip, + BZip2, + PPMd, + Deflate, + Rar, + LZMA, + BCJ, + BCJ2, + LZip, + Xz, + Unknown, + Deflate64 +} diff --git a/src/SharpCompress/Common/CryptographicException.cs b/src/SharpCompress/Common/CryptographicException.cs index 450cd237..c20775a8 100644 --- a/src/SharpCompress/Common/CryptographicException.cs +++ b/src/SharpCompress/Common/CryptographicException.cs @@ -1,12 +1,8 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class CryptographicException : Exception { - public class CryptographicException : Exception - { - public CryptographicException(string message) - : base(message) - { - } - } -} \ No newline at end of file + public CryptographicException(string message) : base(message) { } +} diff --git a/src/SharpCompress/Common/Entry.cs b/src/SharpCompress/Common/Entry.cs index 06afcabd..85219a43 100644 --- a/src/SharpCompress/Common/Entry.cs +++ b/src/SharpCompress/Common/Entry.cs @@ -2,91 +2,89 @@ using System; using System.Collections.Generic; using System.Linq; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public abstract class Entry : IEntry { - public abstract class Entry : IEntry - { - /// - /// The File's 32 bit CRC Hash - /// - public abstract long Crc { get; } + /// + /// The File's 32 bit CRC Hash + /// + public abstract long Crc { get; } - /// - /// The string key of the file internal to the Archive. - /// - public abstract string Key { get; } + /// + /// The string key of the file internal to the Archive. + /// + public abstract string Key { get; } - /// - /// The target of a symlink entry internal to the Archive. Will be null if not a symlink. - /// - public abstract string? LinkTarget { get; } + /// + /// The target of a symlink entry internal to the Archive. Will be null if not a symlink. + /// + public abstract string? LinkTarget { get; } - /// - /// The compressed file size - /// - public abstract long CompressedSize { get; } + /// + /// The compressed file size + /// + public abstract long CompressedSize { get; } - /// - /// The compression type - /// - public abstract CompressionType CompressionType { get; } + /// + /// The compression type + /// + public abstract CompressionType CompressionType { get; } - /// - /// The uncompressed file size - /// - public abstract long Size { get; } + /// + /// The uncompressed file size + /// + public abstract long Size { get; } - /// - /// The entry last modified time in the archive, if recorded - /// - public abstract DateTime? LastModifiedTime { get; } + /// + /// The entry last modified time in the archive, if recorded + /// + public abstract DateTime? LastModifiedTime { get; } - /// - /// The entry create time in the archive, if recorded - /// - public abstract DateTime? CreatedTime { get; } + /// + /// The entry create time in the archive, if recorded + /// + public abstract DateTime? CreatedTime { get; } - /// - /// The entry last accessed time in the archive, if recorded - /// - public abstract DateTime? LastAccessedTime { get; } + /// + /// The entry last accessed time in the archive, if recorded + /// + public abstract DateTime? LastAccessedTime { get; } - /// - /// The entry time when archived, if recorded - /// - public abstract DateTime? ArchivedTime { get; } + /// + /// The entry time when archived, if recorded + /// + public abstract DateTime? ArchivedTime { get; } - /// - /// Entry is password protected and encrypted and cannot be extracted. - /// - public abstract bool IsEncrypted { get; } + /// + /// Entry is password protected and encrypted and cannot be extracted. + /// + public abstract bool IsEncrypted { get; } - /// - /// Entry is directory. - /// - public abstract bool IsDirectory { get; } + /// + /// Entry is directory. + /// + public abstract bool IsDirectory { get; } - /// - /// Entry is split among multiple volumes - /// - public abstract bool IsSplitAfter { get; } + /// + /// Entry is split among multiple volumes + /// + public abstract bool IsSplitAfter { get; } - public int VolumeIndexFirst => this.Parts?.FirstOrDefault()?.Index ?? 0; - public int VolumeIndexLast => this.Parts?.LastOrDefault()?.Index ?? 0; - /// - public override string ToString() => Key; + public int VolumeIndexFirst => Parts?.FirstOrDefault()?.Index ?? 0; + public int VolumeIndexLast => Parts?.LastOrDefault()?.Index ?? 0; - internal abstract IEnumerable Parts { get; } + /// + public override string ToString() => Key; - public bool IsSolid { get; set; } + internal abstract IEnumerable Parts { get; } - internal virtual void Close() - { - } + public bool IsSolid { get; set; } - /// - /// Entry file attribute. - /// - public virtual int? Attrib => throw new NotImplementedException(); - } + internal virtual void Close() { } + + /// + /// Entry file attribute. + /// + public virtual int? Attrib => throw new NotImplementedException(); } diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs index 43ca0537..b3e7edea 100644 --- a/src/SharpCompress/Common/EntryStream.cs +++ b/src/SharpCompress/Common/EntryStream.cs @@ -1,93 +1,86 @@ -using System; +using System; using System.IO; using SharpCompress.Readers; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class EntryStream : Stream { - public class EntryStream : Stream + private readonly IReader _reader; + private readonly Stream _stream; + private bool _completed; + private bool _isDisposed; + + internal EntryStream(IReader reader, Stream stream) { - private readonly IReader _reader; - private readonly Stream _stream; - private bool _completed; - private bool _isDisposed; + _reader = reader; + _stream = stream; + } - internal EntryStream(IReader reader, Stream stream) + /// + /// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry. + /// + public void SkipEntry() + { + this.Skip(); + _completed = true; + } + + protected override void Dispose(bool disposing) + { + if (!(_completed || _reader.Cancelled)) { - _reader = reader; - _stream = stream; + SkipEntry(); } - - /// - /// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry. - /// - public void SkipEntry() + if (_isDisposed) + { + return; + } + _isDisposed = true; + base.Dispose(disposing); + _stream.Dispose(); + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() { } + + public override long Length => _stream.Length; + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + var read = _stream.Read(buffer, offset, count); + if (read <= 0) { - this.Skip(); _completed = true; } - - protected override void Dispose(bool disposing) - { - if (!(_completed || _reader.Cancelled)) - { - SkipEntry(); - } - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - _stream.Dispose(); - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - } - - public override long Length => _stream.Length; - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - int read = _stream.Read(buffer, offset, count); - if (read <= 0) - { - _completed = true; - } - return read; - } - - public override int ReadByte() - { - int value = _stream.ReadByte(); - if (value == -1) - { - _completed = true; - } - return value; - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + return read; } -} \ No newline at end of file + + public override int ReadByte() + { + var value = _stream.ReadByte(); + if (value == -1) + { + _completed = true; + } + return value; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Common/ExtractionException.cs b/src/SharpCompress/Common/ExtractionException.cs index be5e688c..44316b73 100644 --- a/src/SharpCompress/Common/ExtractionException.cs +++ b/src/SharpCompress/Common/ExtractionException.cs @@ -1,17 +1,10 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class ExtractionException : Exception { - public class ExtractionException : Exception - { - public ExtractionException(string message) - : base(message) - { - } + public ExtractionException(string message) : base(message) { } - public ExtractionException(string message, Exception inner) - : base(message, inner) - { - } - } -} \ No newline at end of file + public ExtractionException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/SharpCompress/Common/ExtractionMethods.cs b/src/SharpCompress/Common/ExtractionMethods.cs index 51ded979..c8e5896e 100644 --- a/src/SharpCompress/Common/ExtractionMethods.cs +++ b/src/SharpCompress/Common/ExtractionMethods.cs @@ -1,104 +1,117 @@ -using System; +using System; using System.IO; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +internal static class ExtractionMethods { - internal static class ExtractionMethods + /// + /// Extract to specific directory, retaining filename + /// + public static void WriteEntryToDirectory( + IEntry entry, + string destinationDirectory, + ExtractionOptions? options, + Action write + ) { - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteEntryToDirectory(IEntry entry, - string destinationDirectory, - ExtractionOptions? options, - Action write) + string destinationFileName; + string fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory); + + //check for trailing slash. + if ( + fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1] + != Path.DirectorySeparatorChar + ) { - string destinationFileName; - string fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory); - - //check for trailing slash. - if (fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1] != Path.DirectorySeparatorChar) - { - fullDestinationDirectoryPath += Path.DirectorySeparatorChar; - } - - if (!Directory.Exists(fullDestinationDirectoryPath)) - { - throw new ExtractionException($"Directory does not exist to extract to: {fullDestinationDirectoryPath}"); - } - - options ??= new ExtractionOptions() - { - Overwrite = true - }; - - string file = Path.GetFileName(entry.Key); - if (options.ExtractFullPath) - { - string folder = Path.GetDirectoryName(entry.Key)!; - string destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder)); - - if (!Directory.Exists(destdir)) - { - if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal)) - { - throw new ExtractionException("Entry is trying to create a directory outside of the destination directory."); - } - - Directory.CreateDirectory(destdir); - } - destinationFileName = Path.Combine(destdir, file); - } - else - { - destinationFileName = Path.Combine(fullDestinationDirectoryPath, file); - - } - - if (!entry.IsDirectory) - { - destinationFileName = Path.GetFullPath(destinationFileName); - - if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal)) - { - throw new ExtractionException("Entry is trying to write a file outside of the destination directory."); - } - write(destinationFileName, options); - } - else if (options.ExtractFullPath && !Directory.Exists(destinationFileName)) - { - Directory.CreateDirectory(destinationFileName); - } + fullDestinationDirectoryPath += Path.DirectorySeparatorChar; } - public static void WriteEntryToFile(IEntry entry, string destinationFileName, - ExtractionOptions? options, - Action openAndWrite) + if (!Directory.Exists(fullDestinationDirectoryPath)) { - if (entry.LinkTarget != null) - { - if (options?.WriteSymbolicLink is null) - { - throw new ExtractionException("Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"); - } - options.WriteSymbolicLink(destinationFileName, entry.LinkTarget); - } - else - { - FileMode fm = FileMode.Create; - options ??= new ExtractionOptions() - { - Overwrite = true - }; + throw new ExtractionException( + $"Directory does not exist to extract to: {fullDestinationDirectoryPath}" + ); + } - if (!options.Overwrite) + options ??= new ExtractionOptions() { Overwrite = true }; + + string file = Path.GetFileName(entry.Key); + if (options.ExtractFullPath) + { + string folder = Path.GetDirectoryName(entry.Key)!; + string destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder)); + + if (!Directory.Exists(destdir)) + { + if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal)) { - fm = FileMode.CreateNew; + throw new ExtractionException( + "Entry is trying to create a directory outside of the destination directory." + ); } - openAndWrite(destinationFileName, fm); - entry.PreserveExtractionOptions(destinationFileName, options); + Directory.CreateDirectory(destdir); } + destinationFileName = Path.Combine(destdir, file); + } + else + { + destinationFileName = Path.Combine(fullDestinationDirectoryPath, file); + } + + if (!entry.IsDirectory) + { + destinationFileName = Path.GetFullPath(destinationFileName); + + if ( + !destinationFileName.StartsWith( + fullDestinationDirectoryPath, + StringComparison.Ordinal + ) + ) + { + throw new ExtractionException( + "Entry is trying to write a file outside of the destination directory." + ); + } + write(destinationFileName, options); + } + else if (options.ExtractFullPath && !Directory.Exists(destinationFileName)) + { + Directory.CreateDirectory(destinationFileName); } } -} \ No newline at end of file + + public static void WriteEntryToFile( + IEntry entry, + string destinationFileName, + ExtractionOptions? options, + Action openAndWrite + ) + { + if (entry.LinkTarget != null) + { + if (options?.WriteSymbolicLink is null) + { + throw new ExtractionException( + "Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null" + ); + } + options.WriteSymbolicLink(destinationFileName, entry.LinkTarget); + } + else + { + FileMode fm = FileMode.Create; + options ??= new ExtractionOptions() { Overwrite = true }; + + if (!options.Overwrite) + { + fm = FileMode.CreateNew; + } + + openAndWrite(destinationFileName, fm); + entry.PreserveExtractionOptions(destinationFileName, options); + } + } +} diff --git a/src/SharpCompress/Common/ExtractionOptions.cs b/src/SharpCompress/Common/ExtractionOptions.cs index 54dbe79f..18c0f0ac 100644 --- a/src/SharpCompress/Common/ExtractionOptions.cs +++ b/src/SharpCompress/Common/ExtractionOptions.cs @@ -1,40 +1,40 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class ExtractionOptions { - public class ExtractionOptions + /// + /// overwrite target if it exists + /// + public bool Overwrite { get; set; } + + /// + /// extract with internal directory structure + /// + public bool ExtractFullPath { get; set; } + + /// + /// preserve file time + /// + public bool PreserveFileTime { get; set; } + + /// + /// preserve windows file attributes + /// + public bool PreserveAttributes { get; set; } + + /// + /// Delegate for writing symbolic links to disk. + /// sourcePath is where the symlink is created. + /// targetPath is what the symlink refers to. + /// + public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath); + + public SymbolicLinkWriterDelegate WriteSymbolicLink = (sourcePath, targetPath) => { - /// - /// overwrite target if it exists - /// - public bool Overwrite { get; set; } - - /// - /// extract with internal directory structure - /// - public bool ExtractFullPath { get; set; } - - /// - /// preserve file time - /// - public bool PreserveFileTime { get; set; } - - /// - /// preserve windows file attributes - /// - public bool PreserveAttributes { get; set; } - - /// - /// Delegate for writing symbolic links to disk. - /// sourcePath is where the symlink is created. - /// targetPath is what the symlink refers to. - /// - public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath); - - public SymbolicLinkWriterDelegate WriteSymbolicLink = - (sourcePath, targetPath) => - { - Console.WriteLine($"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"); - }; - } -} \ No newline at end of file + Console.WriteLine( + $"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271" + ); + }; +} diff --git a/src/SharpCompress/Common/FilePart.cs b/src/SharpCompress/Common/FilePart.cs index fad184aa..3c286d54 100644 --- a/src/SharpCompress/Common/FilePart.cs +++ b/src/SharpCompress/Common/FilePart.cs @@ -1,21 +1,17 @@ using System.IO; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public abstract class FilePart { - public abstract class FilePart - { - protected FilePart(ArchiveEncoding archiveEncoding) - { - ArchiveEncoding = archiveEncoding; - } + protected FilePart(ArchiveEncoding archiveEncoding) => ArchiveEncoding = archiveEncoding; - internal ArchiveEncoding ArchiveEncoding { get; } + internal ArchiveEncoding ArchiveEncoding { get; } - internal abstract string FilePartName { get; } - public int Index { get; set; } + internal abstract string FilePartName { get; } + public int Index { get; set; } - internal abstract Stream GetCompressedStream(); - internal abstract Stream? GetRawStream(); - internal bool Skipped { get; set; } - } + internal abstract Stream GetCompressedStream(); + internal abstract Stream? GetRawStream(); + internal bool Skipped { get; set; } } diff --git a/src/SharpCompress/Common/FilePartExtractionBeginEventArgs.cs b/src/SharpCompress/Common/FilePartExtractionBeginEventArgs.cs index ee80a4ab..d5b8328c 100644 --- a/src/SharpCompress/Common/FilePartExtractionBeginEventArgs.cs +++ b/src/SharpCompress/Common/FilePartExtractionBeginEventArgs.cs @@ -1,29 +1,28 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public sealed class FilePartExtractionBeginEventArgs : EventArgs { - public sealed class FilePartExtractionBeginEventArgs : EventArgs + public FilePartExtractionBeginEventArgs(string name, long size, long compressedSize) { - public FilePartExtractionBeginEventArgs(string name, long size, long compressedSize) - { - Name = name; - Size = size; - CompressedSize = compressedSize; - } - - /// - /// File name for the part for the current entry - /// - public string Name { get; } - - /// - /// Uncompressed size of the current entry in the part - /// - public long Size { get; } - - /// - /// Compressed size of the current entry in the part - /// - public long CompressedSize { get; } + Name = name; + Size = size; + CompressedSize = compressedSize; } -} \ No newline at end of file + + /// + /// File name for the part for the current entry + /// + public string Name { get; } + + /// + /// Uncompressed size of the current entry in the part + /// + public long Size { get; } + + /// + /// Compressed size of the current entry in the part + /// + public long CompressedSize { get; } +} diff --git a/src/SharpCompress/Common/FlagUtility.cs b/src/SharpCompress/Common/FlagUtility.cs index 8a3aaee1..73215817 100644 --- a/src/SharpCompress/Common/FlagUtility.cs +++ b/src/SharpCompress/Common/FlagUtility.cs @@ -1,108 +1,86 @@ using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +internal static class FlagUtility { - internal static class FlagUtility + /// + /// Returns true if the flag is set on the specified bit field. + /// Currently only works with 32-bit bitfields. + /// + /// Enumeration with Flags attribute + /// Flagged variable + /// Flag to test + /// + public static bool HasFlag(long bitField, T flag) where T : struct => + HasFlag(bitField, flag); + + /// + /// Returns true if the flag is set on the specified bit field. + /// Currently only works with 32-bit bitfields. + /// + /// Enumeration with Flags attribute + /// Flagged variable + /// Flag to test + /// + public static bool HasFlag(ulong bitField, T flag) where T : struct => + HasFlag(bitField, flag); + + /// + /// Returns true if the flag is set on the specified bit field. + /// Currently only works with 32-bit bitfields. + /// + /// Flagged variable + /// Flag to test + /// + public static bool HasFlag(ulong bitField, ulong flag) => ((bitField & flag) == flag); + + public static bool HasFlag(short bitField, short flag) => ((bitField & flag) == flag); + + /// + /// Returns true if the flag is set on the specified bit field. + /// Currently only works with 32-bit bitfields. + /// + /// Enumeration with Flags attribute + /// Flagged variable + /// Flag to test + /// + public static bool HasFlag(T bitField, T flag) where T : struct => + HasFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag)); + + /// + /// Returns true if the flag is set on the specified bit field. + /// Currently only works with 32-bit bitfields. + /// + /// Flagged variable + /// Flag to test + /// + public static bool HasFlag(long bitField, long flag) => ((bitField & flag) == flag); + + /// + /// Sets a bit-field to either on or off for the specified flag. + /// + /// Flagged variable + /// Flag to change + /// bool + /// The flagged variable with the flag changed + public static long SetFlag(long bitField, long flag, bool on) { - /// - /// Returns true if the flag is set on the specified bit field. - /// Currently only works with 32-bit bitfields. - /// - /// Enumeration with Flags attribute - /// Flagged variable - /// Flag to test - /// - public static bool HasFlag(long bitField, T flag) - where T : struct + if (on) { - return HasFlag(bitField, flag); - } - - /// - /// Returns true if the flag is set on the specified bit field. - /// Currently only works with 32-bit bitfields. - /// - /// Enumeration with Flags attribute - /// Flagged variable - /// Flag to test - /// - public static bool HasFlag(ulong bitField, T flag) - where T : struct - { - return HasFlag(bitField, flag); - } - - /// - /// Returns true if the flag is set on the specified bit field. - /// Currently only works with 32-bit bitfields. - /// - /// Flagged variable - /// Flag to test - /// - public static bool HasFlag(ulong bitField, ulong flag) - { - return ((bitField & flag) == flag); - } - - public static bool HasFlag(short bitField, short flag) - { - return ((bitField & flag) == flag); - } - - /// - /// Returns true if the flag is set on the specified bit field. - /// Currently only works with 32-bit bitfields. - /// - /// Enumeration with Flags attribute - /// Flagged variable - /// Flag to test - /// - public static bool HasFlag(T bitField, T flag) - where T : struct - { - return HasFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag)); - } - - /// - /// Returns true if the flag is set on the specified bit field. - /// Currently only works with 32-bit bitfields. - /// - /// Flagged variable - /// Flag to test - /// - public static bool HasFlag(long bitField, long flag) - { - return ((bitField & flag) == flag); - } - - /// - /// Sets a bit-field to either on or off for the specified flag. - /// - /// Flagged variable - /// Flag to change - /// bool - /// The flagged variable with the flag changed - public static long SetFlag(long bitField, long flag, bool on) - { - if (on) - { - return bitField | flag; - } - return bitField & (~flag); - } - - /// - /// Sets a bit-field to either on or off for the specified flag. - /// - /// Enumeration with Flags attribute - /// Flagged variable - /// Flag to change - /// bool - /// The flagged variable with the flag changed - public static long SetFlag(T bitField, T flag, bool on) - where T : struct - { - return SetFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag), on); + return bitField | flag; } + return bitField & (~flag); } -} \ No newline at end of file + + /// + /// Sets a bit-field to either on or off for the specified flag. + /// + /// Enumeration with Flags attribute + /// Flagged variable + /// Flag to change + /// bool + /// The flagged variable with the flag changed + public static long SetFlag(T bitField, T flag, bool on) where T : struct => + SetFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag), on); +} diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 1204cfd7..bb9a22da 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -2,48 +2,44 @@ using System.Collections.Generic; using System.IO; -namespace SharpCompress.Common.GZip +namespace SharpCompress.Common.GZip; + +public class GZipEntry : Entry { - public class GZipEntry : Entry + private readonly GZipFilePart _filePart; + + internal GZipEntry(GZipFilePart filePart) => _filePart = filePart; + + public override CompressionType CompressionType => CompressionType.GZip; + + public override long Crc => _filePart.Crc ?? 0; + + public override string Key => _filePart.FilePartName; + + public override string? LinkTarget => null; + + public override long CompressedSize => 0; + + public override long Size => _filePart.UncompressedSize ?? 0; + + public override DateTime? LastModifiedTime => _filePart.DateModified; + + 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 Parts => _filePart.AsEnumerable(); + + internal static IEnumerable GetEntries(Stream stream, OptionsBase options) { - private readonly GZipFilePart _filePart; - - internal GZipEntry(GZipFilePart filePart) - { - _filePart = filePart; - } - - public override CompressionType CompressionType => CompressionType.GZip; - - public override long Crc => _filePart.Crc ?? 0; - - public override string Key => _filePart.FilePartName; - - public override string? LinkTarget => null; - - public override long CompressedSize => 0; - - public override long Size => _filePart.UncompressedSize ?? 0; - - public override DateTime? LastModifiedTime => _filePart.DateModified; - - 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 Parts => _filePart.AsEnumerable(); - - internal static IEnumerable GetEntries(Stream stream, OptionsBase options) - { - yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding)); - } + yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding)); } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index c6fd9849..0dd9a2d1 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -6,133 +6,125 @@ using SharpCompress.Common.Tar.Headers; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; -namespace SharpCompress.Common.GZip +namespace SharpCompress.Common.GZip; + +internal sealed class GZipFilePart : FilePart { - internal sealed class GZipFilePart : FilePart + private string? _name; + private readonly Stream _stream; + + internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding) : base(archiveEncoding) { - private string? _name; - private readonly Stream _stream; - - internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding) - : base(archiveEncoding) + _stream = stream; + ReadAndValidateGzipHeader(); + if (stream.CanSeek) { - _stream = stream; - ReadAndValidateGzipHeader(); - if (stream.CanSeek) - { - long position = stream.Position; - stream.Position = stream.Length - 8; - ReadTrailer(); - stream.Position = position; - } - EntryStartPosition = stream.Position; + var position = stream.Position; + stream.Position = stream.Length - 8; + ReadTrailer(); + stream.Position = position; + } + EntryStartPosition = stream.Position; + } + + internal long EntryStartPosition { get; } + + internal DateTime? DateModified { get; private set; } + internal uint? Crc { get; private set; } + internal uint? UncompressedSize { get; private set; } + + internal override string FilePartName => _name!; + + internal override Stream GetCompressedStream() => + new DeflateStream(_stream, CompressionMode.Decompress, CompressionLevel.Default); + + internal override Stream GetRawStream() => _stream; + + private void ReadTrailer() + { + // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 + Span trailer = stackalloc byte[8]; + var n = _stream.Read(trailer); + + Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer); + UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4)); + } + + private void ReadAndValidateGzipHeader() + { + // read the header on the first read + Span header = stackalloc byte[10]; + var n = _stream.Read(header); + + // workitem 8501: handle edge case (decompress empty stream) + if (n == 0) + { + return; } - internal long EntryStartPosition { get; } - - internal DateTime? DateModified { get; private set; } - internal uint? Crc { get; private set; } - internal uint? UncompressedSize { get; private set; } - - internal override string FilePartName => _name!; - - internal override Stream GetCompressedStream() + if (n != 10) { - return new DeflateStream(_stream, CompressionMode.Decompress, CompressionLevel.Default); + throw new ZlibException("Not a valid GZIP stream."); } - internal override Stream GetRawStream() + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) { - return _stream; + throw new ZlibException("Bad GZIP header."); } - private void ReadTrailer() + var timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4)); + DateModified = TarHeader.EPOCH.AddSeconds(timet); + if ((header[3] & 0x04) == 0x04) { - // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 - Span trailer = stackalloc byte[8]; - int n = _stream.Read(trailer); + // read and discard extra field + n = _stream.Read(header.Slice(0, 2)); // 2-byte length field - Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer); - UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4)); + var extraLength = (short)(header[0] + (header[1] * 256)); + var extra = new byte[extraLength]; + + if (!_stream.ReadFully(extra)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header."); + } + n = extraLength; } - - private void ReadAndValidateGzipHeader() + if ((header[3] & 0x08) == 0x08) { - // read the header on the first read - Span header = stackalloc byte[10]; - int n = _stream.Read(header); - - // workitem 8501: handle edge case (decompress empty stream) - if (n == 0) - { - return; - } - - if (n != 10) - { - throw new ZlibException("Not a valid GZIP stream."); - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - throw new ZlibException("Bad GZIP header."); - } - - int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4)); - DateModified = TarHeader.EPOCH.AddSeconds(timet); - if ((header[3] & 0x04) == 0x04) - { - // read and discard extra field - n = _stream.Read(header.Slice(0, 2)); // 2-byte length field - - short extraLength = (short)(header[0] + header[1] * 256); - byte[] extra = new byte[extraLength]; - - if (!_stream.ReadFully(extra)) - { - throw new ZlibException("Unexpected end-of-file reading GZIP header."); - } - n = extraLength; - } - if ((header[3] & 0x08) == 0x08) - { - _name = ReadZeroTerminatedString(_stream); - } - if ((header[3] & 0x10) == 0x010) - { - ReadZeroTerminatedString(_stream); - } - if ((header[3] & 0x02) == 0x02) - { - _stream.ReadByte(); // CRC16, ignore - } + _name = ReadZeroTerminatedString(_stream); } - - private string ReadZeroTerminatedString(Stream stream) + if ((header[3] & 0x10) == 0x010) { - Span buf1 = stackalloc byte[1]; - var list = new List(); - bool done = false; - do - { - // workitem 7740 - int n = stream.Read(buf1); - if (n != 1) - { - throw new ZlibException("Unexpected EOF reading GZIP header."); - } - if (buf1[0] == 0) - { - done = true; - } - else - { - list.Add(buf1[0]); - } - } - while (!done); - byte[] buffer = list.ToArray(); - return ArchiveEncoding.Decode(buffer); + ReadZeroTerminatedString(_stream); + } + if ((header[3] & 0x02) == 0x02) + { + _stream.ReadByte(); // CRC16, ignore } } + + private string ReadZeroTerminatedString(Stream stream) + { + Span buf1 = stackalloc byte[1]; + var list = new List(); + var done = false; + do + { + // workitem 7740 + var n = stream.Read(buf1); + if (n != 1) + { + throw new ZlibException("Unexpected EOF reading GZIP header."); + } + if (buf1[0] == 0) + { + done = true; + } + else + { + list.Add(buf1[0]); + } + } while (!done); + var buffer = list.ToArray(); + return ArchiveEncoding.Decode(buffer); + } } diff --git a/src/SharpCompress/Common/GZip/GZipVolume.cs b/src/SharpCompress/Common/GZip/GZipVolume.cs index 6d77029f..0dd9b8d9 100644 --- a/src/SharpCompress/Common/GZip/GZipVolume.cs +++ b/src/SharpCompress/Common/GZip/GZipVolume.cs @@ -1,23 +1,17 @@ using System.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.GZip +namespace SharpCompress.Common.GZip; + +public class GZipVolume : Volume { - public class GZipVolume : Volume - { - public GZipVolume(Stream stream, ReaderOptions options, int index = 0) - : base(stream, options, index) - { - } + public GZipVolume(Stream stream, ReaderOptions options, int index = 0) + : base(stream, options, index) { } - public GZipVolume(FileInfo fileInfo, ReaderOptions options) - : base(fileInfo.OpenRead(), options) - { - options.LeaveStreamOpen = false; - } + public GZipVolume(FileInfo fileInfo, ReaderOptions options) + : base(fileInfo.OpenRead(), options) => options.LeaveStreamOpen = false; - public override bool IsFirstVolume => true; + public override bool IsFirstVolume => true; - public override bool IsMultiVolume => true; - } + public override bool IsMultiVolume => true; } diff --git a/src/SharpCompress/Common/IEntry.Extensions.cs b/src/SharpCompress/Common/IEntry.Extensions.cs index 02acd59a..7e9b79a3 100644 --- a/src/SharpCompress/Common/IEntry.Extensions.cs +++ b/src/SharpCompress/Common/IEntry.Extensions.cs @@ -1,45 +1,48 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +internal static class EntryExtensions { - internal static class EntryExtensions + internal static void PreserveExtractionOptions( + this IEntry entry, + string destinationFileName, + ExtractionOptions options + ) { - internal static void PreserveExtractionOptions(this IEntry entry, string destinationFileName, - ExtractionOptions options) + if (options.PreserveFileTime || options.PreserveAttributes) { - if (options.PreserveFileTime || options.PreserveAttributes) + var nf = new FileInfo(destinationFileName); + if (!nf.Exists) { - FileInfo nf = new FileInfo(destinationFileName); - if (!nf.Exists) + return; + } + + // update file time to original packed time + if (options.PreserveFileTime) + { + if (entry.CreatedTime.HasValue) { - return; + nf.CreationTime = entry.CreatedTime.Value; } - // update file time to original packed time - if (options.PreserveFileTime) + if (entry.LastModifiedTime.HasValue) { - if (entry.CreatedTime.HasValue) - { - nf.CreationTime = entry.CreatedTime.Value; - } - - if (entry.LastModifiedTime.HasValue) - { - nf.LastWriteTime = entry.LastModifiedTime.Value; - } - - if (entry.LastAccessedTime.HasValue) - { - nf.LastAccessTime = entry.LastAccessedTime.Value; - } + nf.LastWriteTime = entry.LastModifiedTime.Value; } - if (options.PreserveAttributes) + if (entry.LastAccessedTime.HasValue) { - if (entry.Attrib.HasValue) - { - nf.Attributes = (FileAttributes)System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value); - } + nf.LastAccessTime = entry.LastAccessedTime.Value; + } + } + + if (options.PreserveAttributes) + { + if (entry.Attrib.HasValue) + { + nf.Attributes = (FileAttributes) + System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value); } } } diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index 63e8f9ca..df1fa603 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -1,26 +1,24 @@ using System; -using System.Collections.Generic; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public interface IEntry { - public interface IEntry - { - CompressionType CompressionType { get; } - DateTime? ArchivedTime { get; } - long CompressedSize { get; } - long Crc { get; } - DateTime? CreatedTime { get; } - string Key { get; } - string? LinkTarget { get; } - bool IsDirectory { get; } - bool IsEncrypted { get; } - bool IsSplitAfter { get; } - bool IsSolid { get; } - int VolumeIndexFirst { get; } - int VolumeIndexLast { get; } - DateTime? LastAccessedTime { get; } - DateTime? LastModifiedTime { get; } - long Size { get; } - int? Attrib { get; } - } + CompressionType CompressionType { get; } + DateTime? ArchivedTime { get; } + long CompressedSize { get; } + long Crc { get; } + DateTime? CreatedTime { get; } + string Key { get; } + string? LinkTarget { get; } + bool IsDirectory { get; } + bool IsEncrypted { get; } + bool IsSplitAfter { get; } + bool IsSolid { get; } + int VolumeIndexFirst { get; } + int VolumeIndexLast { get; } + DateTime? LastAccessedTime { get; } + DateTime? LastModifiedTime { get; } + long Size { get; } + int? Attrib { get; } } diff --git a/src/SharpCompress/Common/IExtractionListener.cs b/src/SharpCompress/Common/IExtractionListener.cs index b6fb50fc..e1389810 100644 --- a/src/SharpCompress/Common/IExtractionListener.cs +++ b/src/SharpCompress/Common/IExtractionListener.cs @@ -1,8 +1,7 @@ -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public interface IExtractionListener { - internal interface IExtractionListener - { - void FireFilePartExtractionBegin(string name, long size, long compressedSize); - void FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes); - } -} \ No newline at end of file + void FireFilePartExtractionBegin(string name, long size, long compressedSize); + void FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes); +} diff --git a/src/SharpCompress/Common/IVolume.cs b/src/SharpCompress/Common/IVolume.cs index b29d6be9..abbc0406 100644 --- a/src/SharpCompress/Common/IVolume.cs +++ b/src/SharpCompress/Common/IVolume.cs @@ -1,11 +1,10 @@ using System; -namespace SharpCompress.Common -{ - public interface IVolume : IDisposable - { - int Index { get; } +namespace SharpCompress.Common; - string FileName { get; } - } +public interface IVolume : IDisposable +{ + int Index { get; } + + string FileName { get; } } diff --git a/src/SharpCompress/Common/IncompleteArchiveException.cs b/src/SharpCompress/Common/IncompleteArchiveException.cs index 78d567f4..f1ce3641 100644 --- a/src/SharpCompress/Common/IncompleteArchiveException.cs +++ b/src/SharpCompress/Common/IncompleteArchiveException.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class IncompleteArchiveException : ArchiveException { - public class IncompleteArchiveException : ArchiveException - { - public IncompleteArchiveException(string message) - : base(message) - { - } - } -} \ No newline at end of file + public IncompleteArchiveException(string message) : base(message) { } +} diff --git a/src/SharpCompress/Common/InvalidFormatException.cs b/src/SharpCompress/Common/InvalidFormatException.cs index fa141cb4..b9501369 100644 --- a/src/SharpCompress/Common/InvalidFormatException.cs +++ b/src/SharpCompress/Common/InvalidFormatException.cs @@ -1,17 +1,10 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class InvalidFormatException : ExtractionException { - public class InvalidFormatException : ExtractionException - { - public InvalidFormatException(string message) - : base(message) - { - } + public InvalidFormatException(string message) : base(message) { } - public InvalidFormatException(string message, Exception inner) - : base(message, inner) - { - } - } -} \ No newline at end of file + public InvalidFormatException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/SharpCompress/Common/MultiVolumeExtractionException.cs b/src/SharpCompress/Common/MultiVolumeExtractionException.cs index d9b97fa3..ee2f95ad 100644 --- a/src/SharpCompress/Common/MultiVolumeExtractionException.cs +++ b/src/SharpCompress/Common/MultiVolumeExtractionException.cs @@ -1,17 +1,11 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class MultiVolumeExtractionException : ExtractionException { - public class MultiVolumeExtractionException : ExtractionException - { - public MultiVolumeExtractionException(string message) - : base(message) - { - } + public MultiVolumeExtractionException(string message) : base(message) { } - public MultiVolumeExtractionException(string message, Exception inner) - : base(message, inner) - { - } - } -} \ No newline at end of file + public MultiVolumeExtractionException(string message, Exception inner) : base(message, inner) + { } +} diff --git a/src/SharpCompress/Common/MultipartStreamRequiredException.cs b/src/SharpCompress/Common/MultipartStreamRequiredException.cs index cf030ed6..bbe3ae93 100644 --- a/src/SharpCompress/Common/MultipartStreamRequiredException.cs +++ b/src/SharpCompress/Common/MultipartStreamRequiredException.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class MultipartStreamRequiredException : ExtractionException { - public class MultipartStreamRequiredException : ExtractionException - { - public MultipartStreamRequiredException(string message) - : base(message) - { - } - } -} \ No newline at end of file + public MultipartStreamRequiredException(string message) : base(message) { } +} diff --git a/src/SharpCompress/Common/OptionsBase.cs b/src/SharpCompress/Common/OptionsBase.cs index b98f0a60..61a6cb55 100644 --- a/src/SharpCompress/Common/OptionsBase.cs +++ b/src/SharpCompress/Common/OptionsBase.cs @@ -1,13 +1,11 @@ - -namespace SharpCompress.Common -{ - public class OptionsBase - { - /// - /// SharpCompress will keep the supplied streams open. Default is true. - /// - public bool LeaveStreamOpen { get; set; } = true; +namespace SharpCompress.Common; - public ArchiveEncoding ArchiveEncoding { get; set; } = new ArchiveEncoding(); - } -} \ No newline at end of file +public class OptionsBase +{ + /// + /// SharpCompress will keep the supplied streams open. Default is true. + /// + public bool LeaveStreamOpen { get; set; } = true; + + public ArchiveEncoding ArchiveEncoding { get; set; } = new ArchiveEncoding(); +} diff --git a/src/SharpCompress/Common/PasswordProtectedException.cs b/src/SharpCompress/Common/PasswordProtectedException.cs index 58dc7660..7b559625 100644 --- a/src/SharpCompress/Common/PasswordProtectedException.cs +++ b/src/SharpCompress/Common/PasswordProtectedException.cs @@ -1,17 +1,10 @@ -using System; +using System; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public class PasswordProtectedException : ExtractionException { - public class PasswordProtectedException : ExtractionException - { - public PasswordProtectedException(string message) - : base(message) - { - } + public PasswordProtectedException(string message) : base(message) { } - public PasswordProtectedException(string message, Exception inner) - : base(message, inner) - { - } - } -} \ No newline at end of file + public PasswordProtectedException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs index 44d2b15a..cd51d80b 100644 --- a/src/SharpCompress/Common/Rar/Headers/AVHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/AVHeader.cs @@ -1,32 +1,31 @@ -using SharpCompress.IO; +using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class AvHeader : RarHeader { - internal class AvHeader : RarHeader + public AvHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Av) { - public AvHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Av) + if (IsRar5) { - if (IsRar5) - { - throw new InvalidFormatException("unexpected rar5 record"); - } + throw new InvalidFormatException("unexpected rar5 record"); } - - protected override void ReadFinish(MarkingBinaryReader reader) - { - UnpackVersion = reader.ReadByte(); - Method = reader.ReadByte(); - AvVersion = reader.ReadByte(); - AvInfoCrc = reader.ReadInt32(); - } - - internal int AvInfoCrc { get; private set; } - - internal byte UnpackVersion { get; private set; } - - internal byte Method { get; private set; } - - internal byte AvVersion { get; private set; } } -} \ No newline at end of file + + protected override void ReadFinish(MarkingBinaryReader reader) + { + UnpackVersion = reader.ReadByte(); + Method = reader.ReadByte(); + AvVersion = reader.ReadByte(); + AvInfoCrc = reader.ReadInt32(); + } + + internal int AvInfoCrc { get; private set; } + + internal byte UnpackVersion { get; private set; } + + internal byte Method { get; private set; } + + internal byte AvVersion { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs index 51cf05b2..619e8fc9 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs @@ -1,59 +1,50 @@ -#nullable disable +#nullable disable using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class ArchiveCryptHeader : RarHeader { - internal class ArchiveCryptHeader : RarHeader + private const int CRYPT_VERSION = 0; // Supported encryption version. + private const int SIZE_SALT50 = 16; + private const int SIZE_PSWCHECK = 8; + private const int SIZE_PSWCHECK_CSUM = 4; + private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count. + + private bool _usePswCheck; + private uint _lg2Count; // Log2 of PBKDF2 repetition count. + private byte[] _salt; + private byte[] _pswCheck; + private byte[] _pswCheckCsm; + + public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Crypt) { } + + protected override void ReadFinish(MarkingBinaryReader reader) { - - private const int CRYPT_VERSION = 0; // Supported encryption version. - private const int SIZE_SALT50 = 16; - private const int SIZE_SALT30 = 8; - private const int SIZE_INITV = 16; - private const int SIZE_PSWCHECK = 8; - private const int SIZE_PSWCHECK_CSUM = 4; - private const int CRYPT5_KDF_LG2_COUNT = 15; // LOG2 of PDKDF2 iteration count. - private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count. - - - private bool _usePswCheck; - private uint _lg2Count; // Log2 of PBKDF2 repetition count. - private byte[] _salt; - private byte[] _pswCheck; - private byte[] _pswCheckCsm; - - public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Crypt) + var cryptVersion = reader.ReadRarVIntUInt32(); + if (cryptVersion > CRYPT_VERSION) { + //error? + return; + } + var encryptionFlags = reader.ReadRarVIntUInt32(); + _usePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + _lg2Count = reader.ReadRarVIntByte(1); + + //UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + if (_lg2Count > CRYPT5_KDF_LG2_COUNT_MAX) + { + //error? + return; } - protected override void ReadFinish(MarkingBinaryReader reader) + _salt = reader.ReadBytes(SIZE_SALT50); + if (_usePswCheck) { - var cryptVersion = reader.ReadRarVIntUInt32(); - if (cryptVersion > CRYPT_VERSION) - { - //error? - return; - } - var encryptionFlags = reader.ReadRarVIntUInt32(); - _usePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); - _lg2Count = reader.ReadRarVIntByte(1); - - - //UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); - if (_lg2Count > CRYPT5_KDF_LG2_COUNT_MAX) - { - //error? - return; - } - - _salt = reader.ReadBytes(SIZE_SALT50); - if (_usePswCheck) - { - _pswCheck = reader.ReadBytes(SIZE_PSWCHECK); - _pswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM); - } + _pswCheck = reader.ReadBytes(SIZE_PSWCHECK); + _pswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM); } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 39249656..e8f344ad 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -1,88 +1,59 @@ using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal sealed class ArchiveHeader : RarHeader { - internal sealed class ArchiveHeader : RarHeader + public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Archive) { } + + protected override void ReadFinish(MarkingBinaryReader reader) { - public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Archive) + if (IsRar5) { - } - - protected override void ReadFinish(MarkingBinaryReader reader) - { - if (IsRar5) + Flags = reader.ReadRarVIntUInt16(); + if (HasFlag(ArchiveFlagsV5.HAS_VOLUME_NUMBER)) { - Flags = reader.ReadRarVIntUInt16(); - if (HasFlag(ArchiveFlagsV5.HAS_VOLUME_NUMBER)) - { - VolumeNumber = (int)reader.ReadRarVIntUInt32(); - } - // later: we may have a locator record if we need it - //if (ExtraSize != 0) { - // ReadLocator(reader); - //} + VolumeNumber = (int)reader.ReadRarVIntUInt32(); } - else + // later: we may have a locator record if we need it + //if (ExtraSize != 0) { + // ReadLocator(reader); + //} + } + else + { + Flags = HeaderFlags; + HighPosAv = reader.ReadInt16(); + PosAv = reader.ReadInt32(); + if (HasFlag(ArchiveFlagsV4.ENCRYPT_VER)) { - Flags = HeaderFlags; - HighPosAv = reader.ReadInt16(); - PosAv = reader.ReadInt32(); - if (HasFlag(ArchiveFlagsV4.ENCRYPT_VER)) - { - EncryptionVersion = reader.ReadByte(); - } + EncryptionVersion = reader.ReadByte(); } } - - private void ReadLocator(MarkingBinaryReader reader) - { - var size = reader.ReadRarVIntUInt16(); - var type = reader.ReadRarVIntUInt16(); - if (type != 1) - { - throw new InvalidFormatException("expected locator record"); - } - - var flags = reader.ReadRarVIntUInt16(); - const ushort hasQuickOpenOffset = 0x01; - const ushort hasRecoveryOffset = 0x02; - ulong quickOpenOffset = 0; - if ((flags & hasQuickOpenOffset) == hasQuickOpenOffset) - { - quickOpenOffset = reader.ReadRarVInt(); - } - ulong recoveryOffset = 0; - if ((flags & hasRecoveryOffset) == hasRecoveryOffset) - { - recoveryOffset = reader.ReadRarVInt(); - } - } - - private ushort Flags { get; set; } - - private bool HasFlag(ushort flag) - { - return (Flags & flag) == flag; - } - - internal int? VolumeNumber { get; private set; } - - internal short? HighPosAv { get; private set; } - - internal int? PosAv { get; private set; } - - private byte? EncryptionVersion { get; set; } - - public bool? IsEncrypted => IsRar5 ? (bool?)null : HasFlag(ArchiveFlagsV4.PASSWORD); - - public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NEW_NUMBERING); - - public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.VOLUME : ArchiveFlagsV4.VOLUME); - - // RAR5: Volume number field is present. True for all volumes except first. - public bool IsFirstVolume => IsRar5 ? VolumeNumber is null : HasFlag(ArchiveFlagsV4.FIRST_VOLUME); - - public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.SOLID : ArchiveFlagsV4.SOLID); } -} \ No newline at end of file + + private ushort Flags { get; set; } + + private bool HasFlag(ushort flag) => (Flags & flag) == flag; + + internal int? VolumeNumber { get; private set; } + + internal short? HighPosAv { get; private set; } + + internal int? PosAv { get; private set; } + + private byte? EncryptionVersion { get; set; } + + public bool? IsEncrypted => IsRar5 ? null : HasFlag(ArchiveFlagsV4.PASSWORD); + + public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NEW_NUMBERING); + + public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.VOLUME : ArchiveFlagsV4.VOLUME); + + // RAR5: Volume number field is present. True for all volumes except first. + public bool IsFirstVolume => + IsRar5 ? VolumeNumber is null : HasFlag(ArchiveFlagsV4.FIRST_VOLUME); + + public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.SOLID : ArchiveFlagsV4.SOLID); +} diff --git a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs index 00a9cc25..54abf9cc 100644 --- a/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/CommentHeader.cs @@ -1,31 +1,30 @@ -using SharpCompress.IO; +using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class CommentHeader : RarHeader { - internal class CommentHeader : RarHeader + protected CommentHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Comment) { - protected CommentHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Comment) + if (IsRar5) { - if (IsRar5) - { - throw new InvalidFormatException("unexpected rar5 record"); - } + throw new InvalidFormatException("unexpected rar5 record"); } - - protected override void ReadFinish(MarkingBinaryReader reader) - { - UnpSize = reader.ReadInt16(); - UnpVersion = reader.ReadByte(); - UnpMethod = reader.ReadByte(); - CommCrc = reader.ReadInt16(); - } - - internal short UnpSize { get; private set; } - - internal byte UnpVersion { get; private set; } - - internal byte UnpMethod { get; private set; } - internal short CommCrc { get; private set; } } -} \ No newline at end of file + + protected override void ReadFinish(MarkingBinaryReader reader) + { + UnpSize = reader.ReadInt16(); + UnpVersion = reader.ReadByte(); + UnpMethod = reader.ReadByte(); + CommCrc = reader.ReadInt16(); + } + + internal short UnpSize { get; private set; } + + internal byte UnpVersion { get; private set; } + + internal byte UnpMethod { get; private set; } + internal short CommCrc { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs index d993f1d9..f5bc4523 100644 --- a/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/EndArchiveHeader.cs @@ -1,43 +1,37 @@ using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class EndArchiveHeader : RarHeader { - internal class EndArchiveHeader : RarHeader + public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.EndArchive) { } + + protected override void ReadFinish(MarkingBinaryReader reader) { - public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.EndArchive) + if (IsRar5) { + Flags = reader.ReadRarVIntUInt16(); } - - protected override void ReadFinish(MarkingBinaryReader reader) + else { - if (IsRar5) + Flags = HeaderFlags; + if (HasFlag(EndArchiveFlagsV4.DATA_CRC)) { - Flags = reader.ReadRarVIntUInt16(); + ArchiveCrc = reader.ReadInt32(); } - else + if (HasFlag(EndArchiveFlagsV4.VOLUME_NUMBER)) { - Flags = HeaderFlags; - if (HasFlag(EndArchiveFlagsV4.DATA_CRC)) - { - ArchiveCrc = reader.ReadInt32(); - } - if (HasFlag(EndArchiveFlagsV4.VOLUME_NUMBER)) - { - VolumeNumber = reader.ReadInt16(); - } + VolumeNumber = reader.ReadInt16(); } } - - private ushort Flags { get; set; } - - private bool HasFlag(ushort flag) - { - return (Flags & flag) == flag; - } - - internal int? ArchiveCrc { get; private set; } - - internal short? VolumeNumber { get; private set; } } -} \ No newline at end of file + + private ushort Flags { get; set; } + + private bool HasFlag(ushort flag) => (Flags & flag) == flag; + + internal int? ArchiveCrc { get; private set; } + + internal short? VolumeNumber { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 2c361a09..e95052ef 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -1,8 +1,6 @@ #nullable disable #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; using size_t = System.UInt32; #else using nint = System.Int64; @@ -15,442 +13,451 @@ using System; using System.IO; using System.Text; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class FileHeader : RarHeader { - internal class FileHeader : RarHeader + private uint _fileCrc; + + public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) + : base(header, reader, headerType) { } + + protected override void ReadFinish(MarkingBinaryReader reader) { - private uint _fileCrc; - - public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) - : base(header, reader, headerType) + if (IsRar5) { + ReadFromReaderV5(reader); + } + else + { + ReadFromReaderV4(reader); + } + } + + private void ReadFromReaderV5(MarkingBinaryReader reader) + { + Flags = reader.ReadRarVIntUInt16(); + + var lvalue = checked((long)reader.ReadRarVInt()); + + // long.MaxValue causes the unpack code to finish when the input stream is exhausted + UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue; + + FileAttributes = reader.ReadRarVIntUInt32(); + + if (HasFlag(FileFlagsV5.HAS_MOD_TIME)) + { + FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32()); } - protected override void ReadFinish(MarkingBinaryReader reader) + if (HasFlag(FileFlagsV5.HAS_CRC32)) { - if (IsRar5) - { - ReadFromReaderV5(reader); - } - else - { - ReadFromReaderV4(reader); - } - } - - private void ReadFromReaderV5(MarkingBinaryReader reader) - { - Flags = reader.ReadRarVIntUInt16(); - - var lvalue = checked((long)reader.ReadRarVInt()); - - // long.MaxValue causes the unpack code to finish when the input stream is exhausted - UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue; - - FileAttributes = reader.ReadRarVIntUInt32(); - - if (HasFlag(FileFlagsV5.HAS_MOD_TIME)) - { - FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32()); - } - - if (HasFlag(FileFlagsV5.HAS_CRC32)) - { - FileCrc = reader.ReadUInt32(); - } - - var compressionInfo = reader.ReadRarVIntUInt16(); - - // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. - // "+ 50" to not mix with old RAR format algorithms. For example, - // we may need to use the compression algorithm 15 in the future, - // but it was already used in RAR 1.5 and Unpack needs to distinguish - // them. - CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50); - - // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. - // It can be set only for file headers and is never set for service headers. - IsSolid = (compressionInfo & 0x40) == 0x40; - - // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. - CompressionMethod = (byte)((compressionInfo >> 7) & 0x7); - - // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. - WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo >> 10) & 0xf); - - HostOs = reader.ReadRarVIntByte(); - - var nameSize = reader.ReadRarVIntUInt16(); - - // Variable length field containing Name length bytes in UTF-8 format without trailing zero. - // For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names. - // Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field. - // - // TODO: not sure if anything needs to be done to handle the following: - // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8 - // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character - // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting. - // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not - // portable and can be correctly unpacked only on the same system where they were created. - // - // For service header this field contains a name of service header. Now the following names are used: - // CMT Archive comment - // QO Archive quick open data - // ACL NTFS file permissions - // STM NTFS alternate data stream - // RR Recovery record - var b = reader.ReadBytes(nameSize); - FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length)); - - // extra size seems to be redudant since we know the total header size - if (ExtraSize != RemainingHeaderBytes(reader)) - { - throw new InvalidFormatException("rar5 header size / extra size inconsistency"); - } - - isEncryptedRar5 = false; - - while (RemainingHeaderBytes(reader) > 0) - { - var size = reader.ReadRarVIntUInt16(); - int n = RemainingHeaderBytes(reader); - var type = reader.ReadRarVIntUInt16(); - switch (type) - { - //TODO - case 1: // file encryption - { - isEncryptedRar5 = true; - - //var version = reader.ReadRarVIntByte(); - //if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version); - } - break; - // case 2: // file hash - // { - // - // } - // break; - case 3: // file time - { - ushort flags = reader.ReadRarVIntUInt16(); - var isWindowsTime = (flags & 1) == 0; - if ((flags & 0x2) == 0x2) - { - FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime); - } - if ((flags & 0x4) == 0x4) - { - FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime); - } - if ((flags & 0x8) == 0x8) - { - FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime); - } - } - break; - //TODO - // case 4: // file version - // { - // - // } - // break; - // case 5: // file system redirection - // { - // - // } - // break; - // case 6: // unix owner - // { - // - // } - // break; - // case 7: // service data - // { - // - // } - // break; - - default: - // skip unknown record types to allow new record types to be added in the future - break; - } - // drain any trailing bytes of extra record - int did = n - RemainingHeaderBytes(reader); - int drain = size - did; - if (drain > 0) - { - reader.ReadBytes(drain); - } - } - - if (AdditionalDataSize != 0) - { - CompressedSize = AdditionalDataSize; - } - } - - - private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime) - { - if (isWindowsTime) - { - return DateTime.FromFileTime(reader.ReadInt64()); - } - else - { - return Utility.UnixTimeToDateTime(reader.ReadUInt32()); - } - } - - private static string ConvertPathV5(string path) - { - if (Path.DirectorySeparatorChar == '\\') - { - // replace embedded \\ with valid filename char - return path.Replace('\\', '-').Replace('/', '\\'); - } - return path; - } - - - private void ReadFromReaderV4(MarkingBinaryReader reader) - { - Flags = HeaderFlags; - IsSolid = HasFlag(FileFlagsV4.SOLID); - WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5); - - uint lowUncompressedSize = reader.ReadUInt32(); - - HostOs = reader.ReadByte(); - FileCrc = reader.ReadUInt32(); + } - FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); + var compressionInfo = reader.ReadRarVIntUInt16(); - CompressionAlgorithm = reader.ReadByte(); - CompressionMethod = (byte)(reader.ReadByte() - 0x30); + // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0. + // "+ 50" to not mix with old RAR format algorithms. For example, + // we may need to use the compression algorithm 15 in the future, + // but it was already used in RAR 1.5 and Unpack needs to distinguish + // them. + CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50); - short nameSize = reader.ReadInt16(); + // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files. + // It can be set only for file headers and is never set for service headers. + IsSolid = (compressionInfo & 0x40) == 0x40; - FileAttributes = reader.ReadUInt32(); + // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression. + CompressionMethod = (byte)((compressionInfo >> 7) & 0x7); - uint highCompressedSize = 0; - uint highUncompressedkSize = 0; - if (HasFlag(FileFlagsV4.LARGE)) + // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB. + WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo >> 10) & 0xf); + + HostOs = reader.ReadRarVIntByte(); + + var nameSize = reader.ReadRarVIntUInt16(); + + // Variable length field containing Name length bytes in UTF-8 format without trailing zero. + // For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names. + // Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field. + // + // TODO: not sure if anything needs to be done to handle the following: + // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8 + // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character + // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting. + // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not + // portable and can be correctly unpacked only on the same system where they were created. + // + // For service header this field contains a name of service header. Now the following names are used: + // CMT Archive comment + // QO Archive quick open data + // ACL NTFS file permissions + // STM NTFS alternate data stream + // RR Recovery record + var b = reader.ReadBytes(nameSize); + FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length)); + + // extra size seems to be redudant since we know the total header size + if (ExtraSize != RemainingHeaderBytes(reader)) + { + throw new InvalidFormatException("rar5 header size / extra size inconsistency"); + } + + isEncryptedRar5 = false; + + while (RemainingHeaderBytes(reader) > 0) + { + var size = reader.ReadRarVIntUInt16(); + var n = RemainingHeaderBytes(reader); + var type = reader.ReadRarVIntUInt16(); + switch (type) { - highCompressedSize = reader.ReadUInt32(); - highUncompressedkSize = reader.ReadUInt32(); - } - else - { - if (lowUncompressedSize == 0xffffffff) - { - lowUncompressedSize = 0xffffffff; - highUncompressedkSize = int.MaxValue; - } - } - CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize)); - UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); + //TODO + case 1: // file encryption - nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; - - byte[] fileNameBytes = reader.ReadBytes(nameSize); - - const int saltSize = 8; - const int newLhdSize = 32; - - switch (HeaderCode) - { - case HeaderCodeV.RAR4_FILE_HEADER: { - if (HasFlag(FileFlagsV4.UNICODE)) + isEncryptedRar5 = true; + + //var version = reader.ReadRarVIntByte(); + //if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version); + } + break; + // case 2: // file hash + // { + // + // } + // break; + case 3: // file time + + { + var flags = reader.ReadRarVIntUInt16(); + var isWindowsTime = (flags & 1) == 0; + if ((flags & 0x2) == 0x2) { - int length = 0; - while (length < fileNameBytes.Length - && fileNameBytes[length] != 0) - { - length++; - } - if (length != nameSize) - { - length++; - FileName = FileNameDecoder.Decode(fileNameBytes, length); - } - else - { - FileName = ArchiveEncoding.Decode(fileNameBytes); - } + FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x4) == 0x4) + { + FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + if ((flags & 0x8) == 0x8) + { + FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime); + } + } + break; + //TODO + // case 4: // file version + // { + // + // } + // break; + // case 5: // file system redirection + // { + // + // } + // break; + // case 6: // unix owner + // { + // + // } + // break; + // case 7: // service data + // { + // + // } + // break; + + default: + // skip unknown record types to allow new record types to be added in the future + break; + } + // drain any trailing bytes of extra record + var did = n - RemainingHeaderBytes(reader); + var drain = size - did; + if (drain > 0) + { + reader.ReadBytes(drain); + } + } + + if (AdditionalDataSize != 0) + { + CompressedSize = AdditionalDataSize; + } + } + + private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime) + { + if (isWindowsTime) + { + return DateTime.FromFileTime(reader.ReadInt64()); + } + else + { + return Utility.UnixTimeToDateTime(reader.ReadUInt32()); + } + } + + private static string ConvertPathV5(string path) + { + if (Path.DirectorySeparatorChar == '\\') + { + // replace embedded \\ with valid filename char + return path.Replace('\\', '-').Replace('/', '\\'); + } + return path; + } + + private void ReadFromReaderV4(MarkingBinaryReader reader) + { + Flags = HeaderFlags; + IsSolid = HasFlag(FileFlagsV4.SOLID); + WindowSize = IsDirectory + ? 0U + : ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5); + + var lowUncompressedSize = reader.ReadUInt32(); + + HostOs = reader.ReadByte(); + + FileCrc = reader.ReadUInt32(); + + FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32()); + + CompressionAlgorithm = reader.ReadByte(); + CompressionMethod = (byte)(reader.ReadByte() - 0x30); + + var nameSize = reader.ReadInt16(); + + FileAttributes = reader.ReadUInt32(); + + uint highCompressedSize = 0; + uint highUncompressedkSize = 0; + if (HasFlag(FileFlagsV4.LARGE)) + { + highCompressedSize = reader.ReadUInt32(); + highUncompressedkSize = reader.ReadUInt32(); + } + else + { + if (lowUncompressedSize == 0xffffffff) + { + lowUncompressedSize = 0xffffffff; + highUncompressedkSize = int.MaxValue; + } + } + CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize)); + UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); + + nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; + + var fileNameBytes = reader.ReadBytes(nameSize); + + const int saltSize = 8; + const int newLhdSize = 32; + + switch (HeaderCode) + { + case HeaderCodeV.RAR4_FILE_HEADER: + + { + if (HasFlag(FileFlagsV4.UNICODE)) + { + var length = 0; + while (length < fileNameBytes.Length && fileNameBytes[length] != 0) + { + length++; + } + if (length != nameSize) + { + length++; + FileName = FileNameDecoder.Decode(fileNameBytes, length); } else { FileName = ArchiveEncoding.Decode(fileNameBytes); } - FileName = ConvertPathV4(FileName); } - break; - case HeaderCodeV.RAR4_NEW_SUB_HEADER: + else { - int datasize = HeaderSize - newLhdSize - nameSize; - if (HasFlag(FileFlagsV4.SALT)) - { - datasize -= saltSize; - } - if (datasize > 0) - { - SubData = reader.ReadBytes(datasize); - } - - if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes)) - { - RecoverySectors = SubData[8] + (SubData[9] << 8) - + (SubData[10] << 16) + (SubData[11] << 24); - } + FileName = ArchiveEncoding.Decode(fileNameBytes); } - break; - } - - if (HasFlag(FileFlagsV4.SALT)) - { - R4Salt = reader.ReadBytes(saltSize); - } - if (HasFlag(FileFlagsV4.EXT_TIME)) - { - // verify that the end of the header hasn't been reached before reading the Extended Time. - // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. - if (RemainingHeaderBytes(reader) >= 2) - { - ushort extendedFlags = reader.ReadUInt16(); - FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0); - FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1); - FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2); - FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3); + FileName = ConvertPathV4(FileName); } - } - } + break; + case HeaderCodeV.RAR4_NEW_SUB_HEADER: - private static long UInt32To64(uint x, uint y) - { - long l = x; - l <<= 32; - return l + y; - } - - private static DateTime? ProcessExtendedTimeV4(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, int i) - { - uint rmode = (uint)extendedFlags >> (3 - i) * 4; - if ((rmode & 8) == 0) - { - return null; - } - if (i != 0) - { - uint dosTime = reader.ReadUInt32(); - time = Utility.DosDateToDateTime(dosTime); - } - if ((rmode & 4) == 0) - { - time = time.Value.AddSeconds(1); - } - uint nanosecondHundreds = 0; - int count = (int)rmode & 3; - for (int j = 0; j < count; j++) - { - byte b = reader.ReadByte(); - nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8)); - } - - //10^-7 to 10^-3 - return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4)); - } - - private static string ConvertPathV4(string path) - { - if (Path.DirectorySeparatorChar == '/') - { - return path.Replace('\\', '/'); - } - else if (Path.DirectorySeparatorChar == '\\') - { - return path.Replace('/', '\\'); - } - return path; - } - - public override string ToString() - { - return FileName; - } - - private ushort Flags { get; set; } - - private bool HasFlag(ushort flag) - { - return (Flags & flag) == flag; - } - - internal uint FileCrc - { - get - { - if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32)) { - //!!! rar5: - throw new InvalidOperationException("TODO rar5"); + var datasize = HeaderSize - newLhdSize - nameSize; + if (HasFlag(FileFlagsV4.SALT)) + { + datasize -= saltSize; + } + if (datasize > 0) + { + SubData = reader.ReadBytes(datasize); + } + + if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes)) + { + RecoverySectors = + SubData[8] + + (SubData[9] << 8) + + (SubData[10] << 16) + + (SubData[11] << 24); + } } - return _fileCrc; - } - private set => _fileCrc = value; + break; } - // 0 - storing - // 1 - fastest compression - // 2 - fast compression - // 3 - normal compression - // 4 - good compression - // 5 - best compression - internal byte CompressionMethod { get; private set; } - internal bool IsStored => CompressionMethod == 0; - - // eg (see DoUnpack()) - //case 15: // rar 1.5 compression - //case 20: // rar 2.x compression - //case 26: // files larger than 2GB - //case 29: // rar 3.x compression - //case 50: // RAR 5.0 compression algorithm. - internal byte CompressionAlgorithm { get; private set; } - - public bool IsSolid { get; private set; } - - // unused for UnpackV1 implementation (limitation) - internal size_t WindowSize { get; private set; } - - internal byte[] R4Salt { get; private set; } - - private byte HostOs { get; set; } - internal uint FileAttributes { get; private set; } - internal long CompressedSize { get; private set; } - internal long UncompressedSize { get; private set; } - internal string FileName { get; private set; } - internal byte[] SubData { get; private set; } - internal int RecoverySectors { get; private set; } - internal long DataStartPosition { get; set; } - public Stream PackedStream { get; set; } - - public bool IsSplitBefore => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE); - public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER); - - public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY); - - private bool isEncryptedRar5 = false; - public bool IsEncrypted => IsRar5 ? isEncryptedRar5 : HasFlag(FileFlagsV4.PASSWORD); - - internal DateTime? FileLastModifiedTime { get; private set; } - - internal DateTime? FileCreatedTime { get; private set; } - - internal DateTime? FileLastAccessedTime { get; private set; } - - internal DateTime? FileArchivedTime { get; private set; } + if (HasFlag(FileFlagsV4.SALT)) + { + R4Salt = reader.ReadBytes(saltSize); + } + if (HasFlag(FileFlagsV4.EXT_TIME)) + { + // verify that the end of the header hasn't been reached before reading the Extended Time. + // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate. + if (RemainingHeaderBytes(reader) >= 2) + { + var extendedFlags = reader.ReadUInt16(); + FileLastModifiedTime = ProcessExtendedTimeV4( + extendedFlags, + FileLastModifiedTime, + reader, + 0 + ); + FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1); + FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2); + FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3); + } + } } -} \ No newline at end of file + + private static long UInt32To64(uint x, uint y) + { + long l = x; + l <<= 32; + return l + y; + } + + private static DateTime? ProcessExtendedTimeV4( + ushort extendedFlags, + DateTime? time, + MarkingBinaryReader reader, + int i + ) + { + var rmode = (uint)extendedFlags >> ((3 - i) * 4); + if ((rmode & 8) == 0) + { + return null; + } + if (i != 0) + { + var dosTime = reader.ReadUInt32(); + time = Utility.DosDateToDateTime(dosTime); + } + if ((rmode & 4) == 0) + { + time = time.Value.AddSeconds(1); + } + uint nanosecondHundreds = 0; + var count = (int)rmode & 3; + for (var j = 0; j < count; j++) + { + var b = reader.ReadByte(); + nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8)); + } + + //10^-7 to 10^-3 + return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4)); + } + + private static string ConvertPathV4(string path) + { + if (Path.DirectorySeparatorChar == '/') + { + return path.Replace('\\', '/'); + } + else if (Path.DirectorySeparatorChar == '\\') + { + return path.Replace('/', '\\'); + } + return path; + } + + public override string ToString() => FileName; + + private ushort Flags { get; set; } + + private bool HasFlag(ushort flag) => (Flags & flag) == flag; + + internal uint FileCrc + { + get + { + if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32)) + { + //!!! rar5: + throw new InvalidOperationException("TODO rar5"); + } + return _fileCrc; + } + private set => _fileCrc = value; + } + + // 0 - storing + // 1 - fastest compression + // 2 - fast compression + // 3 - normal compression + // 4 - good compression + // 5 - best compression + internal byte CompressionMethod { get; private set; } + internal bool IsStored => CompressionMethod == 0; + + // eg (see DoUnpack()) + //case 15: // rar 1.5 compression + //case 20: // rar 2.x compression + //case 26: // files larger than 2GB + //case 29: // rar 3.x compression + //case 50: // RAR 5.0 compression algorithm. + internal byte CompressionAlgorithm { get; private set; } + + public bool IsSolid { get; private set; } + + // unused for UnpackV1 implementation (limitation) + internal size_t WindowSize { get; private set; } + + internal byte[] R4Salt { get; private set; } + + private byte HostOs { get; set; } + internal uint FileAttributes { get; private set; } + internal long CompressedSize { get; private set; } + internal long UncompressedSize { get; private set; } + internal string FileName { get; private set; } + internal byte[] SubData { get; private set; } + internal int RecoverySectors { get; private set; } + internal long DataStartPosition { get; set; } + public Stream PackedStream { get; set; } + + public bool IsSplitBefore => + IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE); + public bool IsSplitAfter => + IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER); + + public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY); + + private bool isEncryptedRar5 = false; + public bool IsEncrypted => IsRar5 ? isEncryptedRar5 : HasFlag(FileFlagsV4.PASSWORD); + + internal DateTime? FileLastModifiedTime { get; private set; } + + internal DateTime? FileCreatedTime { get; private set; } + + internal DateTime? FileLastAccessedTime { get; private set; } + + internal DateTime? FileArchivedTime { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/FileNameDecoder.cs b/src/SharpCompress/Common/Rar/Headers/FileNameDecoder.cs index 98da75da..38bdb3df 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileNameDecoder.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileNameDecoder.cs @@ -1,78 +1,78 @@ using System.Text; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +/// +/// This is for the crazy Rar encoding that I don't understand +/// +internal static class FileNameDecoder { - /// - /// This is for the crazy Rar encoding that I don't understand - /// - internal static class FileNameDecoder + internal static int GetChar(byte[] name, int pos) => name[pos] & 0xff; + + internal static string Decode(byte[] name, int encPos) { - internal static int GetChar(byte[] name, int pos) - { - return name[pos] & 0xff; - } + var decPos = 0; + var flags = 0; + var flagBits = 0; - internal static string Decode(byte[] name, int encPos) + var low = 0; + var high = 0; + var highByte = GetChar(name, encPos++); + var buf = new StringBuilder(); + while (encPos < name.Length) { - int decPos = 0; - int flags = 0; - int flagBits = 0; - - int low = 0; - int high = 0; - int highByte = GetChar(name, encPos++); - StringBuilder buf = new StringBuilder(); - while (encPos < name.Length) + if (flagBits == 0) { - if (flagBits == 0) - { - flags = GetChar(name, encPos++); - flagBits = 8; - } - switch (flags >> 6) - { - case 0: - buf.Append((char)(GetChar(name, encPos++))); - ++decPos; - break; - - case 1: - buf.Append((char)(GetChar(name, encPos++) + (highByte << 8))); - ++decPos; - break; - - case 2: - low = GetChar(name, encPos); - high = GetChar(name, encPos + 1); - buf.Append((char)((high << 8) + low)); - ++decPos; - encPos += 2; - break; - - case 3: - int length = GetChar(name, encPos++); - if ((length & 0x80) != 0) - { - int correction = GetChar(name, encPos++); - for (length = (length & 0x7f) + 2; length > 0 && decPos < name.Length; length--, decPos++) - { - low = (GetChar(name, decPos) + correction) & 0xff; - buf.Append((char)((highByte << 8) + low)); - } - } - else - { - for (length += 2; length > 0 && decPos < name.Length; length--, decPos++) - { - buf.Append((char)(GetChar(name, decPos))); - } - } - break; - } - flags = (flags << 2) & 0xff; - flagBits -= 2; + flags = GetChar(name, encPos++); + flagBits = 8; } - return buf.ToString(); + switch (flags >> 6) + { + case 0: + buf.Append((char)(GetChar(name, encPos++))); + ++decPos; + break; + + case 1: + buf.Append((char)(GetChar(name, encPos++) + (highByte << 8))); + ++decPos; + break; + + case 2: + low = GetChar(name, encPos); + high = GetChar(name, encPos + 1); + buf.Append((char)((high << 8) + low)); + ++decPos; + encPos += 2; + break; + + case 3: + var length = GetChar(name, encPos++); + if ((length & 0x80) != 0) + { + var correction = GetChar(name, encPos++); + for ( + length = (length & 0x7f) + 2; + length > 0 && decPos < name.Length; + length--, decPos++ + ) + { + low = (GetChar(name, decPos) + correction) & 0xff; + buf.Append((char)((highByte << 8) + low)); + } + } + else + { + for (length += 2; length > 0 && decPos < name.Length; length--, decPos++) + { + buf.Append((char)(GetChar(name, decPos))); + } + } + break; + } + flags = (flags << 2) & 0xff; + flagBits -= 2; } + return buf.ToString(); } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index b6457661..5a52f001 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -1,149 +1,148 @@ -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +public enum HeaderType : byte { - internal enum HeaderType : byte - { - Null, - Mark, - Archive, - File, - Service, - Comment, - Av, - Protect, - Sign, - NewSub, - EndArchive, - Crypt - } + Null, + Mark, + Archive, + File, + Service, + Comment, + Av, + Protect, + Sign, + NewSub, + EndArchive, + Crypt +} - internal static class HeaderCodeV - { - public const byte RAR4_MARK_HEADER = 0x72; - public const byte RAR4_ARCHIVE_HEADER = 0x73; - public const byte RAR4_FILE_HEADER = 0x74; - public const byte RAR4_COMMENT_HEADER = 0x75; - public const byte RAR4_AV_HEADER = 0x76; - public const byte RAR4_SUB_HEADER = 0x77; - public const byte RAR4_PROTECT_HEADER = 0x78; - public const byte RAR4_SIGN_HEADER = 0x79; - public const byte RAR4_NEW_SUB_HEADER = 0x7a; - public const byte RAR4_END_ARCHIVE_HEADER = 0x7b; +internal static class HeaderCodeV +{ + public const byte RAR4_MARK_HEADER = 0x72; + public const byte RAR4_ARCHIVE_HEADER = 0x73; + public const byte RAR4_FILE_HEADER = 0x74; + public const byte RAR4_COMMENT_HEADER = 0x75; + public const byte RAR4_AV_HEADER = 0x76; + public const byte RAR4_SUB_HEADER = 0x77; + public const byte RAR4_PROTECT_HEADER = 0x78; + public const byte RAR4_SIGN_HEADER = 0x79; + public const byte RAR4_NEW_SUB_HEADER = 0x7a; + public const byte RAR4_END_ARCHIVE_HEADER = 0x7b; - public const byte RAR5_ARCHIVE_HEADER = 0x01; - public const byte RAR5_FILE_HEADER = 0x02; - public const byte RAR5_SERVICE_HEADER = 0x03; - public const byte RAR5_ARCHIVE_ENCRYPTION_HEADER = 0x04; - public const byte RAR5_END_ARCHIVE_HEADER = 0x05; - } + public const byte RAR5_ARCHIVE_HEADER = 0x01; + public const byte RAR5_FILE_HEADER = 0x02; + public const byte RAR5_SERVICE_HEADER = 0x03; + public const byte RAR5_ARCHIVE_ENCRYPTION_HEADER = 0x04; + public const byte RAR5_END_ARCHIVE_HEADER = 0x05; +} - internal static class HeaderFlagsV4 - { - public const ushort HAS_DATA = 0x8000; - } +internal static class HeaderFlagsV4 +{ + public const ushort HAS_DATA = 0x8000; +} - internal static class EncryptionFlagsV5 - { - // RAR 5.0 archive encryption header specific flags. - public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present. +internal static class EncryptionFlagsV5 +{ + // RAR 5.0 archive encryption header specific flags. + public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present. - public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present. - public const uint FHEXTRA_CRYPT_HASHMAC = 0x02; - } + public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present. + public const uint FHEXTRA_CRYPT_HASHMAC = 0x02; +} - internal static class HeaderFlagsV5 - { - public const ushort HAS_EXTRA = 0x0001; - public const ushort HAS_DATA = 0x0002; - public const ushort KEEP = 0x0004; // block must be kept during an update - public const ushort SPLIT_BEFORE = 0x0008; - public const ushort SPLIT_AFTER = 0x0010; - public const ushort CHILD = 0x0020; // ??? Block depends on preceding file block. - public const ushort PRESERVE_CHILD = 0x0040; // ???? Preserve a child block if host block is modified - } +internal static class HeaderFlagsV5 +{ + public const ushort HAS_EXTRA = 0x0001; + public const ushort HAS_DATA = 0x0002; + public const ushort KEEP = 0x0004; // block must be kept during an update + public const ushort SPLIT_BEFORE = 0x0008; + public const ushort SPLIT_AFTER = 0x0010; + public const ushort CHILD = 0x0020; // ??? Block depends on preceding file block. + public const ushort PRESERVE_CHILD = 0x0040; // ???? Preserve a child block if host block is modified +} - internal static class ArchiveFlagsV4 - { - public const ushort VOLUME = 0x0001; - public const ushort COMMENT = 0x0002; - public const ushort LOCK = 0x0004; - public const ushort SOLID = 0x0008; - public const ushort NEW_NUMBERING = 0x0010; - public const ushort AV = 0x0020; - public const ushort PROTECT = 0x0040; - public const ushort PASSWORD = 0x0080; - public const ushort FIRST_VOLUME = 0x0100; - public const ushort ENCRYPT_VER = 0x0200; - } +internal static class ArchiveFlagsV4 +{ + public const ushort VOLUME = 0x0001; + public const ushort COMMENT = 0x0002; + public const ushort LOCK = 0x0004; + public const ushort SOLID = 0x0008; + public const ushort NEW_NUMBERING = 0x0010; + public const ushort AV = 0x0020; + public const ushort PROTECT = 0x0040; + public const ushort PASSWORD = 0x0080; + public const ushort FIRST_VOLUME = 0x0100; + public const ushort ENCRYPT_VER = 0x0200; +} - internal static class ArchiveFlagsV5 - { - public const ushort VOLUME = 0x0001; - public const ushort HAS_VOLUME_NUMBER = 0x0002; - public const ushort SOLID = 0x0004; - public const ushort PROTECT = 0x0008; - public const ushort LOCK = 0x0010; - } +internal static class ArchiveFlagsV5 +{ + public const ushort VOLUME = 0x0001; + public const ushort HAS_VOLUME_NUMBER = 0x0002; + public const ushort SOLID = 0x0004; + public const ushort PROTECT = 0x0008; + public const ushort LOCK = 0x0010; +} - internal static class HostOsV4 - { - public const byte MS_DOS = 0; - public const byte OS2 = 1; - public const byte WIN32 = 2; - public const byte UNIX = 3; - public const byte MAC_OS = 4; - public const byte BE_OS = 5; - } +internal static class HostOsV4 +{ + public const byte MS_DOS = 0; + public const byte OS2 = 1; + public const byte WIN32 = 2; + public const byte UNIX = 3; + public const byte MAC_OS = 4; + public const byte BE_OS = 5; +} - internal static class HostOsV5 - { - public const byte WINDOWS = 0; - public const byte UNIX = 1; - } +internal static class HostOsV5 +{ + public const byte WINDOWS = 0; + public const byte UNIX = 1; +} - internal static class FileFlagsV4 - { - public const ushort SPLIT_BEFORE = 0x0001; - public const ushort SPLIT_AFTER = 0x0002; - public const ushort PASSWORD = 0x0004; - public const ushort COMMENT = 0x0008; - public const ushort SOLID = 0x0010; +internal static class FileFlagsV4 +{ + public const ushort SPLIT_BEFORE = 0x0001; + public const ushort SPLIT_AFTER = 0x0002; + public const ushort PASSWORD = 0x0004; + public const ushort COMMENT = 0x0008; + public const ushort SOLID = 0x0010; - public const ushort WINDOW_MASK = 0x00e0; - public const ushort WINDOW64 = 0x0000; - public const ushort WINDOW128 = 0x0020; - public const ushort WINDOW256 = 0x0040; - public const ushort WINDOW512 = 0x0060; - public const ushort WINDOW1024 = 0x0080; - public const ushort WINDOW2048 = 0x00a0; - public const ushort WINDOW4096 = 0x00c0; - public const ushort DIRECTORY = 0x00e0; + public const ushort WINDOW_MASK = 0x00e0; + public const ushort WINDOW64 = 0x0000; + public const ushort WINDOW128 = 0x0020; + public const ushort WINDOW256 = 0x0040; + public const ushort WINDOW512 = 0x0060; + public const ushort WINDOW1024 = 0x0080; + public const ushort WINDOW2048 = 0x00a0; + public const ushort WINDOW4096 = 0x00c0; + public const ushort DIRECTORY = 0x00e0; - public const ushort LARGE = 0x0100; - public const ushort UNICODE = 0x0200; - public const ushort SALT = 0x0400; - public const ushort VERSION = 0x0800; - public const ushort EXT_TIME = 0x1000; - public const ushort EXT_FLAGS = 0x2000; - } + public const ushort LARGE = 0x0100; + public const ushort UNICODE = 0x0200; + public const ushort SALT = 0x0400; + public const ushort VERSION = 0x0800; + public const ushort EXT_TIME = 0x1000; + public const ushort EXT_FLAGS = 0x2000; +} - internal static class FileFlagsV5 - { - public const ushort DIRECTORY = 0x0001; - public const ushort HAS_MOD_TIME = 0x0002; - public const ushort HAS_CRC32 = 0x0004; - public const ushort UNPACKED_SIZE_UNKNOWN = 0x0008; - } +internal static class FileFlagsV5 +{ + public const ushort DIRECTORY = 0x0001; + public const ushort HAS_MOD_TIME = 0x0002; + public const ushort HAS_CRC32 = 0x0004; + public const ushort UNPACKED_SIZE_UNKNOWN = 0x0008; +} - internal static class EndArchiveFlagsV4 - { - public const ushort NEXT_VOLUME = 0x0001; - public const ushort DATA_CRC = 0x0002; - public const ushort REV_SPACE = 0x0004; - public const ushort VOLUME_NUMBER = 0x0008; - } +internal static class EndArchiveFlagsV4 +{ + public const ushort NEXT_VOLUME = 0x0001; + public const ushort DATA_CRC = 0x0002; + public const ushort REV_SPACE = 0x0004; + public const ushort VOLUME_NUMBER = 0x0008; +} - internal static class EndArchiveFlagsV5 - { - public const ushort HAS_NEXT_VOLUME = 0x0001; - } -} \ No newline at end of file +internal static class EndArchiveFlagsV5 +{ + public const ushort HAS_NEXT_VOLUME = 0x0001; +} diff --git a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs index 5d3433b7..17a1a930 100644 --- a/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/IRarHeader.cs @@ -1,7 +1,6 @@ -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +public interface IRarHeader { - internal interface IRarHeader - { - HeaderType HeaderType { get; } - } -} \ No newline at end of file + HeaderType HeaderType { get; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index 82b40ad8..9ca0f9b2 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -1,123 +1,132 @@ using System; using System.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class MarkHeader : IRarHeader { - internal class MarkHeader : IRarHeader + private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 + + internal bool OldNumberingFormat { get; private set; } + + public bool IsRar5 { get; } + + private MarkHeader(bool isRar5) => IsRar5 = isRar5; + + public HeaderType HeaderType => HeaderType.Mark; + + private static byte GetByte(Stream stream) { - private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136 - - internal bool OldNumberingFormat { get; private set; } - - public bool IsRar5 { get; } - - private MarkHeader(bool isRar5) + var b = stream.ReadByte(); + if (b != -1) { - IsRar5 = isRar5; + return (byte)b; } + throw new EndOfStreamException(); + } - public HeaderType HeaderType => HeaderType.Mark; - - private static byte GetByte(Stream stream) + public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) + { + var maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0; + try { - var b = stream.ReadByte(); - if (b != -1) + var start = -1; + var b = GetByte(stream); + start++; + while (start <= maxScanIndex) { - return (byte)b; - } - throw new EndOfStreamException(); - } - - public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) - { - int maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0; - try - { - int start = -1; - var b = GetByte(stream); start++; - while (start <= maxScanIndex) + // Rar old signature: 52 45 7E 5E + // Rar4 signature: 52 61 72 21 1A 07 00 + // Rar5 signature: 52 61 72 21 1A 07 01 00 + if (b == 0x52) { - // Rar old signature: 52 45 7E 5E - // Rar4 signature: 52 61 72 21 1A 07 00 - // Rar5 signature: 52 61 72 21 1A 07 01 00 - if (b == 0x52) + b = GetByte(stream); + start++; + if (b == 0x61) { - b = GetByte(stream); start++; - if (b == 0x61) + b = GetByte(stream); + start++; + if (b != 0x72) { - b = GetByte(stream); start++; - if (b != 0x72) - { - continue; - } - - b = GetByte(stream); start++; - if (b != 0x21) - { - continue; - } - - b = GetByte(stream); start++; - if (b != 0x1a) - { - continue; - } - - b = GetByte(stream); start++; - if (b != 0x07) - { - continue; - } - - b = GetByte(stream); start++; - if (b == 1) - { - b = GetByte(stream); start++; - if (b != 0) - { - continue; - } - - return new MarkHeader(true); // Rar5 - } - else if (b == 0) - { - return new MarkHeader(false); // Rar4 - } + continue; } - else if (b == 0x45) + + b = GetByte(stream); + start++; + if (b != 0x21) { - b = GetByte(stream); start++; - if (b != 0x7e) + continue; + } + + b = GetByte(stream); + start++; + if (b != 0x1a) + { + continue; + } + + b = GetByte(stream); + start++; + if (b != 0x07) + { + continue; + } + + b = GetByte(stream); + start++; + if (b == 1) + { + b = GetByte(stream); + start++; + if (b != 0) { continue; } - b = GetByte(stream); start++; - if (b != 0x5e) - { - continue; - } - - throw new InvalidFormatException("Rar format version pre-4 is unsupported."); + return new MarkHeader(true); // Rar5 + } + else if (b == 0) + { + return new MarkHeader(false); // Rar4 } } - else + else if (b == 0x45) { - b = GetByte(stream); start++; + b = GetByte(stream); + start++; + if (b != 0x7e) + { + continue; + } + + b = GetByte(stream); + start++; + if (b != 0x5e) + { + continue; + } + + throw new InvalidFormatException( + "Rar format version pre-4 is unsupported." + ); } } - } - catch (Exception e) - { - if (!leaveStreamOpen) + else { - stream.Dispose(); + b = GetByte(stream); + start++; } - throw new InvalidFormatException("Error trying to read rar signature.", e); } - - throw new InvalidFormatException("Rar signature not found"); } + catch (Exception e) + { + if (!leaveStreamOpen) + { + stream.Dispose(); + } + throw new InvalidFormatException("Error trying to read rar signature.", e); + } + + throw new InvalidFormatException("Rar signature not found"); } } diff --git a/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs index 5e07c92e..b45f98bb 100644 --- a/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs +++ b/src/SharpCompress/Common/Rar/Headers/NewSubHeaderType.cs @@ -1,49 +1,45 @@ using System; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal sealed class NewSubHeaderType : IEquatable { - internal sealed class NewSubHeaderType : IEquatable + internal static readonly NewSubHeaderType SUBHEAD_TYPE_CMT = new('C', 'M', 'T'); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_ACL = new (new byte[]{'A','C','L'}); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_STREAM = new (new byte[]{'S','T','M'}); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_UOWNER = new (new byte[]{'U','O','W'}); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_AV = new (new byte[]{'A','V'}); + + internal static readonly NewSubHeaderType SUBHEAD_TYPE_RR = new('R', 'R'); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_OS2EA = new (new byte[]{'E','A','2'}); + + //internal static final NewSubHeaderType SUBHEAD_TYPE_BEOSEA = new (new byte[]{'E','A','B','E'}); + + private readonly byte[] _bytes; + + private NewSubHeaderType(params char[] chars) { - internal static readonly NewSubHeaderType SUBHEAD_TYPE_CMT = new('C', 'M', 'T'); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_ACL = new (new byte[]{'A','C','L'}); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_STREAM = new (new byte[]{'S','T','M'}); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_UOWNER = new (new byte[]{'U','O','W'}); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_AV = new (new byte[]{'A','V'}); - - internal static readonly NewSubHeaderType SUBHEAD_TYPE_RR = new('R', 'R'); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_OS2EA = new (new byte[]{'E','A','2'}); - - //internal static final NewSubHeaderType SUBHEAD_TYPE_BEOSEA = new (new byte[]{'E','A','B','E'}); - - private readonly byte[] _bytes; - - private NewSubHeaderType(params char[] chars) + _bytes = new byte[chars.Length]; + for (var i = 0; i < chars.Length; ++i) { - _bytes = new byte[chars.Length]; - for (int i = 0; i < chars.Length; ++i) - { - _bytes[i] = (byte)chars[i]; - } - } - - internal bool Equals(byte[] bytes) - { - if (_bytes.Length != bytes.Length) - { - return false; - } - - return _bytes.AsSpan().SequenceEqual(bytes); - } - - public bool Equals(NewSubHeaderType? other) - { - return other is not null && Equals(other._bytes); + _bytes[i] = (byte)chars[i]; } } -} \ No newline at end of file + + internal bool Equals(byte[] bytes) + { + if (_bytes.Length != bytes.Length) + { + return false; + } + + return _bytes.AsSpan().SequenceEqual(bytes); + } + + public bool Equals(NewSubHeaderType? other) => other is not null && Equals(other._bytes); +} diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index 27d5297b..cf8db3c0 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -1,31 +1,30 @@ -using SharpCompress.IO; +using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +// ProtectHeader is part of the Recovery Record feature +internal sealed class ProtectHeader : RarHeader { - // ProtectHeader is part of the Recovery Record feature - internal sealed class ProtectHeader : RarHeader + public ProtectHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Protect) { - public ProtectHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Protect) + if (IsRar5) { - if (IsRar5) - { - throw new InvalidFormatException("unexpected rar5 record"); - } + throw new InvalidFormatException("unexpected rar5 record"); } - - protected override void ReadFinish(MarkingBinaryReader reader) - { - Version = reader.ReadByte(); - RecSectors = reader.ReadUInt16(); - TotalBlocks = reader.ReadUInt32(); - Mark = reader.ReadBytes(8); - } - - internal uint DataSize => checked((uint)AdditionalDataSize); - internal byte Version { get; private set; } - internal ushort RecSectors { get; private set; } - internal uint TotalBlocks { get; private set; } - internal byte[]? Mark { get; private set; } } -} \ No newline at end of file + + protected override void ReadFinish(MarkingBinaryReader reader) + { + Version = reader.ReadByte(); + RecSectors = reader.ReadUInt16(); + TotalBlocks = reader.ReadUInt32(); + Mark = reader.ReadBytes(8); + } + + internal uint DataSize => checked((uint)AdditionalDataSize); + internal byte Version { get; private set; } + internal ushort RecSectors { get; private set; } + internal uint TotalBlocks { get; private set; } + internal byte[]? Mark { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index cb58b51e..81002fc4 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -2,133 +2,129 @@ using System.IO; using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +// http://www.forensicswiki.org/w/images/5/5b/RARFileStructure.txt +// https://www.rarlab.com/technote.htm +internal class RarHeader : IRarHeader { - // http://www.forensicswiki.org/w/images/5/5b/RARFileStructure.txt - // https://www.rarlab.com/technote.htm - internal class RarHeader : IRarHeader + private readonly HeaderType _headerType; + private readonly bool _isRar5; + + internal static RarHeader? TryReadBase( + RarCrcBinaryReader reader, + bool isRar5, + ArchiveEncoding archiveEncoding + ) { - private readonly HeaderType _headerType; - private readonly bool _isRar5; - - internal static RarHeader? TryReadBase(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) + try { - try - { - return new RarHeader(reader, isRar5, archiveEncoding); - } - catch (EndOfStreamException) - { - return null; - } + return new RarHeader(reader, isRar5, archiveEncoding); } - - private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) + catch (EndOfStreamException) { - _headerType = HeaderType.Null; - _isRar5 = isRar5; - ArchiveEncoding = archiveEncoding; - if (IsRar5) - { - HeaderCrc = reader.ReadUInt32(); - reader.ResetCrc(); - HeaderSize = (int)reader.ReadRarVIntUInt32(3); - reader.Mark(); - HeaderCode = reader.ReadRarVIntByte(); - HeaderFlags = reader.ReadRarVIntUInt16(2); - - if (HasHeaderFlag(HeaderFlagsV5.HAS_EXTRA)) - { - ExtraSize = reader.ReadRarVIntUInt32(); - } - if (HasHeaderFlag(HeaderFlagsV5.HAS_DATA)) - { - AdditionalDataSize = (long)reader.ReadRarVInt(); - } - } - else - { - reader.Mark(); - HeaderCrc = reader.ReadUInt16(); - reader.ResetCrc(); - HeaderCode = reader.ReadByte(); - HeaderFlags = reader.ReadUInt16(); - HeaderSize = reader.ReadInt16(); - if (HasHeaderFlag(HeaderFlagsV4.HAS_DATA)) - { - AdditionalDataSize = reader.ReadUInt32(); - } - } + return null; } - - protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) - { - _headerType = headerType; - _isRar5 = header.IsRar5; - HeaderCrc = header.HeaderCrc; - HeaderCode = header.HeaderCode; - HeaderFlags = header.HeaderFlags; - HeaderSize = header.HeaderSize; - ExtraSize = header.ExtraSize; - AdditionalDataSize = header.AdditionalDataSize; - ArchiveEncoding = header.ArchiveEncoding; - ReadFinish(reader); - - int n = RemainingHeaderBytes(reader); - if (n > 0) - { - reader.ReadBytes(n); - } - - VerifyHeaderCrc(reader.GetCrc32()); - } - - protected int RemainingHeaderBytes(MarkingBinaryReader reader) - { - return checked(HeaderSize - (int)reader.CurrentReadByteCount); - } - - protected virtual void ReadFinish(MarkingBinaryReader reader) - { - throw new NotImplementedException(); - } - - private void VerifyHeaderCrc(uint crc32) - { - var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; - if (!b) - { - throw new InvalidFormatException("rar header crc mismatch"); - } - } - - public HeaderType HeaderType => _headerType; - - protected bool IsRar5 => _isRar5; - - protected uint HeaderCrc { get; } - - internal byte HeaderCode { get; } - - protected ushort HeaderFlags { get; } - - protected bool HasHeaderFlag(ushort flag) - { - return (HeaderFlags & flag) == flag; - } - - protected int HeaderSize { get; } - - internal ArchiveEncoding ArchiveEncoding { get; } - - /// - /// Extra header size. - /// - protected uint ExtraSize { get; } - - /// - /// Size of additional data (eg file contents) - /// - protected long AdditionalDataSize { get; } } -} \ No newline at end of file + + private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding) + { + _headerType = HeaderType.Null; + _isRar5 = isRar5; + ArchiveEncoding = archiveEncoding; + if (IsRar5) + { + HeaderCrc = reader.ReadUInt32(); + reader.ResetCrc(); + HeaderSize = (int)reader.ReadRarVIntUInt32(3); + reader.Mark(); + HeaderCode = reader.ReadRarVIntByte(); + HeaderFlags = reader.ReadRarVIntUInt16(2); + + if (HasHeaderFlag(HeaderFlagsV5.HAS_EXTRA)) + { + ExtraSize = reader.ReadRarVIntUInt32(); + } + if (HasHeaderFlag(HeaderFlagsV5.HAS_DATA)) + { + AdditionalDataSize = (long)reader.ReadRarVInt(); + } + } + else + { + reader.Mark(); + HeaderCrc = reader.ReadUInt16(); + reader.ResetCrc(); + HeaderCode = reader.ReadByte(); + HeaderFlags = reader.ReadUInt16(); + HeaderSize = reader.ReadInt16(); + if (HasHeaderFlag(HeaderFlagsV4.HAS_DATA)) + { + AdditionalDataSize = reader.ReadUInt32(); + } + } + } + + protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType) + { + _headerType = headerType; + _isRar5 = header.IsRar5; + HeaderCrc = header.HeaderCrc; + HeaderCode = header.HeaderCode; + HeaderFlags = header.HeaderFlags; + HeaderSize = header.HeaderSize; + ExtraSize = header.ExtraSize; + AdditionalDataSize = header.AdditionalDataSize; + ArchiveEncoding = header.ArchiveEncoding; + ReadFinish(reader); + + var n = RemainingHeaderBytes(reader); + if (n > 0) + { + reader.ReadBytes(n); + } + + VerifyHeaderCrc(reader.GetCrc32()); + } + + protected int RemainingHeaderBytes(MarkingBinaryReader reader) => + checked(HeaderSize - (int)reader.CurrentReadByteCount); + + protected virtual void ReadFinish(MarkingBinaryReader reader) => + throw new NotImplementedException(); + + private void VerifyHeaderCrc(uint crc32) + { + var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc; + if (!b) + { + throw new InvalidFormatException("rar header crc mismatch"); + } + } + + public HeaderType HeaderType => _headerType; + + protected bool IsRar5 => _isRar5; + + protected uint HeaderCrc { get; } + + internal byte HeaderCode { get; } + + protected ushort HeaderFlags { get; } + + protected bool HasHeaderFlag(ushort flag) => (HeaderFlags & flag) == flag; + + protected int HeaderSize { get; } + + internal ArchiveEncoding ArchiveEncoding { get; } + + /// + /// Extra header size. + /// + protected uint ExtraSize { get; } + + /// + /// Size of additional data (eg file contents) + /// + protected long AdditionalDataSize { get; } +} diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 6c8d5686..d371166d 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -3,186 +3,197 @@ using System.IO; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +public class RarHeaderFactory { - internal class RarHeaderFactory + private bool _isRar5; + + public RarHeaderFactory(StreamingMode mode, ReaderOptions options) { - private bool _isRar5; + StreamingMode = mode; + Options = options; + } - internal RarHeaderFactory(StreamingMode mode, ReaderOptions options) + public ReaderOptions Options { get; } + public StreamingMode StreamingMode { get; } + public bool IsEncrypted { get; private set; } + + public IEnumerable ReadHeaders(Stream stream) + { + var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader); + _isRar5 = markHeader.IsRar5; + yield return markHeader; + + RarHeader? header; + while ((header = TryReadNextHeader(stream)) != null) { - StreamingMode = mode; - Options = options; - } - - private ReaderOptions Options { get; } - internal StreamingMode StreamingMode { get; } - internal bool IsEncrypted { get; private set; } - - internal IEnumerable ReadHeaders(Stream stream) - { - var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader); - _isRar5 = markHeader.IsRar5; - yield return markHeader; - - RarHeader? header; - while ((header = TryReadNextHeader(stream)) != null) + yield return header; + if (header.HeaderType == HeaderType.EndArchive) { - yield return header; - if (header.HeaderType == HeaderType.EndArchive) - { - // End of archive marker. RAR does not read anything after this header letting to use third - // party tools to add extra information such as a digital signature to archive. - yield break; - } - } - } - - private RarHeader? TryReadNextHeader(Stream stream) - { - RarCrcBinaryReader reader; - if (!IsEncrypted) - { - reader = new RarCrcBinaryReader(stream); - } - else - { - if (Options.Password is null) - { - throw new CryptographicException("Encrypted Rar archive has no password specified."); - } - reader = new RarCryptoBinaryReader(stream, Options.Password); - } - - var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding); - if (header is null) - { - return null; - } - switch (header.HeaderCode) - { - case HeaderCodeV.RAR5_ARCHIVE_HEADER: - case HeaderCodeV.RAR4_ARCHIVE_HEADER: - { - var ah = new ArchiveHeader(header, reader); - if (ah.IsEncrypted == true) - { - //!!! rar5 we don't know yet - IsEncrypted = true; - } - return ah; - } - - case HeaderCodeV.RAR4_PROTECT_HEADER: - { - var ph = new ProtectHeader(header, reader); - // skip the recovery record data, we do not use it. - switch (StreamingMode) - { - case StreamingMode.Seekable: - { - reader.BaseStream.Position += ph.DataSize; - } - break; - case StreamingMode.Streaming: - { - reader.BaseStream.Skip(ph.DataSize); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } - } - - return ph; - } - - case HeaderCodeV.RAR5_SERVICE_HEADER: - { - var fh = new FileHeader(header, reader, HeaderType.Service); - SkipData(fh, reader); - return fh; - } - - case HeaderCodeV.RAR4_NEW_SUB_HEADER: - { - var fh = new FileHeader(header, reader, HeaderType.NewSub); - SkipData(fh, reader); - return fh; - } - - case HeaderCodeV.RAR5_FILE_HEADER: - case HeaderCodeV.RAR4_FILE_HEADER: - { - var fh = new FileHeader(header, reader, HeaderType.File); - switch (StreamingMode) - { - case StreamingMode.Seekable: - { - fh.DataStartPosition = reader.BaseStream.Position; - reader.BaseStream.Position += fh.CompressedSize; - } - break; - case StreamingMode.Streaming: - { - var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); - if (fh.R4Salt is null) - { - fh.PackedStream = ms; - } - else - { - fh.PackedStream = new RarCryptoWrapper(ms, Options.Password!, fh.R4Salt); - } - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } - } - return fh; - } - case HeaderCodeV.RAR5_END_ARCHIVE_HEADER: - case HeaderCodeV.RAR4_END_ARCHIVE_HEADER: - { - return new EndArchiveHeader(header, reader); - } - case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER: - { - var ch = new ArchiveCryptHeader(header, reader); - IsEncrypted = true; - return ch; - } - default: - { - throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode); - } - } - } - - private void SkipData(FileHeader fh, RarCrcBinaryReader reader) - { - switch (StreamingMode) - { - case StreamingMode.Seekable: - { - fh.DataStartPosition = reader.BaseStream.Position; - reader.BaseStream.Position += fh.CompressedSize; - } - break; - case StreamingMode.Streaming: - { - //skip the data because it's useless? - reader.BaseStream.Skip(fh.CompressedSize); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } + // End of archive marker. RAR does not read anything after this header letting to use third + // party tools to add extra information such as a digital signature to archive. + yield break; } } } -} \ No newline at end of file + + private RarHeader? TryReadNextHeader(Stream stream) + { + RarCrcBinaryReader reader; + if (!IsEncrypted) + { + reader = new RarCrcBinaryReader(stream); + } + else + { + if (Options.Password is null) + { + throw new CryptographicException( + "Encrypted Rar archive has no password specified." + ); + } + reader = new RarCryptoBinaryReader(stream, Options.Password); + } + + var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding); + if (header is null) + { + return null; + } + switch (header.HeaderCode) + { + case HeaderCodeV.RAR5_ARCHIVE_HEADER: + case HeaderCodeV.RAR4_ARCHIVE_HEADER: + { + var ah = new ArchiveHeader(header, reader); + if (ah.IsEncrypted == true) + { + //!!! rar5 we don't know yet + IsEncrypted = true; + } + return ah; + } + + case HeaderCodeV.RAR4_PROTECT_HEADER: + { + var ph = new ProtectHeader(header, reader); + // skip the recovery record data, we do not use it. + switch (StreamingMode) + { + case StreamingMode.Seekable: + + { + reader.BaseStream.Position += ph.DataSize; + } + break; + case StreamingMode.Streaming: + + { + reader.BaseStream.Skip(ph.DataSize); + } + break; + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + + return ph; + } + + case HeaderCodeV.RAR5_SERVICE_HEADER: + { + var fh = new FileHeader(header, reader, HeaderType.Service); + SkipData(fh, reader); + return fh; + } + + case HeaderCodeV.RAR4_NEW_SUB_HEADER: + { + var fh = new FileHeader(header, reader, HeaderType.NewSub); + SkipData(fh, reader); + return fh; + } + + case HeaderCodeV.RAR5_FILE_HEADER: + case HeaderCodeV.RAR4_FILE_HEADER: + { + var fh = new FileHeader(header, reader, HeaderType.File); + switch (StreamingMode) + { + case StreamingMode.Seekable: + + { + fh.DataStartPosition = reader.BaseStream.Position; + reader.BaseStream.Position += fh.CompressedSize; + } + break; + case StreamingMode.Streaming: + + { + var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); + if (fh.R4Salt is null) + { + fh.PackedStream = ms; + } + else + { + fh.PackedStream = new RarCryptoWrapper( + ms, + Options.Password!, + fh.R4Salt + ); + } + } + break; + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + return fh; + } + case HeaderCodeV.RAR5_END_ARCHIVE_HEADER: + case HeaderCodeV.RAR4_END_ARCHIVE_HEADER: + { + return new EndArchiveHeader(header, reader); + } + case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER: + { + var ch = new ArchiveCryptHeader(header, reader); + IsEncrypted = true; + return ch; + } + default: + { + throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode); + } + } + } + + private void SkipData(FileHeader fh, RarCrcBinaryReader reader) + { + switch (StreamingMode) + { + case StreamingMode.Seekable: + + { + fh.DataStartPosition = reader.BaseStream.Position; + reader.BaseStream.Position += fh.CompressedSize; + } + break; + case StreamingMode.Streaming: + + { + //skip the data because it's useless? + reader.BaseStream.Skip(fh.CompressedSize); + } + break; + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + } +} diff --git a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs index b45c76b2..837ff3dc 100644 --- a/src/SharpCompress/Common/Rar/Headers/SignHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/SignHeader.cs @@ -1,29 +1,28 @@ -using SharpCompress.IO; +using SharpCompress.IO; -namespace SharpCompress.Common.Rar.Headers +namespace SharpCompress.Common.Rar.Headers; + +internal class SignHeader : RarHeader { - internal class SignHeader : RarHeader + protected SignHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader, HeaderType.Sign) { - protected SignHeader(RarHeader header, RarCrcBinaryReader reader) - : base(header, reader, HeaderType.Sign) + if (IsRar5) { - if (IsRar5) - { - throw new InvalidFormatException("unexpected rar5 record"); - } + throw new InvalidFormatException("unexpected rar5 record"); } - - protected override void ReadFinish(MarkingBinaryReader reader) - { - CreationTime = reader.ReadInt32(); - ArcNameSize = reader.ReadInt16(); - UserNameSize = reader.ReadInt16(); - } - - internal int CreationTime { get; private set; } - - internal short ArcNameSize { get; private set; } - - internal short UserNameSize { get; private set; } } -} \ No newline at end of file + + protected override void ReadFinish(MarkingBinaryReader reader) + { + CreationTime = reader.ReadInt32(); + ArcNameSize = reader.ReadInt16(); + UserNameSize = reader.ReadInt16(); + } + + internal int CreationTime { get; private set; } + + internal short ArcNameSize { get; private set; } + + internal short UserNameSize { get; private set; } +} diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index ddfd8b98..97a8aaa9 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -2,49 +2,33 @@ using System.IO; using SharpCompress.Compressors.Rar; using SharpCompress.IO; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +internal class RarCrcBinaryReader : MarkingBinaryReader { - internal class RarCrcBinaryReader : MarkingBinaryReader + private uint _currentCrc; + + public RarCrcBinaryReader(Stream stream) : base(stream) { } + + public uint GetCrc32() => ~_currentCrc; + + public void ResetCrc() => _currentCrc = 0xffffffff; + + protected void UpdateCrc(byte b) => _currentCrc = RarCRC.CheckCrc(_currentCrc, b); + + protected byte[] ReadBytesNoCrc(int count) => base.ReadBytes(count); + + public override byte ReadByte() { - private uint _currentCrc; - - public RarCrcBinaryReader(Stream stream) - : base(stream) - { - } - - public uint GetCrc32() - { - return ~_currentCrc; - } - - public void ResetCrc() - { - _currentCrc = 0xffffffff; - } - - protected void UpdateCrc(byte b) - { - _currentCrc = RarCRC.CheckCrc(_currentCrc, b); - } - - protected byte[] ReadBytesNoCrc(int count) - { - return base.ReadBytes(count); - } - - public override byte ReadByte() - { - var b = base.ReadByte(); - _currentCrc = RarCRC.CheckCrc(_currentCrc, b); - return b; - } - - public override byte[] ReadBytes(int count) - { - var result = base.ReadBytes(count); - _currentCrc = RarCRC.CheckCrc(_currentCrc, result, 0, result.Length); - return result; - } + var b = base.ReadByte(); + _currentCrc = RarCRC.CheckCrc(_currentCrc, b); + return b; } -} \ No newline at end of file + + public override byte[] ReadBytes(int count) + { + var result = base.ReadBytes(count); + _currentCrc = RarCRC.CheckCrc(_currentCrc, result, 0, result.Length); + return result; + } +} diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index e5b7cf33..71902fdf 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -1,111 +1,103 @@ using System.Collections.Generic; using System.IO; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader { - internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader + private RarRijndael _rijndael; + private byte[] _salt; + private readonly string _password; + private readonly Queue _data = new Queue(); + private long _readCount; + + public RarCryptoBinaryReader(Stream stream, string password) : base(stream) { - private RarRijndael _rijndael; - private byte[] _salt; - private readonly string _password; - private readonly Queue _data = new Queue(); - private long _readCount; + _password = password; - public RarCryptoBinaryReader(Stream stream, string password) - : base(stream) + // coderb: not sure why this was being done at this logical point + //SkipQueue(); + var salt = ReadBytes(8); + + _salt = salt; + _rijndael = RarRijndael.InitializeFrom(_password, salt); + } + + // track read count ourselves rather than using the underlying stream since we buffer + public override long CurrentReadByteCount + { + get => _readCount; + protected set { - _password = password; - - // coderb: not sure why this was being done at this logical point - //SkipQueue(); - byte[] salt = ReadBytes(8); - - _salt = salt; - _rijndael = RarRijndael.InitializeFrom(_password, salt); - } - - // track read count ourselves rather than using the underlying stream since we buffer - public override long CurrentReadByteCount - { - get => _readCount; - protected set - { - // ignore - } - } - - public override void Mark() - { - _readCount = 0; - } - - private bool UseEncryption => _salt != null; - - public override byte ReadByte() - { - if (UseEncryption) - { - return ReadAndDecryptBytes(1)[0]; - } - - _readCount++; - return base.ReadByte(); - } - - public override byte[] ReadBytes(int count) - { - if (UseEncryption) - { - return ReadAndDecryptBytes(count); - } - - _readCount += count; - return base.ReadBytes(count); - } - - private byte[] ReadAndDecryptBytes(int count) - { - int queueSize = _data.Count; - int sizeToRead = count - queueSize; - - if (sizeToRead > 0) - { - int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); - for (int i = 0; i < alignedSize / 16; i++) - { - //long ax = System.currentTimeMillis(); - byte[] cipherText = ReadBytesNoCrc(16); - var readBytes = _rijndael.ProcessBlock(cipherText); - foreach (var readByte in readBytes) - { - _data.Enqueue(readByte); - } - } - } - - var decryptedBytes = new byte[count]; - - for (int i = 0; i < count; i++) - { - var b = _data.Dequeue(); - decryptedBytes[i] = b; - UpdateCrc(b); - } - - _readCount += count; - return decryptedBytes; - } - - public void ClearQueue() - { - _data.Clear(); - } - - public void SkipQueue() - { - var position = BaseStream.Position; - BaseStream.Position = position + _data.Count; - ClearQueue(); + // ignore } } -} \ No newline at end of file + + public override void Mark() => _readCount = 0; + + private bool UseEncryption => _salt != null; + + public override byte ReadByte() + { + if (UseEncryption) + { + return ReadAndDecryptBytes(1)[0]; + } + + _readCount++; + return base.ReadByte(); + } + + public override byte[] ReadBytes(int count) + { + if (UseEncryption) + { + return ReadAndDecryptBytes(count); + } + + _readCount += count; + return base.ReadBytes(count); + } + + private byte[] ReadAndDecryptBytes(int count) + { + var queueSize = _data.Count; + var sizeToRead = count - queueSize; + + if (sizeToRead > 0) + { + var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); + for (var i = 0; i < alignedSize / 16; i++) + { + //long ax = System.currentTimeMillis(); + var cipherText = ReadBytesNoCrc(16); + var readBytes = _rijndael.ProcessBlock(cipherText); + foreach (var readByte in readBytes) + { + _data.Enqueue(readByte); + } + } + } + + var decryptedBytes = new byte[count]; + + for (var i = 0; i < count; i++) + { + var b = _data.Dequeue(); + decryptedBytes[i] = b; + UpdateCrc(b); + } + + _readCount += count; + return decryptedBytes; + } + + public void ClearQueue() => _data.Clear(); + + public void SkipQueue() + { + var position = BaseStream.Position; + BaseStream.Position = position + _data.Count; + ClearQueue(); + } +} diff --git a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs index 78fee933..e92056b4 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs @@ -2,98 +2,86 @@ using System; using System.Collections.Generic; using System.IO; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +internal sealed class RarCryptoWrapper : Stream { - internal sealed class RarCryptoWrapper : Stream + private readonly Stream _actualStream; + private readonly byte[] _salt; + private RarRijndael _rijndael; + private readonly Queue _data = new Queue(); + + public RarCryptoWrapper(Stream actualStream, string password, byte[] salt) { - private readonly Stream _actualStream; - private readonly byte[] _salt; - private RarRijndael _rijndael; - private readonly Queue _data = new Queue(); + _actualStream = actualStream; + _salt = salt; + _rijndael = RarRijndael.InitializeFrom(password, salt); + } - public RarCryptoWrapper(Stream actualStream, string password, byte[] salt) + public override void Flush() => throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (_salt is null) { - _actualStream = actualStream; - _salt = salt; - _rijndael = RarRijndael.InitializeFrom(password, salt); + return _actualStream.Read(buffer, offset, count); } + return ReadAndDecrypt(buffer, offset, count); + } - public override void Flush() - { - throw new NotSupportedException(); - } + public int ReadAndDecrypt(byte[] buffer, int offset, int count) + { + var queueSize = _data.Count; + var sizeToRead = count - queueSize; - public override long Seek(long offset, SeekOrigin origin) + if (sizeToRead > 0) { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_salt is null) + var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); + Span cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE]; + for (var i = 0; i < alignedSize / 16; i++) { - return _actualStream.Read(buffer, offset, count); - } - return ReadAndDecrypt(buffer, offset, count); - } + //long ax = System.currentTimeMillis(); + _actualStream.Read(cipherText); - public int ReadAndDecrypt(byte[] buffer, int offset, int count) - { - int queueSize = _data.Count; - int sizeToRead = count - queueSize; - - if (sizeToRead > 0) - { - int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); - Span cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE]; - for (int i = 0; i < alignedSize / 16; i++) + var readBytes = _rijndael.ProcessBlock(cipherText); + foreach (var readByte in readBytes) { - //long ax = System.currentTimeMillis(); - _actualStream.Read(cipherText); - - var readBytes = _rijndael.ProcessBlock(cipherText); - foreach (var readByte in readBytes) - { - _data.Enqueue(readByte); - } - } - - for (int i = 0; i < count; i++) - { - buffer[offset + i] = _data.Dequeue(); + _data.Enqueue(readByte); } } - return count; - } - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => throw new NotSupportedException(); - - public override long Position { get; set; } - - protected override void Dispose(bool disposing) - { - if (_rijndael != null) + for (var i = 0; i < count; i++) { - _rijndael.Dispose(); - _rijndael = null!; + buffer[offset + i] = _data.Dequeue(); } - base.Dispose(disposing); } + return count; + } + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get; set; } + + protected override void Dispose(bool disposing) + { + if (_rijndael != null) + { + _rijndael.Dispose(); + _rijndael = null!; + } + base.Dispose(disposing); } } diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index fc084d5c..f3614a40 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -1,65 +1,71 @@ using System; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +public abstract class RarEntry : Entry { - public abstract class RarEntry : Entry - { - internal abstract FileHeader FileHeader { get; } + internal abstract FileHeader FileHeader { get; } - /// - /// As the V2017 port isn't complete, add this check to use the legacy Rar code. - /// - internal bool IsRarV3 => FileHeader.CompressionAlgorithm == 20 || FileHeader.CompressionAlgorithm == 26 || FileHeader.CompressionAlgorithm == 29 || FileHeader.CompressionAlgorithm == 36; //Nanook - Added 20+26 as Test arc from WinRar2.8 (algo 20) was failing with 2017 code + /// + /// As the V2017 port isn't complete, add this check to use the legacy Rar code. + /// + internal bool IsRarV3 => + FileHeader.CompressionAlgorithm == 20 + || FileHeader.CompressionAlgorithm == 26 + || FileHeader.CompressionAlgorithm == 29 + || FileHeader.CompressionAlgorithm == 36; //Nanook - Added 20+26 as Test arc from WinRar2.8 (algo 20) was failing with 2017 code - /// - /// The File's 32 bit CRC Hash - /// - public override long Crc => FileHeader.FileCrc; + /// + /// The File's 32 bit CRC Hash + /// + public override long Crc => FileHeader.FileCrc; - /// - /// The path of the file internal to the Rar Archive. - /// - public override string Key => FileHeader.FileName; + /// + /// The path of the file internal to the Rar Archive. + /// + public override string Key => FileHeader.FileName; - public override string? LinkTarget => null; + public override string? LinkTarget => null; - /// - /// The entry last modified time in the archive, if recorded - /// - public override DateTime? LastModifiedTime => FileHeader.FileLastModifiedTime; + /// + /// The entry last modified time in the archive, if recorded + /// + public override DateTime? LastModifiedTime => FileHeader.FileLastModifiedTime; - /// - /// The entry create time in the archive, if recorded - /// - public override DateTime? CreatedTime => FileHeader.FileCreatedTime; + /// + /// The entry create time in the archive, if recorded + /// + public override DateTime? CreatedTime => FileHeader.FileCreatedTime; - /// - /// The entry last accessed time in the archive, if recorded - /// - public override DateTime? LastAccessedTime => FileHeader.FileLastAccessedTime; + /// + /// The entry last accessed time in the archive, if recorded + /// + public override DateTime? LastAccessedTime => FileHeader.FileLastAccessedTime; - /// - /// The entry time whend archived, if recorded - /// - public override DateTime? ArchivedTime => FileHeader.FileArchivedTime; + /// + /// The entry time whend archived, if recorded + /// + public override DateTime? ArchivedTime => FileHeader.FileArchivedTime; - /// - /// Entry is password protected and encrypted and cannot be extracted. - /// - public override bool IsEncrypted => FileHeader.IsEncrypted; + /// + /// Entry is password protected and encrypted and cannot be extracted. + /// + public override bool IsEncrypted => FileHeader.IsEncrypted; - /// - /// Entry is password protected and encrypted and cannot be extracted. - /// - public override bool IsDirectory => FileHeader.IsDirectory; + /// + /// Entry is password protected and encrypted and cannot be extracted. + /// + public override bool IsDirectory => FileHeader.IsDirectory; - public override bool IsSplitAfter => FileHeader.IsSplitAfter; + public override bool IsSplitAfter => FileHeader.IsSplitAfter; - public override string ToString() - { - return string.Format("Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}", - Key, CompressedSize, Size, Crc); - } - } -} \ No newline at end of file + public override string ToString() => + string.Format( + "Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}", + Key, + CompressedSize, + Size, + Crc + ); +} diff --git a/src/SharpCompress/Common/Rar/RarFilePart.cs b/src/SharpCompress/Common/Rar/RarFilePart.cs index 4075198e..7608d0ac 100644 --- a/src/SharpCompress/Common/Rar/RarFilePart.cs +++ b/src/SharpCompress/Common/Rar/RarFilePart.cs @@ -1,28 +1,23 @@ using System.IO; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +/// +/// This represents a single file part that exists in a rar volume. A compressed file is one or many file parts that are spread across one or may rar parts. +/// +internal abstract class RarFilePart : FilePart { - /// - /// This represents a single file part that exists in a rar volume. A compressed file is one or many file parts that are spread across one or may rar parts. - /// - internal abstract class RarFilePart : FilePart + internal RarFilePart(MarkHeader mh, FileHeader fh, int index) : base(fh.ArchiveEncoding) { - internal RarFilePart(MarkHeader mh, FileHeader fh, int index) - : base(fh.ArchiveEncoding) - { - MarkHeader = mh; - FileHeader = fh; - Index = index; - } - - internal MarkHeader MarkHeader { get; } - - internal FileHeader FileHeader { get; } - - internal override Stream? GetRawStream() - { - return null; - } + MarkHeader = mh; + FileHeader = fh; + Index = index; } + + internal MarkHeader MarkHeader { get; } + + internal FileHeader FileHeader { get; } + + internal override Stream? GetRawStream() => null; } diff --git a/src/SharpCompress/Common/Rar/RarRijndael.cs b/src/SharpCompress/Common/Rar/RarRijndael.cs index 44531159..94592639 100644 --- a/src/SharpCompress/Common/Rar/RarRijndael.cs +++ b/src/SharpCompress/Common/Rar/RarRijndael.cs @@ -1,113 +1,114 @@ -#nullable disable +#nullable disable using System; using System.Security.Cryptography; using System.Text; using SharpCompress.Crypto; -namespace SharpCompress.Common.Rar +namespace SharpCompress.Common.Rar; + +internal class RarRijndael : IDisposable { - internal class RarRijndael : IDisposable + internal const int CRYPTO_BLOCK_SIZE = 16; + + private readonly string _password; + private readonly byte[] _salt; + private byte[] _aesInitializationVector; + private RijndaelEngine _rijndael; + + private RarRijndael(string password, byte[] salt) { - internal const int CRYPTO_BLOCK_SIZE = 16; - - private readonly string _password; - private readonly byte[] _salt; - private byte[] _aesInitializationVector; - private RijndaelEngine _rijndael; - - private RarRijndael(string password, byte[] salt) - { - _password = password; - _salt = salt; - } - - private void Initialize() - { - - _rijndael = new RijndaelEngine(); - _aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE]; - int rawLength = 2 * _password.Length; - byte[] rawPassword = new byte[rawLength + 8]; - byte[] passwordBytes = Encoding.UTF8.GetBytes(_password); - for (int i = 0; i < _password.Length; i++) - { - rawPassword[i * 2] = passwordBytes[i]; - rawPassword[i * 2 + 1] = 0; - } - for (int i = 0; i < _salt.Length; i++) - { - rawPassword[i + rawLength] = _salt[i]; - } - - const int noOfRounds = (1 << 18); - const int iblock = 3; - byte[] digest; - byte[] data = new byte[(rawPassword.Length + iblock) * noOfRounds]; - - //TODO slow code below, find ways to optimize - for (int i = 0; i < noOfRounds; i++) - { - rawPassword.CopyTo(data, i * (rawPassword.Length + iblock)); - - data[i * (rawPassword.Length + iblock) + rawPassword.Length + 0] = (byte)i; - data[i * (rawPassword.Length + iblock) + rawPassword.Length + 1] = (byte)(i >> 8); - data[i * (rawPassword.Length + iblock) + rawPassword.Length + 2] = (byte)(i >> CRYPTO_BLOCK_SIZE); - - if (i % (noOfRounds / CRYPTO_BLOCK_SIZE) == 0) - { - digest = SHA1.Create().ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock)); - _aesInitializationVector[i / (noOfRounds / CRYPTO_BLOCK_SIZE)] = digest[19]; - } - } - digest = SHA1.Create().ComputeHash(data); - //slow code ends - - byte[] aesKey = new byte[CRYPTO_BLOCK_SIZE]; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - aesKey[i * 4 + j] = (byte) - (((digest[i * 4] * 0x1000000) & 0xff000000 | - (uint)((digest[i * 4 + 1] * 0x10000) & 0xff0000) | - (uint)((digest[i * 4 + 2] * 0x100) & 0xff00) | - (uint)(digest[i * 4 + 3] & 0xff)) >> (j * 8)); - } - } - - _rijndael.Init(false, new KeyParameter(aesKey)); - - } - - public static RarRijndael InitializeFrom(string password, byte[] salt) - { - var rijndael = new RarRijndael(password, salt); - rijndael.Initialize(); - return rijndael; - } - - public byte[] ProcessBlock(ReadOnlySpan cipherText) - { - Span plainText = stackalloc byte[CRYPTO_BLOCK_SIZE]; // 16 bytes - byte[] decryptedBytes = new byte[CRYPTO_BLOCK_SIZE]; - _rijndael.ProcessBlock(cipherText, plainText); - - for (int j = 0; j < CRYPTO_BLOCK_SIZE; j++) - { - decryptedBytes[j] = (byte)(plainText[j] ^ _aesInitializationVector[j % 16]); //32:114, 33:101 - } - - for (int j = 0; j < _aesInitializationVector.Length; j++) - { - _aesInitializationVector[j] = cipherText[j]; - } - - return decryptedBytes; - } - - public void Dispose() - { - } + _password = password; + _salt = salt; } + + private void Initialize() + { + _rijndael = new RijndaelEngine(); + _aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE]; + var rawLength = 2 * _password.Length; + var rawPassword = new byte[rawLength + 8]; + var passwordBytes = Encoding.UTF8.GetBytes(_password); + for (var i = 0; i < _password.Length; i++) + { + rawPassword[i * 2] = passwordBytes[i]; + rawPassword[(i * 2) + 1] = 0; + } + for (var i = 0; i < _salt.Length; i++) + { + rawPassword[i + rawLength] = _salt[i]; + } + + const int noOfRounds = (1 << 18); + const int iblock = 3; + byte[] digest; + var data = new byte[(rawPassword.Length + iblock) * noOfRounds]; + + //TODO slow code below, find ways to optimize + for (var i = 0; i < noOfRounds; i++) + { + rawPassword.CopyTo(data, i * (rawPassword.Length + iblock)); + + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i; + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8); + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)( + i >> CRYPTO_BLOCK_SIZE + ); + + if (i % (noOfRounds / CRYPTO_BLOCK_SIZE) == 0) + { + digest = SHA1.Create() + .ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock)); + _aesInitializationVector[i / (noOfRounds / CRYPTO_BLOCK_SIZE)] = digest[19]; + } + } + digest = SHA1.Create().ComputeHash(data); + //slow code ends + + var aesKey = new byte[CRYPTO_BLOCK_SIZE]; + for (var i = 0; i < 4; i++) + { + for (var j = 0; j < 4; j++) + { + aesKey[(i * 4) + j] = (byte)( + ( + ((digest[i * 4] * 0x1000000) & 0xff000000) + | (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000) + | (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00) + | (uint)(digest[(i * 4) + 3] & 0xff) + ) >> (j * 8) + ); + } + } + + _rijndael.Init(false, new KeyParameter(aesKey)); + } + + public static RarRijndael InitializeFrom(string password, byte[] salt) + { + var rijndael = new RarRijndael(password, salt); + rijndael.Initialize(); + return rijndael; + } + + public byte[] ProcessBlock(ReadOnlySpan cipherText) + { + Span plainText = stackalloc byte[CRYPTO_BLOCK_SIZE]; // 16 bytes + var decryptedBytes = new byte[CRYPTO_BLOCK_SIZE]; + _rijndael.ProcessBlock(cipherText, plainText); + + for (var j = 0; j < CRYPTO_BLOCK_SIZE; j++) + { + decryptedBytes[j] = (byte)(plainText[j] ^ _aesInitializationVector[j % 16]); //32:114, 33:101 + } + + for (var j = 0; j < _aesInitializationVector.Length; j++) + { + _aesInitializationVector[j] = cipherText[j]; + } + + return decryptedBytes; + } + + public void Dispose() { } } diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 4efbdd92..7be719ec 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -6,158 +6,184 @@ using SharpCompress.Common.Rar.Headers; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.Rar -{ - /// - /// A RarArchiveVolume is a single rar file that may or may not be a split RarArchive. A Rar Archive is one to many Rar Parts - /// - public abstract class RarVolume : Volume - { - private readonly RarHeaderFactory _headerFactory; - internal int _maxCompressionAlgorithm; +namespace SharpCompress.Common.Rar; - internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index = 0) - : base(stream, options, index) - { - _headerFactory = new RarHeaderFactory(mode, options); - } +/// +/// A RarArchiveVolume is a single rar file that may or may not be a split RarArchive. A Rar Archive is one to many Rar Parts +/// +public abstract class RarVolume : Volume +{ + private readonly RarHeaderFactory _headerFactory; + internal int _maxCompressionAlgorithm; + + internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index = 0) + : base(stream, options, index) => _headerFactory = new RarHeaderFactory(mode, options); #nullable disable - internal ArchiveHeader ArchiveHeader { get; private set; } + internal ArchiveHeader ArchiveHeader { get; private set; } + #nullable enable - internal StreamingMode Mode => _headerFactory.StreamingMode; + internal StreamingMode Mode => _headerFactory.StreamingMode; - internal abstract IEnumerable ReadFileParts(); + internal abstract IEnumerable ReadFileParts(); - internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader); + internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader); - internal IEnumerable GetVolumeFileParts() + internal IEnumerable GetVolumeFileParts() + { + MarkHeader? lastMarkHeader = null; + foreach (var header in _headerFactory.ReadHeaders(Stream)) { - MarkHeader? lastMarkHeader = null; - foreach (var header in _headerFactory.ReadHeaders(Stream)) + switch (header.HeaderType) { - switch (header.HeaderType) - { - case HeaderType.Mark: + case HeaderType.Mark: + + { + lastMarkHeader = (MarkHeader)header; + } + break; + case HeaderType.Archive: + + { + ArchiveHeader = (ArchiveHeader)header; + } + break; + case HeaderType.File: + + { + var fh = (FileHeader)header; + if (_maxCompressionAlgorithm < fh.CompressionAlgorithm) { - lastMarkHeader = (MarkHeader)header; + _maxCompressionAlgorithm = fh.CompressionAlgorithm; } - break; - case HeaderType.Archive: + + yield return CreateFilePart(lastMarkHeader!, fh); + } + break; + case HeaderType.Service: + + { + var fh = (FileHeader)header; + if (fh.FileName == "CMT") { - ArchiveHeader = (ArchiveHeader)header; + var part = CreateFilePart(lastMarkHeader!, fh); + var buffer = new byte[fh.CompressedSize]; + part.GetCompressedStream().Read(buffer, 0, buffer.Length); + Comment = System.Text.Encoding.UTF8.GetString( + buffer, + 0, + buffer.Length - 1 + ); } - break; - case HeaderType.File: - { - var fh = (FileHeader)header; - if (_maxCompressionAlgorithm < fh.CompressionAlgorithm) - _maxCompressionAlgorithm = fh.CompressionAlgorithm; - yield return CreateFilePart(lastMarkHeader!, fh); - } - break; - case HeaderType.Service: - { - var fh = (FileHeader)header; - if (fh.FileName == "CMT") - { - var part = CreateFilePart(lastMarkHeader!, fh); - var buffer = new byte[fh.CompressedSize]; - part.GetCompressedStream().Read(buffer, 0, buffer.Length); - Comment = System.Text.Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1); - } - } - break; - } + } + break; } } - - private void EnsureArchiveHeaderLoaded() - { - if (ArchiveHeader is null) - { - if (Mode == StreamingMode.Streaming) - { - throw new InvalidOperationException("ArchiveHeader should never been null in a streaming read."); - } - - // we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream - GetVolumeFileParts().First(); - Stream.Position = 0; - } - } - - /// - /// RarArchive is the first volume of a multi-part archive. - /// Only Rar 3.0 format and higher - /// - public override bool IsFirstVolume - { - get - { - EnsureArchiveHeaderLoaded(); - return ArchiveHeader.IsFirstVolume; - } - } - - /// - /// RarArchive is part of a multi-part archive. - /// - public override bool IsMultiVolume - { - get - { - EnsureArchiveHeaderLoaded(); - return ArchiveHeader.IsVolume; - } - } - - /// - /// RarArchive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). - /// Currently, SharpCompress cannot decompress SOLID archives. - /// - public bool IsSolidArchive - { - get - { - EnsureArchiveHeaderLoaded(); - return ArchiveHeader.IsSolid; - } - } - - public int MinVersion - { - get - { - EnsureArchiveHeaderLoaded(); - if (_maxCompressionAlgorithm >= 50) - return 5; //5-6 - else if (_maxCompressionAlgorithm >= 29) - return 3; //3-4 - else if (_maxCompressionAlgorithm >= 20) - return 2; //2 - else - return 1; - } - } - - public int MaxVersion - { - get - { - EnsureArchiveHeaderLoaded(); - if (_maxCompressionAlgorithm >= 50) - return 6; //5-6 - else if (_maxCompressionAlgorithm >= 29) - return 4; //3-4 - else if (_maxCompressionAlgorithm >= 20) - return 2; //2 - else - return 1; - } - } - - public string? Comment { get; internal set; } } + + private void EnsureArchiveHeaderLoaded() + { + if (ArchiveHeader is null) + { + if (Mode == StreamingMode.Streaming) + { + throw new InvalidOperationException( + "ArchiveHeader should never been null in a streaming read." + ); + } + + // we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream + GetVolumeFileParts().First(); + Stream.Position = 0; + } + } + + /// + /// RarArchive is the first volume of a multi-part archive. + /// Only Rar 3.0 format and higher + /// + public override bool IsFirstVolume + { + get + { + EnsureArchiveHeaderLoaded(); + return ArchiveHeader.IsFirstVolume; + } + } + + /// + /// RarArchive is part of a multi-part archive. + /// + public override bool IsMultiVolume + { + get + { + EnsureArchiveHeaderLoaded(); + return ArchiveHeader.IsVolume; + } + } + + /// + /// RarArchive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). + /// Currently, SharpCompress cannot decompress SOLID archives. + /// + public bool IsSolidArchive + { + get + { + EnsureArchiveHeaderLoaded(); + return ArchiveHeader.IsSolid; + } + } + + public int MinVersion + { + get + { + EnsureArchiveHeaderLoaded(); + if (_maxCompressionAlgorithm >= 50) + { + return 5; //5-6 + } + else if (_maxCompressionAlgorithm >= 29) + { + return 3; //3-4 + } + else if (_maxCompressionAlgorithm >= 20) + { + return 2; //2 + } + else + { + return 1; + } + } + } + + public int MaxVersion + { + get + { + EnsureArchiveHeaderLoaded(); + if (_maxCompressionAlgorithm >= 50) + { + return 6; //5-6 + } + else if (_maxCompressionAlgorithm >= 29) + { + return 4; //3-4 + } + else if (_maxCompressionAlgorithm >= 20) + { + return 2; //2 + } + else + { + return 1; + } + } + } + + public string? Comment { get; internal set; } } diff --git a/src/SharpCompress/Common/ReaderExtractionEventArgs.cs b/src/SharpCompress/Common/ReaderExtractionEventArgs.cs index 32d5b710..7c4363e2 100644 --- a/src/SharpCompress/Common/ReaderExtractionEventArgs.cs +++ b/src/SharpCompress/Common/ReaderExtractionEventArgs.cs @@ -1,18 +1,17 @@ -using System; +using System; using SharpCompress.Readers; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public sealed class ReaderExtractionEventArgs : EventArgs { - public sealed class ReaderExtractionEventArgs : EventArgs + internal ReaderExtractionEventArgs(T entry, ReaderProgress? readerProgress = null) { - internal ReaderExtractionEventArgs(T entry, ReaderProgress? readerProgress = null) - { - Item = entry; - ReaderProgress = readerProgress; - } - - public T Item { get; } - - public ReaderProgress? ReaderProgress { get; } + Item = entry; + ReaderProgress = readerProgress; } -} \ No newline at end of file + + public T Item { get; } + + public ReaderProgress? ReaderProgress { get; } +} diff --git a/src/SharpCompress/Common/SevenZip/ArchiveDatabase.cs b/src/SharpCompress/Common/SevenZip/ArchiveDatabase.cs index 2722bf55..5e42494a 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveDatabase.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveDatabase.cs @@ -6,180 +6,163 @@ using System.IO; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.LZMA.Utilites; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class ArchiveDatabase { - internal class ArchiveDatabase + internal byte _majorVersion; + internal byte _minorVersion; + internal long _startPositionAfterHeader; + internal long _dataStartPosition; + + internal List _packSizes = new List(); + internal List _packCrCs = new List(); + internal List _folders = new List(); + internal List _numUnpackStreamsVector; + internal List _files = new List(); + + internal List _packStreamStartPositions = new List(); + internal List _folderStartFileIndex = new List(); + internal List _fileIndexToFolderIndexMap = new List(); + + internal IPasswordProvider PasswordProvider { get; } + + public ArchiveDatabase(IPasswordProvider passwordProvider) => + PasswordProvider = passwordProvider; + + internal void Clear() { - internal byte _majorVersion; - internal byte _minorVersion; - internal long _startPositionAfterHeader; - internal long _dataStartPosition; + _packSizes.Clear(); + _packCrCs.Clear(); + _folders.Clear(); + _numUnpackStreamsVector = null!; + _files.Clear(); - internal List _packSizes = new List(); - internal List _packCrCs = new List(); - internal List _folders = new List(); - internal List _numUnpackStreamsVector; - internal List _files = new List(); + _packStreamStartPositions.Clear(); + _folderStartFileIndex.Clear(); + _fileIndexToFolderIndexMap.Clear(); + } - internal List _packStreamStartPositions = new List(); - internal List _folderStartFileIndex = new List(); - internal List _fileIndexToFolderIndexMap = new List(); + internal bool IsEmpty() => + _packSizes.Count == 0 + && _packCrCs.Count == 0 + && _folders.Count == 0 + && _numUnpackStreamsVector.Count == 0 + && _files.Count == 0; - internal IPasswordProvider PasswordProvider { get; } + private void FillStartPos() + { + _packStreamStartPositions.Clear(); - public ArchiveDatabase(IPasswordProvider passwordProvider) + long startPos = 0; + for (var i = 0; i < _packSizes.Count; i++) { - PasswordProvider = passwordProvider; - } - - internal void Clear() - { - _packSizes.Clear(); - _packCrCs.Clear(); - _folders.Clear(); - _numUnpackStreamsVector = null!; - _files.Clear(); - - _packStreamStartPositions.Clear(); - _folderStartFileIndex.Clear(); - _fileIndexToFolderIndexMap.Clear(); - } - - internal bool IsEmpty() - { - return _packSizes.Count == 0 - && _packCrCs.Count == 0 - && _folders.Count == 0 - && _numUnpackStreamsVector.Count == 0 - && _files.Count == 0; - } - - private void FillStartPos() - { - _packStreamStartPositions.Clear(); - - long startPos = 0; - for (int i = 0; i < _packSizes.Count; i++) - { - _packStreamStartPositions.Add(startPos); - startPos += _packSizes[i]; - } - } - - private void FillFolderStartFileIndex() - { - _folderStartFileIndex.Clear(); - _fileIndexToFolderIndexMap.Clear(); - - int folderIndex = 0; - int indexInFolder = 0; - for (int i = 0; i < _files.Count; i++) - { - CFileItem file = _files[i]; - - bool emptyStream = !file.HasStream; - - if (emptyStream && indexInFolder == 0) - { - _fileIndexToFolderIndexMap.Add(-1); - continue; - } - - if (indexInFolder == 0) - { - // v3.13 incorrectly worked with empty folders - // v4.07: Loop for skipping empty folders - for (; ; ) - { - if (folderIndex >= _folders.Count) - { - throw new InvalidOperationException(); - } - - _folderStartFileIndex.Add(i); // check it - - if (_numUnpackStreamsVector![folderIndex] != 0) - { - break; - } - - folderIndex++; - } - } - - _fileIndexToFolderIndexMap.Add(folderIndex); - - if (emptyStream) - { - continue; - } - - indexInFolder++; - - if (indexInFolder >= _numUnpackStreamsVector![folderIndex]) - { - folderIndex++; - indexInFolder = 0; - } - } - } - - public void Fill() - { - FillStartPos(); - FillFolderStartFileIndex(); - } - - internal long GetFolderStreamPos(CFolder folder, int indexInFolder) - { - int index = folder._firstPackStreamId + indexInFolder; - return _dataStartPosition + _packStreamStartPositions[index]; - } - - internal long GetFolderFullPackSize(int folderIndex) - { - int packStreamIndex = _folders[folderIndex]._firstPackStreamId; - CFolder folder = _folders[folderIndex]; - - long size = 0; - for (int i = 0; i < folder._packStreams.Count; i++) - { - size += _packSizes[packStreamIndex + i]; - } - - return size; - } - - internal Stream GetFolderStream(Stream stream, CFolder folder, IPasswordProvider pw) - { - int packStreamIndex = folder._firstPackStreamId; - long folderStartPackPos = GetFolderStreamPos(folder, 0); - int count = folder._packStreams.Count; - long[] packSizes = new long[count]; - for (int j = 0; j < count; j++) - { - packSizes[j] = _packSizes[packStreamIndex + j]; - } - - return DecoderStreamHelper.CreateDecoderStream(stream, folderStartPackPos, packSizes, folder, pw); - } - - private long GetFolderPackStreamSize(int folderIndex, int streamIndex) - { - return _packSizes[_folders[folderIndex]._firstPackStreamId + streamIndex]; - } - - private long GetFilePackSize(int fileIndex) - { - int folderIndex = _fileIndexToFolderIndexMap[fileIndex]; - if (folderIndex != -1) - { - if (_folderStartFileIndex[folderIndex] == fileIndex) - { - return GetFolderFullPackSize(folderIndex); - } - } - return 0; + _packStreamStartPositions.Add(startPos); + startPos += _packSizes[i]; } } + + private void FillFolderStartFileIndex() + { + _folderStartFileIndex.Clear(); + _fileIndexToFolderIndexMap.Clear(); + + var folderIndex = 0; + var indexInFolder = 0; + for (var i = 0; i < _files.Count; i++) + { + var file = _files[i]; + + var emptyStream = !file.HasStream; + + if (emptyStream && indexInFolder == 0) + { + _fileIndexToFolderIndexMap.Add(-1); + continue; + } + + if (indexInFolder == 0) + { + // v3.13 incorrectly worked with empty folders + // v4.07: Loop for skipping empty folders + for (; ; ) + { + if (folderIndex >= _folders.Count) + { + throw new InvalidOperationException(); + } + + _folderStartFileIndex.Add(i); // check it + + if (_numUnpackStreamsVector![folderIndex] != 0) + { + break; + } + + folderIndex++; + } + } + + _fileIndexToFolderIndexMap.Add(folderIndex); + + if (emptyStream) + { + continue; + } + + indexInFolder++; + + if (indexInFolder >= _numUnpackStreamsVector![folderIndex]) + { + folderIndex++; + indexInFolder = 0; + } + } + } + + public void Fill() + { + FillStartPos(); + FillFolderStartFileIndex(); + } + + internal long GetFolderStreamPos(CFolder folder, int indexInFolder) + { + var index = folder._firstPackStreamId + indexInFolder; + return _dataStartPosition + _packStreamStartPositions[index]; + } + + internal long GetFolderFullPackSize(int folderIndex) + { + var packStreamIndex = _folders[folderIndex]._firstPackStreamId; + var folder = _folders[folderIndex]; + + long size = 0; + for (var i = 0; i < folder._packStreams.Count; i++) + { + size += _packSizes[packStreamIndex + i]; + } + + return size; + } + + internal Stream GetFolderStream(Stream stream, CFolder folder, IPasswordProvider pw) + { + var packStreamIndex = folder._firstPackStreamId; + var folderStartPackPos = GetFolderStreamPos(folder, 0); + var count = folder._packStreams.Count; + var packSizes = new long[count]; + for (var j = 0; j < count; j++) + { + packSizes[j] = _packSizes[packStreamIndex + j]; + } + + return DecoderStreamHelper.CreateDecoderStream( + stream, + folderStartPackPos, + packSizes, + folder, + pw + ); + } } diff --git a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs index 6a2b8ea4..42b5b6b2 100644 --- a/src/SharpCompress/Common/SevenZip/ArchiveReader.cs +++ b/src/SharpCompress/Common/SevenZip/ArchiveReader.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; @@ -9,1574 +9,1629 @@ using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.IO; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class ArchiveReader { - internal class ArchiveReader + internal Stream _stream; + internal Stack _readerStack = new Stack(); + internal DataReader _currentReader; + internal long _streamOrigin; + internal long _streamEnding; + internal byte[] _header; + + private readonly Dictionary _cachedStreams = new Dictionary(); + + internal void AddByteStream(byte[] buffer, int offset, int length) { - internal Stream _stream; - internal Stack _readerStack = new Stack(); - internal DataReader _currentReader; - internal long _streamOrigin; - internal long _streamEnding; - internal byte[] _header; + _readerStack.Push(_currentReader); + _currentReader = new DataReader(buffer, offset, length); + } - private readonly Dictionary _cachedStreams = new Dictionary(); + internal void DeleteByteStream() => _currentReader = _readerStack.Pop(); - internal void AddByteStream(byte[] buffer, int offset, int length) + #region Private Methods - Data Reader + + internal byte ReadByte() => _currentReader.ReadByte(); + + private void ReadBytes(byte[] buffer, int offset, int length) => + _currentReader.ReadBytes(buffer, offset, length); + + private ulong ReadNumber() => _currentReader.ReadNumber(); + + internal int ReadNum() => _currentReader.ReadNum(); + + private uint ReadUInt32() => _currentReader.ReadUInt32(); + + private ulong ReadUInt64() => _currentReader.ReadUInt64(); + + private BlockType? ReadId() + { + var id = _currentReader.ReadNumber(); + if (id > 25) { - _readerStack.Push(_currentReader); - _currentReader = new DataReader(buffer, offset, length); - } - - internal void DeleteByteStream() - { - _currentReader = _readerStack.Pop(); - } - - #region Private Methods - Data Reader - - internal Byte ReadByte() - { - return _currentReader.ReadByte(); - } - - private void ReadBytes(byte[] buffer, int offset, int length) - { - _currentReader.ReadBytes(buffer, offset, length); - } - - private ulong ReadNumber() - { - return _currentReader.ReadNumber(); - } - - internal int ReadNum() - { - return _currentReader.ReadNum(); - } - - private uint ReadUInt32() - { - return _currentReader.ReadUInt32(); - } - - private ulong ReadUInt64() - { - return _currentReader.ReadUInt64(); - } - - private BlockType? ReadId() - { - ulong id = _currentReader.ReadNumber(); - if (id > 25) - { - return null; - } -#if DEBUG - Log.WriteLine("ReadId: {0}", (BlockType)id); -#endif - return (BlockType)id; - } - - private void SkipData(long size) - { - _currentReader.SkipData(size); - } - - private void SkipData() - { - _currentReader.SkipData(); - } - - private void WaitAttribute(BlockType attribute) - { - for (; ; ) - { - BlockType? type = ReadId(); - if (type == attribute) - { - return; - } - if (type == BlockType.End) - { - throw new InvalidOperationException(); - } - SkipData(); - } - } - - private void ReadArchiveProperties() - { - while (ReadId() != BlockType.End) - { - SkipData(); - } - } - - #endregion - - #region Private Methods - Reader Utilities - - private BitVector ReadBitVector(int length) - { - var bits = new BitVector(length); - - byte data = 0; - byte mask = 0; - - for (int i = 0; i < length; i++) - { - if (mask == 0) - { - data = ReadByte(); - mask = 0x80; - } - - if ((data & mask) != 0) - { - bits.SetBit(i); - } - - mask >>= 1; - } - - return bits; - } - - private BitVector ReadOptionalBitVector(int length) - { - byte allTrue = ReadByte(); - if (allTrue != 0) - { - return new BitVector(length, true); - } - - return ReadBitVector(length); - } - - private void ReadNumberVector(List dataVector, int numFiles, Action action) - { - var defined = ReadOptionalBitVector(numFiles); - - using (CStreamSwitch streamSwitch = new CStreamSwitch()) - { - streamSwitch.Set(this, dataVector); - - for (int i = 0; i < numFiles; i++) - { - if (defined[i]) - { - action(i, checked((long)ReadUInt64())); - } - else - { - action(i, null); - } - } - } - } - - private DateTime TranslateTime(long time) - { - // FILETIME = 100-nanosecond intervals since January 1, 1601 (UTC) - return DateTime.FromFileTimeUtc(time).ToLocalTime(); - } - - private DateTime? TranslateTime(long? time) - { - if (time.HasValue && time.Value >= 0 && time.Value <= 2650467743999999999) //maximum Windows file time 31.12.9999 - { - return TranslateTime(time.Value); - } return null; } +#if DEBUG + Log.WriteLine("ReadId: {0}", (BlockType)id); +#endif + return (BlockType)id; + } - private void ReadDateTimeVector(List dataVector, int numFiles, Action action) + private void SkipData(long size) => _currentReader.SkipData(size); + + private void SkipData() => _currentReader.SkipData(); + + private void WaitAttribute(BlockType attribute) + { + for (; ; ) { - ReadNumberVector(dataVector, numFiles, (index, value) => action(index, TranslateTime(value))); + var type = ReadId(); + if (type == attribute) + { + return; + } + if (type == BlockType.End) + { + throw new InvalidOperationException(); + } + SkipData(); + } + } + + private void ReadArchiveProperties() + { + while (ReadId() != BlockType.End) + { + SkipData(); + } + } + + #endregion + + #region Private Methods - Reader Utilities + + private BitVector ReadBitVector(int length) + { + var bits = new BitVector(length); + + byte data = 0; + byte mask = 0; + + for (var i = 0; i < length; i++) + { + if (mask == 0) + { + data = ReadByte(); + mask = 0x80; + } + + if ((data & mask) != 0) + { + bits.SetBit(i); + } + + mask >>= 1; } - private void ReadAttributeVector(List dataVector, int numFiles, Action action) + return bits; + } + + private BitVector ReadOptionalBitVector(int length) + { + var allTrue = ReadByte(); + if (allTrue != 0) { - BitVector boolVector = ReadOptionalBitVector(numFiles); - using (var streamSwitch = new CStreamSwitch()) + return new BitVector(length, true); + } + + return ReadBitVector(length); + } + + private void ReadNumberVector(List dataVector, int numFiles, Action action) + { + var defined = ReadOptionalBitVector(numFiles); + + using var streamSwitch = new CStreamSwitch(); + streamSwitch.Set(this, dataVector); + + for (var i = 0; i < numFiles; i++) + { + if (defined[i]) { - streamSwitch.Set(this, dataVector); - for (int i = 0; i < numFiles; i++) - { - if (boolVector[i]) - { - action(i, ReadUInt32()); - } - else - { - action(i, null); - } - } + action(i, checked((long)ReadUInt64())); + } + else + { + action(i, null); } } + } - #endregion + private DateTime TranslateTime(long time) => + // FILETIME = 100-nanosecond intervals since January 1, 1601 (UTC) + DateTime.FromFileTimeUtc(time).ToLocalTime(); - #region Private Methods - - private void GetNextFolderItem(CFolder folder) + private DateTime? TranslateTime(long? time) + { + if (time.HasValue && time.Value >= 0 && time.Value <= 2650467743999999999) //maximum Windows file time 31.12.9999 { -#if DEBUG - Log.WriteLine("-- GetNextFolderItem --"); - Log.PushIndent(); -#endif - try + return TranslateTime(time.Value); + } + return null; + } + + private void ReadDateTimeVector( + List dataVector, + int numFiles, + Action action + ) => + ReadNumberVector( + dataVector, + numFiles, + (index, value) => action(index, TranslateTime(value)) + ); + + private void ReadAttributeVector( + List dataVector, + int numFiles, + Action action + ) + { + var boolVector = ReadOptionalBitVector(numFiles); + using var streamSwitch = new CStreamSwitch(); + streamSwitch.Set(this, dataVector); + for (var i = 0; i < numFiles; i++) + { + if (boolVector[i]) { - int numCoders = ReadNum(); -#if DEBUG - Log.WriteLine("NumCoders: " + numCoders); -#endif - folder._coders = new List(numCoders); - int numInStreams = 0; - int numOutStreams = 0; - for (int i = 0; i < numCoders; i++) - { -#if DEBUG - Log.WriteLine("-- Coder --"); - Log.PushIndent(); -#endif - try - { - CCoderInfo coder = new CCoderInfo(); - folder._coders.Add(coder); + action(i, ReadUInt32()); + } + else + { + action(i, null); + } + } + } - byte mainByte = ReadByte(); - int idSize = (mainByte & 0xF); - byte[] longId = new byte[idSize]; - ReadBytes(longId, 0, idSize); -#if DEBUG - Log.WriteLine("MethodId: " + String.Join("", Enumerable.Range(0, idSize).Select(x => longId[x].ToString("x2")).ToArray())); -#endif - if (idSize > 8) - { - throw new NotSupportedException(); - } - ulong id = 0; - for (int j = 0; j < idSize; j++) - { - id |= (ulong)longId[idSize - 1 - j] << (8 * j); - } - coder._methodId = new CMethodId(id); + #endregion - if ((mainByte & 0x10) != 0) - { - coder._numInStreams = ReadNum(); - coder._numOutStreams = ReadNum(); -#if DEBUG - Log.WriteLine("Complex Stream (In: " + coder._numInStreams + " - Out: " + coder._numOutStreams + ")"); -#endif - } - else - { -#if DEBUG - Log.WriteLine("Simple Stream (In: 1 - Out: 1)"); -#endif - coder._numInStreams = 1; - coder._numOutStreams = 1; - } + #region Private Methods - if ((mainByte & 0x20) != 0) - { - int propsSize = ReadNum(); - coder._props = new byte[propsSize]; - ReadBytes(coder._props, 0, propsSize); + private void GetNextFolderItem(CFolder folder) + { #if DEBUG - Log.WriteLine("Settings: " + String.Join("", coder._props.Select(bt => bt.ToString("x2")).ToArray())); + Log.WriteLine("-- GetNextFolderItem --"); + Log.PushIndent(); #endif - } - - if ((mainByte & 0x80) != 0) - { - throw new NotSupportedException(); - } - - numInStreams += coder._numInStreams; - numOutStreams += coder._numOutStreams; - } - finally - { + try + { + var numCoders = ReadNum(); #if DEBUG - Log.PopIndent(); + Log.WriteLine("NumCoders: " + numCoders); #endif - } - } - - int numBindPairs = numOutStreams - 1; - folder._bindPairs = new List(numBindPairs); + folder._coders = new List(numCoders); + var numInStreams = 0; + var numOutStreams = 0; + for (var i = 0; i < numCoders; i++) + { #if DEBUG - Log.WriteLine("BindPairs: " + numBindPairs); + Log.WriteLine("-- Coder --"); Log.PushIndent(); #endif - for (int i = 0; i < numBindPairs; i++) + try { - CBindPair bp = new CBindPair(); - bp._inIndex = ReadNum(); - bp._outIndex = ReadNum(); - folder._bindPairs.Add(bp); + var coder = new CCoderInfo(); + folder._coders.Add(coder); + + var mainByte = ReadByte(); + var idSize = (mainByte & 0xF); + var longId = new byte[idSize]; + ReadBytes(longId, 0, idSize); #if DEBUG - Log.WriteLine("#" + i + " - In: " + bp._inIndex + " - Out: " + bp._outIndex); + Log.WriteLine( + "MethodId: " + + string.Join( + "", + Enumerable + .Range(0, idSize) + .Select(x => longId[x].ToString("x2")) + .ToArray() + ) + ); #endif - } -#if DEBUG - Log.PopIndent(); -#endif - - if (numInStreams < numBindPairs) - { - throw new NotSupportedException(); - } - - int numPackStreams = numInStreams - numBindPairs; - - //folder.PackStreams.Reserve(numPackStreams); - if (numPackStreams == 1) - { - for (int i = 0; i < numInStreams; i++) - { - if (folder.FindBindPairForInStream(i) < 0) - { -#if DEBUG - Log.WriteLine("Single PackStream: #" + i); -#endif - folder._packStreams.Add(i); - break; - } - } - - if (folder._packStreams.Count != 1) + if (idSize > 8) { throw new NotSupportedException(); } - } - else - { -#if DEBUG - Log.WriteLine("Multiple PackStreams ..."); - Log.PushIndent(); -#endif - for (int i = 0; i < numPackStreams; i++) + ulong id = 0; + for (var j = 0; j < idSize; j++) { - var num = ReadNum(); -#if DEBUG - Log.WriteLine("#" + i + " - " + num); -#endif - folder._packStreams.Add(num); + id |= (ulong)longId[idSize - 1 - j] << (8 * j); } + coder._methodId = new CMethodId(id); + + if ((mainByte & 0x10) != 0) + { + coder._numInStreams = ReadNum(); + coder._numOutStreams = ReadNum(); +#if DEBUG + Log.WriteLine( + "Complex Stream (In: " + + coder._numInStreams + + " - Out: " + + coder._numOutStreams + + ")" + ); +#endif + } + else + { +#if DEBUG + Log.WriteLine("Simple Stream (In: 1 - Out: 1)"); +#endif + coder._numInStreams = 1; + coder._numOutStreams = 1; + } + + if ((mainByte & 0x20) != 0) + { + var propsSize = ReadNum(); + coder._props = new byte[propsSize]; + ReadBytes(coder._props, 0, propsSize); +#if DEBUG + Log.WriteLine( + "Settings: " + + string.Join( + "", + coder._props.Select(bt => bt.ToString("x2")).ToArray() + ) + ); +#endif + } + + if ((mainByte & 0x80) != 0) + { + throw new NotSupportedException(); + } + + numInStreams += coder._numInStreams; + numOutStreams += coder._numOutStreams; + } + finally + { #if DEBUG Log.PopIndent(); #endif } } - finally + + var numBindPairs = numOutStreams - 1; + folder._bindPairs = new List(numBindPairs); +#if DEBUG + Log.WriteLine("BindPairs: " + numBindPairs); + Log.PushIndent(); +#endif + for (var i = 0; i < numBindPairs; i++) { + var bp = new CBindPair(); + bp._inIndex = ReadNum(); + bp._outIndex = ReadNum(); + folder._bindPairs.Add(bp); +#if DEBUG + Log.WriteLine("#" + i + " - In: " + bp._inIndex + " - Out: " + bp._outIndex); +#endif + } +#if DEBUG + Log.PopIndent(); +#endif + + if (numInStreams < numBindPairs) + { + throw new NotSupportedException(); + } + + var numPackStreams = numInStreams - numBindPairs; + + //folder.PackStreams.Reserve(numPackStreams); + if (numPackStreams == 1) + { + for (var i = 0; i < numInStreams; i++) + { + if (folder.FindBindPairForInStream(i) < 0) + { +#if DEBUG + Log.WriteLine("Single PackStream: #" + i); +#endif + folder._packStreams.Add(i); + break; + } + } + + if (folder._packStreams.Count != 1) + { + throw new NotSupportedException(); + } + } + else + { +#if DEBUG + Log.WriteLine("Multiple PackStreams ..."); + Log.PushIndent(); +#endif + for (var i = 0; i < numPackStreams; i++) + { + var num = ReadNum(); +#if DEBUG + Log.WriteLine("#" + i + " - " + num); +#endif + folder._packStreams.Add(num); + } #if DEBUG Log.PopIndent(); #endif } } - - private List ReadHashDigests(int count) + finally { #if DEBUG - Log.Write("ReadHashDigests:"); + Log.PopIndent(); +#endif + } + } + + private List ReadHashDigests(int count) + { +#if DEBUG + Log.Write("ReadHashDigests:"); #endif - var defined = ReadOptionalBitVector(count); - var digests = new List(count); - for (int i = 0; i < count; i++) + var defined = ReadOptionalBitVector(count); + var digests = new List(count); + for (var i = 0; i < count; i++) + { + if (defined[i]) { - if (defined[i]) - { - uint crc = ReadUInt32(); + var crc = ReadUInt32(); #if DEBUG - Log.Write(" " + crc.ToString("x8")); + Log.Write(" " + crc.ToString("x8")); #endif - digests.Add(crc); - } - else - { + digests.Add(crc); + } + else + { #if DEBUG - Log.Write(" ########"); + Log.Write(" ########"); #endif - digests.Add(null); - } + digests.Add(null); + } + } +#if DEBUG + + Log.WriteLine(); +#endif + return digests; + } + + private void ReadPackInfo( + out long dataOffset, + out List packSizes, + out List packCrCs + ) + { +#if DEBUG + Log.WriteLine("-- ReadPackInfo --"); + Log.PushIndent(); +#endif + try + { + packCrCs = null; + + dataOffset = checked((long)ReadNumber()); +#if DEBUG + Log.WriteLine("DataOffset: " + dataOffset); +#endif + + var numPackStreams = ReadNum(); +#if DEBUG + Log.WriteLine("NumPackStreams: " + numPackStreams); +#endif + + WaitAttribute(BlockType.Size); + packSizes = new List(numPackStreams); +#if DEBUG + Log.Write("Sizes:"); +#endif + for (var i = 0; i < numPackStreams; i++) + { + var size = checked((long)ReadNumber()); +#if DEBUG + Log.Write(" " + size); +#endif + packSizes.Add(size); } #if DEBUG - Log.WriteLine(); #endif - return digests; - } - private void ReadPackInfo(out long dataOffset, out List packSizes, out List packCrCs) + BlockType? type; + for (; ; ) + { + type = ReadId(); + if (type == BlockType.End) + { + break; + } + if (type == BlockType.Crc) + { + packCrCs = ReadHashDigests(numPackStreams); + continue; + } + SkipData(); + } + + if (packCrCs is null) + { + packCrCs = new List(numPackStreams); + for (var i = 0; i < numPackStreams; i++) + { + packCrCs.Add(null); + } + } + } + finally { #if DEBUG - Log.WriteLine("-- ReadPackInfo --"); - Log.PushIndent(); + Log.PopIndent(); #endif - try + } + } + + private void ReadUnpackInfo(List dataVector, out List folders) + { +#if DEBUG + Log.WriteLine("-- ReadUnpackInfo --"); + Log.PushIndent(); +#endif + try + { + WaitAttribute(BlockType.Folder); + var numFolders = ReadNum(); +#if DEBUG + Log.WriteLine("NumFolders: {0}", numFolders); +#endif + + using (var streamSwitch = new CStreamSwitch()) { - packCrCs = null; + streamSwitch.Set(this, dataVector); - dataOffset = checked((long)ReadNumber()); -#if DEBUG - Log.WriteLine("DataOffset: " + dataOffset); -#endif + //folders.Clear(); + //folders.Reserve(numFolders); + folders = new List(numFolders); + var index = 0; + for (var i = 0; i < numFolders; i++) + { + var f = new CFolder { _firstPackStreamId = index }; + folders.Add(f); + GetNextFolderItem(f); + index += f._packStreams.Count; + } + } - int numPackStreams = ReadNum(); + WaitAttribute(BlockType.CodersUnpackSize); #if DEBUG - Log.WriteLine("NumPackStreams: " + numPackStreams); + Log.WriteLine("UnpackSizes:"); #endif - - WaitAttribute(BlockType.Size); - packSizes = new List(numPackStreams); + for (var i = 0; i < numFolders; i++) + { + var folder = folders[i]; #if DEBUG - Log.Write("Sizes:"); + Log.Write(" #" + i + ":"); #endif - for (int i = 0; i < numPackStreams; i++) + var numOutStreams = folder.GetNumOutStreams(); + for (var j = 0; j < numOutStreams; j++) { var size = checked((long)ReadNumber()); #if DEBUG Log.Write(" " + size); #endif - packSizes.Add(size); + folder._unpackSizes.Add(size); } #if DEBUG Log.WriteLine(); #endif - - BlockType? type; - for (; ; ) - { - type = ReadId(); - if (type == BlockType.End) - { - break; - } - if (type == BlockType.Crc) - { - packCrCs = ReadHashDigests(numPackStreams); - continue; - } - SkipData(); - } - - if (packCrCs is null) - { - packCrCs = new List(numPackStreams); - for (int i = 0; i < numPackStreams; i++) - { - packCrCs.Add(null); - } - } } - finally + + for (; ; ) { -#if DEBUG - Log.PopIndent(); -#endif - } - } - - private void ReadUnpackInfo(List dataVector, out List folders) - { -#if DEBUG - Log.WriteLine("-- ReadUnpackInfo --"); - Log.PushIndent(); -#endif - try - { - WaitAttribute(BlockType.Folder); - int numFolders = ReadNum(); -#if DEBUG - Log.WriteLine("NumFolders: {0}", numFolders); -#endif - - using (CStreamSwitch streamSwitch = new CStreamSwitch()) - { - streamSwitch.Set(this, dataVector); - - //folders.Clear(); - //folders.Reserve(numFolders); - folders = new List(numFolders); - int index = 0; - for (int i = 0; i < numFolders; i++) - { - var f = new CFolder { _firstPackStreamId = index }; - folders.Add(f); - GetNextFolderItem(f); - index += f._packStreams.Count; - } - } - - WaitAttribute(BlockType.CodersUnpackSize); -#if DEBUG - Log.WriteLine("UnpackSizes:"); -#endif - for (int i = 0; i < numFolders; i++) - { - CFolder folder = folders[i]; -#if DEBUG - Log.Write(" #" + i + ":"); -#endif - int numOutStreams = folder.GetNumOutStreams(); - for (int j = 0; j < numOutStreams; j++) - { - long size = checked((long)ReadNumber()); -#if DEBUG - Log.Write(" " + size); -#endif - folder._unpackSizes.Add(size); - } -#if DEBUG - Log.WriteLine(); -#endif - } - - for (; ; ) - { - BlockType? type = ReadId(); - if (type == BlockType.End) - { - return; - } - - if (type == BlockType.Crc) - { - List crcs = ReadHashDigests(numFolders); - for (int i = 0; i < numFolders; i++) - { - folders[i]._unpackCrc = crcs[i]; - } - continue; - } - - SkipData(); - } - } - finally - { -#if DEBUG - Log.PopIndent(); -#endif - } - } - - private void ReadSubStreamsInfo(List folders, out List numUnpackStreamsInFolders, - out List unpackSizes, out List digests) - { -#if DEBUG - Log.WriteLine("-- ReadSubStreamsInfo --"); - Log.PushIndent(); -#endif - try - { - numUnpackStreamsInFolders = null; - - BlockType? type; - for (; ; ) - { - type = ReadId(); - if (type == BlockType.NumUnpackStream) - { - numUnpackStreamsInFolders = new List(folders.Count); -#if DEBUG - Log.Write("NumUnpackStreams:"); -#endif - for (int i = 0; i < folders.Count; i++) - { - var num = ReadNum(); -#if DEBUG - Log.Write(" " + num); -#endif - numUnpackStreamsInFolders.Add(num); - } -#if DEBUG - Log.WriteLine(); -#endif - continue; - } - if (type is BlockType.Crc or BlockType.Size) - { - break; - } - if (type == BlockType.End) - { - break; - } - SkipData(); - } - - if (numUnpackStreamsInFolders is null) - { - numUnpackStreamsInFolders = new List(folders.Count); - for (int i = 0; i < folders.Count; i++) - { - numUnpackStreamsInFolders.Add(1); - } - } - - unpackSizes = new List(folders.Count); - for (int i = 0; i < numUnpackStreamsInFolders.Count; i++) - { - // v3.13 incorrectly worked with empty folders - // v4.07: we check that folder is empty - int numSubstreams = numUnpackStreamsInFolders[i]; - if (numSubstreams == 0) - { - continue; - } -#if DEBUG - Log.Write("#{0} StreamSizes:", i); -#endif - long sum = 0; - for (int j = 1; j < numSubstreams; j++) - { - if (type == BlockType.Size) - { - long size = checked((long)ReadNumber()); -#if DEBUG - Log.Write(" " + size); -#endif - unpackSizes.Add(size); - sum += size; - } - } - unpackSizes.Add(folders[i].GetUnpackSize() - sum); -#if DEBUG - Log.WriteLine(" - rest: " + unpackSizes.Last()); -#endif - } - if (type == BlockType.Size) - { - type = ReadId(); - } - - int numDigests = 0; - int numDigestsTotal = 0; - for (int i = 0; i < folders.Count; i++) - { - int numSubstreams = numUnpackStreamsInFolders[i]; - if (numSubstreams != 1 || !folders[i].UnpackCrcDefined) - { - numDigests += numSubstreams; - } - numDigestsTotal += numSubstreams; - } - - digests = null; - - for (; ; ) - { - if (type == BlockType.Crc) - { - digests = new List(numDigestsTotal); - - List digests2 = ReadHashDigests(numDigests); - - int digestIndex = 0; - for (int i = 0; i < folders.Count; i++) - { - int numSubstreams = numUnpackStreamsInFolders[i]; - CFolder folder = folders[i]; - if (numSubstreams == 1 && folder.UnpackCrcDefined) - { - digests.Add(folder._unpackCrc.Value); - } - else - { - for (int j = 0; j < numSubstreams; j++, digestIndex++) - { - digests.Add(digests2[digestIndex]); - } - } - } - - if (digestIndex != numDigests || numDigestsTotal != digests.Count) - { - Debugger.Break(); - } - } - else if (type == BlockType.End) - { - if (digests is null) - { - digests = new List(numDigestsTotal); - for (int i = 0; i < numDigestsTotal; i++) - { - digests.Add(null); - } - } - return; - } - else - { - SkipData(); - } - - type = ReadId(); - } - } - finally - { -#if DEBUG - Log.PopIndent(); -#endif - } - } - - private void ReadStreamsInfo( - List dataVector, - out long dataOffset, - out List packSizes, - out List packCrCs, - out List folders, - out List numUnpackStreamsInFolders, - out List unpackSizes, - out List digests) - { -#if DEBUG - Log.WriteLine("-- ReadStreamsInfo --"); - Log.PushIndent(); -#endif - try - { - dataOffset = long.MinValue; - packSizes = null; - packCrCs = null; - folders = null; - numUnpackStreamsInFolders = null; - unpackSizes = null; - digests = null; - - for (; ; ) - { - switch (ReadId()) - { - case BlockType.End: - return; - case BlockType.PackInfo: - ReadPackInfo(out dataOffset, out packSizes, out packCrCs); - break; - case BlockType.UnpackInfo: - ReadUnpackInfo(dataVector, out folders); - break; - case BlockType.SubStreamsInfo: - ReadSubStreamsInfo(folders, out numUnpackStreamsInFolders, out unpackSizes, out digests); - break; - default: - throw new InvalidOperationException(); - } - } - } - finally - { -#if DEBUG - Log.PopIndent(); -#endif - } - } - - private List ReadAndDecodePackedStreams(long baseOffset, IPasswordProvider pass) - { -#if DEBUG - Log.WriteLine("-- ReadAndDecodePackedStreams --"); - Log.PushIndent(); -#endif - try - { - ReadStreamsInfo(null, - out long dataStartPos, - out List packSizes, - out List packCrCs, - out List folders, - out List numUnpackStreamsInFolders, - out List unpackSizes, - out List digests); - - dataStartPos += baseOffset; - - var dataVector = new List(folders.Count); - int packIndex = 0; - foreach (var folder in folders) - { - long oldDataStartPos = dataStartPos; - long[] myPackSizes = new long[folder._packStreams.Count]; - for (int i = 0; i < myPackSizes.Length; i++) - { - long packSize = packSizes[packIndex + i]; - myPackSizes[i] = packSize; - dataStartPos += packSize; - } - - var outStream = DecoderStreamHelper.CreateDecoderStream(_stream, oldDataStartPos, myPackSizes, - folder, pass); - - int unpackSize = checked((int)folder.GetUnpackSize()); - byte[] data = new byte[unpackSize]; - outStream.ReadExact(data, 0, data.Length); - if (outStream.ReadByte() >= 0) - { - throw new InvalidOperationException("Decoded stream is longer than expected."); - } - dataVector.Add(data); - - if (folder.UnpackCrcDefined) - { - if (Crc.Finish(Crc.Update(Crc.INIT_CRC, data, 0, unpackSize)) != folder._unpackCrc) - { - throw new InvalidOperationException("Decoded stream does not match expected CRC."); - } - } - } - return dataVector; - } - finally - { -#if DEBUG - Log.PopIndent(); -#endif - } - } - - private void ReadHeader(ArchiveDatabase db, IPasswordProvider getTextPassword) - { -#if DEBUG - Log.WriteLine("-- ReadHeader --"); - Log.PushIndent(); -#endif - try - { - BlockType? type = ReadId(); - - if (type == BlockType.ArchiveProperties) - { - ReadArchiveProperties(); - type = ReadId(); - } - - List dataVector = null; - if (type == BlockType.AdditionalStreamsInfo) - { - dataVector = ReadAndDecodePackedStreams(db._startPositionAfterHeader, getTextPassword); - type = ReadId(); - } - - List unpackSizes; - List digests; - - if (type == BlockType.MainStreamsInfo) - { - ReadStreamsInfo(dataVector, - out db._dataStartPosition, - out db._packSizes, - out db._packCrCs, - out db._folders, - out db._numUnpackStreamsVector, - out unpackSizes, - out digests); - - db._dataStartPosition += db._startPositionAfterHeader; - type = ReadId(); - } - else - { - unpackSizes = new List(db._folders.Count); - digests = new List(db._folders.Count); - db._numUnpackStreamsVector = new List(db._folders.Count); - for (int i = 0; i < db._folders.Count; i++) - { - var folder = db._folders[i]; - unpackSizes.Add(folder.GetUnpackSize()); - digests.Add(folder._unpackCrc); - db._numUnpackStreamsVector.Add(1); - } - } - - db._files.Clear(); - + var type = ReadId(); if (type == BlockType.End) { return; } - if (type != BlockType.FilesInfo) + if (type == BlockType.Crc) { - throw new InvalidOperationException(); + var crcs = ReadHashDigests(numFolders); + for (var i = 0; i < numFolders; i++) + { + folders[i]._unpackCrc = crcs[i]; + } + continue; } - int numFiles = ReadNum(); -#if DEBUG - Log.WriteLine("NumFiles: " + numFiles); -#endif - db._files = new List(numFiles); - for (int i = 0; i < numFiles; i++) - { - db._files.Add(new CFileItem()); - } - - BitVector emptyStreamVector = new BitVector(numFiles); - BitVector emptyFileVector = null; - BitVector antiFileVector = null; - int numEmptyStreams = 0; - - for (; ; ) - { - type = ReadId(); - if (type == BlockType.End) - { - break; - } - - long size = checked((long)ReadNumber()); // TODO: throw invalid data on negative - int oldPos = _currentReader.Offset; - switch (type) - { - case BlockType.Name: - using (var streamSwitch = new CStreamSwitch()) - { - streamSwitch.Set(this, dataVector); -#if DEBUG - Log.Write("FileNames:"); -#endif - for (int i = 0; i < db._files.Count; i++) - { - db._files[i].Name = _currentReader.ReadString(); -#if DEBUG - Log.Write(" " + db._files[i].Name); -#endif - } -#if DEBUG - Log.WriteLine(); -#endif - } - break; - case BlockType.WinAttributes: -#if DEBUG - Log.Write("WinAttributes:"); -#endif - ReadAttributeVector(dataVector, numFiles, delegate (int i, uint? attr) - { - // Some third party implementations established an unofficial extension - // of the 7z archive format by placing posix file attributes in the high - // bits of the windows file attributes. This makes use of the fact that - // the official implementation does not perform checks on this value. - // - // Newer versions of the official 7z GUI client will try to parse this - // extension, thus acknowledging the unofficial use of these bits. - // - // For us it is safe to just discard the upper bits if they are set and - // keep the windows attributes from the lower bits (which should be set - // properly even if posix file attributes are present, in order to be - // compatible with older 7z archive readers) - // - // Note that the 15th bit is used by some implementations to indicate - // presence of the extension, but not all implementations do that so - // we can't trust that bit and must ignore it. - // - if (attr.HasValue && (attr.Value >> 16) != 0) - { - attr = attr.Value & 0x7FFFu; - } - - db._files[i].Attrib = attr; -#if DEBUG - Log.Write(" " + (attr.HasValue ? attr.Value.ToString("x8") : "n/a")); -#endif - }); -#if DEBUG - Log.WriteLine(); -#endif - break; - case BlockType.EmptyStream: - emptyStreamVector = ReadBitVector(numFiles); -#if DEBUG - - Log.Write("EmptyStream: "); -#endif - for (int i = 0; i < emptyStreamVector.Length; i++) - { - if (emptyStreamVector[i]) - { -#if DEBUG - Log.Write("x"); -#endif - numEmptyStreams++; - } - else - { -#if DEBUG - Log.Write("."); -#endif - } - } -#if DEBUG - Log.WriteLine(); -#endif - - emptyFileVector = new BitVector(numEmptyStreams); - antiFileVector = new BitVector(numEmptyStreams); - break; - case BlockType.EmptyFile: - emptyFileVector = ReadBitVector(numEmptyStreams); -#if DEBUG - Log.Write("EmptyFile: "); - for (int i = 0; i < numEmptyStreams; i++) - { - Log.Write(emptyFileVector[i] ? "x" : "."); - } - Log.WriteLine(); -#endif - break; - case BlockType.Anti: - antiFileVector = ReadBitVector(numEmptyStreams); -#if DEBUG - Log.Write("Anti: "); - for (int i = 0; i < numEmptyStreams; i++) - { - Log.Write(antiFileVector[i] ? "x" : "."); - } - Log.WriteLine(); -#endif - break; - case BlockType.StartPos: -#if DEBUG - Log.Write("StartPos:"); -#endif - ReadNumberVector(dataVector, numFiles, delegate (int i, long? startPos) - { - db._files[i].StartPos = startPos; -#if DEBUG - Log.Write(" " + (startPos.HasValue ? startPos.Value.ToString() : "n/a")); -#endif - }); -#if DEBUG - Log.WriteLine(); -#endif - break; - case BlockType.CTime: -#if DEBUG - Log.Write("CTime:"); -#endif - ReadDateTimeVector(dataVector, numFiles, delegate (int i, DateTime? time) - { - db._files[i].CTime = time; -#if DEBUG - Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); -#endif - }); -#if DEBUG - Log.WriteLine(); -#endif - break; - case BlockType.ATime: -#if DEBUG - Log.Write("ATime:"); -#endif - ReadDateTimeVector(dataVector, numFiles, delegate (int i, DateTime? time) - { - db._files[i].ATime = time; -#if DEBUG - Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); -#endif - }); -#if DEBUG - Log.WriteLine(); -#endif - break; - case BlockType.MTime: -#if DEBUG - Log.Write("MTime:"); -#endif - ReadDateTimeVector(dataVector, numFiles, delegate (int i, DateTime? time) - { - db._files[i].MTime = time; -#if DEBUG - Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); -#endif - }); -#if DEBUG - Log.WriteLine(); -#endif - break; - case BlockType.Dummy: -#if DEBUG - Log.Write("Dummy: " + size); -#endif - for (long j = 0; j < size; j++) - { - if (ReadByte() != 0) - { - throw new InvalidOperationException(); - } - } - break; - default: - SkipData(size); - break; - } - - // since 0.3 record sizes must be correct - bool checkRecordsSize = (db._majorVersion > 0 || db._minorVersion > 2); - if (checkRecordsSize && _currentReader.Offset - oldPos != size) - { - throw new InvalidOperationException(); - } - } - - int emptyFileIndex = 0; - int sizeIndex = 0; - for (int i = 0; i < numFiles; i++) - { - CFileItem file = db._files[i]; - file.HasStream = !emptyStreamVector[i]; - if (file.HasStream) - { - file.IsDir = false; - file.IsAnti = false; - file.Size = unpackSizes[sizeIndex]; - file.Crc = digests[sizeIndex]; - sizeIndex++; - } - else - { - file.IsDir = !emptyFileVector[emptyFileIndex]; - file.IsAnti = antiFileVector[emptyFileIndex]; - emptyFileIndex++; - file.Size = 0; - file.Crc = null; - } - } - } - finally - { -#if DEBUG - Log.PopIndent(); -#endif + SkipData(); } } - - #endregion - - #region Public Methods - - public void Open(Stream stream) + finally { - Close(); - - _streamOrigin = stream.Position; - _streamEnding = stream.Length; - - // TODO: Check Signature! - _header = new byte[0x20]; - for (int offset = 0; offset < 0x20;) - { - int delta = stream.Read(_header, offset, 0x20 - offset); - if (delta == 0) - { - throw new EndOfStreamException(); - } - offset += delta; - } - - _stream = stream; - } - - public void Close() - { - if (_stream != null) - { - _stream.Dispose(); - } - - foreach (var stream in _cachedStreams.Values) - { - stream.Dispose(); - } - - _cachedStreams.Clear(); - } - - public ArchiveDatabase ReadDatabase(IPasswordProvider pass) - { - var db = new ArchiveDatabase(pass); - db.Clear(); - - db._majorVersion = _header[6]; - db._minorVersion = _header[7]; - - if (db._majorVersion != 0) - { - throw new InvalidOperationException(); - } - - uint crcFromArchive = DataReader.Get32(_header, 8); - long nextHeaderOffset = (long)DataReader.Get64(_header, 0xC); - long nextHeaderSize = (long)DataReader.Get64(_header, 0x14); - uint nextHeaderCrc = DataReader.Get32(_header, 0x1C); - - uint crc = Crc.INIT_CRC; - crc = Crc.Update(crc, nextHeaderOffset); - crc = Crc.Update(crc, nextHeaderSize); - crc = Crc.Update(crc, nextHeaderCrc); - crc = Crc.Finish(crc); - - if (crc != crcFromArchive) - { - throw new InvalidOperationException(); - } - - db._startPositionAfterHeader = _streamOrigin + 0x20; - - // empty header is ok - if (nextHeaderSize == 0) - { - db.Fill(); - return db; - } - - if (nextHeaderOffset < 0 || nextHeaderSize < 0 || nextHeaderSize > Int32.MaxValue) - { - throw new InvalidOperationException(); - } - - if (nextHeaderOffset > _streamEnding - db._startPositionAfterHeader) - { - throw new InvalidOperationException("nextHeaderOffset is invalid"); - } - - _stream.Seek(nextHeaderOffset, SeekOrigin.Current); - - byte[] header = new byte[nextHeaderSize]; - _stream.ReadExact(header, 0, header.Length); - - if (Crc.Finish(Crc.Update(Crc.INIT_CRC, header, 0, header.Length)) != nextHeaderCrc) - { - throw new InvalidOperationException(); - } - - using (CStreamSwitch streamSwitch = new CStreamSwitch()) - { - streamSwitch.Set(this, header); - - BlockType? type = ReadId(); - if (type != BlockType.Header) - { - if (type != BlockType.EncodedHeader) - { - throw new InvalidOperationException(); - } - - var dataVector = ReadAndDecodePackedStreams(db._startPositionAfterHeader, db.PasswordProvider); - - // compressed header without content is odd but ok - if (dataVector.Count == 0) - { - db.Fill(); - return db; - } - - if (dataVector.Count != 1) - { - throw new InvalidOperationException(); - } - - streamSwitch.Set(this, dataVector[0]); - - if (ReadId() != BlockType.Header) - { - throw new InvalidOperationException(); - } - } - - ReadHeader(db, db.PasswordProvider); - } - db.Fill(); - return db; - } - - internal class CExtractFolderInfo - { - internal int _fileIndex; - internal int _folderIndex; - internal List _extractStatuses = new List(); - - internal CExtractFolderInfo(int fileIndex, int folderIndex) - { - _fileIndex = fileIndex; - _folderIndex = folderIndex; - if (fileIndex != -1) - { - _extractStatuses.Add(true); - } - } - } - - private class FolderUnpackStream : Stream - { - private readonly ArchiveDatabase _db; - private readonly int _startIndex; - private readonly List _extractStatuses; - - public FolderUnpackStream(ArchiveDatabase db, int p, int startIndex, List list) - { - _db = db; - _startIndex = startIndex; - _extractStatuses = list; - } - - #region Stream - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - private Stream _stream; - private long _rem; - private int _currentIndex; - - private void ProcessEmptyFiles() - { - while (_currentIndex < _extractStatuses.Count && _db._files[_startIndex + _currentIndex].Size == 0) - { - OpenFile(); - _stream.Dispose(); - _stream = null; - _currentIndex++; - } - } - - private void OpenFile() - { - int index = _startIndex + _currentIndex; #if DEBUG - Log.WriteLine(_db._files[index].Name); + Log.PopIndent(); #endif - if (_db._files[index].CrcDefined) + } + } + + private void ReadSubStreamsInfo( + List folders, + out List numUnpackStreamsInFolders, + out List unpackSizes, + out List digests + ) + { +#if DEBUG + Log.WriteLine("-- ReadSubStreamsInfo --"); + Log.PushIndent(); +#endif + try + { + numUnpackStreamsInFolders = null; + + BlockType? type; + for (; ; ) + { + type = ReadId(); + if (type == BlockType.NumUnpackStream) { - _stream = new CrcCheckStream(_db._files[index].Crc.Value); + numUnpackStreamsInFolders = new List(folders.Count); +#if DEBUG + Log.Write("NumUnpackStreams:"); +#endif + for (var i = 0; i < folders.Count; i++) + { + var num = ReadNum(); +#if DEBUG + Log.Write(" " + num); +#endif + numUnpackStreamsInFolders.Add(num); + } +#if DEBUG + Log.WriteLine(); +#endif + continue; + } + if (type is BlockType.Crc or BlockType.Size) + { + break; + } + if (type == BlockType.End) + { + break; + } + SkipData(); + } + + if (numUnpackStreamsInFolders is null) + { + numUnpackStreamsInFolders = new List(folders.Count); + for (var i = 0; i < folders.Count; i++) + { + numUnpackStreamsInFolders.Add(1); + } + } + + unpackSizes = new List(folders.Count); + for (var i = 0; i < numUnpackStreamsInFolders.Count; i++) + { + // v3.13 incorrectly worked with empty folders + // v4.07: we check that folder is empty + var numSubstreams = numUnpackStreamsInFolders[i]; + if (numSubstreams == 0) + { + continue; + } +#if DEBUG + Log.Write("#{0} StreamSizes:", i); +#endif + long sum = 0; + for (var j = 1; j < numSubstreams; j++) + { + if (type == BlockType.Size) + { + var size = checked((long)ReadNumber()); +#if DEBUG + Log.Write(" " + size); +#endif + unpackSizes.Add(size); + sum += size; + } + } + unpackSizes.Add(folders[i].GetUnpackSize() - sum); +#if DEBUG + Log.WriteLine(" - rest: " + unpackSizes.Last()); +#endif + } + if (type == BlockType.Size) + { + type = ReadId(); + } + + var numDigests = 0; + var numDigestsTotal = 0; + for (var i = 0; i < folders.Count; i++) + { + var numSubstreams = numUnpackStreamsInFolders[i]; + if (numSubstreams != 1 || !folders[i].UnpackCrcDefined) + { + numDigests += numSubstreams; + } + numDigestsTotal += numSubstreams; + } + + digests = null; + + for (; ; ) + { + if (type == BlockType.Crc) + { + digests = new List(numDigestsTotal); + + var digests2 = ReadHashDigests(numDigests); + + var digestIndex = 0; + for (var i = 0; i < folders.Count; i++) + { + var numSubstreams = numUnpackStreamsInFolders[i]; + var folder = folders[i]; + if (numSubstreams == 1 && folder.UnpackCrcDefined) + { + digests.Add(folder._unpackCrc.Value); + } + else + { + for (var j = 0; j < numSubstreams; j++, digestIndex++) + { + digests.Add(digests2[digestIndex]); + } + } + } + + if (digestIndex != numDigests || numDigestsTotal != digests.Count) + { + Debugger.Break(); + } + } + else if (type == BlockType.End) + { + if (digests is null) + { + digests = new List(numDigestsTotal); + for (var i = 0; i < numDigestsTotal; i++) + { + digests.Add(null); + } + } + return; } else { - _stream = new MemoryStream(); + SkipData(); } - _rem = _db._files[index].Size; - } - public override void Write(byte[] buffer, int offset, int count) + type = ReadId(); + } + } + finally + { +#if DEBUG + Log.PopIndent(); +#endif + } + } + + private void ReadStreamsInfo( + List dataVector, + out long dataOffset, + out List packSizes, + out List packCrCs, + out List folders, + out List numUnpackStreamsInFolders, + out List unpackSizes, + out List digests + ) + { +#if DEBUG + Log.WriteLine("-- ReadStreamsInfo --"); + Log.PushIndent(); +#endif + try + { + dataOffset = long.MinValue; + packSizes = null; + packCrCs = null; + folders = null; + numUnpackStreamsInFolders = null; + unpackSizes = null; + digests = null; + + for (; ; ) { - while (count != 0) + switch (ReadId()) { - if (_stream != null) + case BlockType.End: + return; + case BlockType.PackInfo: + ReadPackInfo(out dataOffset, out packSizes, out packCrCs); + break; + case BlockType.UnpackInfo: + ReadUnpackInfo(dataVector, out folders); + break; + case BlockType.SubStreamsInfo: + ReadSubStreamsInfo( + folders, + out numUnpackStreamsInFolders, + out unpackSizes, + out digests + ); + break; + default: + throw new InvalidOperationException(); + } + } + } + finally + { +#if DEBUG + Log.PopIndent(); +#endif + } + } + + private List ReadAndDecodePackedStreams(long baseOffset, IPasswordProvider pass) + { +#if DEBUG + Log.WriteLine("-- ReadAndDecodePackedStreams --"); + Log.PushIndent(); +#endif + try + { + ReadStreamsInfo( + null, + out var dataStartPos, + out var packSizes, + out var packCrCs, + out var folders, + out var numUnpackStreamsInFolders, + out var unpackSizes, + out var digests + ); + + dataStartPos += baseOffset; + + var dataVector = new List(folders.Count); + var packIndex = 0; + foreach (var folder in folders) + { + var oldDataStartPos = dataStartPos; + var myPackSizes = new long[folder._packStreams.Count]; + for (var i = 0; i < myPackSizes.Length; i++) + { + var packSize = packSizes[packIndex + i]; + myPackSizes[i] = packSize; + dataStartPos += packSize; + } + + var outStream = DecoderStreamHelper.CreateDecoderStream( + _stream, + oldDataStartPos, + myPackSizes, + folder, + pass + ); + + var unpackSize = checked((int)folder.GetUnpackSize()); + var data = new byte[unpackSize]; + outStream.ReadExact(data, 0, data.Length); + if (outStream.ReadByte() >= 0) + { + throw new InvalidOperationException("Decoded stream is longer than expected."); + } + dataVector.Add(data); + + if (folder.UnpackCrcDefined) + { + if ( + Crc.Finish(Crc.Update(Crc.INIT_CRC, data, 0, unpackSize)) + != folder._unpackCrc + ) { - int write = count; - if (write > _rem) - { - write = (int)_rem; - } - _stream.Write(buffer, offset, write); - count -= write; - _rem -= write; - offset += write; - if (_rem == 0) - { - _stream.Dispose(); - _stream = null; - _currentIndex++; - ProcessEmptyFiles(); - } - } - else - { - ProcessEmptyFiles(); - if (_currentIndex == _extractStatuses.Count) - { - // we support partial extracting - Debugger.Break(); - throw new NotSupportedException(); - } - OpenFile(); + throw new InvalidOperationException( + "Decoded stream does not match expected CRC." + ); } } } - - #endregion + return dataVector; } - - private Stream GetCachedDecoderStream(ArchiveDatabase db, int folderIndex) + finally { - if (!_cachedStreams.TryGetValue(folderIndex, out Stream s)) +#if DEBUG + Log.PopIndent(); +#endif + } + } + + private void ReadHeader(ArchiveDatabase db, IPasswordProvider getTextPassword) + { +#if DEBUG + Log.WriteLine("-- ReadHeader --"); + Log.PushIndent(); +#endif + try + { + var type = ReadId(); + + if (type == BlockType.ArchiveProperties) { - CFolder folderInfo = db._folders[folderIndex]; - int packStreamIndex = db._folders[folderIndex]._firstPackStreamId; - long folderStartPackPos = db.GetFolderStreamPos(folderInfo, 0); - var count = folderInfo._packStreams.Count; - long[] packSizes = new long[count]; - for (int j = 0; j < count; j++) + ReadArchiveProperties(); + type = ReadId(); + } + + List dataVector = null; + if (type == BlockType.AdditionalStreamsInfo) + { + dataVector = ReadAndDecodePackedStreams( + db._startPositionAfterHeader, + getTextPassword + ); + type = ReadId(); + } + + List unpackSizes; + List digests; + + if (type == BlockType.MainStreamsInfo) + { + ReadStreamsInfo( + dataVector, + out db._dataStartPosition, + out db._packSizes, + out db._packCrCs, + out db._folders, + out db._numUnpackStreamsVector, + out unpackSizes, + out digests + ); + + db._dataStartPosition += db._startPositionAfterHeader; + type = ReadId(); + } + else + { + unpackSizes = new List(db._folders.Count); + digests = new List(db._folders.Count); + db._numUnpackStreamsVector = new List(db._folders.Count); + for (var i = 0; i < db._folders.Count; i++) { - packSizes[j] = db._packSizes[packStreamIndex + j]; + var folder = db._folders[i]; + unpackSizes.Add(folder.GetUnpackSize()); + digests.Add(folder._unpackCrc); + db._numUnpackStreamsVector.Add(1); } - - s = DecoderStreamHelper.CreateDecoderStream(_stream, folderStartPackPos, packSizes, folderInfo, - db.PasswordProvider); - _cachedStreams.Add(folderIndex, s); - } - return s; - } - - public Stream OpenStream(ArchiveDatabase db, int fileIndex) - { - int folderIndex = db._fileIndexToFolderIndexMap[fileIndex]; - int numFilesInFolder = db._numUnpackStreamsVector[folderIndex]; - int firstFileIndex = db._folderStartFileIndex[folderIndex]; - if (firstFileIndex > fileIndex || fileIndex - firstFileIndex >= numFilesInFolder) - { - throw new InvalidOperationException(); } - int skipCount = fileIndex - firstFileIndex; - long skipSize = 0; - for (int i = 0; i < skipCount; i++) - { - skipSize += db._files[firstFileIndex + i].Size; - } + db._files.Clear(); - Stream s = GetCachedDecoderStream(db, folderIndex); - s.Position = skipSize; - return new ReadOnlySubStream(s, db._files[fileIndex].Size); - } - - public void Extract(ArchiveDatabase db, int[] indices) - { - bool allFilesMode = (indices is null); - - int numItems = allFilesMode - ? db._files.Count - : indices.Length; - - if (numItems == 0) + if (type == BlockType.End) { return; } - List extractFolderInfoVector = new List(); - for (int i = 0; i < numItems; i++) + if (type != BlockType.FilesInfo) { - int fileIndex = allFilesMode ? i : indices[i]; + throw new InvalidOperationException(); + } - int folderIndex = db._fileIndexToFolderIndexMap[fileIndex]; - if (folderIndex == -1) + var numFiles = ReadNum(); +#if DEBUG + Log.WriteLine("NumFiles: " + numFiles); +#endif + db._files = new List(numFiles); + for (var i = 0; i < numFiles; i++) + { + db._files.Add(new CFileItem()); + } + + var emptyStreamVector = new BitVector(numFiles); + BitVector emptyFileVector = null; + BitVector antiFileVector = null; + var numEmptyStreams = 0; + + for (; ; ) + { + type = ReadId(); + if (type == BlockType.End) { - extractFolderInfoVector.Add(new CExtractFolderInfo(fileIndex, -1)); - continue; + break; } - if (extractFolderInfoVector.Count == 0 || folderIndex != extractFolderInfoVector.Last()._folderIndex) + var size = checked((long)ReadNumber()); // TODO: throw invalid data on negative + var oldPos = _currentReader.Offset; + switch (type) { - extractFolderInfoVector.Add(new CExtractFolderInfo(-1, folderIndex)); + case BlockType.Name: + using (var streamSwitch = new CStreamSwitch()) + { + streamSwitch.Set(this, dataVector); +#if DEBUG + Log.Write("FileNames:"); +#endif + for (var i = 0; i < db._files.Count; i++) + { + db._files[i].Name = _currentReader.ReadString(); +#if DEBUG + Log.Write(" " + db._files[i].Name); +#endif + } +#if DEBUG + Log.WriteLine(); +#endif + } + break; + case BlockType.WinAttributes: +#if DEBUG + Log.Write("WinAttributes:"); +#endif + ReadAttributeVector( + dataVector, + numFiles, + delegate(int i, uint? attr) + { + // Some third party implementations established an unofficial extension + // of the 7z archive format by placing posix file attributes in the high + // bits of the windows file attributes. This makes use of the fact that + // the official implementation does not perform checks on this value. + // + // Newer versions of the official 7z GUI client will try to parse this + // extension, thus acknowledging the unofficial use of these bits. + // + // For us it is safe to just discard the upper bits if they are set and + // keep the windows attributes from the lower bits (which should be set + // properly even if posix file attributes are present, in order to be + // compatible with older 7z archive readers) + // + // Note that the 15th bit is used by some implementations to indicate + // presence of the extension, but not all implementations do that so + // we can't trust that bit and must ignore it. + // + if (attr.HasValue && (attr.Value >> 16) != 0) + { + attr = attr.Value & 0x7FFFu; + } + + db._files[i].Attrib = attr; +#if DEBUG + Log.Write( + " " + (attr.HasValue ? attr.Value.ToString("x8") : "n/a") + ); +#endif + } + ); +#if DEBUG + Log.WriteLine(); +#endif + break; + case BlockType.EmptyStream: + emptyStreamVector = ReadBitVector(numFiles); +#if DEBUG + + Log.Write("EmptyStream: "); +#endif + for (var i = 0; i < emptyStreamVector.Length; i++) + { + if (emptyStreamVector[i]) + { +#if DEBUG + Log.Write("x"); +#endif + numEmptyStreams++; + } + else + { +#if DEBUG + Log.Write("."); +#endif + } + } +#if DEBUG + Log.WriteLine(); +#endif + + emptyFileVector = new BitVector(numEmptyStreams); + antiFileVector = new BitVector(numEmptyStreams); + break; + case BlockType.EmptyFile: + emptyFileVector = ReadBitVector(numEmptyStreams); +#if DEBUG + Log.Write("EmptyFile: "); + for (var i = 0; i < numEmptyStreams; i++) + { + Log.Write(emptyFileVector[i] ? "x" : "."); + } + Log.WriteLine(); +#endif + break; + case BlockType.Anti: + antiFileVector = ReadBitVector(numEmptyStreams); +#if DEBUG + Log.Write("Anti: "); + for (var i = 0; i < numEmptyStreams; i++) + { + Log.Write(antiFileVector[i] ? "x" : "."); + } + Log.WriteLine(); +#endif + break; + case BlockType.StartPos: +#if DEBUG + Log.Write("StartPos:"); +#endif + ReadNumberVector( + dataVector, + numFiles, + delegate(int i, long? startPos) + { + db._files[i].StartPos = startPos; +#if DEBUG + Log.Write( + " " + (startPos.HasValue ? startPos.Value.ToString() : "n/a") + ); +#endif + } + ); +#if DEBUG + Log.WriteLine(); +#endif + break; + case BlockType.CTime: +#if DEBUG + Log.Write("CTime:"); +#endif + ReadDateTimeVector( + dataVector, + numFiles, + delegate(int i, DateTime? time) + { + db._files[i].CTime = time; +#if DEBUG + Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); +#endif + } + ); +#if DEBUG + Log.WriteLine(); +#endif + break; + case BlockType.ATime: +#if DEBUG + Log.Write("ATime:"); +#endif + ReadDateTimeVector( + dataVector, + numFiles, + delegate(int i, DateTime? time) + { + db._files[i].ATime = time; +#if DEBUG + Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); +#endif + } + ); +#if DEBUG + Log.WriteLine(); +#endif + break; + case BlockType.MTime: +#if DEBUG + Log.Write("MTime:"); +#endif + ReadDateTimeVector( + dataVector, + numFiles, + delegate(int i, DateTime? time) + { + db._files[i].MTime = time; +#if DEBUG + Log.Write(" " + (time.HasValue ? time.Value.ToString() : "n/a")); +#endif + } + ); +#if DEBUG + Log.WriteLine(); +#endif + break; + case BlockType.Dummy: +#if DEBUG + Log.Write("Dummy: " + size); +#endif + for (long j = 0; j < size; j++) + { + if (ReadByte() != 0) + { + throw new InvalidOperationException(); + } + } + break; + default: + SkipData(size); + break; } - CExtractFolderInfo efi = extractFolderInfoVector.Last(); - - int startIndex = db._folderStartFileIndex[folderIndex]; - for (int index = efi._extractStatuses.Count; index <= fileIndex - startIndex; index++) + // since 0.3 record sizes must be correct + var checkRecordsSize = (db._majorVersion > 0 || db._minorVersion > 2); + if (checkRecordsSize && _currentReader.Offset - oldPos != size) { - efi._extractStatuses.Add(index == fileIndex - startIndex); + throw new InvalidOperationException(); } } - byte[] buffer = null; - foreach (CExtractFolderInfo efi in extractFolderInfoVector) + var emptyFileIndex = 0; + var sizeIndex = 0; + for (var i = 0; i < numFiles; i++) { - int startIndex; - if (efi._fileIndex != -1) + var file = db._files[i]; + file.HasStream = !emptyStreamVector[i]; + if (file.HasStream) { - startIndex = efi._fileIndex; + file.IsDir = false; + file.IsAnti = false; + file.Size = unpackSizes[sizeIndex]; + file.Crc = digests[sizeIndex]; + sizeIndex++; } else { - startIndex = db._folderStartFileIndex[efi._folderIndex]; - } - - var outStream = new FolderUnpackStream(db, 0, startIndex, efi._extractStatuses); - - if (efi._fileIndex != -1) - { - continue; - } - - int folderIndex = efi._folderIndex; - CFolder folderInfo = db._folders[folderIndex]; - - int packStreamIndex = db._folders[folderIndex]._firstPackStreamId; - long folderStartPackPos = db.GetFolderStreamPos(folderInfo, 0); - - var count = folderInfo._packStreams.Count; - long[] packSizes = new long[count]; - for (int j = 0; j < count; j++) - { - packSizes[j] = db._packSizes[packStreamIndex + j]; - } - - // TODO: If the decoding fails the last file may be extracted incompletely. Delete it? - - Stream s = DecoderStreamHelper.CreateDecoderStream(_stream, folderStartPackPos, packSizes, - folderInfo, db.PasswordProvider); - buffer ??= new byte[4 << 10]; - for (; ; ) - { - int processed = s.Read(buffer, 0, buffer.Length); - if (processed == 0) - { - break; - } - outStream.Write(buffer, 0, processed); + file.IsDir = !emptyFileVector[emptyFileIndex]; + file.IsAnti = antiFileVector[emptyFileIndex]; + emptyFileIndex++; + file.Size = 0; + file.Crc = null; } } } - - public IEnumerable GetFiles(ArchiveDatabase db) + finally { - return db._files; +#if DEBUG + Log.PopIndent(); +#endif + } + } + + #endregion + + #region Public Methods + + public void Open(Stream stream) + { + Close(); + + _streamOrigin = stream.Position; + _streamEnding = stream.Length; + + // TODO: Check Signature! + _header = new byte[0x20]; + for (var offset = 0; offset < 0x20; ) + { + var delta = stream.Read(_header, offset, 0x20 - offset); + if (delta == 0) + { + throw new EndOfStreamException(); + } + offset += delta; } - public int GetFileIndex(ArchiveDatabase db, CFileItem item) + _stream = stream; + } + + public void Close() + { + _stream?.Dispose(); + + foreach (var stream in _cachedStreams.Values) { - return db._files.IndexOf(item); + stream.Dispose(); + } + + _cachedStreams.Clear(); + } + + public ArchiveDatabase ReadDatabase(IPasswordProvider pass) + { + var db = new ArchiveDatabase(pass); + db.Clear(); + + db._majorVersion = _header[6]; + db._minorVersion = _header[7]; + + if (db._majorVersion != 0) + { + throw new InvalidOperationException(); + } + + var crcFromArchive = DataReader.Get32(_header, 8); + var nextHeaderOffset = (long)DataReader.Get64(_header, 0xC); + var nextHeaderSize = (long)DataReader.Get64(_header, 0x14); + var nextHeaderCrc = DataReader.Get32(_header, 0x1C); + + var crc = Crc.INIT_CRC; + crc = Crc.Update(crc, nextHeaderOffset); + crc = Crc.Update(crc, nextHeaderSize); + crc = Crc.Update(crc, nextHeaderCrc); + crc = Crc.Finish(crc); + + if (crc != crcFromArchive) + { + throw new InvalidOperationException(); + } + + db._startPositionAfterHeader = _streamOrigin + 0x20; + + // empty header is ok + if (nextHeaderSize == 0) + { + db.Fill(); + return db; + } + + if (nextHeaderOffset < 0 || nextHeaderSize < 0 || nextHeaderSize > int.MaxValue) + { + throw new InvalidOperationException(); + } + + if (nextHeaderOffset > _streamEnding - db._startPositionAfterHeader) + { + throw new InvalidOperationException("nextHeaderOffset is invalid"); + } + + _stream.Seek(nextHeaderOffset, SeekOrigin.Current); + + var header = new byte[nextHeaderSize]; + _stream.ReadExact(header, 0, header.Length); + + if (Crc.Finish(Crc.Update(Crc.INIT_CRC, header, 0, header.Length)) != nextHeaderCrc) + { + throw new InvalidOperationException(); + } + + using (var streamSwitch = new CStreamSwitch()) + { + streamSwitch.Set(this, header); + + var type = ReadId(); + if (type != BlockType.Header) + { + if (type != BlockType.EncodedHeader) + { + throw new InvalidOperationException(); + } + + var dataVector = ReadAndDecodePackedStreams( + db._startPositionAfterHeader, + db.PasswordProvider + ); + + // compressed header without content is odd but ok + if (dataVector.Count == 0) + { + db.Fill(); + return db; + } + + if (dataVector.Count != 1) + { + throw new InvalidOperationException(); + } + + streamSwitch.Set(this, dataVector[0]); + + if (ReadId() != BlockType.Header) + { + throw new InvalidOperationException(); + } + } + + ReadHeader(db, db.PasswordProvider); + } + db.Fill(); + return db; + } + + internal class CExtractFolderInfo + { + internal int _fileIndex; + internal int _folderIndex; + internal List _extractStatuses = new List(); + + internal CExtractFolderInfo(int fileIndex, int folderIndex) + { + _fileIndex = fileIndex; + _folderIndex = folderIndex; + if (fileIndex != -1) + { + _extractStatuses.Add(true); + } + } + } + + private class FolderUnpackStream : Stream + { + private readonly ArchiveDatabase _db; + private readonly int _startIndex; + private readonly List _extractStatuses; + + public FolderUnpackStream(ArchiveDatabase db, int p, int startIndex, List list) + { + _db = db; + _startIndex = startIndex; + _extractStatuses = list; + } + + #region Stream + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + private Stream _stream; + private long _rem; + private int _currentIndex; + + private void ProcessEmptyFiles() + { + while ( + _currentIndex < _extractStatuses.Count + && _db._files[_startIndex + _currentIndex].Size == 0 + ) + { + OpenFile(); + _stream.Dispose(); + _stream = null; + _currentIndex++; + } + } + + private void OpenFile() + { + var index = _startIndex + _currentIndex; +#if DEBUG + Log.WriteLine(_db._files[index].Name); +#endif + if (_db._files[index].CrcDefined) + { + _stream = new CrcCheckStream(_db._files[index].Crc.Value); + } + else + { + _stream = new MemoryStream(); + } + _rem = _db._files[index].Size; + } + + public override void Write(byte[] buffer, int offset, int count) + { + while (count != 0) + { + if (_stream != null) + { + var write = count; + if (write > _rem) + { + write = (int)_rem; + } + _stream.Write(buffer, offset, write); + count -= write; + _rem -= write; + offset += write; + if (_rem == 0) + { + _stream.Dispose(); + _stream = null; + _currentIndex++; + ProcessEmptyFiles(); + } + } + else + { + ProcessEmptyFiles(); + if (_currentIndex == _extractStatuses.Count) + { + // we support partial extracting + Debugger.Break(); + throw new NotSupportedException(); + } + OpenFile(); + } + } } #endregion } + + private Stream GetCachedDecoderStream(ArchiveDatabase db, int folderIndex) + { + if (!_cachedStreams.TryGetValue(folderIndex, out var s)) + { + var folderInfo = db._folders[folderIndex]; + var packStreamIndex = db._folders[folderIndex]._firstPackStreamId; + var folderStartPackPos = db.GetFolderStreamPos(folderInfo, 0); + var count = folderInfo._packStreams.Count; + var packSizes = new long[count]; + for (var j = 0; j < count; j++) + { + packSizes[j] = db._packSizes[packStreamIndex + j]; + } + + s = DecoderStreamHelper.CreateDecoderStream( + _stream, + folderStartPackPos, + packSizes, + folderInfo, + db.PasswordProvider + ); + _cachedStreams.Add(folderIndex, s); + } + return s; + } + + public Stream OpenStream(ArchiveDatabase db, int fileIndex) + { + var folderIndex = db._fileIndexToFolderIndexMap[fileIndex]; + var numFilesInFolder = db._numUnpackStreamsVector[folderIndex]; + var firstFileIndex = db._folderStartFileIndex[folderIndex]; + if (firstFileIndex > fileIndex || fileIndex - firstFileIndex >= numFilesInFolder) + { + throw new InvalidOperationException(); + } + + var skipCount = fileIndex - firstFileIndex; + long skipSize = 0; + for (var i = 0; i < skipCount; i++) + { + skipSize += db._files[firstFileIndex + i].Size; + } + + var s = GetCachedDecoderStream(db, folderIndex); + s.Position = skipSize; + return new ReadOnlySubStream(s, db._files[fileIndex].Size); + } + + public void Extract(ArchiveDatabase db, int[] indices) + { + var allFilesMode = (indices is null); + + var numItems = allFilesMode ? db._files.Count : indices.Length; + + if (numItems == 0) + { + return; + } + + var extractFolderInfoVector = new List(); + for (var i = 0; i < numItems; i++) + { + var fileIndex = allFilesMode ? i : indices[i]; + + var folderIndex = db._fileIndexToFolderIndexMap[fileIndex]; + if (folderIndex == -1) + { + extractFolderInfoVector.Add(new CExtractFolderInfo(fileIndex, -1)); + continue; + } + + if ( + extractFolderInfoVector.Count == 0 + || folderIndex != extractFolderInfoVector.Last()._folderIndex + ) + { + extractFolderInfoVector.Add(new CExtractFolderInfo(-1, folderIndex)); + } + + var efi = extractFolderInfoVector.Last(); + + var startIndex = db._folderStartFileIndex[folderIndex]; + for (var index = efi._extractStatuses.Count; index <= fileIndex - startIndex; index++) + { + efi._extractStatuses.Add(index == fileIndex - startIndex); + } + } + + byte[] buffer = null; + foreach (var efi in extractFolderInfoVector) + { + int startIndex; + if (efi._fileIndex != -1) + { + startIndex = efi._fileIndex; + } + else + { + startIndex = db._folderStartFileIndex[efi._folderIndex]; + } + + var outStream = new FolderUnpackStream(db, 0, startIndex, efi._extractStatuses); + + if (efi._fileIndex != -1) + { + continue; + } + + var folderIndex = efi._folderIndex; + var folderInfo = db._folders[folderIndex]; + + var packStreamIndex = db._folders[folderIndex]._firstPackStreamId; + var folderStartPackPos = db.GetFolderStreamPos(folderInfo, 0); + + var count = folderInfo._packStreams.Count; + var packSizes = new long[count]; + for (var j = 0; j < count; j++) + { + packSizes[j] = db._packSizes[packStreamIndex + j]; + } + + // TODO: If the decoding fails the last file may be extracted incompletely. Delete it? + + var s = DecoderStreamHelper.CreateDecoderStream( + _stream, + folderStartPackPos, + packSizes, + folderInfo, + db.PasswordProvider + ); + buffer ??= new byte[4 << 10]; + for (; ; ) + { + var processed = s.Read(buffer, 0, buffer.Length); + if (processed == 0) + { + break; + } + outStream.Write(buffer, 0, processed); + } + } + } + + public IEnumerable GetFiles(ArchiveDatabase db) => db._files; + + public int GetFileIndex(ArchiveDatabase db, CFileItem item) => db._files.IndexOf(item); + + #endregion } diff --git a/src/SharpCompress/Common/SevenZip/CBindPair.cs b/src/SharpCompress/Common/SevenZip/CBindPair.cs index ad947967..f9d752e6 100644 --- a/src/SharpCompress/Common/SevenZip/CBindPair.cs +++ b/src/SharpCompress/Common/SevenZip/CBindPair.cs @@ -1,8 +1,7 @@ -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class CBindPair { - internal class CBindPair - { - internal int _inIndex; - internal int _outIndex; - } -} \ No newline at end of file + internal int _inIndex; + internal int _outIndex; +} diff --git a/src/SharpCompress/Common/SevenZip/CCoderInfo.cs b/src/SharpCompress/Common/SevenZip/CCoderInfo.cs index af6345d3..d53c44f0 100644 --- a/src/SharpCompress/Common/SevenZip/CCoderInfo.cs +++ b/src/SharpCompress/Common/SevenZip/CCoderInfo.cs @@ -1,12 +1,11 @@ -#nullable disable +#nullable disable -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class CCoderInfo { - internal class CCoderInfo - { - internal CMethodId _methodId; - internal byte[] _props; - internal int _numInStreams; - internal int _numOutStreams; - } -} \ No newline at end of file + internal CMethodId _methodId; + internal byte[] _props; + internal int _numInStreams; + internal int _numOutStreams; +} diff --git a/src/SharpCompress/Common/SevenZip/CFileItem.cs b/src/SharpCompress/Common/SevenZip/CFileItem.cs index 7624f8b3..c6509ee0 100644 --- a/src/SharpCompress/Common/SevenZip/CFileItem.cs +++ b/src/SharpCompress/Common/SevenZip/CFileItem.cs @@ -2,37 +2,30 @@ using System; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class CFileItem { - internal class CFileItem - { - public long Size { get; internal set; } - public uint? Attrib { get; internal set; } - public uint? Crc { get; internal set; } - public string Name { get; internal set; } + public long Size { get; internal set; } + public uint? Attrib { get; internal set; } + public uint? Crc { get; internal set; } + public string Name { get; internal set; } - public bool HasStream { get; internal set; } - public bool IsDir { get; internal set; } + public bool HasStream { get; internal set; } + public bool IsDir { get; internal set; } - public bool CrcDefined => Crc != null; + public bool CrcDefined => Crc != null; - public bool AttribDefined => Attrib != null; + public bool AttribDefined => Attrib != null; - public void SetAttrib(uint attrib) - { - Attrib = attrib; - } + public void SetAttrib(uint attrib) => Attrib = attrib; - public DateTime? CTime { get; internal set; } - public DateTime? ATime { get; internal set; } - public DateTime? MTime { get; internal set; } + public DateTime? CTime { get; internal set; } + public DateTime? ATime { get; internal set; } + public DateTime? MTime { get; internal set; } - public long? StartPos { get; internal set; } - public bool IsAnti { get; internal set; } + public long? StartPos { get; internal set; } + public bool IsAnti { get; internal set; } - internal CFileItem() - { - HasStream = true; - } - } -} \ No newline at end of file + internal CFileItem() => HasStream = true; +} diff --git a/src/SharpCompress/Common/SevenZip/CFolder.cs b/src/SharpCompress/Common/SevenZip/CFolder.cs index c15da80c..9d3516b1 100644 --- a/src/SharpCompress/Common/SevenZip/CFolder.cs +++ b/src/SharpCompress/Common/SevenZip/CFolder.cs @@ -1,188 +1,187 @@ -using System; +using System; using System.Collections.Generic; using SharpCompress.Compressors.LZMA; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class CFolder { - internal class CFolder + internal List _coders = new List(); + internal List _bindPairs = new List(); + internal List _packStreams = new List(); + internal int _firstPackStreamId; + internal List _unpackSizes = new List(); + internal uint? _unpackCrc; + + internal bool UnpackCrcDefined => _unpackCrc != null; + + public long GetUnpackSize() { - internal List _coders = new List(); - internal List _bindPairs = new List(); - internal List _packStreams = new List(); - internal int _firstPackStreamId; - internal List _unpackSizes = new List(); - internal uint? _unpackCrc; - - internal bool UnpackCrcDefined => _unpackCrc != null; - - public long GetUnpackSize() + if (_unpackSizes.Count == 0) { - if (_unpackSizes.Count == 0) - { - return 0; - } - - for (int i = _unpackSizes.Count - 1; i >= 0; i--) - { - if (FindBindPairForOutStream(i) < 0) - { - return _unpackSizes[i]; - } - } - - throw new InvalidOperationException(); + return 0; } - public int GetNumOutStreams() + for (var i = _unpackSizes.Count - 1; i >= 0; i--) { - int count = 0; - for (int i = 0; i < _coders.Count; i++) + if (FindBindPairForOutStream(i) < 0) { - count += _coders[i]._numOutStreams; + return _unpackSizes[i]; } - - return count; } - public int FindBindPairForInStream(int inStreamIndex) - { - for (int i = 0; i < _bindPairs.Count; i++) - { - if (_bindPairs[i]._inIndex == inStreamIndex) - { - return i; - } - } + throw new InvalidOperationException(); + } - return -1; + public int GetNumOutStreams() + { + var count = 0; + for (var i = 0; i < _coders.Count; i++) + { + count += _coders[i]._numOutStreams; } - public int FindBindPairForOutStream(int outStreamIndex) - { - for (int i = 0; i < _bindPairs.Count; i++) - { - if (_bindPairs[i]._outIndex == outStreamIndex) - { - return i; - } - } + return count; + } - return -1; + public int FindBindPairForInStream(int inStreamIndex) + { + for (var i = 0; i < _bindPairs.Count; i++) + { + if (_bindPairs[i]._inIndex == inStreamIndex) + { + return i; + } } - public int FindPackStreamArrayIndex(int inStreamIndex) - { - for (int i = 0; i < _packStreams.Count; i++) - { - if (_packStreams[i] == inStreamIndex) - { - return i; - } - } + return -1; + } - return -1; + public int FindBindPairForOutStream(int outStreamIndex) + { + for (var i = 0; i < _bindPairs.Count; i++) + { + if (_bindPairs[i]._outIndex == outStreamIndex) + { + return i; + } } - public bool IsEncrypted() - { - for (int i = _coders.Count - 1; i >= 0; i--) - { - if (_coders[i]._methodId == CMethodId.K_AES) - { - return true; - } - } + return -1; + } + public int FindPackStreamArrayIndex(int inStreamIndex) + { + for (var i = 0; i < _packStreams.Count; i++) + { + if (_packStreams[i] == inStreamIndex) + { + return i; + } + } + + return -1; + } + + public bool IsEncrypted() + { + for (var i = _coders.Count - 1; i >= 0; i--) + { + if (_coders[i]._methodId == CMethodId.K_AES) + { + return true; + } + } + + return false; + } + + public bool CheckStructure() + { + const int kNumCodersMax = 32; // don't change it + const int kMaskSize = 32; // it must be >= kNumCodersMax + const int kNumBindsMax = 32; + + if (_coders.Count > kNumCodersMax || _bindPairs.Count > kNumBindsMax) + { return false; } - public bool CheckStructure() { - const int kNumCodersMax = 32; // don't change it - const int kMaskSize = 32; // it must be >= kNumCodersMax - const int kNumBindsMax = 32; + var v = new BitVector(_bindPairs.Count + _packStreams.Count); - if (_coders.Count > kNumCodersMax || _bindPairs.Count > kNumBindsMax) + for (var i = 0; i < _bindPairs.Count; i++) { - return false; - } - - { - var v = new BitVector(_bindPairs.Count + _packStreams.Count); - - for (int i = 0; i < _bindPairs.Count; i++) - { - if (v.GetAndSet(_bindPairs[i]._inIndex)) - { - return false; - } - } - - for (int i = 0; i < _packStreams.Count; i++) - { - if (v.GetAndSet(_packStreams[i])) - { - return false; - } - } - } - - { - var v = new BitVector(_unpackSizes.Count); - for (int i = 0; i < _bindPairs.Count; i++) - { - if (v.GetAndSet(_bindPairs[i]._outIndex)) - { - return false; - } - } - } - - uint[] mask = new uint[kMaskSize]; - - { - List inStreamToCoder = new List(); - List outStreamToCoder = new List(); - for (int i = 0; i < _coders.Count; i++) - { - CCoderInfo coder = _coders[i]; - for (int j = 0; j < coder._numInStreams; j++) - { - inStreamToCoder.Add(i); - } - for (int j = 0; j < coder._numOutStreams; j++) - { - outStreamToCoder.Add(i); - } - } - - for (int i = 0; i < _bindPairs.Count; i++) - { - CBindPair bp = _bindPairs[i]; - mask[inStreamToCoder[bp._inIndex]] |= (1u << outStreamToCoder[bp._outIndex]); - } - } - - for (int i = 0; i < kMaskSize; i++) - { - for (int j = 0; j < kMaskSize; j++) - { - if (((1u << j) & mask[i]) != 0) - { - mask[i] |= mask[j]; - } - } - } - - for (int i = 0; i < kMaskSize; i++) - { - if (((1u << i) & mask[i]) != 0) + if (v.GetAndSet(_bindPairs[i]._inIndex)) { return false; } } - return true; + for (var i = 0; i < _packStreams.Count; i++) + { + if (v.GetAndSet(_packStreams[i])) + { + return false; + } + } } + + { + var v = new BitVector(_unpackSizes.Count); + for (var i = 0; i < _bindPairs.Count; i++) + { + if (v.GetAndSet(_bindPairs[i]._outIndex)) + { + return false; + } + } + } + + var mask = new uint[kMaskSize]; + + { + var inStreamToCoder = new List(); + var outStreamToCoder = new List(); + for (var i = 0; i < _coders.Count; i++) + { + var coder = _coders[i]; + for (var j = 0; j < coder._numInStreams; j++) + { + inStreamToCoder.Add(i); + } + for (var j = 0; j < coder._numOutStreams; j++) + { + outStreamToCoder.Add(i); + } + } + + for (var i = 0; i < _bindPairs.Count; i++) + { + var bp = _bindPairs[i]; + mask[inStreamToCoder[bp._inIndex]] |= (1u << outStreamToCoder[bp._outIndex]); + } + } + + for (var i = 0; i < kMaskSize; i++) + { + for (var j = 0; j < kMaskSize; j++) + { + if (((1u << j) & mask[i]) != 0) + { + mask[i] |= mask[j]; + } + } + } + + for (var i = 0; i < kMaskSize; i++) + { + if (((1u << i) & mask[i]) != 0) + { + return false; + } + } + + return true; } } diff --git a/src/SharpCompress/Common/SevenZip/CMethodId.cs b/src/SharpCompress/Common/SevenZip/CMethodId.cs index 5069b051..6dce5773 100644 --- a/src/SharpCompress/Common/SevenZip/CMethodId.cs +++ b/src/SharpCompress/Common/SevenZip/CMethodId.cs @@ -1,57 +1,38 @@ -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal readonly struct CMethodId { - internal readonly struct CMethodId + public const ulong K_COPY_ID = 0; + public const ulong K_LZMA_ID = 0x030101; + public const ulong K_LZMA2_ID = 0x21; + public const ulong K_AES_ID = 0x06F10701; + + public static readonly CMethodId K_COPY = new CMethodId(K_COPY_ID); + public static readonly CMethodId K_LZMA = new CMethodId(K_LZMA_ID); + public static readonly CMethodId K_LZMA2 = new CMethodId(K_LZMA2_ID); + public static readonly CMethodId K_AES = new CMethodId(K_AES_ID); + + public readonly ulong _id; + + public CMethodId(ulong id) => _id = id; + + public override int GetHashCode() => _id.GetHashCode(); + + public override bool Equals(object? obj) => obj is CMethodId other && Equals(other); + + public bool Equals(CMethodId other) => _id == other._id; + + public static bool operator ==(CMethodId left, CMethodId right) => left._id == right._id; + + public static bool operator !=(CMethodId left, CMethodId right) => left._id != right._id; + + public int GetLength() { - public const ulong K_COPY_ID = 0; - public const ulong K_LZMA_ID = 0x030101; - public const ulong K_LZMA2_ID = 0x21; - public const ulong K_AES_ID = 0x06F10701; - - public static readonly CMethodId K_COPY = new CMethodId(K_COPY_ID); - public static readonly CMethodId K_LZMA = new CMethodId(K_LZMA_ID); - public static readonly CMethodId K_LZMA2 = new CMethodId(K_LZMA2_ID); - public static readonly CMethodId K_AES = new CMethodId(K_AES_ID); - - public readonly ulong _id; - - public CMethodId(ulong id) + var bytes = 0; + for (var value = _id; value != 0; value >>= 8) { - _id = id; - } - - public override int GetHashCode() - { - return _id.GetHashCode(); - } - - public override bool Equals(object? obj) - { - return obj is CMethodId other && Equals(other); - } - - public bool Equals(CMethodId other) - { - return _id == other._id; - } - - public static bool operator ==(CMethodId left, CMethodId right) - { - return left._id == right._id; - } - - public static bool operator !=(CMethodId left, CMethodId right) - { - return left._id != right._id; - } - - public int GetLength() - { - int bytes = 0; - for (ulong value = _id; value != 0; value >>= 8) - { - bytes++; - } - return bytes; + bytes++; } + return bytes; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/SevenZip/CStreamSwitch.cs b/src/SharpCompress/Common/SevenZip/CStreamSwitch.cs index c1cd51c9..9238a5a1 100644 --- a/src/SharpCompress/Common/SevenZip/CStreamSwitch.cs +++ b/src/SharpCompress/Common/SevenZip/CStreamSwitch.cs @@ -1,69 +1,68 @@ -using System; +using System; using System.Collections.Generic; using SharpCompress.Compressors.LZMA; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal struct CStreamSwitch : IDisposable { - internal struct CStreamSwitch : IDisposable + private ArchiveReader _archive; + private bool _needRemove; + private bool _active; + + public void Dispose() { - private ArchiveReader _archive; - private bool _needRemove; - private bool _active; - - public void Dispose() + if (_active) { - if (_active) - { - _active = false; + _active = false; #if DEBUG - Log.WriteLine("[end of switch]"); + Log.WriteLine("[end of switch]"); #endif - } - - if (_needRemove) - { - _needRemove = false; - _archive.DeleteByteStream(); - } } - public void Set(ArchiveReader archive, byte[] dataVector) + if (_needRemove) { - Dispose(); + _needRemove = false; + _archive.DeleteByteStream(); + } + } + + public void Set(ArchiveReader archive, byte[] dataVector) + { + Dispose(); + _archive = archive; + _archive.AddByteStream(dataVector, 0, dataVector.Length); + _needRemove = true; + _active = true; + } + + public void Set(ArchiveReader archive, List dataVector) + { + Dispose(); + _active = true; + + var external = archive.ReadByte(); + if (external != 0) + { + var dataIndex = archive.ReadNum(); + if (dataIndex < 0 || dataIndex >= dataVector.Count) + { + throw new InvalidOperationException(); + } + +#if DEBUG + Log.WriteLine("[switch to stream {0}]", dataIndex); +#endif _archive = archive; - _archive.AddByteStream(dataVector, 0, dataVector.Length); + _archive.AddByteStream(dataVector[dataIndex], 0, dataVector[dataIndex].Length); _needRemove = true; _active = true; } - - public void Set(ArchiveReader archive, List dataVector) + else { - Dispose(); - _active = true; - - byte external = archive.ReadByte(); - if (external != 0) - { - int dataIndex = archive.ReadNum(); - if (dataIndex < 0 || dataIndex >= dataVector.Count) - { - throw new InvalidOperationException(); - } - #if DEBUG - Log.WriteLine("[switch to stream {0}]", dataIndex); + Log.WriteLine("[inline data]"); #endif - _archive = archive; - _archive.AddByteStream(dataVector[dataIndex], 0, dataVector[dataIndex].Length); - _needRemove = true; - _active = true; - } - else - { -#if DEBUG - Log.WriteLine("[inline data]"); -#endif - } } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/SevenZip/DataReader.cs b/src/SharpCompress/Common/SevenZip/DataReader.cs index 48fcd099..f280a3c1 100644 --- a/src/SharpCompress/Common/SevenZip/DataReader.cs +++ b/src/SharpCompress/Common/SevenZip/DataReader.cs @@ -3,184 +3,176 @@ using System.IO; using System.Text; using SharpCompress.Compressors.LZMA; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class DataReader { - internal class DataReader + #region Static Methods + + public static uint Get32(byte[] buffer, int offset) => + buffer[offset] + + ((uint)buffer[offset + 1] << 8) + + ((uint)buffer[offset + 2] << 16) + + ((uint)buffer[offset + 3] << 24); + + public static ulong Get64(byte[] buffer, int offset) => + buffer[offset] + + ((ulong)buffer[offset + 1] << 8) + + ((ulong)buffer[offset + 2] << 16) + + ((ulong)buffer[offset + 3] << 24) + + ((ulong)buffer[offset + 4] << 32) + + ((ulong)buffer[offset + 5] << 40) + + ((ulong)buffer[offset + 6] << 48) + + ((ulong)buffer[offset + 7] << 56); + + #endregion + + #region Variables + + private readonly byte[] _buffer; + private readonly int _ending; + + #endregion + + #region Public Methods + + public DataReader(byte[] buffer, int offset, int length) { - #region Static Methods - - public static uint Get32(byte[] buffer, int offset) - { - return buffer[offset] - + ((uint)buffer[offset + 1] << 8) - + ((uint)buffer[offset + 2] << 16) - + ((uint)buffer[offset + 3] << 24); - } - - public static ulong Get64(byte[] buffer, int offset) - { - return buffer[offset] - + ((ulong)buffer[offset + 1] << 8) - + ((ulong)buffer[offset + 2] << 16) - + ((ulong)buffer[offset + 3] << 24) - + ((ulong)buffer[offset + 4] << 32) - + ((ulong)buffer[offset + 5] << 40) - + ((ulong)buffer[offset + 6] << 48) - + ((ulong)buffer[offset + 7] << 56); - } - - #endregion - - #region Variables - - private readonly byte[] _buffer; - private readonly int _ending; - - #endregion - - #region Public Methods - - public DataReader(byte[] buffer, int offset, int length) - { - _buffer = buffer; - Offset = offset; - _ending = offset + length; - } - - public int Offset { get; private set; } - - public Byte ReadByte() - { - if (Offset >= _ending) - { - throw new EndOfStreamException(); - } - - return _buffer[Offset++]; - } - - public void ReadBytes(byte[] buffer, int offset, int length) - { - if (length > _ending - Offset) - { - throw new EndOfStreamException(); - } - - while (length-- > 0) - { - buffer[offset++] = _buffer[Offset++]; - } - } - - public void SkipData(long size) - { - if (size > _ending - Offset) - { - throw new EndOfStreamException(); - } - - Offset += (int)size; -#if DEBUG - Log.WriteLine("SkipData {0}", size); -#endif - } - - public void SkipData() - { - SkipData(checked((long)ReadNumber())); - } - - public ulong ReadNumber() - { - if (Offset >= _ending) - { - throw new EndOfStreamException(); - } - - byte firstByte = _buffer[Offset++]; - byte mask = 0x80; - ulong value = 0; - - for (int i = 0; i < 8; i++) - { - if ((firstByte & mask) == 0) - { - ulong highPart = firstByte & (mask - 1u); - value += highPart << (i * 8); - return value; - } - - if (Offset >= _ending) - { - throw new EndOfStreamException(); - } - - value |= (ulong)_buffer[Offset++] << (8 * i); - mask >>= 1; - } - - return value; - } - - public int ReadNum() - { - ulong value = ReadNumber(); - if (value > Int32.MaxValue) - { - throw new NotSupportedException(); - } - - return (int)value; - } - - public uint ReadUInt32() - { - if (Offset + 4 > _ending) - { - throw new EndOfStreamException(); - } - - uint res = Get32(_buffer, Offset); - Offset += 4; - return res; - } - - public ulong ReadUInt64() - { - if (Offset + 8 > _ending) - { - throw new EndOfStreamException(); - } - - ulong res = Get64(_buffer, Offset); - Offset += 8; - return res; - } - - public string ReadString() - { - int ending = Offset; - - for (; ; ) - { - if (ending + 2 > _ending) - { - throw new EndOfStreamException(); - } - - if (_buffer[ending] == 0 && _buffer[ending + 1] == 0) - { - break; - } - - ending += 2; - } - - string str = Encoding.Unicode.GetString(_buffer, Offset, ending - Offset); - Offset = ending + 2; - return str; - } - - #endregion + _buffer = buffer; + Offset = offset; + _ending = offset + length; } -} \ No newline at end of file + + public int Offset { get; private set; } + + public byte ReadByte() + { + if (Offset >= _ending) + { + throw new EndOfStreamException(); + } + + return _buffer[Offset++]; + } + + public void ReadBytes(byte[] buffer, int offset, int length) + { + if (length > _ending - Offset) + { + throw new EndOfStreamException(); + } + + while (length-- > 0) + { + buffer[offset++] = _buffer[Offset++]; + } + } + + public void SkipData(long size) + { + if (size > _ending - Offset) + { + throw new EndOfStreamException(); + } + + Offset += (int)size; +#if DEBUG + Log.WriteLine("SkipData {0}", size); +#endif + } + + public void SkipData() => SkipData(checked((long)ReadNumber())); + + public ulong ReadNumber() + { + if (Offset >= _ending) + { + throw new EndOfStreamException(); + } + + var firstByte = _buffer[Offset++]; + byte mask = 0x80; + ulong value = 0; + + for (var i = 0; i < 8; i++) + { + if ((firstByte & mask) == 0) + { + ulong highPart = firstByte & (mask - 1u); + value += highPart << (i * 8); + return value; + } + + if (Offset >= _ending) + { + throw new EndOfStreamException(); + } + + value |= (ulong)_buffer[Offset++] << (8 * i); + mask >>= 1; + } + + return value; + } + + public int ReadNum() + { + var value = ReadNumber(); + if (value > int.MaxValue) + { + throw new NotSupportedException(); + } + + return (int)value; + } + + public uint ReadUInt32() + { + if (Offset + 4 > _ending) + { + throw new EndOfStreamException(); + } + + var res = Get32(_buffer, Offset); + Offset += 4; + return res; + } + + public ulong ReadUInt64() + { + if (Offset + 8 > _ending) + { + throw new EndOfStreamException(); + } + + var res = Get64(_buffer, Offset); + Offset += 8; + return res; + } + + public string ReadString() + { + var ending = Offset; + + for (; ; ) + { + if (ending + 2 > _ending) + { + throw new EndOfStreamException(); + } + + if (_buffer[ending] == 0 && _buffer[ending + 1] == 0) + { + break; + } + + ending += 2; + } + + var str = Encoding.Unicode.GetString(_buffer, Offset, ending - Offset); + Offset = ending + 2; + return str; + } + + #endregion +} diff --git a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs index d2dd2cd1..66170ba8 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs @@ -1,45 +1,42 @@ using System; using System.Collections.Generic; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +public class SevenZipEntry : Entry { - public class SevenZipEntry : Entry - { - internal SevenZipEntry(SevenZipFilePart filePart) - { - FilePart = filePart; - } + internal SevenZipEntry(SevenZipFilePart filePart) => FilePart = filePart; - internal SevenZipFilePart FilePart { get; } + internal SevenZipFilePart FilePart { get; } - public override CompressionType CompressionType => FilePart.CompressionType; + public override CompressionType CompressionType => FilePart.CompressionType; - public override long Crc => FilePart.Header.Crc ?? 0; + public override long Crc => FilePart.Header.Crc ?? 0; - public override string Key => FilePart.Header.Name; + public override string Key => FilePart.Header.Name; - public override string? LinkTarget => null; + public override string? LinkTarget => null; - public override long CompressedSize => 0; + public override long CompressedSize => 0; - public override long Size => FilePart.Header.Size; + public override long Size => FilePart.Header.Size; - public override DateTime? LastModifiedTime => FilePart.Header.MTime; + public override DateTime? LastModifiedTime => FilePart.Header.MTime; - public override DateTime? CreatedTime => null; + public override DateTime? CreatedTime => null; - public override DateTime? LastAccessedTime => null; + public override DateTime? LastAccessedTime => null; - public override DateTime? ArchivedTime => null; + public override DateTime? ArchivedTime => null; - public override bool IsEncrypted => FilePart.IsEncrypted; + public override bool IsEncrypted => FilePart.IsEncrypted; - public override bool IsDirectory => FilePart.Header.IsDir; + public override bool IsDirectory => FilePart.Header.IsDir; - public override bool IsSplitAfter => false; + public override bool IsSplitAfter => false; - public override int? Attrib => FilePart.Header.Attrib.HasValue ? (int?)FilePart.Header.Attrib.Value : null; + public override int? Attrib => + FilePart.Header.Attrib.HasValue ? (int?)FilePart.Header.Attrib.Value : null; - internal override IEnumerable Parts => FilePart.AsEnumerable(); - } -} \ No newline at end of file + internal override IEnumerable Parts => FilePart.AsEnumerable(); +} diff --git a/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs b/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs index 4a5a8d43..7aac0ade 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipFilePart.cs @@ -3,105 +3,98 @@ using System.IO; using System.Linq; using SharpCompress.IO; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +internal class SevenZipFilePart : FilePart { - internal class SevenZipFilePart : FilePart + private CompressionType? _type; + private readonly Stream _stream; + private readonly ArchiveDatabase _database; + + internal SevenZipFilePart( + Stream stream, + ArchiveDatabase database, + int index, + CFileItem fileEntry, + ArchiveEncoding archiveEncoding + ) : base(archiveEncoding) { - private CompressionType? _type; - private readonly Stream _stream; - private readonly ArchiveDatabase _database; - - internal SevenZipFilePart(Stream stream, ArchiveDatabase database, int index, CFileItem fileEntry, ArchiveEncoding archiveEncoding) - : base(archiveEncoding) + _stream = stream; + _database = database; + Index = index; + Header = fileEntry; + if (Header.HasStream) { - _stream = stream; - _database = database; - Index = index; - Header = fileEntry; - if (Header.HasStream) - { - Folder = database._folders[database._fileIndexToFolderIndexMap[index]]; - } + Folder = database._folders[database._fileIndexToFolderIndexMap[index]]; } - - internal CFileItem Header { get; } - internal CFolder? Folder { get; } - - internal override string FilePartName => Header.Name; - - internal override Stream? GetRawStream() - { - return null; - } - - internal override Stream GetCompressedStream() - { - if (!Header.HasStream) - { - return null!; - } - var folderStream = _database.GetFolderStream(_stream, Folder!, _database.PasswordProvider); - - int firstFileIndex = _database._folderStartFileIndex[_database._folders.IndexOf(Folder!)]; - int skipCount = Index - firstFileIndex; - long skipSize = 0; - for (int i = 0; i < skipCount; i++) - { - skipSize += _database._files[firstFileIndex + i].Size; - } - if (skipSize > 0) - { - folderStream.Skip(skipSize); - } - return new ReadOnlySubStream(folderStream, Header.Size); - } - - public CompressionType CompressionType - { - get - { - if (_type is null) - { - _type = GetCompression(); - } - return _type.Value; - } - } - - //copied from DecoderRegistry - private const uint K_COPY = 0x0; - private const uint K_DELTA = 3; - private const uint K_LZMA2 = 0x21; - private const uint K_LZMA = 0x030101; - private const uint K_PPMD = 0x030401; - private const uint K_BCJ = 0x03030103; - private const uint K_BCJ2 = 0x0303011B; - private const uint K_DEFLATE = 0x040108; - private const uint K_B_ZIP2 = 0x040202; - - internal CompressionType GetCompression() - { - var coder = Folder!._coders.First(); - switch (coder._methodId._id) - { - case K_LZMA: - case K_LZMA2: - { - return CompressionType.LZMA; - } - case K_PPMD: - { - return CompressionType.PPMd; - } - case K_B_ZIP2: - { - return CompressionType.BZip2; - } - default: - throw new NotImplementedException(); - } - } - - internal bool IsEncrypted => Folder!._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1; } + + internal CFileItem Header { get; } + internal CFolder? Folder { get; } + + internal override string FilePartName => Header.Name; + + internal override Stream? GetRawStream() => null; + + internal override Stream GetCompressedStream() + { + if (!Header.HasStream) + { + return null!; + } + var folderStream = _database.GetFolderStream(_stream, Folder!, _database.PasswordProvider); + + var firstFileIndex = _database._folderStartFileIndex[_database._folders.IndexOf(Folder!)]; + var skipCount = Index - firstFileIndex; + long skipSize = 0; + for (var i = 0; i < skipCount; i++) + { + skipSize += _database._files[firstFileIndex + i].Size; + } + if (skipSize > 0) + { + folderStream.Skip(skipSize); + } + return new ReadOnlySubStream(folderStream, Header.Size); + } + + public CompressionType CompressionType + { + get + { + _type ??= GetCompression(); + return _type.Value; + } + } + + private const uint K_LZMA2 = 0x21; + private const uint K_LZMA = 0x030101; + private const uint K_PPMD = 0x030401; + private const uint K_B_ZIP2 = 0x040202; + + internal CompressionType GetCompression() + { + var coder = Folder!._coders.First(); + switch (coder._methodId._id) + { + case K_LZMA: + case K_LZMA2: + { + return CompressionType.LZMA; + } + case K_PPMD: + { + return CompressionType.PPMd; + } + case K_B_ZIP2: + { + return CompressionType.BZip2; + } + default: + throw new NotImplementedException(); + } + } + + internal bool IsEncrypted => + Folder!._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1; } diff --git a/src/SharpCompress/Common/SevenZip/SevenZipVolume.cs b/src/SharpCompress/Common/SevenZip/SevenZipVolume.cs index 79d6c9d0..e4b330af 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipVolume.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipVolume.cs @@ -1,13 +1,10 @@ using System.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.SevenZip +namespace SharpCompress.Common.SevenZip; + +public class SevenZipVolume : Volume { - public class SevenZipVolume : Volume - { - public SevenZipVolume(Stream stream, ReaderOptions readerFactoryOptions, int index = 0) - : base(stream, readerFactoryOptions, index) - { - } - } + public SevenZipVolume(Stream stream, ReaderOptions readerFactoryOptions, int index = 0) + : base(stream, readerFactoryOptions, index) { } } diff --git a/src/SharpCompress/Common/Tar/Headers/EntryType.cs b/src/SharpCompress/Common/Tar/Headers/EntryType.cs index 06ba1f0d..3e6877c7 100644 --- a/src/SharpCompress/Common/Tar/Headers/EntryType.cs +++ b/src/SharpCompress/Common/Tar/Headers/EntryType.cs @@ -1,19 +1,18 @@ -namespace SharpCompress.Common.Tar.Headers +namespace SharpCompress.Common.Tar.Headers; + +internal enum EntryType : byte { - internal enum EntryType : byte - { - File = 0, - OldFile = (byte)'0', - HardLink = (byte)'1', - SymLink = (byte)'2', - CharDevice = (byte)'3', - BlockDevice = (byte)'4', - Directory = (byte)'5', - Fifo = (byte)'6', - LongLink = (byte)'K', - LongName = (byte)'L', - SparseFile = (byte)'S', - VolumeHeader = (byte)'V', - GlobalExtendedHeader = (byte)'g' - } -} \ No newline at end of file + File = 0, + OldFile = (byte)'0', + HardLink = (byte)'1', + SymLink = (byte)'2', + CharDevice = (byte)'3', + BlockDevice = (byte)'4', + Directory = (byte)'5', + Fifo = (byte)'6', + LongLink = (byte)'K', + LongName = (byte)'L', + SparseFile = (byte)'S', + VolumeHeader = (byte)'V', + GlobalExtendedHeader = (byte)'g' +} diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 6dad0682..ef86ab00 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -5,302 +5,305 @@ using System.Buffers.Binary; using System.IO; using System.Text; -namespace SharpCompress.Common.Tar.Headers +namespace SharpCompress.Common.Tar.Headers; + +internal sealed class TarHeader { - internal sealed class TarHeader + internal static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public TarHeader(ArchiveEncoding archiveEncoding) => ArchiveEncoding = archiveEncoding; + + internal string Name { get; set; } + internal string LinkName { get; set; } + + internal long Mode { get; set; } + internal long UserId { get; set; } + internal long GroupId { get; set; } + internal long Size { get; set; } + internal DateTime LastModifiedTime { get; set; } + internal EntryType EntryType { get; set; } + internal Stream PackedStream { get; set; } + internal ArchiveEncoding ArchiveEncoding { get; } + + internal const int BLOCK_SIZE = 512; + + internal void Write(Stream output) { - internal static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var buffer = new byte[BLOCK_SIZE]; - public TarHeader(ArchiveEncoding archiveEncoding) + WriteOctalBytes(511, buffer, 100, 8); // file mode + WriteOctalBytes(0, buffer, 108, 8); // owner ID + WriteOctalBytes(0, buffer, 116, 8); // group ID + + //ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257); + var nameByteCount = ArchiveEncoding.GetEncoding().GetByteCount(Name); + if (nameByteCount > 100) { - ArchiveEncoding = archiveEncoding; + // Set mock filename and filetype to indicate the next block is the actual name of the file + WriteStringBytes("././@LongLink", buffer, 0, 100); + buffer[156] = (byte)EntryType.LongName; + WriteOctalBytes(nameByteCount + 1, buffer, 124, 12); + } + else + { + WriteStringBytes(ArchiveEncoding.Encode(Name), buffer, 100); + WriteOctalBytes(Size, buffer, 124, 12); + var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds; + WriteOctalBytes(time, buffer, 136, 12); + buffer[156] = (byte)EntryType; + + if (Size >= 0x1FFFFFFFF) + { + Span bytes12 = stackalloc byte[12]; + BinaryPrimitives.WriteInt64BigEndian(bytes12.Slice(4), Size); + bytes12[0] |= 0x80; + bytes12.CopyTo(buffer.AsSpan(124)); + } } - internal string Name { get; set; } - internal string LinkName { get; set; } + var crc = RecalculateChecksum(buffer); + WriteOctalBytes(crc, buffer, 148, 8); - internal long Mode { get; set; } - internal long UserId { get; set; } - internal long GroupId { get; set; } - internal long Size { get; set; } - internal DateTime LastModifiedTime { get; set; } - internal EntryType EntryType { get; set; } - internal Stream PackedStream { get; set; } - internal ArchiveEncoding ArchiveEncoding { get; } + output.Write(buffer, 0, buffer.Length); - internal const int BLOCK_SIZE = 512; - - internal void Write(Stream output) + if (nameByteCount > 100) { - byte[] buffer = new byte[BLOCK_SIZE]; + WriteLongFilenameHeader(output); + // update to short name lower than 100 - [max bytes of one character]. + // subtracting bytes is needed because preventing infinite loop(example code is here). + // + // var bytes = Encoding.UTF8.GetBytes(new string(0x3042, 100)); + // var truncated = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(bytes, 0, 100)); + // + // and then infinite recursion is occured in WriteLongFilenameHeader because truncated.Length is 102. + Name = ArchiveEncoding.Decode( + ArchiveEncoding.Encode(Name), + 0, + 100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1) + ); + Write(output); + } + } - WriteOctalBytes(511, buffer, 100, 8); // file mode - WriteOctalBytes(0, buffer, 108, 8); // owner ID - WriteOctalBytes(0, buffer, 116, 8); // group ID + private void WriteLongFilenameHeader(Stream output) + { + var nameBytes = ArchiveEncoding.Encode(Name); + output.Write(nameBytes, 0, nameBytes.Length); - //ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257); - var nameByteCount = ArchiveEncoding.GetEncoding().GetByteCount(Name); - if (nameByteCount > 100) + // pad to multiple of BlockSize bytes, and make sure a terminating null is added + var numPaddingBytes = BLOCK_SIZE - (nameBytes.Length % BLOCK_SIZE); + if (numPaddingBytes == 0) + { + numPaddingBytes = BLOCK_SIZE; + } + output.Write(stackalloc byte[numPaddingBytes]); + } + + internal bool Read(BinaryReader reader) + { + var buffer = ReadBlock(reader); + if (buffer.Length == 0) + { + return false; + } + + // for symlinks, additionally read the linkname + if (ReadEntryType(buffer) == EntryType.SymLink) + { + LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls(); + } + + if (ReadEntryType(buffer) == EntryType.LongName) + { + Name = ReadLongName(reader, buffer); + buffer = ReadBlock(reader); + } + else + { + Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls(); + } + + EntryType = ReadEntryType(buffer); + Size = ReadSize(buffer); + + Mode = ReadAsciiInt64Base8(buffer, 100, 7); + if (EntryType == EntryType.Directory) + { + Mode |= 0b1_000_000_000; + } + + UserId = ReadAsciiInt64Base8(buffer, 108, 7); + GroupId = ReadAsciiInt64Base8(buffer, 116, 7); + var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11); + LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime(); + + Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls(); + + if (!string.IsNullOrEmpty(Magic) && "ustar".Equals(Magic)) + { + var namePrefix = ArchiveEncoding.Decode(buffer, 345, 157); + namePrefix = namePrefix.TrimNulls(); + if (!string.IsNullOrEmpty(namePrefix)) { - // Set mock filename and filetype to indicate the next block is the actual name of the file - WriteStringBytes("././@LongLink", buffer, 0, 100); - buffer[156] = (byte)EntryType.LongName; - WriteOctalBytes(nameByteCount + 1, buffer, 124, 12); + Name = namePrefix + "/" + Name; + } + } + if (EntryType != EntryType.LongName && Name.Length == 0) + { + return false; + } + return true; + } + + private string ReadLongName(BinaryReader reader, byte[] buffer) + { + var size = ReadSize(buffer); + var nameLength = (int)size; + var nameBytes = reader.ReadBytes(nameLength); + var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE); + + // Read the rest of the block and discard the data + if (remainingBytesToRead < BLOCK_SIZE) + { + reader.ReadBytes(remainingBytesToRead); + } + return ArchiveEncoding.Decode(nameBytes, 0, nameBytes.Length).TrimNulls(); + } + + private static EntryType ReadEntryType(byte[] buffer) => (EntryType)buffer[156]; + + private long ReadSize(byte[] buffer) + { + if ((buffer[124] & 0x80) == 0x80) // if size in binary + { + return BinaryPrimitives.ReadInt64BigEndian(buffer.AsSpan(0x80)); + } + + return ReadAsciiInt64Base8(buffer, 124, 11); + } + + private static byte[] ReadBlock(BinaryReader reader) + { + var buffer = reader.ReadBytes(BLOCK_SIZE); + + if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE) + { + throw new InvalidOperationException("Buffer is invalid size"); + } + return buffer; + } + + private static void WriteStringBytes(ReadOnlySpan name, Span buffer, int length) + { + name.CopyTo(buffer); + var i = Math.Min(length, name.Length); + buffer.Slice(i, length - i).Clear(); + } + + private static void WriteStringBytes(string name, byte[] buffer, int offset, int length) + { + int i; + + for (i = 0; i < length && i < name.Length; ++i) + { + buffer[offset + i] = (byte)name[i]; + } + + for (; i < length; ++i) + { + buffer[offset + i] = 0; + } + } + + private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length) + { + var val = Convert.ToString(value, 8); + var shift = length - val.Length - 1; + for (var i = 0; i < shift; i++) + { + buffer[offset + i] = (byte)' '; + } + for (var i = 0; i < val.Length; i++) + { + buffer[offset + i + shift] = (byte)val[i]; + } + } + + private static int ReadAsciiInt32Base8(byte[] buffer, int offset, int count) + { + var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); + if (string.IsNullOrEmpty(s)) + { + return 0; + } + return Convert.ToInt32(s, 8); + } + + private static long ReadAsciiInt64Base8(byte[] buffer, int offset, int count) + { + var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); + if (string.IsNullOrEmpty(s)) + { + return 0; + } + return Convert.ToInt64(s, 8); + } + + private static long ReadAsciiInt64(byte[] buffer, int offset, int count) + { + var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); + if (string.IsNullOrEmpty(s)) + { + return 0; + } + return Convert.ToInt64(s); + } + + private static readonly byte[] eightSpaces = + { + (byte)' ', + (byte)' ', + (byte)' ', + (byte)' ', + (byte)' ', + (byte)' ', + (byte)' ', + (byte)' ' + }; + + internal static int RecalculateChecksum(byte[] buf) + { + // Set default value for checksum. That is 8 spaces. + eightSpaces.CopyTo(buf, 148); + + // Calculate checksum + var headerChecksum = 0; + foreach (var b in buf) + { + headerChecksum += b; + } + return headerChecksum; + } + + internal static int RecalculateAltChecksum(byte[] buf) + { + eightSpaces.CopyTo(buf, 148); + var headerChecksum = 0; + foreach (var b in buf) + { + if ((b & 0x80) == 0x80) + { + headerChecksum -= b ^ 0x80; } else - { - WriteStringBytes(ArchiveEncoding.Encode(Name), buffer, 100); - WriteOctalBytes(Size, buffer, 124, 12); - var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds; - WriteOctalBytes(time, buffer, 136, 12); - buffer[156] = (byte)EntryType; - - if (Size >= 0x1FFFFFFFF) - { - Span bytes12 = stackalloc byte[12]; - BinaryPrimitives.WriteInt64BigEndian(bytes12.Slice(4), Size); - bytes12[0] |= 0x80; - bytes12.CopyTo(buffer.AsSpan(124)); - } - } - - int crc = RecalculateChecksum(buffer); - WriteOctalBytes(crc, buffer, 148, 8); - - output.Write(buffer, 0, buffer.Length); - - if (nameByteCount > 100) - { - WriteLongFilenameHeader(output); - // update to short name lower than 100 - [max bytes of one character]. - // subtracting bytes is needed because preventing infinite loop(example code is here). - // - // var bytes = Encoding.UTF8.GetBytes(new string(0x3042, 100)); - // var truncated = Encoding.UTF8.GetBytes(Encoding.UTF8.GetString(bytes, 0, 100)); - // - // and then infinite recursion is occured in WriteLongFilenameHeader because truncated.Length is 102. - Name = ArchiveEncoding.Decode(ArchiveEncoding.Encode(Name), 0, 100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1)); - Write(output); - } - } - - private void WriteLongFilenameHeader(Stream output) - { - byte[] nameBytes = ArchiveEncoding.Encode(Name); - output.Write(nameBytes, 0, nameBytes.Length); - - // pad to multiple of BlockSize bytes, and make sure a terminating null is added - int numPaddingBytes = BLOCK_SIZE - (nameBytes.Length % BLOCK_SIZE); - if (numPaddingBytes == 0) - { - numPaddingBytes = BLOCK_SIZE; - } - output.Write(stackalloc byte[numPaddingBytes]); - } - - internal bool Read(BinaryReader reader) - { - var buffer = ReadBlock(reader); - if (buffer.Length == 0) - { - return false; - } - - // for symlinks, additionally read the linkname - if (ReadEntryType(buffer) == EntryType.SymLink) - { - LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls(); - } - - if (ReadEntryType(buffer) == EntryType.LongName) - { - Name = ReadLongName(reader, buffer); - buffer = ReadBlock(reader); - } - else - { - Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls(); - } - - EntryType = ReadEntryType(buffer); - Size = ReadSize(buffer); - - Mode = ReadAsciiInt64Base8(buffer, 100, 7); - if(EntryType == EntryType.Directory) - Mode |= 0b1_000_000_000; - - UserId = ReadAsciiInt64Base8(buffer, 108, 7); - GroupId = ReadAsciiInt64Base8(buffer, 116, 7); - long unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11); - LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime(); - - Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls(); - - if (!string.IsNullOrEmpty(Magic) - && "ustar".Equals(Magic)) - { - string namePrefix = ArchiveEncoding.Decode(buffer, 345, 157); - namePrefix = namePrefix.TrimNulls(); - if (!string.IsNullOrEmpty(namePrefix)) - { - Name = namePrefix + "/" + Name; - } - } - if (EntryType != EntryType.LongName - && Name.Length == 0) - { - return false; - } - return true; - } - - private string ReadLongName(BinaryReader reader, byte[] buffer) - { - var size = ReadSize(buffer); - var nameLength = (int)size; - var nameBytes = reader.ReadBytes(nameLength); - var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE); - - // Read the rest of the block and discard the data - if (remainingBytesToRead < BLOCK_SIZE) - { - reader.ReadBytes(remainingBytesToRead); - } - return ArchiveEncoding.Decode(nameBytes, 0, nameBytes.Length).TrimNulls(); - } - - private static EntryType ReadEntryType(byte[] buffer) - { - return (EntryType)buffer[156]; - } - - private long ReadSize(byte[] buffer) - { - if ((buffer[124] & 0x80) == 0x80) // if size in binary - { - return BinaryPrimitives.ReadInt64BigEndian(buffer.AsSpan(0x80)); - } - - return ReadAsciiInt64Base8(buffer, 124, 11); - } - - private static byte[] ReadBlock(BinaryReader reader) - { - byte[] buffer = reader.ReadBytes(BLOCK_SIZE); - - if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE) - { - throw new InvalidOperationException("Buffer is invalid size"); - } - return buffer; - } - - private static void WriteStringBytes(ReadOnlySpan name, Span buffer, int length) - { - name.CopyTo(buffer); - int i = Math.Min(length, name.Length); - buffer.Slice(i, length - i).Clear(); - } - - private static void WriteStringBytes(string name, byte[] buffer, int offset, int length) - { - int i; - - for (i = 0; i < length && i < name.Length; ++i) - { - buffer[offset + i] = (byte)name[i]; - } - - for (; i < length; ++i) - { - buffer[offset + i] = 0; - } - } - - private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length) - { - string val = Convert.ToString(value, 8); - int shift = length - val.Length - 1; - for (int i = 0; i < shift; i++) - { - buffer[offset + i] = (byte)' '; - } - for (int i = 0; i < val.Length; i++) - { - buffer[offset + i + shift] = (byte)val[i]; - } - } - - private static int ReadAsciiInt32Base8(byte[] buffer, int offset, int count) - { - string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); - if (string.IsNullOrEmpty(s)) - { - return 0; - } - return Convert.ToInt32(s, 8); - } - - private static long ReadAsciiInt64Base8(byte[] buffer, int offset, int count) - { - string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); - if (string.IsNullOrEmpty(s)) - { - return 0; - } - return Convert.ToInt64(s, 8); - } - - private static long ReadAsciiInt64(byte[] buffer, int offset, int count) - { - string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); - if (string.IsNullOrEmpty(s)) - { - return 0; - } - return Convert.ToInt64(s); - } - - - private static readonly byte[] eightSpaces = { - (byte)' ', (byte)' ', (byte)' ', (byte)' ', - (byte)' ', (byte)' ', (byte)' ', (byte)' ' - }; - - internal static int RecalculateChecksum(byte[] buf) - { - // Set default value for checksum. That is 8 spaces. - eightSpaces.CopyTo(buf, 148); - - // Calculate checksum - int headerChecksum = 0; - foreach (byte b in buf) { headerChecksum += b; } - return headerChecksum; } - - internal static int RecalculateAltChecksum(byte[] buf) - { - eightSpaces.CopyTo(buf, 148); - int headerChecksum = 0; - foreach (byte b in buf) - { - if ((b & 0x80) == 0x80) - { - headerChecksum -= b ^ 0x80; - } - else - { - headerChecksum += b; - } - } - return headerChecksum; - } - - public long? DataStartPosition { get; set; } - - public string Magic { get; set; } + return headerChecksum; } + + public long? DataStartPosition { get; set; } + + public string Magic { get; set; } } diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index 7de745c8..7b5e4a05 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; @@ -6,69 +6,72 @@ using System.IO; using SharpCompress.Common.Tar.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Tar +namespace SharpCompress.Common.Tar; + +public class TarEntry : Entry { - public class TarEntry : Entry + private readonly TarFilePart _filePart; + + internal TarEntry(TarFilePart filePart, CompressionType type) { - private readonly TarFilePart _filePart; + _filePart = filePart; + CompressionType = type; + } - internal TarEntry(TarFilePart filePart, CompressionType type) + public override CompressionType CompressionType { get; } + + public override long Crc => 0; + + public override string Key => _filePart.Header.Name; + + public override string LinkTarget => _filePart.Header.LinkName; + + public override long CompressedSize => _filePart.Header.Size; + + public override long Size => _filePart.Header.Size; + + public override DateTime? LastModifiedTime => _filePart.Header.LastModifiedTime; + + public override DateTime? CreatedTime => null; + + public override DateTime? LastAccessedTime => null; + + public override DateTime? ArchivedTime => null; + + public override bool IsEncrypted => false; + + public override bool IsDirectory => _filePart.Header.EntryType == EntryType.Directory; + + public override bool IsSplitAfter => false; + + public long Mode => _filePart.Header.Mode; + + public long UserID => _filePart.Header.UserId; + + public long GroupId => _filePart.Header.GroupId; + + internal override IEnumerable Parts => _filePart.AsEnumerable(); + + internal static IEnumerable GetEntries( + StreamingMode mode, + Stream stream, + CompressionType compressionType, + ArchiveEncoding archiveEncoding + ) + { + foreach (var h in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding)) { - this._filePart = filePart; - CompressionType = type; - } - - public override CompressionType CompressionType { get; } - - public override long Crc => 0; - - public override string Key => _filePart.Header.Name; - - public override string LinkTarget => _filePart.Header.LinkName; - - public override long CompressedSize => _filePart.Header.Size; - - public override long Size => _filePart.Header.Size; - - public override DateTime? LastModifiedTime => _filePart.Header.LastModifiedTime; - - public override DateTime? CreatedTime => null; - - public override DateTime? LastAccessedTime => null; - - public override DateTime? ArchivedTime => null; - - public override bool IsEncrypted => false; - - public override bool IsDirectory => _filePart.Header.EntryType == EntryType.Directory; - - public override bool IsSplitAfter => false; - - public long Mode => _filePart.Header.Mode; - - public long UserID => _filePart.Header.UserId; - - public long GroupId => _filePart.Header.GroupId; - - internal override IEnumerable Parts => _filePart.AsEnumerable(); - - internal static IEnumerable GetEntries(StreamingMode mode, Stream stream, - CompressionType compressionType, ArchiveEncoding archiveEncoding) - { - foreach (TarHeader h in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding)) + if (h != null) { - if (h != null) + if (mode == StreamingMode.Seekable) { - if (mode == StreamingMode.Seekable) - { - yield return new TarEntry(new TarFilePart(h, stream), compressionType); - } - else - { - yield return new TarEntry(new TarFilePart(h, null), compressionType); - } + yield return new TarEntry(new TarFilePart(h, stream), compressionType); + } + else + { + yield return new TarEntry(new TarFilePart(h, null), compressionType); } } } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Common/Tar/TarFilePart.cs b/src/SharpCompress/Common/Tar/TarFilePart.cs index 2e11db3d..c81f87e4 100644 --- a/src/SharpCompress/Common/Tar/TarFilePart.cs +++ b/src/SharpCompress/Common/Tar/TarFilePart.cs @@ -1,37 +1,31 @@ using System.IO; using SharpCompress.Common.Tar.Headers; -using SharpCompress.IO; -namespace SharpCompress.Common.Tar +namespace SharpCompress.Common.Tar; + +internal sealed class TarFilePart : FilePart { - internal sealed class TarFilePart : FilePart + private readonly Stream _seekableStream; + + internal TarFilePart(TarHeader header, Stream seekableStream) : base(header.ArchiveEncoding) { - private readonly Stream _seekableStream; - - internal TarFilePart(TarHeader header, Stream seekableStream) - : base(header.ArchiveEncoding) - { - _seekableStream = seekableStream; - Header = header; - } - - internal TarHeader Header { get; } - - internal override string FilePartName => Header.Name; - - internal override Stream GetCompressedStream() - { - if (_seekableStream != null) - { - _seekableStream.Position = Header.DataStartPosition!.Value; - return new TarReadOnlySubStream(_seekableStream, Header.Size); - } - return Header.PackedStream; - } - - internal override Stream? GetRawStream() - { - return null; - } + _seekableStream = seekableStream; + Header = header; } -} \ No newline at end of file + + internal TarHeader Header { get; } + + internal override string FilePartName => Header.Name; + + internal override Stream GetCompressedStream() + { + if (_seekableStream != null) + { + _seekableStream.Position = Header.DataStartPosition!.Value; + return new TarReadOnlySubStream(_seekableStream, Header.Size); + } + return Header.PackedStream; + } + + internal override Stream? GetRawStream() => null; +} diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs index eb4d7464..85eec5b4 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs @@ -1,63 +1,68 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using SharpCompress.Common.Tar.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Tar +namespace SharpCompress.Common.Tar; + +internal static class TarHeaderFactory { - internal static class TarHeaderFactory + internal static IEnumerable ReadHeader( + StreamingMode mode, + Stream stream, + ArchiveEncoding archiveEncoding + ) { - internal static IEnumerable ReadHeader(StreamingMode mode, Stream stream, ArchiveEncoding archiveEncoding) + while (true) { - while (true) + TarHeader? header = null; + try { - TarHeader? header = null; - try + var reader = new BinaryReader(stream); + header = new TarHeader(archiveEncoding); + + if (!header.Read(reader)) { - BinaryReader reader = new BinaryReader(stream); - header = new TarHeader(archiveEncoding); + yield break; + } + switch (mode) + { + case StreamingMode.Seekable: - if (!header.Read(reader)) - { - yield break; - } - switch (mode) - { - case StreamingMode.Seekable: - { - header.DataStartPosition = reader.BaseStream.Position; + { + header.DataStartPosition = reader.BaseStream.Position; - //skip to nearest 512 - reader.BaseStream.Position += PadTo512(header.Size); - } - break; - case StreamingMode.Streaming: - { - header.PackedStream = new TarReadOnlySubStream(stream, header.Size); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } + //skip to nearest 512 + reader.BaseStream.Position += PadTo512(header.Size); + } + break; + case StreamingMode.Streaming: + + { + header.PackedStream = new TarReadOnlySubStream(stream, header.Size); + } + break; + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); } } - catch - { - header = null; - } - yield return header; } - } - - private static long PadTo512(long size) - { - int zeros = (int)(size % 512); - if (zeros == 0) + catch { - return size; + header = null; } - return 512 - zeros + size; + yield return header; } } -} \ No newline at end of file + + private static long PadTo512(long size) + { + var zeros = (int)(size % 512); + if (zeros == 0) + { + return size; + } + return 512 - zeros + size; + } +} diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs index f0782c6e..7ccfd238 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -2,106 +2,95 @@ using System; using System.IO; -namespace SharpCompress.Common.Tar +namespace SharpCompress.Common.Tar; + +internal class TarReadOnlySubStream : NonDisposingStream { - internal class TarReadOnlySubStream : NonDisposingStream + private bool _isDisposed; + private long _amountRead; + + public TarReadOnlySubStream(Stream stream, long bytesToRead) + : base(stream, throwOnDispose: false) => BytesLeftToRead = bytesToRead; + + protected override void Dispose(bool disposing) { - private bool _isDisposed; - private long _amountRead; - - public TarReadOnlySubStream(Stream stream, long bytesToRead) : base(stream, throwOnDispose: false) + if (_isDisposed) { - BytesLeftToRead = bytesToRead; + return; } - protected override void Dispose(bool disposing) + _isDisposed = true; + + if (disposing) { - if (_isDisposed) + // Ensure we read all remaining blocks for this entry. + Stream.Skip(BytesLeftToRead); + _amountRead += BytesLeftToRead; + + // If the last block wasn't a full 512 bytes, skip the remaining padding bytes. + var bytesInLastBlock = _amountRead % 512; + + if (bytesInLastBlock != 0) { - return; + Stream.Skip(512 - bytesInLastBlock); } - - _isDisposed = true; - - if (disposing) - { - // Ensure we read all remaining blocks for this entry. - Stream.Skip(BytesLeftToRead); - _amountRead += BytesLeftToRead; - - // If the last block wasn't a full 512 bytes, skip the remaining padding bytes. - var bytesInLastBlock = _amountRead % 512; - - if (bytesInLastBlock != 0) - { - Stream.Skip(512 - bytesInLastBlock); - } - } - - base.Dispose(disposing); } - private long BytesLeftToRead { get; set; } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - if (BytesLeftToRead < count) - { - count = (int)BytesLeftToRead; - } - int read = Stream.Read(buffer, offset, count); - if (read > 0) - { - BytesLeftToRead -= read; - _amountRead += read; - } - return read; - } - - public override int ReadByte() - { - if (BytesLeftToRead <= 0) - { - return -1; - } - int value = Stream.ReadByte(); - if (value != -1) - { - --BytesLeftToRead; - ++_amountRead; - } - return value; - - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + base.Dispose(disposing); } -} \ No newline at end of file + + private long BytesLeftToRead { get; set; } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (BytesLeftToRead < count) + { + count = (int)BytesLeftToRead; + } + int read = Stream.Read(buffer, offset, count); + if (read > 0) + { + BytesLeftToRead -= read; + _amountRead += read; + } + return read; + } + + public override int ReadByte() + { + if (BytesLeftToRead <= 0) + { + return -1; + } + int value = Stream.ReadByte(); + if (value != -1) + { + --BytesLeftToRead; + ++_amountRead; + } + return value; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Common/Tar/TarVolume.cs b/src/SharpCompress/Common/Tar/TarVolume.cs index 5b76c56f..da00886a 100644 --- a/src/SharpCompress/Common/Tar/TarVolume.cs +++ b/src/SharpCompress/Common/Tar/TarVolume.cs @@ -1,13 +1,10 @@ using System.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.Tar +namespace SharpCompress.Common.Tar; + +public class TarVolume : Volume { - public class TarVolume : Volume - { - public TarVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) - { - } - } + public TarVolume(Stream stream, ReaderOptions readerOptions, int index = 0) + : base(stream, readerOptions, index) { } } diff --git a/src/SharpCompress/Common/Volume.cs b/src/SharpCompress/Common/Volume.cs index 8e1446b9..1f259257 100644 --- a/src/SharpCompress/Common/Volume.cs +++ b/src/SharpCompress/Common/Volume.cs @@ -3,54 +3,53 @@ using System.IO; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Common +namespace SharpCompress.Common; + +public abstract class Volume : IVolume { - public abstract class Volume : IVolume + private readonly Stream _actualStream; + + internal Volume(Stream stream, ReaderOptions readerOptions, int index = 0) { - private readonly Stream _actualStream; - - internal Volume(Stream stream, ReaderOptions readerOptions, int index = 0) + Index = index; + ReaderOptions = readerOptions; + if (readerOptions.LeaveStreamOpen) { - Index = index; - ReaderOptions = readerOptions; - if (readerOptions.LeaveStreamOpen) - { - stream = NonDisposingStream.Create(stream); - } - _actualStream = stream; + stream = NonDisposingStream.Create(stream); } + _actualStream = stream; + } - internal Stream Stream => _actualStream; + internal Stream Stream => _actualStream; - protected ReaderOptions ReaderOptions { get; } + protected ReaderOptions ReaderOptions { get; } - /// - /// RarArchive is the first volume of a multi-part archive. - /// Only Rar 3.0 format and higher - /// - public virtual bool IsFirstVolume => true; + /// + /// RarArchive is the first volume of a multi-part archive. + /// Only Rar 3.0 format and higher + /// + public virtual bool IsFirstVolume => true; - public virtual int Index { get; internal set; } + public virtual int Index { get; internal set; } - public string FileName { get { return (_actualStream as FileStream)?.Name!; } } + public string FileName => (_actualStream as FileStream)?.Name!; - /// - /// RarArchive is part of a multi-part archive. - /// - public virtual bool IsMultiVolume => true; + /// + /// RarArchive is part of a multi-part archive. + /// + public virtual bool IsMultiVolume => true; - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (disposing) { - if (disposing) - { - _actualStream.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + _actualStream.Dispose(); } } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs index db950602..943ae47c 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs @@ -1,44 +1,41 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class DirectoryEndHeader : ZipHeader { - internal class DirectoryEndHeader : ZipHeader + public DirectoryEndHeader() : base(ZipHeaderType.DirectoryEnd) { } + + internal override void Read(BinaryReader reader) { - public DirectoryEndHeader() - : base(ZipHeaderType.DirectoryEnd) - { - } - - internal override void Read(BinaryReader reader) - { - VolumeNumber = reader.ReadUInt16(); - FirstVolumeWithDirectory = reader.ReadUInt16(); - TotalNumberOfEntriesInDisk = reader.ReadUInt16(); - TotalNumberOfEntries = reader.ReadUInt16(); - DirectorySize = reader.ReadUInt32(); - DirectoryStartOffsetRelativeToDisk = reader.ReadUInt32(); - CommentLength = reader.ReadUInt16(); - Comment = reader.ReadBytes(CommentLength); - } - - public ushort VolumeNumber { get; private set; } - - public ushort FirstVolumeWithDirectory { get; private set; } - - public ushort TotalNumberOfEntriesInDisk { get; private set; } - - public uint DirectorySize { get; private set; } - - public uint DirectoryStartOffsetRelativeToDisk { get; private set; } - - public ushort CommentLength { get; private set; } - - public byte[]? Comment { get; private set; } - - public ushort TotalNumberOfEntries { get; private set; } - - public bool IsZip64 => TotalNumberOfEntriesInDisk == ushort.MaxValue - || DirectorySize == uint.MaxValue - || DirectoryStartOffsetRelativeToDisk == uint.MaxValue; + VolumeNumber = reader.ReadUInt16(); + FirstVolumeWithDirectory = reader.ReadUInt16(); + TotalNumberOfEntriesInDisk = reader.ReadUInt16(); + TotalNumberOfEntries = reader.ReadUInt16(); + DirectorySize = reader.ReadUInt32(); + DirectoryStartOffsetRelativeToDisk = reader.ReadUInt32(); + CommentLength = reader.ReadUInt16(); + Comment = reader.ReadBytes(CommentLength); } -} \ No newline at end of file + + public ushort VolumeNumber { get; private set; } + + public ushort FirstVolumeWithDirectory { get; private set; } + + public ushort TotalNumberOfEntriesInDisk { get; private set; } + + public uint DirectorySize { get; private set; } + + public uint DirectoryStartOffsetRelativeToDisk { get; private set; } + + public ushort CommentLength { get; private set; } + + public byte[]? Comment { get; private set; } + + public ushort TotalNumberOfEntries { get; private set; } + + public bool IsZip64 => + TotalNumberOfEntriesInDisk == ushort.MaxValue + || DirectorySize == uint.MaxValue + || DirectoryStartOffsetRelativeToDisk == uint.MaxValue; +} diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs index 8b55dcd6..fc2cb262 100644 --- a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs @@ -1,99 +1,103 @@ -using System.IO; +using System.IO; using System.Linq; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class DirectoryEntryHeader : ZipFileEntry { - internal class DirectoryEntryHeader : ZipFileEntry + public DirectoryEntryHeader(ArchiveEncoding archiveEncoding) + : base(ZipHeaderType.DirectoryEntry, archiveEncoding) { } + + internal override void Read(BinaryReader reader) { - public DirectoryEntryHeader(ArchiveEncoding archiveEncoding) - : base(ZipHeaderType.DirectoryEntry, archiveEncoding) + Version = reader.ReadUInt16(); + VersionNeededToExtract = reader.ReadUInt16(); + Flags = (HeaderFlags)reader.ReadUInt16(); + CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); + LastModifiedTime = reader.ReadUInt16(); + LastModifiedDate = reader.ReadUInt16(); + Crc = reader.ReadUInt32(); + CompressedSize = reader.ReadUInt32(); + UncompressedSize = reader.ReadUInt32(); + var nameLength = reader.ReadUInt16(); + var extraLength = reader.ReadUInt16(); + var commentLength = reader.ReadUInt16(); + DiskNumberStart = reader.ReadUInt16(); + InternalFileAttributes = reader.ReadUInt16(); + ExternalFileAttributes = reader.ReadUInt32(); + RelativeOffsetOfEntryHeader = reader.ReadUInt32(); + + var name = reader.ReadBytes(nameLength); + var extra = reader.ReadBytes(extraLength); + var comment = reader.ReadBytes(commentLength); + + // According to .ZIP File Format Specification + // + // For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + // + // Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + + if (Flags.HasFlag(HeaderFlags.Efs)) { + Name = ArchiveEncoding.DecodeUTF8(name); + Comment = ArchiveEncoding.DecodeUTF8(comment); + } + else + { + Name = ArchiveEncoding.Decode(name); + Comment = ArchiveEncoding.Decode(comment); } - internal override void Read(BinaryReader reader) + LoadExtra(extra); + + var unicodePathExtra = Extra.FirstOrDefault( + u => u.Type == ExtraDataType.UnicodePathExtraField + ); + if (unicodePathExtra != null && ArchiveEncoding.Forced == null) { - Version = reader.ReadUInt16(); - VersionNeededToExtract = reader.ReadUInt16(); - Flags = (HeaderFlags)reader.ReadUInt16(); - CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); - LastModifiedTime = reader.ReadUInt16(); - LastModifiedDate = reader.ReadUInt16(); - Crc = reader.ReadUInt32(); - CompressedSize = reader.ReadUInt32(); - UncompressedSize = reader.ReadUInt32(); - ushort nameLength = reader.ReadUInt16(); - ushort extraLength = reader.ReadUInt16(); - ushort commentLength = reader.ReadUInt16(); - DiskNumberStart = reader.ReadUInt16(); - InternalFileAttributes = reader.ReadUInt16(); - ExternalFileAttributes = reader.ReadUInt32(); - RelativeOffsetOfEntryHeader = reader.ReadUInt32(); - - byte[] name = reader.ReadBytes(nameLength); - byte[] extra = reader.ReadBytes(extraLength); - byte[] comment = reader.ReadBytes(commentLength); - - // According to .ZIP File Format Specification - // - // For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT - // - // Bit 11: Language encoding flag (EFS). If this bit is set, - // the filename and comment fields for this file - // MUST be encoded using UTF-8. (see APPENDIX D) - - if (Flags.HasFlag(HeaderFlags.Efs)) - { - Name = ArchiveEncoding.DecodeUTF8(name); - Comment = ArchiveEncoding.DecodeUTF8(comment); - } - else - { - Name = ArchiveEncoding.Decode(name); - Comment = ArchiveEncoding.Decode(comment); - } - - LoadExtra(extra); - - var unicodePathExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnicodePathExtraField); - if (unicodePathExtra != null && ArchiveEncoding.Forced == null) - { - Name = ((ExtraUnicodePathExtraField)unicodePathExtra).UnicodeName; - } - - var zip64ExtraData = Extra.OfType().FirstOrDefault(); - if (zip64ExtraData != null) - { - zip64ExtraData.Process(UncompressedSize, CompressedSize, RelativeOffsetOfEntryHeader, DiskNumberStart); - - if (CompressedSize == uint.MaxValue) - { - CompressedSize = zip64ExtraData.CompressedSize; - } - - if (UncompressedSize == uint.MaxValue) - { - UncompressedSize = zip64ExtraData.UncompressedSize; - } - - if (RelativeOffsetOfEntryHeader == uint.MaxValue) - { - RelativeOffsetOfEntryHeader = zip64ExtraData.RelativeOffsetOfEntryHeader; - } - } + Name = ((ExtraUnicodePathExtraField)unicodePathExtra).UnicodeName; } - internal ushort Version { get; private set; } + var zip64ExtraData = Extra.OfType().FirstOrDefault(); + if (zip64ExtraData != null) + { + zip64ExtraData.Process( + UncompressedSize, + CompressedSize, + RelativeOffsetOfEntryHeader, + DiskNumberStart + ); - public ushort VersionNeededToExtract { get; set; } + if (CompressedSize == uint.MaxValue) + { + CompressedSize = zip64ExtraData.CompressedSize; + } - public long RelativeOffsetOfEntryHeader { get; set; } + if (UncompressedSize == uint.MaxValue) + { + UncompressedSize = zip64ExtraData.UncompressedSize; + } - public uint ExternalFileAttributes { get; set; } - - public ushort InternalFileAttributes { get; set; } - - public ushort DiskNumberStart { get; set; } - - public string? Comment { get; private set; } + if (RelativeOffsetOfEntryHeader == uint.MaxValue) + { + RelativeOffsetOfEntryHeader = zip64ExtraData.RelativeOffsetOfEntryHeader; + } + } } -} \ No newline at end of file + + internal ushort Version { get; private set; } + + public ushort VersionNeededToExtract { get; set; } + + public long RelativeOffsetOfEntryHeader { get; set; } + + public uint ExternalFileAttributes { get; set; } + + public ushort InternalFileAttributes { get; set; } + + public ushort DiskNumberStart { get; set; } + + public string? Comment { get; private set; } +} diff --git a/src/SharpCompress/Common/Zip/Headers/HeaderFlags.cs b/src/SharpCompress/Common/Zip/Headers/HeaderFlags.cs index 92aaaca7..5aef3394 100644 --- a/src/SharpCompress/Common/Zip/Headers/HeaderFlags.cs +++ b/src/SharpCompress/Common/Zip/Headers/HeaderFlags.cs @@ -1,17 +1,17 @@ -using System; +using System; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +[Flags] +internal enum HeaderFlags : ushort { - [Flags] - internal enum HeaderFlags : ushort - { - None = 0, - Encrypted = 1, // http://www.pkware.com/documents/casestudies/APPNOTE.TXT - Bit1 = 2, - Bit2 = 4, - UsePostDataDescriptor = 8, - EnhancedDeflate = 16, - //Bit 11: Language encoding flag - Efs = 2048 - } -} \ No newline at end of file + None = 0, + Encrypted = 1, // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + Bit1 = 2, + Bit2 = 4, + UsePostDataDescriptor = 8, + EnhancedDeflate = 16, + + //Bit 11: Language encoding flag + Efs = 2048 +} diff --git a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs index 0a086ad7..4852228c 100644 --- a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs @@ -1,16 +1,10 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class IgnoreHeader : ZipHeader { - internal class IgnoreHeader : ZipHeader - { - public IgnoreHeader(ZipHeaderType type) - : base(type) - { - } + public IgnoreHeader(ZipHeaderType type) : base(type) { } - internal override void Read(BinaryReader reader) - { - } - } -} \ No newline at end of file + internal override void Read(BinaryReader reader) { } +} diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs index 422ca6f4..93cc55ac 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs @@ -1,71 +1,70 @@ -using System.IO; +using System.IO; using System.Linq; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class LocalEntryHeader : ZipFileEntry { - internal class LocalEntryHeader : ZipFileEntry + public LocalEntryHeader(ArchiveEncoding archiveEncoding) + : base(ZipHeaderType.LocalEntry, archiveEncoding) { } + + internal override void Read(BinaryReader reader) { - public LocalEntryHeader(ArchiveEncoding archiveEncoding) - : base(ZipHeaderType.LocalEntry, archiveEncoding) + Version = reader.ReadUInt16(); + Flags = (HeaderFlags)reader.ReadUInt16(); + CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); + LastModifiedTime = reader.ReadUInt16(); + LastModifiedDate = reader.ReadUInt16(); + Crc = reader.ReadUInt32(); + CompressedSize = reader.ReadUInt32(); + UncompressedSize = reader.ReadUInt32(); + var nameLength = reader.ReadUInt16(); + var extraLength = reader.ReadUInt16(); + var name = reader.ReadBytes(nameLength); + var extra = reader.ReadBytes(extraLength); + + // According to .ZIP File Format Specification + // + // For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + // + // Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + + if (Flags.HasFlag(HeaderFlags.Efs)) { + Name = ArchiveEncoding.DecodeUTF8(name); + } + else + { + Name = ArchiveEncoding.Decode(name); } - internal override void Read(BinaryReader reader) + LoadExtra(extra); + + var unicodePathExtra = Extra.FirstOrDefault( + u => u.Type == ExtraDataType.UnicodePathExtraField + ); + if (unicodePathExtra != null && ArchiveEncoding.Forced == null) { - Version = reader.ReadUInt16(); - Flags = (HeaderFlags)reader.ReadUInt16(); - CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16(); - LastModifiedTime = reader.ReadUInt16(); - LastModifiedDate = reader.ReadUInt16(); - Crc = reader.ReadUInt32(); - CompressedSize = reader.ReadUInt32(); - UncompressedSize = reader.ReadUInt32(); - ushort nameLength = reader.ReadUInt16(); - ushort extraLength = reader.ReadUInt16(); - byte[] name = reader.ReadBytes(nameLength); - byte[] extra = reader.ReadBytes(extraLength); - - // According to .ZIP File Format Specification - // - // For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT - // - // Bit 11: Language encoding flag (EFS). If this bit is set, - // the filename and comment fields for this file - // MUST be encoded using UTF-8. (see APPENDIX D) - - if (Flags.HasFlag(HeaderFlags.Efs)) - { - Name = ArchiveEncoding.DecodeUTF8(name); - } - else - { - Name = ArchiveEncoding.Decode(name); - } - - LoadExtra(extra); - - var unicodePathExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnicodePathExtraField); - if (unicodePathExtra != null && ArchiveEncoding.Forced == null) - { - Name = ((ExtraUnicodePathExtraField)unicodePathExtra).UnicodeName; - } - - var zip64ExtraData = Extra.OfType().FirstOrDefault(); - if (zip64ExtraData != null) - { - zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0); - - if (CompressedSize == uint.MaxValue) - { - CompressedSize = zip64ExtraData.CompressedSize; - } - if (UncompressedSize == uint.MaxValue) - { - UncompressedSize = zip64ExtraData.UncompressedSize; - } - } + Name = ((ExtraUnicodePathExtraField)unicodePathExtra).UnicodeName; } - internal ushort Version { get; private set; } + var zip64ExtraData = Extra.OfType().FirstOrDefault(); + if (zip64ExtraData != null) + { + zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0); + + if (CompressedSize == uint.MaxValue) + { + CompressedSize = zip64ExtraData.CompressedSize; + } + if (UncompressedSize == uint.MaxValue) + { + UncompressedSize = zip64ExtraData.UncompressedSize; + } + } } -} \ No newline at end of file + + internal ushort Version { get; private set; } +} diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs index efc7af3a..80ec9b8f 100644 --- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs +++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeaderExtraFactory.cs @@ -2,151 +2,158 @@ using System.Buffers.Binary; using System.Text; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal enum ExtraDataType : ushort { - internal enum ExtraDataType : ushort + WinZipAes = 0x9901, + + NotImplementedExtraData = 0xFFFF, + + // Third Party Mappings + // -Info-ZIP Unicode Path Extra Field + UnicodePathExtraField = 0x7075, + Zip64ExtendedInformationExtraField = 0x0001 +} + +internal class ExtraData +{ + public ExtraData(ExtraDataType type, ushort length, byte[] dataBytes) { - WinZipAes = 0x9901, - - NotImplementedExtraData = 0xFFFF, - - // Third Party Mappings - // -Info-ZIP Unicode Path Extra Field - UnicodePathExtraField = 0x7075, - Zip64ExtendedInformationExtraField = 0x0001 + Type = type; + Length = length; + DataBytes = dataBytes; } - internal class ExtraData + internal ExtraDataType Type { get; } + internal ushort Length { get; } + internal byte[] DataBytes { get; } +} + +internal sealed class ExtraUnicodePathExtraField : ExtraData +{ + public ExtraUnicodePathExtraField(ExtraDataType type, ushort length, byte[] dataBytes) + : base(type, length, dataBytes) { } + + internal byte Version => DataBytes[0]; + + internal byte[] NameCrc32 { - public ExtraData(ExtraDataType type, ushort length, byte[] dataBytes) + get { - Type = type; - Length = length; - DataBytes = dataBytes; - } - - internal ExtraDataType Type { get; } - internal ushort Length { get; } - internal byte[] DataBytes { get; } - } - - internal sealed class ExtraUnicodePathExtraField : ExtraData - { - public ExtraUnicodePathExtraField(ExtraDataType type, ushort length, byte[] dataBytes) - : base(type, length, dataBytes) - { - } - - internal byte Version => DataBytes[0]; - - internal byte[] NameCrc32 - { - get - { - var crc = new byte[4]; - Buffer.BlockCopy(DataBytes, 1, crc, 0, 4); - return crc; - } - } - - internal string UnicodeName - { - get - { - // PathNamelength = dataLength - Version(1 byte) - NameCRC32(4 bytes) - var length = Length - 5; - var nameStr = Encoding.UTF8.GetString(DataBytes, 5, length); - return nameStr; - } + var crc = new byte[4]; + Buffer.BlockCopy(DataBytes, 1, crc, 0, 4); + return crc; } } - internal sealed class Zip64ExtendedInformationExtraField : ExtraData + internal string UnicodeName { - public Zip64ExtendedInformationExtraField(ExtraDataType type, ushort length, byte[] dataBytes) - : base(type, length, dataBytes) + get { - } - - // From the spec, values are only in the extradata if the standard - // value is set to 0xFFFFFFFF (or 0xFFFF for the Disk Start Number). - // Values, if present, must appear in the following order: - // - Original Size - // - Compressed Size - // - Relative Header Offset - // - Disk Start Number - public void Process(long uncompressedFileSize, long compressedFileSize, long relativeHeaderOffset, ushort diskNumber) - { - var bytesRequired = ((uncompressedFileSize == uint.MaxValue) ? 8 : 0) - + ((compressedFileSize == uint.MaxValue) ? 8 : 0) - + ((relativeHeaderOffset == uint.MaxValue) ? 8 : 0) - + ((diskNumber == ushort.MaxValue) ? 4 : 0); - var currentIndex = 0; - - if (bytesRequired > DataBytes.Length) - { - throw new ArchiveException("Zip64 extended information extra field is not large enough for the required information"); - } - - if (uncompressedFileSize == uint.MaxValue) - { - UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); - currentIndex += 8; - } - - if (compressedFileSize == uint.MaxValue) - { - CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); - currentIndex += 8; - } - - if (relativeHeaderOffset == uint.MaxValue) - { - RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); - currentIndex += 8; - } - - if (diskNumber == ushort.MaxValue) - { - VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(currentIndex)); - } - } - - /// - /// Uncompressed file size. Only valid after has been called and if the - /// original entry header had a corresponding 0xFFFFFFFF value. - /// - public long UncompressedSize { get; private set; } - - /// - /// Compressed file size. Only valid after has been called and if the - /// original entry header had a corresponding 0xFFFFFFFF value. - /// - public long CompressedSize { get; private set; } - - /// - /// Relative offset of the entry header. Only valid after has been called and if the - /// original entry header had a corresponding 0xFFFFFFFF value. - /// - public long RelativeOffsetOfEntryHeader { get; private set; } - - /// - /// Volume number. Only valid after has been called and if the - /// original entry header had a corresponding 0xFFFF value. - /// - public uint VolumeNumber { get; private set; } - } - - internal static class LocalEntryHeaderExtraFactory - { - internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extraData) - { - return type switch - { - ExtraDataType.UnicodePathExtraField => new ExtraUnicodePathExtraField(type, length, extraData), - ExtraDataType.Zip64ExtendedInformationExtraField => new Zip64ExtendedInformationExtraField(type, length, extraData), - _ => new ExtraData(type, length, extraData) - }; + // PathNamelength = dataLength - Version(1 byte) - NameCRC32(4 bytes) + var length = Length - 5; + var nameStr = Encoding.UTF8.GetString(DataBytes, 5, length); + return nameStr; } } } + +internal sealed class Zip64ExtendedInformationExtraField : ExtraData +{ + public Zip64ExtendedInformationExtraField(ExtraDataType type, ushort length, byte[] dataBytes) + : base(type, length, dataBytes) { } + + // From the spec, values are only in the extradata if the standard + // value is set to 0xFFFFFFFF (or 0xFFFF for the Disk Start Number). + // Values, if present, must appear in the following order: + // - Original Size + // - Compressed Size + // - Relative Header Offset + // - Disk Start Number + public void Process( + long uncompressedFileSize, + long compressedFileSize, + long relativeHeaderOffset, + ushort diskNumber + ) + { + var bytesRequired = + ((uncompressedFileSize == uint.MaxValue) ? 8 : 0) + + ((compressedFileSize == uint.MaxValue) ? 8 : 0) + + ((relativeHeaderOffset == uint.MaxValue) ? 8 : 0) + + ((diskNumber == ushort.MaxValue) ? 4 : 0); + var currentIndex = 0; + + if (bytesRequired > DataBytes.Length) + { + throw new ArchiveException( + "Zip64 extended information extra field is not large enough for the required information" + ); + } + + if (uncompressedFileSize == uint.MaxValue) + { + UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian( + DataBytes.AsSpan(currentIndex) + ); + currentIndex += 8; + } + + if (compressedFileSize == uint.MaxValue) + { + CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex)); + currentIndex += 8; + } + + if (relativeHeaderOffset == uint.MaxValue) + { + RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian( + DataBytes.AsSpan(currentIndex) + ); + currentIndex += 8; + } + + if (diskNumber == ushort.MaxValue) + { + VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(currentIndex)); + } + } + + /// + /// Uncompressed file size. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// + public long UncompressedSize { get; private set; } + + /// + /// Compressed file size. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// + public long CompressedSize { get; private set; } + + /// + /// Relative offset of the entry header. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFFFFFF value. + /// + public long RelativeOffsetOfEntryHeader { get; private set; } + + /// + /// Volume number. Only valid after has been called and if the + /// original entry header had a corresponding 0xFFFF value. + /// + public uint VolumeNumber { get; private set; } +} + +internal static class LocalEntryHeaderExtraFactory +{ + internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extraData) => + type switch + { + ExtraDataType.UnicodePathExtraField + => new ExtraUnicodePathExtraField(type, length, extraData), + ExtraDataType.Zip64ExtendedInformationExtraField + => new Zip64ExtendedInformationExtraField(type, length, extraData), + _ => new ExtraData(type, length, extraData) + }; +} diff --git a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs index ee7ca0b8..e151d4e5 100644 --- a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs @@ -1,18 +1,11 @@ using System; using System.IO; -namespace SharpCompress.Common.Zip.Headers -{ - internal class SplitHeader : ZipHeader - { - public SplitHeader() - : base(ZipHeaderType.Split) - { - } +namespace SharpCompress.Common.Zip.Headers; - internal override void Read(BinaryReader reader) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file +internal class SplitHeader : ZipHeader +{ + public SplitHeader() : base(ZipHeaderType.Split) { } + + internal override void Read(BinaryReader reader) => throw new NotImplementedException(); +} diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs index 0e0b72fb..0356c48c 100644 --- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs @@ -1,48 +1,49 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class Zip64DirectoryEndHeader : ZipHeader { - internal class Zip64DirectoryEndHeader : ZipHeader + public Zip64DirectoryEndHeader() : base(ZipHeaderType.Zip64DirectoryEnd) { } + + internal override void Read(BinaryReader reader) { - public Zip64DirectoryEndHeader() - : base(ZipHeaderType.Zip64DirectoryEnd) - { - } - - internal override void Read(BinaryReader reader) - { - SizeOfDirectoryEndRecord = (long)reader.ReadUInt64(); - VersionMadeBy = reader.ReadUInt16(); - VersionNeededToExtract = reader.ReadUInt16(); - VolumeNumber = reader.ReadUInt32(); - FirstVolumeWithDirectory = reader.ReadUInt32(); - TotalNumberOfEntriesInDisk = (long)reader.ReadUInt64(); - TotalNumberOfEntries = (long)reader.ReadUInt64(); - DirectorySize = (long)reader.ReadUInt64(); - DirectoryStartOffsetRelativeToDisk = (long)reader.ReadUInt64(); - DataSector = reader.ReadBytes((int)(SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS)); - } - - private const int SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS = 44; - - public long SizeOfDirectoryEndRecord { get; private set; } - - public ushort VersionMadeBy { get; private set; } - - public ushort VersionNeededToExtract { get; private set; } - - public uint VolumeNumber { get; private set; } - - public uint FirstVolumeWithDirectory { get; private set; } - - public long TotalNumberOfEntriesInDisk { get; private set; } - - public long TotalNumberOfEntries { get; private set; } - - public long DirectorySize { get; private set; } - - public long DirectoryStartOffsetRelativeToDisk { get; private set; } - - public byte[]? DataSector { get; private set; } + SizeOfDirectoryEndRecord = (long)reader.ReadUInt64(); + VersionMadeBy = reader.ReadUInt16(); + VersionNeededToExtract = reader.ReadUInt16(); + VolumeNumber = reader.ReadUInt32(); + FirstVolumeWithDirectory = reader.ReadUInt32(); + TotalNumberOfEntriesInDisk = (long)reader.ReadUInt64(); + TotalNumberOfEntries = (long)reader.ReadUInt64(); + DirectorySize = (long)reader.ReadUInt64(); + DirectoryStartOffsetRelativeToDisk = (long)reader.ReadUInt64(); + DataSector = reader.ReadBytes( + (int)( + SizeOfDirectoryEndRecord + - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS + ) + ); } -} \ No newline at end of file + + private const int SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS = 44; + + public long SizeOfDirectoryEndRecord { get; private set; } + + public ushort VersionMadeBy { get; private set; } + + public ushort VersionNeededToExtract { get; private set; } + + public uint VolumeNumber { get; private set; } + + public uint FirstVolumeWithDirectory { get; private set; } + + public long TotalNumberOfEntriesInDisk { get; private set; } + + public long TotalNumberOfEntries { get; private set; } + + public long DirectorySize { get; private set; } + + public long DirectoryStartOffsetRelativeToDisk { get; private set; } + + public byte[]? DataSector { get; private set; } +} diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs index 82a6562e..9bbf2670 100644 --- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs @@ -1,25 +1,21 @@ using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal class Zip64DirectoryEndLocatorHeader : ZipHeader { - internal class Zip64DirectoryEndLocatorHeader : ZipHeader + public Zip64DirectoryEndLocatorHeader() : base(ZipHeaderType.Zip64DirectoryEndLocator) { } + + internal override void Read(BinaryReader reader) { - public Zip64DirectoryEndLocatorHeader() - : base(ZipHeaderType.Zip64DirectoryEndLocator) - { - } - - internal override void Read(BinaryReader reader) - { - FirstVolumeWithDirectory = reader.ReadUInt32(); - RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64(); - TotalNumberOfVolumes = reader.ReadUInt32(); - } - - public uint FirstVolumeWithDirectory { get; private set; } - - public long RelativeOffsetOfTheEndOfDirectoryRecord { get; private set; } - - public uint TotalNumberOfVolumes { get; private set; } + FirstVolumeWithDirectory = reader.ReadUInt32(); + RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64(); + TotalNumberOfVolumes = reader.ReadUInt32(); } -} \ No newline at end of file + + public uint FirstVolumeWithDirectory { get; private set; } + + public long RelativeOffsetOfTheEndOfDirectoryRecord { get; private set; } + + public uint TotalNumberOfVolumes { get; private set; } +} diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs index 5c72d54c..79347014 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs @@ -1,110 +1,106 @@ -#nullable disable +#nullable disable using System; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal abstract class ZipFileEntry : ZipHeader { - internal abstract class ZipFileEntry : ZipHeader + protected ZipFileEntry(ZipHeaderType type, ArchiveEncoding archiveEncoding) : base(type) { - protected ZipFileEntry(ZipHeaderType type, ArchiveEncoding archiveEncoding) - : base(type) - { - Extra = new List(); - ArchiveEncoding = archiveEncoding; - } - - internal bool IsDirectory - { - get - { - if (Name.EndsWith('/')) - { - return true; - } - - //.NET Framework 4.5 : System.IO.Compression::CreateFromDirectory() probably writes backslashes to headers - return CompressedSize == 0 - && UncompressedSize == 0 - && Name.EndsWith('\\'); - } - } - - internal Stream PackedStream { get; set; } - - internal ArchiveEncoding ArchiveEncoding { get; } - - internal string Name { get; set; } - - internal HeaderFlags Flags { get; set; } - - internal ZipCompressionMethod CompressionMethod { get; set; } - - internal long CompressedSize { get; set; } - - internal long? DataStartPosition { get; set; } - - internal long UncompressedSize { get; set; } - - internal List Extra { get; set; } - - public string Password { get; set; } - - internal PkwareTraditionalEncryptionData ComposeEncryptionData(Stream archiveStream) - { - if (archiveStream is null) - { - throw new ArgumentNullException(nameof(archiveStream)); - } - - var buffer = new byte[12]; - archiveStream.ReadFully(buffer); - - PkwareTraditionalEncryptionData encryptionData = PkwareTraditionalEncryptionData.ForRead(Password!, this, buffer); - - return encryptionData; - } - - internal WinzipAesEncryptionData WinzipAesEncryptionData { get; set; } - - internal ushort LastModifiedDate { get; set; } - - internal ushort LastModifiedTime { get; set; } - - internal uint Crc { get; set; } - - protected void LoadExtra(byte[] extra) - { - for (int i = 0; i < extra.Length - 4;) - { - ExtraDataType type = (ExtraDataType)BinaryPrimitives.ReadUInt16LittleEndian(extra.AsSpan(i)); - if (!Enum.IsDefined(typeof(ExtraDataType), type)) - { - type = ExtraDataType.NotImplementedExtraData; - } - - ushort length = BinaryPrimitives.ReadUInt16LittleEndian(extra.AsSpan(i + 2)); - - // 7zip has this same kind of check to ignore extras blocks that don't conform to the standard 2-byte ID, 2-byte length, N-byte value. - // CPP/7Zip/Zip/ZipIn.cpp: CInArchive::ReadExtra - if (length > extra.Length) - { - // bad extras block - return; - } - - byte[] data = new byte[length]; - Buffer.BlockCopy(extra, i + 4, data, 0, length); - Extra.Add(LocalEntryHeaderExtraFactory.Create(type, length, data)); - - i += length + 4; - } - } - - internal ZipFilePart Part { get; set; } - - internal bool IsZip64 => CompressedSize >= uint.MaxValue; + Extra = new List(); + ArchiveEncoding = archiveEncoding; } + + internal bool IsDirectory + { + get + { + if (Name.EndsWith('/')) + { + return true; + } + + //.NET Framework 4.5 : System.IO.Compression::CreateFromDirectory() probably writes backslashes to headers + return CompressedSize == 0 && UncompressedSize == 0 && Name.EndsWith('\\'); + } + } + + internal Stream PackedStream { get; set; } + + internal ArchiveEncoding ArchiveEncoding { get; } + + internal string Name { get; set; } + + internal HeaderFlags Flags { get; set; } + + internal ZipCompressionMethod CompressionMethod { get; set; } + + internal long CompressedSize { get; set; } + + internal long? DataStartPosition { get; set; } + + internal long UncompressedSize { get; set; } + + internal List Extra { get; set; } + + public string Password { get; set; } + + internal PkwareTraditionalEncryptionData ComposeEncryptionData(Stream archiveStream) + { + if (archiveStream is null) + { + throw new ArgumentNullException(nameof(archiveStream)); + } + + var buffer = new byte[12]; + archiveStream.ReadFully(buffer); + + var encryptionData = PkwareTraditionalEncryptionData.ForRead(Password!, this, buffer); + + return encryptionData; + } + + internal WinzipAesEncryptionData WinzipAesEncryptionData { get; set; } + + internal ushort LastModifiedDate { get; set; } + + internal ushort LastModifiedTime { get; set; } + + internal uint Crc { get; set; } + + protected void LoadExtra(byte[] extra) + { + for (var i = 0; i < extra.Length - 4; ) + { + var type = (ExtraDataType)BinaryPrimitives.ReadUInt16LittleEndian(extra.AsSpan(i)); + if (!Enum.IsDefined(typeof(ExtraDataType), type)) + { + type = ExtraDataType.NotImplementedExtraData; + } + + var length = BinaryPrimitives.ReadUInt16LittleEndian(extra.AsSpan(i + 2)); + + // 7zip has this same kind of check to ignore extras blocks that don't conform to the standard 2-byte ID, 2-byte length, N-byte value. + // CPP/7Zip/Zip/ZipIn.cpp: CInArchive::ReadExtra + if (length > extra.Length) + { + // bad extras block + return; + } + + var data = new byte[length]; + Buffer.BlockCopy(extra, i + 4, data, 0, length); + Extra.Add(LocalEntryHeaderExtraFactory.Create(type, length, data)); + + i += length + 4; + } + } + + internal ZipFilePart Part { get; set; } + + internal bool IsZip64 => CompressedSize >= uint.MaxValue; } diff --git a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs index d62513da..36d40a82 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs @@ -1,19 +1,18 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal abstract class ZipHeader { - internal abstract class ZipHeader + protected ZipHeader(ZipHeaderType type) { - protected ZipHeader(ZipHeaderType type) - { - ZipHeaderType = type; - HasData = true; - } - - internal ZipHeaderType ZipHeaderType { get; } - - internal abstract void Read(BinaryReader reader); - - internal bool HasData { get; set; } + ZipHeaderType = type; + HasData = true; } -} \ No newline at end of file + + internal ZipHeaderType ZipHeaderType { get; } + + internal abstract void Read(BinaryReader reader); + + internal bool HasData { get; set; } +} diff --git a/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs b/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs index a4286ded..62c29d89 100644 --- a/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs +++ b/src/SharpCompress/Common/Zip/Headers/ZipHeaderType.cs @@ -1,13 +1,12 @@ -namespace SharpCompress.Common.Zip.Headers +namespace SharpCompress.Common.Zip.Headers; + +internal enum ZipHeaderType { - internal enum ZipHeaderType - { - Ignore, - LocalEntry, - DirectoryEntry, - DirectoryEnd, - Split, - Zip64DirectoryEnd, - Zip64DirectoryEndLocator - } -} \ No newline at end of file + Ignore, + LocalEntry, + DirectoryEntry, + DirectoryEnd, + Split, + Zip64DirectoryEnd, + Zip64DirectoryEndLocator +} diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs index bad5056d..273a7a3c 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalCryptoStream.cs @@ -1,108 +1,109 @@ using System; using System.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal enum CryptoMode { - internal enum CryptoMode + Encrypt, + Decrypt +} + +internal class PkwareTraditionalCryptoStream : Stream +{ + private readonly PkwareTraditionalEncryptionData _encryptor; + private readonly CryptoMode _mode; + private readonly Stream _stream; + private bool _isDisposed; + + public PkwareTraditionalCryptoStream( + Stream stream, + PkwareTraditionalEncryptionData encryptor, + CryptoMode mode + ) { - Encrypt, - Decrypt + _encryptor = encryptor; + _stream = stream; + _mode = mode; } - internal class PkwareTraditionalCryptoStream : Stream + public override bool CanRead => (_mode == CryptoMode.Decrypt); + + public override bool CanSeek => false; + + public override bool CanWrite => (_mode == CryptoMode.Encrypt); + + public override long Length => throw new NotSupportedException(); + + public override long Position { - private readonly PkwareTraditionalEncryptionData _encryptor; - private readonly CryptoMode _mode; - private readonly Stream _stream; - private bool _isDisposed; - - public PkwareTraditionalCryptoStream(Stream stream, PkwareTraditionalEncryptionData encryptor, CryptoMode mode) - { - this._encryptor = encryptor; - this._stream = stream; - this._mode = mode; - } - - public override bool CanRead => (_mode == CryptoMode.Decrypt); - - public override bool CanSeek => false; - - public override bool CanWrite => (_mode == CryptoMode.Encrypt); - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_mode == CryptoMode.Encrypt) - { - throw new NotSupportedException("This stream does not encrypt via Read()"); - } - - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - byte[] temp = new byte[count]; - int readBytes = _stream.Read(temp, 0, count); - byte[] decrypted = _encryptor.Decrypt(temp, readBytes); - Buffer.BlockCopy(decrypted, 0, buffer, offset, readBytes); - return readBytes; - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (_mode == CryptoMode.Decrypt) - { - throw new NotSupportedException("This stream does not Decrypt via Write()"); - } - - if (count == 0) - { - return; - } - - byte[] plaintext; - if (offset != 0) - { - plaintext = new byte[count]; - Buffer.BlockCopy(buffer, offset, plaintext, 0, count); - } - else - { - plaintext = buffer; - } - - byte[] encrypted = _encryptor.Encrypt(plaintext, count); - _stream.Write(encrypted, 0, encrypted.Length); - } - - public override void Flush() - { - //throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - _stream.Dispose(); - } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } -} \ No newline at end of file + + public override int Read(byte[] buffer, int offset, int count) + { + if (_mode == CryptoMode.Encrypt) + { + throw new NotSupportedException("This stream does not encrypt via Read()"); + } + + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + var temp = new byte[count]; + var readBytes = _stream.Read(temp, 0, count); + var decrypted = _encryptor.Decrypt(temp, readBytes); + Buffer.BlockCopy(decrypted, 0, buffer, offset, readBytes); + return readBytes; + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (_mode == CryptoMode.Decrypt) + { + throw new NotSupportedException("This stream does not Decrypt via Write()"); + } + + if (count == 0) + { + return; + } + + byte[] plaintext; + if (offset != 0) + { + plaintext = new byte[count]; + Buffer.BlockCopy(buffer, offset, plaintext, 0, count); + } + else + { + plaintext = buffer; + } + + var encrypted = _encryptor.Encrypt(plaintext, count); + _stream.Write(encrypted, 0, encrypted.Length); + } + + public override void Flush() + { + //throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + base.Dispose(disposing); + _stream.Dispose(); + } +} diff --git a/src/SharpCompress/Common/Zip/PkwareTraditionalEncryptionData.cs b/src/SharpCompress/Common/Zip/PkwareTraditionalEncryptionData.cs index 4021f957..353000d8 100644 --- a/src/SharpCompress/Common/Zip/PkwareTraditionalEncryptionData.cs +++ b/src/SharpCompress/Common/Zip/PkwareTraditionalEncryptionData.cs @@ -1,111 +1,117 @@ -using System; +using System; using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors.Deflate; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class PkwareTraditionalEncryptionData { - internal class PkwareTraditionalEncryptionData + private static readonly CRC32 CRC32 = new CRC32(); + private readonly uint[] _keys = { 0x12345678, 0x23456789, 0x34567890 }; + private readonly ArchiveEncoding _archiveEncoding; + + private PkwareTraditionalEncryptionData(string password, ArchiveEncoding archiveEncoding) { - private static readonly CRC32 CRC32 = new CRC32(); - private readonly UInt32[] _keys = { 0x12345678, 0x23456789, 0x34567890 }; - private readonly ArchiveEncoding _archiveEncoding; + _archiveEncoding = archiveEncoding; + Initialize(password); + } - private PkwareTraditionalEncryptionData(string password, ArchiveEncoding archiveEncoding) + private byte MagicByte + { + get { - _archiveEncoding = archiveEncoding; - Initialize(password); - } - - private byte MagicByte - { - get - { - ushort t = (ushort)((ushort)(_keys[2] & 0xFFFF) | 2); - return (byte)((t * (t ^ 1)) >> 8); - } - } - - public static PkwareTraditionalEncryptionData ForRead(string password, ZipFileEntry header, - byte[] encryptionHeader) - { - var encryptor = new PkwareTraditionalEncryptionData(password, header.ArchiveEncoding); - byte[] plainTextHeader = encryptor.Decrypt(encryptionHeader, encryptionHeader.Length); - if (plainTextHeader[11] != (byte)((header.Crc >> 24) & 0xff)) - { - if (!FlagUtility.HasFlag(header.Flags, HeaderFlags.UsePostDataDescriptor)) - { - throw new CryptographicException("The password did not match."); - } - if (plainTextHeader[11] != (byte)((header.LastModifiedTime >> 8) & 0xff)) - { - throw new CryptographicException("The password did not match."); - } - } - return encryptor; - } - - public byte[] Decrypt(byte[] cipherText, int length) - { - if (length > cipherText.Length) - { - throw new ArgumentOutOfRangeException(nameof(length), - "Bad length during Decryption: the length parameter must be smaller than or equal to the size of the destination array."); - } - - var plainText = new byte[length]; - for (int i = 0; i < length; i++) - { - var c = (byte)(cipherText[i] ^ MagicByte); - UpdateKeys(c); - plainText[i] = c; - } - return plainText; - } - - public byte[] Encrypt(byte[] plainText, int length) - { - if (plainText is null) - { - throw new ArgumentNullException(nameof(plainText)); - } - - if (length > plainText.Length) - { - throw new ArgumentOutOfRangeException(nameof(length), - "Bad length during Encryption: The length parameter must be smaller than or equal to the size of the destination array."); - } - - var cipherText = new byte[length]; - for (int i = 0; i < length; i++) - { - byte c = plainText[i]; - cipherText[i] = (byte)(plainText[i] ^ MagicByte); - UpdateKeys(c); - } - return cipherText; - } - - private void Initialize(string password) - { - byte[] p = StringToByteArray(password); - for (int i = 0; i < password.Length; i++) - { - UpdateKeys(p[i]); - } - } - - internal byte[] StringToByteArray(string value) - { - byte[] a = _archiveEncoding.Password.GetBytes(value); - return a; - } - - private void UpdateKeys(byte byteValue) - { - _keys[0] = (UInt32)CRC32.ComputeCrc32((int)_keys[0], byteValue); - _keys[1] = _keys[1] + (byte)_keys[0]; - _keys[1] = _keys[1] * 0x08088405 + 1; - _keys[2] = (UInt32)CRC32.ComputeCrc32((int)_keys[2], (byte)(_keys[1] >> 24)); + var t = (ushort)((ushort)(_keys[2] & 0xFFFF) | 2); + return (byte)((t * (t ^ 1)) >> 8); } } -} \ No newline at end of file + + public static PkwareTraditionalEncryptionData ForRead( + string password, + ZipFileEntry header, + byte[] encryptionHeader + ) + { + var encryptor = new PkwareTraditionalEncryptionData(password, header.ArchiveEncoding); + var plainTextHeader = encryptor.Decrypt(encryptionHeader, encryptionHeader.Length); + if (plainTextHeader[11] != (byte)((header.Crc >> 24) & 0xff)) + { + if (!FlagUtility.HasFlag(header.Flags, HeaderFlags.UsePostDataDescriptor)) + { + throw new CryptographicException("The password did not match."); + } + if (plainTextHeader[11] != (byte)((header.LastModifiedTime >> 8) & 0xff)) + { + throw new CryptographicException("The password did not match."); + } + } + return encryptor; + } + + public byte[] Decrypt(byte[] cipherText, int length) + { + if (length > cipherText.Length) + { + throw new ArgumentOutOfRangeException( + nameof(length), + "Bad length during Decryption: the length parameter must be smaller than or equal to the size of the destination array." + ); + } + + var plainText = new byte[length]; + for (var i = 0; i < length; i++) + { + var c = (byte)(cipherText[i] ^ MagicByte); + UpdateKeys(c); + plainText[i] = c; + } + return plainText; + } + + public byte[] Encrypt(byte[] plainText, int length) + { + if (plainText is null) + { + throw new ArgumentNullException(nameof(plainText)); + } + + if (length > plainText.Length) + { + throw new ArgumentOutOfRangeException( + nameof(length), + "Bad length during Encryption: The length parameter must be smaller than or equal to the size of the destination array." + ); + } + + var cipherText = new byte[length]; + for (var i = 0; i < length; i++) + { + var c = plainText[i]; + cipherText[i] = (byte)(plainText[i] ^ MagicByte); + UpdateKeys(c); + } + return cipherText; + } + + private void Initialize(string password) + { + var p = StringToByteArray(password); + for (var i = 0; i < password.Length; i++) + { + UpdateKeys(p[i]); + } + } + + internal byte[] StringToByteArray(string value) + { + var a = _archiveEncoding.Password.GetBytes(value); + return a; + } + + private void UpdateKeys(byte byteValue) + { + _keys[0] = (uint)CRC32.ComputeCrc32((int)_keys[0], byteValue); + _keys[1] = _keys[1] + (byte)_keys[0]; + _keys[1] = (_keys[1] * 0x08088405) + 1; + _keys[2] = (uint)CRC32.ComputeCrc32((int)_keys[2], (byte)(_keys[1] >> 24)); + } +} diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index a2046190..dc7e4783 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs @@ -1,54 +1,58 @@ -using System.IO; +using System.IO; using SharpCompress.Common.Zip.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class SeekableZipFilePart : ZipFilePart { - internal class SeekableZipFilePart : ZipFilePart + private bool _isLocalHeaderLoaded; + private readonly SeekableZipHeaderFactory _headerFactory; + private readonly DirectoryEntryHeader _directoryEntryHeader; + + internal SeekableZipFilePart( + SeekableZipHeaderFactory headerFactory, + DirectoryEntryHeader header, + Stream stream + ) : base(header, stream) { - private bool _isLocalHeaderLoaded; - private readonly SeekableZipHeaderFactory _headerFactory; - private readonly DirectoryEntryHeader _directoryEntryHeader; - - internal SeekableZipFilePart(SeekableZipHeaderFactory headerFactory, DirectoryEntryHeader header, Stream stream) - : base(header, stream) - { - _headerFactory = headerFactory; - _directoryEntryHeader = header; - } - - internal override Stream GetCompressedStream() - { - if (!_isLocalHeaderLoaded) - { - LoadLocalHeader(); - _isLocalHeaderLoaded = true; - } - return base.GetCompressedStream(); - } - - internal string? Comment => ((DirectoryEntryHeader)Header).Comment; - - private void LoadLocalHeader() - { - bool hasData = Header.HasData; - Header = _headerFactory.GetLocalHeader(BaseStream, ((DirectoryEntryHeader)Header)); - Header.HasData = hasData; - } - - protected override Stream CreateBaseStream() - { - BaseStream.Position = Header.DataStartPosition!.Value; - - if ((Header.CompressedSize == 0) - && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) - && (_directoryEntryHeader?.HasData == true) - && (_directoryEntryHeader?.CompressedSize != 0)) - { - return new ReadOnlySubStream(BaseStream, _directoryEntryHeader!.CompressedSize); - } - - return BaseStream; - } + _headerFactory = headerFactory; + _directoryEntryHeader = header; } -} \ No newline at end of file + + internal override Stream GetCompressedStream() + { + if (!_isLocalHeaderLoaded) + { + LoadLocalHeader(); + _isLocalHeaderLoaded = true; + } + return base.GetCompressedStream(); + } + + internal string? Comment => ((DirectoryEntryHeader)Header).Comment; + + private void LoadLocalHeader() + { + var hasData = Header.HasData; + Header = _headerFactory.GetLocalHeader(BaseStream, ((DirectoryEntryHeader)Header)); + Header.HasData = hasData; + } + + protected override Stream CreateBaseStream() + { + BaseStream.Position = Header.DataStartPosition!.Value; + + if ( + (Header.CompressedSize == 0) + && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) + && (_directoryEntryHeader?.HasData == true) + && (_directoryEntryHeader?.CompressedSize != 0) + ) + { + return new ReadOnlySubStream(BaseStream, _directoryEntryHeader!.CompressedSize); + } + + return BaseStream; + } +} diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs index 8f9fd879..b99d0e2b 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -1,148 +1,154 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using SharpCompress.Common.Zip.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory { - internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory + private const int MINIMUM_EOCD_LENGTH = 22; + private const int ZIP64_EOCD_LENGTH = 20; + + // Comment may be within 64kb + structure 22 bytes + private const int MAX_SEARCH_LENGTH_FOR_EOCD = 65557; + private bool _zip64; + + internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) + : base(StreamingMode.Seekable, password, archiveEncoding) { } + + internal IEnumerable ReadSeekableHeader(Stream stream) { - private const int MINIMUM_EOCD_LENGTH = 22; - private const int ZIP64_EOCD_LENGTH = 20; - // Comment may be within 64kb + structure 22 bytes - private const int MAX_SEARCH_LENGTH_FOR_EOCD = 65557; - private bool _zip64; + var reader = new BinaryReader(stream); - internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) - : base(StreamingMode.Seekable, password, archiveEncoding) + SeekBackToHeader(stream, reader); + + var eocd_location = stream.Position; + var entry = new DirectoryEndHeader(); + entry.Read(reader); + + if (entry.IsZip64) { + _zip64 = true; + + // ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD + stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin); + var zip64_locator = reader.ReadUInt32(); + if (zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR) + { + throw new ArchiveException("Failed to locate the Zip64 Directory Locator"); + } + + var zip64Locator = new Zip64DirectoryEndLocatorHeader(); + zip64Locator.Read(reader); + + stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin); + var zip64Signature = reader.ReadUInt32(); + if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY) + { + throw new ArchiveException("Failed to locate the Zip64 Header"); + } + + var zip64Entry = new Zip64DirectoryEndHeader(); + zip64Entry.Read(reader); + stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); + } + else + { + stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); } - internal IEnumerable ReadSeekableHeader(Stream stream) + var position = stream.Position; + while (true) { - var reader = new BinaryReader(stream); + stream.Position = position; + var signature = reader.ReadUInt32(); + var nextHeader = ReadHeader(signature, reader, _zip64); + position = stream.Position; - SeekBackToHeader(stream, reader); - - var eocd_location = stream.Position; - var entry = new DirectoryEndHeader(); - entry.Read(reader); - - if (entry.IsZip64) + if (nextHeader is null) { - _zip64 = true; - - // ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD - stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin); - uint zip64_locator = reader.ReadUInt32(); - if (zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR) - { - throw new ArchiveException("Failed to locate the Zip64 Directory Locator"); - } - - var zip64Locator = new Zip64DirectoryEndLocatorHeader(); - zip64Locator.Read(reader); - - stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin); - uint zip64Signature = reader.ReadUInt32(); - if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY) - { - throw new ArchiveException("Failed to locate the Zip64 Header"); - } - - var zip64Entry = new Zip64DirectoryEndHeader(); - zip64Entry.Read(reader); - stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); - } - else - { - stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin); + yield break; } - long position = stream.Position; - while (true) + if (nextHeader is DirectoryEntryHeader entryHeader) { - stream.Position = position; - uint signature = reader.ReadUInt32(); - var nextHeader = ReadHeader(signature, reader, _zip64); - position = stream.Position; - - if (nextHeader is null) - { - yield break; - } - - if (nextHeader is DirectoryEntryHeader entryHeader) - { - //entry could be zero bytes so we need to know that. - entryHeader.HasData = entryHeader.CompressedSize != 0; - yield return entryHeader; - } - else if (nextHeader is DirectoryEndHeader endHeader) - { - yield return endHeader; - } + //entry could be zero bytes so we need to know that. + entryHeader.HasData = entryHeader.CompressedSize != 0; + yield return entryHeader; } - } - - private static bool IsMatch(byte[] haystack, int position, byte[] needle) - { - for (int i = 0; i < needle.Length; i++) + else if (nextHeader is DirectoryEndHeader endHeader) { - if (haystack[position + i] != needle[i]) - { - return false; - } + yield return endHeader; } - - return true; - } - private static void SeekBackToHeader(Stream stream, BinaryReader reader) - { - // Minimum EOCD length - if (stream.Length < MINIMUM_EOCD_LENGTH) - { - throw new ArchiveException("Could not find Zip file Directory at the end of the file. File may be corrupted."); - } - - int len = stream.Length < MAX_SEARCH_LENGTH_FOR_EOCD ? (int)stream.Length : MAX_SEARCH_LENGTH_FOR_EOCD; - // We search for marker in reverse to find the first occurance - byte[] needle = { 0x06, 0x05, 0x4b, 0x50 }; - - stream.Seek(-len, SeekOrigin.End); - - byte[] seek = reader.ReadBytes(len); - - // Search in reverse - Array.Reverse(seek); - - // don't exclude the minimum eocd region, otherwise you fail to locate the header in empty zip files - var max_search_area = len; // - MINIMUM_EOCD_LENGTH; - - for (int pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end) - { - if (IsMatch(seek, pos_from_end, needle)) - { - stream.Seek(-pos_from_end, SeekOrigin.End); - return; - } - } - - throw new ArchiveException("Failed to locate the Zip Header"); - } - - internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader) - { - stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin); - BinaryReader reader = new BinaryReader(stream); - uint signature = reader.ReadUInt32(); - var localEntryHeader = ReadHeader(signature, reader, _zip64) as LocalEntryHeader; - if (localEntryHeader is null) - { - throw new InvalidOperationException(); - } - return localEntryHeader; } } -} \ No newline at end of file + + private static bool IsMatch(byte[] haystack, int position, byte[] needle) + { + for (var i = 0; i < needle.Length; i++) + { + if (haystack[position + i] != needle[i]) + { + return false; + } + } + + return true; + } + + private static void SeekBackToHeader(Stream stream, BinaryReader reader) + { + // Minimum EOCD length + if (stream.Length < MINIMUM_EOCD_LENGTH) + { + throw new ArchiveException( + "Could not find Zip file Directory at the end of the file. File may be corrupted." + ); + } + + var len = + stream.Length < MAX_SEARCH_LENGTH_FOR_EOCD + ? (int)stream.Length + : MAX_SEARCH_LENGTH_FOR_EOCD; + // We search for marker in reverse to find the first occurance + byte[] needle = { 0x06, 0x05, 0x4b, 0x50 }; + + stream.Seek(-len, SeekOrigin.End); + + var seek = reader.ReadBytes(len); + + // Search in reverse + Array.Reverse(seek); + + // don't exclude the minimum eocd region, otherwise you fail to locate the header in empty zip files + var max_search_area = len; // - MINIMUM_EOCD_LENGTH; + + for (var pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end) + { + if (IsMatch(seek, pos_from_end, needle)) + { + stream.Seek(-pos_from_end, SeekOrigin.End); + return; + } + } + + throw new ArchiveException("Failed to locate the Zip Header"); + } + + internal LocalEntryHeader GetLocalHeader( + Stream stream, + DirectoryEntryHeader directoryEntryHeader + ) + { + stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin); + var reader = new BinaryReader(stream); + var signature = reader.ReadUInt32(); + if (ReadHeader(signature, reader, _zip64) is not LocalEntryHeader localEntryHeader) + { + throw new InvalidOperationException(); + } + return localEntryHeader; + } +} diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs index ee0c6be9..c5ed221a 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs @@ -3,100 +3,96 @@ using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors.Deflate; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal sealed class StreamingZipFilePart : ZipFilePart { - internal sealed class StreamingZipFilePart : ZipFilePart + private Stream? _decompressionStream; + + internal StreamingZipFilePart(ZipFileEntry header, Stream stream) : base(header, stream) { } + + protected override Stream CreateBaseStream() => Header.PackedStream; + + internal override Stream GetCompressedStream() { - private Stream? _decompressionStream; - - internal StreamingZipFilePart(ZipFileEntry header, Stream stream) - : base(header, stream) + if (!Header.HasData) { + return Stream.Null; } - - protected override Stream CreateBaseStream() + _decompressionStream = CreateDecompressionStream( + GetCryptoStream(CreateBaseStream()), + Header.CompressionMethod + ); + if (LeaveStreamOpen) { - return Header.PackedStream; + return NonDisposingStream.Create(_decompressionStream); } + return _decompressionStream; + } - internal override Stream GetCompressedStream() + internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStream) + { + if (Header.IsDirectory) { - if (!Header.HasData) - { - return Stream.Null; - } - _decompressionStream = CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod); - if (LeaveStreamOpen) - { - return NonDisposingStream.Create(_decompressionStream); - } - return _decompressionStream; + return new BinaryReader(rewindableStream); } - - internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStream) + if (Header.HasData && !Skipped) { - if (Header.IsDirectory) - { - return new BinaryReader(rewindableStream); - } - if (Header.HasData && !Skipped) - { - _decompressionStream ??= GetCompressedStream(); + _decompressionStream ??= GetCompressedStream(); - if( Header.CompressionMethod != ZipCompressionMethod.None ) + if (Header.CompressionMethod != ZipCompressionMethod.None) + { + _decompressionStream.Skip(); + + // If we had TotalIn / TotalOut we could have used them + Header.CompressedSize = _decompressionStream.Position; + + if (_decompressionStream is DeflateStream deflateStream) { - _decompressionStream.Skip(); - - // If we had TotalIn / TotalOut we could have used them - Header.CompressedSize = _decompressionStream.Position; - - if (_decompressionStream is DeflateStream deflateStream) - { - rewindableStream.Rewind(deflateStream.InputBuffer); - } + rewindableStream.Rewind(deflateStream.InputBuffer); } - else - { - // We would need to search for the magic word - rewindableStream.Position -= 4; - var pos = rewindableStream.Position; - while( Utility.Find(rewindableStream, new byte[] { 0x50,0x4b,0x07,0x08 } ) ) - { - // We should probably check CRC32 for positive matching as well - var size = rewindableStream.Position - pos; - var br = new BinaryReader(rewindableStream); - br.ReadUInt32(); - br.ReadUInt32(); // CRC32 - var compressed_size = br.ReadUInt32(); - var uncompressed_size = br.ReadUInt32(); - var uncompressed_64bit = br.ReadInt64(); - - long test_64bit = (long)uncompressed_size << 32 | (long)compressed_size; - - if( test_64bit == size && test_64bit == uncompressed_64bit ) - { - Header.CompressedSize = test_64bit; - Header.UncompressedSize = uncompressed_64bit; - rewindableStream.Position -= 24; - break; - } - - if (compressed_size == size && compressed_size == uncompressed_size ) - { - Header.CompressedSize = compressed_size; - Header.UncompressedSize = uncompressed_size; - rewindableStream.Position -= 24; - break; - } - rewindableStream.Position -= 20; - } - } - - Skipped = true; } - var reader = new BinaryReader(rewindableStream); - _decompressionStream = null; - return reader; + else + { + // We would need to search for the magic word + rewindableStream.Position -= 4; + var pos = rewindableStream.Position; + while (Utility.Find(rewindableStream, new byte[] { 0x50, 0x4b, 0x07, 0x08 })) + { + // We should probably check CRC32 for positive matching as well + var size = rewindableStream.Position - pos; + var br = new BinaryReader(rewindableStream); + br.ReadUInt32(); + br.ReadUInt32(); // CRC32 + var compressed_size = br.ReadUInt32(); + var uncompressed_size = br.ReadUInt32(); + var uncompressed_64bit = br.ReadInt64(); + + var test_64bit = ((long)uncompressed_size << 32) | compressed_size; + + if (test_64bit == size && test_64bit == uncompressed_64bit) + { + Header.CompressedSize = test_64bit; + Header.UncompressedSize = uncompressed_64bit; + rewindableStream.Position -= 24; + break; + } + + if (compressed_size == size && compressed_size == uncompressed_size) + { + Header.CompressedSize = compressed_size; + Header.UncompressedSize = uncompressed_size; + rewindableStream.Position -= 24; + break; + } + rewindableStream.Position -= 20; + } + } + + Skipped = true; } + var reader = new BinaryReader(rewindableStream); + _decompressionStream = null; + return reader; } } diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs index 836fb039..3e5cd808 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -3,110 +3,115 @@ using System.IO; using SharpCompress.Common.Zip.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class StreamingZipHeaderFactory : ZipHeaderFactory { - internal class StreamingZipHeaderFactory : ZipHeaderFactory + internal StreamingZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) + : base(StreamingMode.Streaming, password, archiveEncoding) { } + + internal IEnumerable ReadStreamHeader(Stream stream) { - internal StreamingZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) - : base(StreamingMode.Streaming, password, archiveEncoding) + RewindableStream rewindableStream; + + if (stream is RewindableStream rs) { + rewindableStream = rs; } - - internal IEnumerable ReadStreamHeader(Stream stream) + else { - RewindableStream rewindableStream; - - if (stream is RewindableStream rs) + rewindableStream = new RewindableStream(stream); + } + while (true) + { + ZipHeader? header; + var reader = new BinaryReader(rewindableStream); + uint headerBytes = 0; + if ( + _lastEntryHeader != null + && ( + FlagUtility.HasFlag(_lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor) + || _lastEntryHeader.IsZip64 + ) + ) { - rewindableStream = rs; - } - else - { - rewindableStream = new RewindableStream(stream); - } - while (true) - { - ZipHeader? header; - BinaryReader reader = new BinaryReader(rewindableStream); - uint headerBytes = 0; - if (_lastEntryHeader != null && - (FlagUtility.HasFlag(_lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor) || _lastEntryHeader.IsZip64)) + reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation( + ref rewindableStream + ); + var pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; + var crc = reader.ReadUInt32(); + if (crc == POST_DATA_DESCRIPTOR) { - reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(ref rewindableStream); - long? pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null; - uint crc = reader.ReadUInt32(); - if (crc == POST_DATA_DESCRIPTOR) - { - crc = reader.ReadUInt32(); - } - _lastEntryHeader.Crc = crc; + crc = reader.ReadUInt32(); + } + _lastEntryHeader.Crc = crc; - // The DataDescriptor can be either 64bit or 32bit - var compressed_size = reader.ReadUInt32(); - var uncompressed_size = reader.ReadUInt32(); + // The DataDescriptor can be either 64bit or 32bit + var compressed_size = reader.ReadUInt32(); + var uncompressed_size = reader.ReadUInt32(); - // Check if we have header or 64bit DataDescriptor + // Check if we have header or 64bit DataDescriptor + headerBytes = reader.ReadUInt32(); + var test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50); + + var test_64bit = ((long)uncompressed_size << 32) | compressed_size; + if (test_64bit == _lastEntryHeader.CompressedSize && test_header) + { + _lastEntryHeader.UncompressedSize = + ((long)reader.ReadUInt32() << 32) | headerBytes; headerBytes = reader.ReadUInt32(); - bool test_header = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50); - - long test_64bit = (long)uncompressed_size << 32 | (long)compressed_size; - if ( test_64bit == _lastEntryHeader.CompressedSize && test_header ) - { - _lastEntryHeader.UncompressedSize = (long)reader.ReadUInt32() << 32 | (long)headerBytes; - headerBytes = reader.ReadUInt32(); - } - else - { - _lastEntryHeader.UncompressedSize = uncompressed_size; - } - - if (pos.HasValue) - { - _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; - } } else { - headerBytes = reader.ReadUInt32(); + _lastEntryHeader.UncompressedSize = uncompressed_size; } - _lastEntryHeader = null; - header = ReadHeader(headerBytes, reader); - if (header is null) + if (pos.HasValue) { - yield break; + _lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize; } - - //entry could be zero bytes so we need to know that. - if (header.ZipHeaderType == ZipHeaderType.LocalEntry) - { - var local_header = ((LocalEntryHeader)header); - - // If we have CompressedSize, there is data to be read - if (local_header.CompressedSize > 0) - { - header.HasData = true; - } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor ) - else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor)) - { - bool isRecording = rewindableStream.IsRecording; - if (!isRecording) - { - rewindableStream.StartRecording(); - } - uint nextHeaderBytes = reader.ReadUInt32(); - - // Check if next data is PostDataDescriptor, streamed file with 0 length - header.HasData = !IsHeader(nextHeaderBytes); - rewindableStream.Rewind(!isRecording); - } - else // We are not streaming and compressed size is 0, we have no data - { - header.HasData = false; - } - } - yield return header; } + else + { + headerBytes = reader.ReadUInt32(); + } + + _lastEntryHeader = null; + header = ReadHeader(headerBytes, reader); + if (header is null) + { + yield break; + } + + //entry could be zero bytes so we need to know that. + if (header.ZipHeaderType == ZipHeaderType.LocalEntry) + { + var local_header = ((LocalEntryHeader)header); + + // If we have CompressedSize, there is data to be read + if (local_header.CompressedSize > 0) + { + header.HasData = true; + } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor ) + else if (local_header.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor)) + { + var isRecording = rewindableStream.IsRecording; + if (!isRecording) + { + rewindableStream.StartRecording(); + } + var nextHeaderBytes = reader.ReadUInt32(); + + // Check if next data is PostDataDescriptor, streamed file with 0 length + header.HasData = !IsHeader(nextHeaderBytes); + rewindableStream.Rewind(!isRecording); + } + else // We are not streaming and compressed size is 0, we have no data + { + header.HasData = false; + } + } + yield return header; } } } diff --git a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs index ea8aea3f..093ac034 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesCryptoStream.cs @@ -3,179 +3,156 @@ using System.Buffers.Binary; using System.IO; using System.Security.Cryptography; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class WinzipAesCryptoStream : Stream { - internal class WinzipAesCryptoStream : Stream + private const int BLOCK_SIZE_IN_BYTES = 16; + private readonly SymmetricAlgorithm _cipher; + private readonly byte[] _counter = new byte[BLOCK_SIZE_IN_BYTES]; + private readonly Stream _stream; + private readonly ICryptoTransform _transform; + private int _nonce = 1; + private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES]; + private bool _isFinalBlock; + private long _totalBytesLeftToRead; + private bool _isDisposed; + + internal WinzipAesCryptoStream( + Stream stream, + WinzipAesEncryptionData winzipAesEncryptionData, + long length + ) { - private const int BLOCK_SIZE_IN_BYTES = 16; - private readonly SymmetricAlgorithm _cipher; - private readonly byte[] _counter = new byte[BLOCK_SIZE_IN_BYTES]; - private readonly Stream _stream; - private readonly ICryptoTransform _transform; - private int _nonce = 1; - private byte[] _counterOut = new byte[BLOCK_SIZE_IN_BYTES]; - private bool _isFinalBlock; - private long _totalBytesLeftToRead; - private bool _isDisposed; + _stream = stream; + _totalBytesLeftToRead = length; - internal WinzipAesCryptoStream(Stream stream, WinzipAesEncryptionData winzipAesEncryptionData, long length) + _cipher = CreateCipher(winzipAesEncryptionData); + + var iv = new byte[BLOCK_SIZE_IN_BYTES]; + _transform = _cipher.CreateEncryptor(winzipAesEncryptionData.KeyBytes, iv); + } + + private SymmetricAlgorithm CreateCipher(WinzipAesEncryptionData winzipAesEncryptionData) + { + var cipher = Aes.Create(); + cipher.BlockSize = BLOCK_SIZE_IN_BYTES * 8; + cipher.KeySize = winzipAesEncryptionData.KeyBytes.Length * 8; + cipher.Mode = CipherMode.ECB; + cipher.Padding = PaddingMode.None; + return cipher; + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) { - this._stream = stream; - _totalBytesLeftToRead = length; - - _cipher = CreateCipher(winzipAesEncryptionData); - - var iv = new byte[BLOCK_SIZE_IN_BYTES]; - _transform = _cipher.CreateEncryptor(winzipAesEncryptionData.KeyBytes, iv); + return; } - - private SymmetricAlgorithm CreateCipher(WinzipAesEncryptionData winzipAesEncryptionData) + _isDisposed = true; + if (disposing) { - var cipher = Aes.Create(); - cipher.BlockSize = BLOCK_SIZE_IN_BYTES * 8; - cipher.KeySize = winzipAesEncryptionData.KeyBytes.Length * 8; - cipher.Mode = CipherMode.ECB; - cipher.Padding = PaddingMode.None; - return cipher; - } - - public override bool CanRead - { - get { return true; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return false; } - } - - public override long Length - { - get { throw new NotSupportedException(); } - } - - public override long Position - { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - if (disposing) - { - //read out last 10 auth bytes - Span ten = stackalloc byte[10]; - _stream.ReadFully(ten); - _stream.Dispose(); - } - } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_totalBytesLeftToRead == 0) - { - return 0; - } - int bytesToRead = count; - if (count > _totalBytesLeftToRead) - { - bytesToRead = (int)_totalBytesLeftToRead; - } - int read = _stream.Read(buffer, offset, bytesToRead); - _totalBytesLeftToRead -= read; - - ReadTransformBlocks(buffer, offset, read); - - return read; - } - - private int ReadTransformOneBlock(byte[] buffer, int offset, int last) - { - if (_isFinalBlock) - { - throw new InvalidOperationException(); - } - - int bytesRemaining = last - offset; - int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES) - ? BLOCK_SIZE_IN_BYTES - : bytesRemaining; - - // update the counter - BinaryPrimitives.WriteInt32LittleEndian(_counter, _nonce++); - - // Determine if this is the final block - if ((bytesToRead == bytesRemaining) && (_totalBytesLeftToRead == 0)) - { - _counterOut = _transform.TransformFinalBlock(_counter, - 0, - BLOCK_SIZE_IN_BYTES); - _isFinalBlock = true; - } - else - { - _transform.TransformBlock(_counter, - 0, // offset - BLOCK_SIZE_IN_BYTES, - _counterOut, - 0); // offset - } - - XorInPlace(buffer, offset, bytesToRead); - return bytesToRead; - } - - - private void XorInPlace(byte[] buffer, int offset, int count) - { - for (int i = 0; i < count; i++) - { - buffer[offset + i] = (byte)(_counterOut[i] ^ buffer[offset + i]); - } - } - - private void ReadTransformBlocks(byte[] buffer, int offset, int count) - { - int posn = offset; - int last = count + offset; - - while (posn < buffer.Length && posn < last) - { - int n = ReadTransformOneBlock(buffer, posn, last); - posn += n; - } - } - - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); + //read out last 10 auth bytes + Span ten = stackalloc byte[10]; + _stream.ReadFully(ten); + _stream.Dispose(); } } + + public override void Flush() => throw new NotSupportedException(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (_totalBytesLeftToRead == 0) + { + return 0; + } + var bytesToRead = count; + if (count > _totalBytesLeftToRead) + { + bytesToRead = (int)_totalBytesLeftToRead; + } + var read = _stream.Read(buffer, offset, bytesToRead); + _totalBytesLeftToRead -= read; + + ReadTransformBlocks(buffer, offset, read); + + return read; + } + + private int ReadTransformOneBlock(byte[] buffer, int offset, int last) + { + if (_isFinalBlock) + { + throw new InvalidOperationException(); + } + + var bytesRemaining = last - offset; + var bytesToRead = + (bytesRemaining > BLOCK_SIZE_IN_BYTES) ? BLOCK_SIZE_IN_BYTES : bytesRemaining; + + // update the counter + BinaryPrimitives.WriteInt32LittleEndian(_counter, _nonce++); + + // Determine if this is the final block + if ((bytesToRead == bytesRemaining) && (_totalBytesLeftToRead == 0)) + { + _counterOut = _transform.TransformFinalBlock(_counter, 0, BLOCK_SIZE_IN_BYTES); + _isFinalBlock = true; + } + else + { + _transform.TransformBlock( + _counter, + 0, // offset + BLOCK_SIZE_IN_BYTES, + _counterOut, + 0 + ); // offset + } + + XorInPlace(buffer, offset, bytesToRead); + return bytesToRead; + } + + private void XorInPlace(byte[] buffer, int offset, int count) + { + for (var i = 0; i < count; i++) + { + buffer[offset + i] = (byte)(_counterOut[i] ^ buffer[offset + i]); + } + } + + private void ReadTransformBlocks(byte[] buffer, int offset, int count) + { + var posn = offset; + var last = count + offset; + + while (posn < buffer.Length && posn < last) + { + var n = ReadTransformOneBlock(buffer, posn, last); + posn += n; + } + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); } diff --git a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs index e893adde..251b919e 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesEncryptionData.cs @@ -4,68 +4,72 @@ using System; using System.Buffers.Binary; using System.Security.Cryptography; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class WinzipAesEncryptionData { - internal class WinzipAesEncryptionData + private const int RFC2898_ITERATIONS = 1000; + + private readonly byte[] _salt; + private readonly WinzipAesKeySize _keySize; + private readonly byte[] _passwordVerifyValue; + private readonly string _password; + + private byte[] _generatedVerifyValue; + + internal WinzipAesEncryptionData( + WinzipAesKeySize keySize, + byte[] salt, + byte[] passwordVerifyValue, + string password + ) { - private const int RFC2898_ITERATIONS = 1000; + _keySize = keySize; + _salt = salt; + _passwordVerifyValue = passwordVerifyValue; + _password = password; + Initialize(); + } - private readonly byte[] _salt; - private readonly WinzipAesKeySize _keySize; - private readonly byte[] _passwordVerifyValue; - private readonly string _password; + internal byte[] IvBytes { get; set; } - private byte[] _generatedVerifyValue; + internal byte[] KeyBytes { get; set; } - internal WinzipAesEncryptionData(WinzipAesKeySize keySize, byte[] salt, byte[] passwordVerifyValue, - string password) + private int KeySizeInBytes => KeyLengthInBytes(_keySize); + + internal static int KeyLengthInBytes(WinzipAesKeySize keySize) => + keySize switch { - this._keySize = keySize; - this._salt = salt; - this._passwordVerifyValue = passwordVerifyValue; - this._password = password; - Initialize(); - } + WinzipAesKeySize.KeySize128 => 16, + WinzipAesKeySize.KeySize192 => 24, + WinzipAesKeySize.KeySize256 => 32, + _ => throw new InvalidOperationException(), + }; - internal byte[] IvBytes { get; set; } + private void Initialize() + { +#if NET7_0 + var rfc2898 = new Rfc2898DeriveBytes( + _password, + _salt, + RFC2898_ITERATIONS, + HashAlgorithmName.SHA1 + ); +#else + var rfc2898 = new Rfc2898DeriveBytes(_password, _salt, RFC2898_ITERATIONS); +#endif - internal byte[] KeyBytes { get; set; } + KeyBytes = rfc2898.GetBytes(KeySizeInBytes); // 16 or 24 or 32 ??? + IvBytes = rfc2898.GetBytes(KeySizeInBytes); + _generatedVerifyValue = rfc2898.GetBytes(2); - private int KeySizeInBytes + var verify = BinaryPrimitives.ReadInt16LittleEndian(_passwordVerifyValue); + if (_password != null) { - get + var generated = BinaryPrimitives.ReadInt16LittleEndian(_generatedVerifyValue); + if (verify != generated) { - return KeyLengthInBytes(_keySize); - } - } - - internal static int KeyLengthInBytes(WinzipAesKeySize keySize) - { - return keySize switch - { - WinzipAesKeySize.KeySize128 => 16, - WinzipAesKeySize.KeySize192 => 24, - WinzipAesKeySize.KeySize256 => 32, - _ => throw new InvalidOperationException(), - }; - } - - private void Initialize() - { - var rfc2898 = new Rfc2898DeriveBytes(_password, _salt, RFC2898_ITERATIONS); - - KeyBytes = rfc2898.GetBytes(KeySizeInBytes); // 16 or 24 or 32 ??? - IvBytes = rfc2898.GetBytes(KeySizeInBytes); - _generatedVerifyValue = rfc2898.GetBytes(2); - - short verify = BinaryPrimitives.ReadInt16LittleEndian(_passwordVerifyValue); - if (_password != null) - { - short generated = BinaryPrimitives.ReadInt16LittleEndian(_generatedVerifyValue); - if (verify != generated) - { - throw new InvalidFormatException("bad password"); - } + throw new InvalidFormatException("bad password"); } } } diff --git a/src/SharpCompress/Common/Zip/WinzipAesKeySize.cs b/src/SharpCompress/Common/Zip/WinzipAesKeySize.cs index 4a39f46d..7772bcaa 100644 --- a/src/SharpCompress/Common/Zip/WinzipAesKeySize.cs +++ b/src/SharpCompress/Common/Zip/WinzipAesKeySize.cs @@ -1,9 +1,8 @@ -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal enum WinzipAesKeySize { - internal enum WinzipAesKeySize - { - KeySize128 = 1, - KeySize192 = 2, - KeySize256 = 3 - } -} \ No newline at end of file + KeySize128 = 1, + KeySize192 = 2, + KeySize256 = 3 +} diff --git a/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs b/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs index bf7df040..599b1ee1 100644 --- a/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs +++ b/src/SharpCompress/Common/Zip/ZipCompressionMethod.cs @@ -1,13 +1,12 @@ -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal enum ZipCompressionMethod { - internal enum ZipCompressionMethod - { - None = 0, - Deflate = 8, - Deflate64 = 9, - BZip2 = 12, - LZMA = 14, - PPMd = 98, - WinzipAes = 0x63 //http://www.winzip.com/aes_info.htm - } -} \ No newline at end of file + None = 0, + Deflate = 8, + Deflate64 = 9, + BZip2 = 12, + LZMA = 14, + PPMd = 98, + WinzipAes = 0x63 //http://www.winzip.com/aes_info.htm +} diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index d82335b5..2c544b94 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -1,87 +1,89 @@ -#nullable disable +#nullable disable using System; using System.Collections.Generic; using SharpCompress.Common.Zip.Headers; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +public class ZipEntry : Entry { - public class ZipEntry : Entry + private readonly ZipFilePart _filePart; + + internal ZipEntry(ZipFilePart filePart) { - private readonly ZipFilePart _filePart; - - internal ZipEntry(ZipFilePart filePart) + if (filePart != null) { - if (filePart != null) - { - this._filePart = filePart; - LastModifiedTime = Utility.DosDateToDateTime(filePart.Header.LastModifiedDate, - filePart.Header.LastModifiedTime); - } + _filePart = filePart; + LastModifiedTime = Utility.DosDateToDateTime( + filePart.Header.LastModifiedDate, + filePart.Header.LastModifiedTime + ); } + } - public override CompressionType CompressionType + public override CompressionType CompressionType + { + get { - get + switch (_filePart.Header.CompressionMethod) { - switch (_filePart.Header.CompressionMethod) + case ZipCompressionMethod.BZip2: { - case ZipCompressionMethod.BZip2: - { - return CompressionType.BZip2; - } - case ZipCompressionMethod.Deflate: - { - return CompressionType.Deflate; - } - case ZipCompressionMethod.Deflate64: - { - return CompressionType.Deflate64; - } - case ZipCompressionMethod.LZMA: - { - return CompressionType.LZMA; - } - case ZipCompressionMethod.PPMd: - { - return CompressionType.PPMd; - } - case ZipCompressionMethod.None: - { - return CompressionType.None; - } - default: - { - return CompressionType.Unknown; - } + return CompressionType.BZip2; + } + case ZipCompressionMethod.Deflate: + { + return CompressionType.Deflate; + } + case ZipCompressionMethod.Deflate64: + { + return CompressionType.Deflate64; + } + case ZipCompressionMethod.LZMA: + { + return CompressionType.LZMA; + } + case ZipCompressionMethod.PPMd: + { + return CompressionType.PPMd; + } + case ZipCompressionMethod.None: + { + return CompressionType.None; + } + default: + { + return CompressionType.Unknown; } } } - - public override long Crc => _filePart.Header.Crc; - - public override string Key => _filePart.Header.Name; - - public override string LinkTarget => null; - - public override long CompressedSize => _filePart.Header.CompressedSize; - - public override long Size => _filePart.Header.UncompressedSize; - - public override DateTime? LastModifiedTime { get; } - - public override DateTime? CreatedTime => null; - - public override DateTime? LastAccessedTime => null; - - public override DateTime? ArchivedTime => null; - - public override bool IsEncrypted => FlagUtility.HasFlag(_filePart.Header.Flags, HeaderFlags.Encrypted); - - public override bool IsDirectory => _filePart.Header.IsDirectory; - - public override bool IsSplitAfter => false; - - internal override IEnumerable Parts => _filePart.AsEnumerable(); } -} \ No newline at end of file + + public override long Crc => _filePart.Header.Crc; + + public override string Key => _filePart.Header.Name; + + public override string LinkTarget => null; + + public override long CompressedSize => _filePart.Header.CompressedSize; + + public override long Size => _filePart.Header.UncompressedSize; + + public override DateTime? LastModifiedTime { get; } + + public override DateTime? CreatedTime => null; + + public override DateTime? LastAccessedTime => null; + + public override DateTime? ArchivedTime => null; + + public override bool IsEncrypted => + FlagUtility.HasFlag(_filePart.Header.Flags, HeaderFlags.Encrypted); + + public override bool IsDirectory => _filePart.Header.IsDirectory; + + public override bool IsSplitAfter => false; + + internal override IEnumerable Parts => _filePart.AsEnumerable(); +} diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 5fd6c4ac..4a8dbc2a 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -11,175 +11,198 @@ using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.PPMd; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal abstract class ZipFilePart : FilePart { - internal abstract class ZipFilePart : FilePart + internal ZipFilePart(ZipFileEntry header, Stream stream) : base(header.ArchiveEncoding) { - internal ZipFilePart(ZipFileEntry header, Stream stream) - : base(header.ArchiveEncoding) + Header = header; + header.Part = this; + BaseStream = stream; + } + + internal Stream BaseStream { get; } + internal ZipFileEntry Header { get; set; } + + internal override string FilePartName => Header.Name; + + internal override Stream GetCompressedStream() + { + if (!Header.HasData) { - Header = header; - header.Part = this; - BaseStream = stream; + return Stream.Null; } - - internal Stream BaseStream { get; } - internal ZipFileEntry Header { get; set; } - - internal override string FilePartName => Header.Name; - - internal override Stream GetCompressedStream() + var decompressionStream = CreateDecompressionStream( + GetCryptoStream(CreateBaseStream()), + Header.CompressionMethod + ); + if (LeaveStreamOpen) { - if (!Header.HasData) - { - return Stream.Null; - } - Stream decompressionStream = CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod); - if (LeaveStreamOpen) - { - return NonDisposingStream.Create(decompressionStream); - } - return decompressionStream; + return NonDisposingStream.Create(decompressionStream); } + return decompressionStream; + } - internal override Stream GetRawStream() + internal override Stream GetRawStream() + { + if (!Header.HasData) { - if (!Header.HasData) - { - return Stream.Null; - } - return CreateBaseStream(); + return Stream.Null; } + return CreateBaseStream(); + } - protected abstract Stream CreateBaseStream(); + protected abstract Stream CreateBaseStream(); - protected bool LeaveStreamOpen => FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64; + protected bool LeaveStreamOpen => + FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64; - protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method) + protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method) + { + switch (method) { - switch (method) + case ZipCompressionMethod.None: { - case ZipCompressionMethod.None: - { - return stream; - } - case ZipCompressionMethod.Deflate: - { - return new DeflateStream(stream, CompressionMode.Decompress); - } - case ZipCompressionMethod.Deflate64: - { - return new Deflate64Stream(stream, CompressionMode.Decompress); - } - case ZipCompressionMethod.BZip2: - { - return new BZip2Stream(stream, CompressionMode.Decompress, false); - } - case ZipCompressionMethod.LZMA: - { - if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted)) - { - throw new NotSupportedException("LZMA with pkware encryption."); - } - var reader = new BinaryReader(stream); - reader.ReadUInt16(); //LZMA version - var props = new byte[reader.ReadUInt16()]; - reader.Read(props, 0, props.Length); - return new LzmaStream(props, stream, - Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1, - FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) - ? -1 - : (long)Header.UncompressedSize); - } - case ZipCompressionMethod.PPMd: - { - Span props = stackalloc byte[2]; - stream.ReadFully(props); - return new PpmdStream(new PpmdProperties(props), stream, false); - } - case ZipCompressionMethod.WinzipAes: - { - ExtraData? data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); - if (data is null) - { - throw new InvalidFormatException("No Winzip AES extra data found."); - } - if (data.Length != 7) - { - throw new InvalidFormatException("Winzip data length is not 7."); - } - ushort compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes); - - if (compressedMethod != 0x01 && compressedMethod != 0x02) - { - throw new InvalidFormatException("Unexpected vendor version number for WinZip AES metadata"); - } - - ushort vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2)); - if (vendorId != 0x4541) - { - throw new InvalidFormatException("Unexpected vendor ID for WinZip AES metadata"); - } - return CreateDecompressionStream(stream, (ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5))); - } - default: - { - throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod); - } + return stream; } - } - - protected Stream GetCryptoStream(Stream plainStream) - { - bool isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted); - - if (Header.CompressedSize == 0 && isFileEncrypted) + case ZipCompressionMethod.Deflate: { - throw new NotSupportedException("Cannot encrypt file with unknown size at start."); + return new DeflateStream(stream, CompressionMode.Decompress); } - - if ((Header.CompressedSize == 0 - && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)) - || Header.IsZip64) + case ZipCompressionMethod.Deflate64: { - plainStream = NonDisposingStream.Create(plainStream); //make sure AES doesn't close + return new Deflate64Stream(stream, CompressionMode.Decompress); } - else + case ZipCompressionMethod.BZip2: { - plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close + return new BZip2Stream(stream, CompressionMode.Decompress, false); } - - if (isFileEncrypted) + case ZipCompressionMethod.LZMA: { - switch (Header.CompressionMethod) + if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted)) { - case ZipCompressionMethod.None: - case ZipCompressionMethod.Deflate: - case ZipCompressionMethod.Deflate64: - case ZipCompressionMethod.BZip2: - case ZipCompressionMethod.LZMA: - case ZipCompressionMethod.PPMd: - { - return new PkwareTraditionalCryptoStream(plainStream, Header.ComposeEncryptionData(plainStream), CryptoMode.Decrypt); - } - - case ZipCompressionMethod.WinzipAes: - { - if (Header.WinzipAesEncryptionData != null) - { - return new WinzipAesCryptoStream(plainStream, Header.WinzipAesEncryptionData, Header.CompressedSize - 10); - } - return plainStream; - } - - default: - { - throw new InvalidOperationException("Header.CompressionMethod is invalid"); - } - + throw new NotSupportedException("LZMA with pkware encryption."); } + var reader = new BinaryReader(stream); + reader.ReadUInt16(); //LZMA version + var props = new byte[reader.ReadUInt16()]; + reader.Read(props, 0, props.Length); + return new LzmaStream( + props, + stream, + Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1, + FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1) + ? -1 + : Header.UncompressedSize + ); + } + case ZipCompressionMethod.PPMd: + { + Span props = stackalloc byte[2]; + stream.ReadFully(props); + return new PpmdStream(new PpmdProperties(props), stream, false); + } + case ZipCompressionMethod.WinzipAes: + { + var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); + if (data is null) + { + throw new InvalidFormatException("No Winzip AES extra data found."); + } + if (data.Length != 7) + { + throw new InvalidFormatException("Winzip data length is not 7."); + } + var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes); + + if (compressedMethod != 0x01 && compressedMethod != 0x02) + { + throw new InvalidFormatException( + "Unexpected vendor version number for WinZip AES metadata" + ); + } + + var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2)); + if (vendorId != 0x4541) + { + throw new InvalidFormatException( + "Unexpected vendor ID for WinZip AES metadata" + ); + } + return CreateDecompressionStream( + stream, + (ZipCompressionMethod) + BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)) + ); + } + default: + { + throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod); } - return plainStream; } } + + protected Stream GetCryptoStream(Stream plainStream) + { + var isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted); + + if (Header.CompressedSize == 0 && isFileEncrypted) + { + throw new NotSupportedException("Cannot encrypt file with unknown size at start."); + } + + if ( + ( + Header.CompressedSize == 0 + && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) + ) || Header.IsZip64 + ) + { + plainStream = NonDisposingStream.Create(plainStream); //make sure AES doesn't close + } + else + { + plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close + } + + if (isFileEncrypted) + { + switch (Header.CompressionMethod) + { + case ZipCompressionMethod.None: + case ZipCompressionMethod.Deflate: + case ZipCompressionMethod.Deflate64: + case ZipCompressionMethod.BZip2: + case ZipCompressionMethod.LZMA: + case ZipCompressionMethod.PPMd: + { + return new PkwareTraditionalCryptoStream( + plainStream, + Header.ComposeEncryptionData(plainStream), + CryptoMode.Decrypt + ); + } + + case ZipCompressionMethod.WinzipAes: + { + if (Header.WinzipAesEncryptionData != null) + { + return new WinzipAesCryptoStream( + plainStream, + Header.WinzipAesEncryptionData, + Header.CompressedSize - 10 + ); + } + return plainStream; + } + + default: + { + throw new InvalidOperationException("Header.CompressionMethod is invalid"); + } + } + } + return plainStream; + } } diff --git a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs index fac558d2..8a58e220 100644 --- a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs @@ -1,184 +1,202 @@ -using System; +using System; using System.IO; using System.Linq; using SharpCompress.Common.Zip.Headers; using SharpCompress.IO; -namespace SharpCompress.Common.Zip +namespace SharpCompress.Common.Zip; + +internal class ZipHeaderFactory { - internal class ZipHeaderFactory + internal const uint ENTRY_HEADER_BYTES = 0x04034b50; + internal const uint POST_DATA_DESCRIPTOR = 0x08074b50; + internal const uint DIRECTORY_START_HEADER_BYTES = 0x02014b50; + internal const uint DIRECTORY_END_HEADER_BYTES = 0x06054b50; + internal const uint DIGITAL_SIGNATURE = 0x05054b50; + internal const uint SPLIT_ARCHIVE_HEADER_BYTES = 0x30304b50; + + internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; + internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50; + + protected LocalEntryHeader? _lastEntryHeader; + private readonly string? _password; + private readonly StreamingMode _mode; + private readonly ArchiveEncoding _archiveEncoding; + + protected ZipHeaderFactory( + StreamingMode mode, + string? password, + ArchiveEncoding archiveEncoding + ) { - internal const uint ENTRY_HEADER_BYTES = 0x04034b50; - internal const uint POST_DATA_DESCRIPTOR = 0x08074b50; - internal const uint DIRECTORY_START_HEADER_BYTES = 0x02014b50; - internal const uint DIRECTORY_END_HEADER_BYTES = 0x06054b50; - internal const uint DIGITAL_SIGNATURE = 0x05054b50; - internal const uint SPLIT_ARCHIVE_HEADER_BYTES = 0x30304b50; + _mode = mode; + _password = password; + _archiveEncoding = archiveEncoding; + } - internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; - internal const uint ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR = 0x07064b50; - - protected LocalEntryHeader? _lastEntryHeader; - private readonly string? _password; - private readonly StreamingMode _mode; - private readonly ArchiveEncoding _archiveEncoding; - - protected ZipHeaderFactory(StreamingMode mode, string? password, ArchiveEncoding archiveEncoding) + protected ZipHeader? ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false) + { + switch (headerBytes) { - this._mode = mode; - this._password = password; - this._archiveEncoding = archiveEncoding; - } - - protected ZipHeader? ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false) - { - switch (headerBytes) + case ENTRY_HEADER_BYTES: { - case ENTRY_HEADER_BYTES: - { - var entryHeader = new LocalEntryHeader(_archiveEncoding); - entryHeader.Read(reader); - LoadHeader(entryHeader, reader.BaseStream); + var entryHeader = new LocalEntryHeader(_archiveEncoding); + entryHeader.Read(reader); + LoadHeader(entryHeader, reader.BaseStream); - _lastEntryHeader = entryHeader; - return entryHeader; - } - case DIRECTORY_START_HEADER_BYTES: - { - var entry = new DirectoryEntryHeader(_archiveEncoding); - entry.Read(reader); - return entry; - } - case POST_DATA_DESCRIPTOR: - { - if (FlagUtility.HasFlag(_lastEntryHeader!.Flags, HeaderFlags.UsePostDataDescriptor)) - { - _lastEntryHeader.Crc = reader.ReadUInt32(); - _lastEntryHeader.CompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32(); - _lastEntryHeader.UncompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32(); - } - else - { - reader.ReadBytes(zip64 ? 20 : 12); - } - return null; - } - case DIGITAL_SIGNATURE: - return null; - case DIRECTORY_END_HEADER_BYTES: - { - var entry = new DirectoryEndHeader(); - entry.Read(reader); - return entry; - } - case SPLIT_ARCHIVE_HEADER_BYTES: - { - return new SplitHeader(); - } - case ZIP64_END_OF_CENTRAL_DIRECTORY: - { - var entry = new Zip64DirectoryEndHeader(); - entry.Read(reader); - return entry; - } - case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR: - { - var entry = new Zip64DirectoryEndLocatorHeader(); - entry.Read(reader); - return entry; - } - default: - return null; + _lastEntryHeader = entryHeader; + return entryHeader; } - } - - internal static bool IsHeader(uint headerBytes) - { - switch (headerBytes) + case DIRECTORY_START_HEADER_BYTES: { - case ENTRY_HEADER_BYTES: - case DIRECTORY_START_HEADER_BYTES: - case POST_DATA_DESCRIPTOR: - case DIGITAL_SIGNATURE: - case DIRECTORY_END_HEADER_BYTES: - case SPLIT_ARCHIVE_HEADER_BYTES: - case ZIP64_END_OF_CENTRAL_DIRECTORY: - case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR: - return true; - default: - return false; + var entry = new DirectoryEntryHeader(_archiveEncoding); + entry.Read(reader); + return entry; } - } - - private void LoadHeader(ZipFileEntry entryHeader, Stream stream) - { - if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.Encrypted)) + case POST_DATA_DESCRIPTOR: { - if (!entryHeader.IsDirectory && entryHeader.CompressedSize == 0 && - FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.UsePostDataDescriptor)) + if (FlagUtility.HasFlag(_lastEntryHeader!.Flags, HeaderFlags.UsePostDataDescriptor)) { - throw new NotSupportedException("SharpCompress cannot currently read non-seekable Zip Streams with encrypted data that has been written in a non-seekable manner."); + _lastEntryHeader.Crc = reader.ReadUInt32(); + _lastEntryHeader.CompressedSize = zip64 + ? (long)reader.ReadUInt64() + : reader.ReadUInt32(); + _lastEntryHeader.UncompressedSize = zip64 + ? (long)reader.ReadUInt64() + : reader.ReadUInt32(); } - - if (_password is null) + else { - throw new CryptographicException("No password supplied for encrypted zip."); - } - - entryHeader.Password = _password; - - if (entryHeader.CompressionMethod == ZipCompressionMethod.WinzipAes) - { - ExtraData? data = entryHeader.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes); - if (data != null) - { - var keySize = (WinzipAesKeySize)data.DataBytes[4]; - - var salt = new byte[WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2]; - var passwordVerifyValue = new byte[2]; - stream.Read(salt, 0, salt.Length); - stream.Read(passwordVerifyValue, 0, 2); - entryHeader.WinzipAesEncryptionData = - new WinzipAesEncryptionData(keySize, salt, passwordVerifyValue, _password); - - entryHeader.CompressedSize -= (uint)(salt.Length + 2); - } + reader.ReadBytes(zip64 ? 20 : 12); } + return null; } - - if (entryHeader.IsDirectory) + case DIGITAL_SIGNATURE: + return null; + case DIRECTORY_END_HEADER_BYTES: { - return; + var entry = new DirectoryEndHeader(); + entry.Read(reader); + return entry; } - - //if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.UsePostDataDescriptor)) - //{ - // entryHeader.PackedStream = new ReadOnlySubStream(stream); - //} - //else - //{ - switch (_mode) + case SPLIT_ARCHIVE_HEADER_BYTES: { - case StreamingMode.Seekable: - { - entryHeader.DataStartPosition = stream.Position; - stream.Position += entryHeader.CompressedSize; - break; - } - - case StreamingMode.Streaming: - { - entryHeader.PackedStream = stream; - break; - } - - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } + return new SplitHeader(); } - - //} + case ZIP64_END_OF_CENTRAL_DIRECTORY: + { + var entry = new Zip64DirectoryEndHeader(); + entry.Read(reader); + return entry; + } + case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR: + { + var entry = new Zip64DirectoryEndLocatorHeader(); + entry.Read(reader); + return entry; + } + default: + return null; } } -} \ No newline at end of file + + internal static bool IsHeader(uint headerBytes) + { + switch (headerBytes) + { + case ENTRY_HEADER_BYTES: + case DIRECTORY_START_HEADER_BYTES: + case POST_DATA_DESCRIPTOR: + case DIGITAL_SIGNATURE: + case DIRECTORY_END_HEADER_BYTES: + case SPLIT_ARCHIVE_HEADER_BYTES: + case ZIP64_END_OF_CENTRAL_DIRECTORY: + case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR: + return true; + default: + return false; + } + } + + private void LoadHeader(ZipFileEntry entryHeader, Stream stream) + { + if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.Encrypted)) + { + if ( + !entryHeader.IsDirectory + && entryHeader.CompressedSize == 0 + && FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.UsePostDataDescriptor) + ) + { + throw new NotSupportedException( + "SharpCompress cannot currently read non-seekable Zip Streams with encrypted data that has been written in a non-seekable manner." + ); + } + + if (_password is null) + { + throw new CryptographicException("No password supplied for encrypted zip."); + } + + entryHeader.Password = _password; + + if (entryHeader.CompressionMethod == ZipCompressionMethod.WinzipAes) + { + var data = entryHeader.Extra.SingleOrDefault( + x => x.Type == ExtraDataType.WinZipAes + ); + if (data != null) + { + var keySize = (WinzipAesKeySize)data.DataBytes[4]; + + var salt = new byte[WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2]; + var passwordVerifyValue = new byte[2]; + stream.Read(salt, 0, salt.Length); + stream.Read(passwordVerifyValue, 0, 2); + entryHeader.WinzipAesEncryptionData = new WinzipAesEncryptionData( + keySize, + salt, + passwordVerifyValue, + _password + ); + + entryHeader.CompressedSize -= (uint)(salt.Length + 2); + } + } + } + + if (entryHeader.IsDirectory) + { + return; + } + + //if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.UsePostDataDescriptor)) + //{ + // entryHeader.PackedStream = new ReadOnlySubStream(stream); + //} + //else + //{ + switch (_mode) + { + case StreamingMode.Seekable: + { + entryHeader.DataStartPosition = stream.Position; + stream.Position += entryHeader.CompressedSize; + break; + } + + case StreamingMode.Streaming: + { + entryHeader.PackedStream = stream; + break; + } + + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + + //} + } +} diff --git a/src/SharpCompress/Common/Zip/ZipVolume.cs b/src/SharpCompress/Common/Zip/ZipVolume.cs index 856a5836..26d471fe 100644 --- a/src/SharpCompress/Common/Zip/ZipVolume.cs +++ b/src/SharpCompress/Common/Zip/ZipVolume.cs @@ -1,15 +1,12 @@ using System.IO; using SharpCompress.Readers; -namespace SharpCompress.Common.Zip -{ - public class ZipVolume : Volume - { - public ZipVolume(Stream stream, ReaderOptions readerOptions, int index = 0) - : base(stream, readerOptions, index) - { - } +namespace SharpCompress.Common.Zip; - public string? Comment { get; internal set; } - } +public class ZipVolume : Volume +{ + public ZipVolume(Stream stream, ReaderOptions readerOptions, int index = 0) + : base(stream, readerOptions, index) { } + + public string? Comment { get; internal set; } } diff --git a/src/SharpCompress/Compressors/ADC/ADCBase.cs b/src/SharpCompress/Compressors/ADC/ADCBase.cs index f8241b2f..6fc755a9 100644 --- a/src/SharpCompress/Compressors/ADC/ADCBase.cs +++ b/src/SharpCompress/Compressors/ADC/ADCBase.cs @@ -26,185 +26,178 @@ using System; using System.IO; -namespace SharpCompress.Compressors.ADC +namespace SharpCompress.Compressors.ADC; + +/// +/// Provides static methods for decompressing Apple Data Compression data +/// +public static class ADCBase { - /// - /// Provides static methods for decompressing Apple Data Compression data - /// - public static class ADCBase + private const int PLAIN = 1; + private const int TWO_BYTE = 2; + private const int THREE_BYTE = 3; + + private static int GetChunkType(byte byt) { - private const int PLAIN = 1; - private const int TWO_BYTE = 2; - private const int THREE_BYTE = 3; - - private static int GetChunkType(byte byt) + if ((byt & 0x80) == 0x80) { - if ((byt & 0x80) == 0x80) - { - return PLAIN; - } - if ((byt & 0x40) == 0x40) - { - return THREE_BYTE; - } - return TWO_BYTE; + return PLAIN; + } + if ((byt & 0x40) == 0x40) + { + return THREE_BYTE; + } + return TWO_BYTE; + } + + private static int GetChunkSize(byte byt) => + GetChunkType(byt) switch + { + PLAIN => (byt & 0x7F) + 1, + TWO_BYTE => ((byt & 0x3F) >> 2) + 3, + THREE_BYTE => (byt & 0x3F) + 4, + _ => -1, + }; + + private static int GetOffset(ReadOnlySpan chunk) => + GetChunkType(chunk[0]) switch + { + PLAIN => 0, + TWO_BYTE => ((chunk[0] & 0x03) << 8) + chunk[1], + THREE_BYTE => (chunk[1] << 8) + chunk[2], + _ => -1, + }; + + /// + /// Decompresses a byte buffer that's compressed with ADC + /// + /// Compressed buffer + /// Buffer to hold decompressed data + /// Max size for decompressed data + /// How many bytes are stored on + public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) => + Decompress(new MemoryStream(input), out output, bufferSize); + + /// + /// Decompresses a stream that's compressed with ADC + /// + /// Stream containing compressed data + /// Buffer to hold decompressed data + /// Max size for decompressed data + /// How many bytes are stored on + public static int Decompress(Stream input, out byte[]? output, int bufferSize = 262144) + { + output = null; + + if (input is null || input.Length == 0) + { + return 0; } - private static int GetChunkSize(byte byt) + var start = (int)input.Position; + var position = (int)input.Position; + int chunkSize; + int offset; + int chunkType; + var buffer = new byte[bufferSize]; + var outPosition = 0; + var full = false; + Span temp = stackalloc byte[3]; + + while (position < input.Length) { - return GetChunkType(byt) switch + var readByte = input.ReadByte(); + if (readByte == -1) { - PLAIN => (byt & 0x7F) + 1, - TWO_BYTE => ((byt & 0x3F) >> 2) + 3, - THREE_BYTE => (byt & 0x3F) + 4, - _ => -1, - }; - } - - private static int GetOffset(ReadOnlySpan chunk) - { - return GetChunkType(chunk[0]) switch - { - PLAIN => 0, - TWO_BYTE => ((chunk[0] & 0x03) << 8) + chunk[1], - THREE_BYTE => (chunk[1] << 8) + chunk[2], - _ => -1, - }; - } - - /// - /// Decompresses a byte buffer that's compressed with ADC - /// - /// Compressed buffer - /// Buffer to hold decompressed data - /// Max size for decompressed data - /// How many bytes are stored on - public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) - { - return Decompress(new MemoryStream(input), out output, bufferSize); - } - - /// - /// Decompresses a stream that's compressed with ADC - /// - /// Stream containing compressed data - /// Buffer to hold decompressed data - /// Max size for decompressed data - /// How many bytes are stored on - public static int Decompress(Stream input, out byte[]? output, int bufferSize = 262144) - { - output = null; - - if (input is null || input.Length == 0) - { - return 0; + break; } - int start = (int)input.Position; - int position = (int)input.Position; - int chunkSize; - int offset; - int chunkType; - byte[] buffer = new byte[bufferSize]; - int outPosition = 0; - bool full = false; - Span temp = stackalloc byte[3]; + chunkType = GetChunkType((byte)readByte); - while (position < input.Length) + switch (chunkType) { - int readByte = input.ReadByte(); - if (readByte == -1) - { + case PLAIN: + chunkSize = GetChunkSize((byte)readByte); + if (outPosition + chunkSize > bufferSize) + { + full = true; + break; + } + input.Read(buffer, outPosition, chunkSize); + outPosition += chunkSize; + position += chunkSize + 1; break; - } - - chunkType = GetChunkType((byte)readByte); - - switch (chunkType) - { - case PLAIN: - chunkSize = GetChunkSize((byte)readByte); - if (outPosition + chunkSize > bufferSize) - { - full = true; - break; - } - input.Read(buffer, outPosition, chunkSize); - outPosition += chunkSize; - position += chunkSize + 1; + case TWO_BYTE: + chunkSize = GetChunkSize((byte)readByte); + temp[0] = (byte)readByte; + temp[1] = (byte)input.ReadByte(); + offset = GetOffset(temp); + if (outPosition + chunkSize > bufferSize) + { + full = true; break; - case TWO_BYTE: - chunkSize = GetChunkSize((byte)readByte); - temp[0] = (byte)readByte; - temp[1] = (byte)input.ReadByte(); - offset = GetOffset(temp); - if (outPosition + chunkSize > bufferSize) + } + if (offset == 0) + { + var lastByte = buffer[outPosition - 1]; + for (var i = 0; i < chunkSize; i++) { - full = true; - break; + buffer[outPosition] = lastByte; + outPosition++; } - if (offset == 0) + position += 2; + } + else + { + for (var i = 0; i < chunkSize; i++) { - byte lastByte = buffer[outPosition - 1]; - for (int i = 0; i < chunkSize; i++) - { - buffer[outPosition] = lastByte; - outPosition++; - } - position += 2; + buffer[outPosition] = buffer[outPosition - offset - 1]; + outPosition++; } - else - { - for (int i = 0; i < chunkSize; i++) - { - buffer[outPosition] = buffer[outPosition - offset - 1]; - outPosition++; - } - position += 2; - } - break; - case THREE_BYTE: - chunkSize = GetChunkSize((byte)readByte); - temp[0] = (byte)readByte; - temp[1] = (byte)input.ReadByte(); - temp[2] = (byte)input.ReadByte(); - offset = GetOffset(temp); - if (outPosition + chunkSize > bufferSize) - { - full = true; - break; - } - if (offset == 0) - { - byte lastByte = buffer[outPosition - 1]; - for (int i = 0; i < chunkSize; i++) - { - buffer[outPosition] = lastByte; - outPosition++; - } - position += 3; - } - else - { - for (int i = 0; i < chunkSize; i++) - { - buffer[outPosition] = buffer[outPosition - offset - 1]; - outPosition++; - } - position += 3; - } - break; - } - - if (full) - { + position += 2; + } + break; + case THREE_BYTE: + chunkSize = GetChunkSize((byte)readByte); + temp[0] = (byte)readByte; + temp[1] = (byte)input.ReadByte(); + temp[2] = (byte)input.ReadByte(); + offset = GetOffset(temp); + if (outPosition + chunkSize > bufferSize) + { + full = true; + break; + } + if (offset == 0) + { + var lastByte = buffer[outPosition - 1]; + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = lastByte; + outPosition++; + } + position += 3; + } + else + { + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = buffer[outPosition - offset - 1]; + outPosition++; + } + position += 3; + } break; - } } - output = new byte[outPosition]; - Array.Copy(buffer, output, outPosition); - return position - start; + if (full) + { + break; + } } + + output = new byte[outPosition]; + Array.Copy(buffer, output, outPosition); + return position - start; } } diff --git a/src/SharpCompress/Compressors/ADC/ADCStream.cs b/src/SharpCompress/Compressors/ADC/ADCStream.cs index 8d973a98..0660d11f 100644 --- a/src/SharpCompress/Compressors/ADC/ADCStream.cs +++ b/src/SharpCompress/Compressors/ADC/ADCStream.cs @@ -29,148 +29,141 @@ using System; using System.IO; -namespace SharpCompress.Compressors.ADC +namespace SharpCompress.Compressors.ADC; + +/// +/// Provides a forward readable only stream that decompresses ADC data +/// +public sealed class ADCStream : Stream { /// - /// Provides a forward readable only stream that decompresses ADC data + /// This stream holds the compressed data /// - public sealed class ADCStream : Stream + private readonly Stream _stream; + + /// + /// Is this instance disposed? + /// + private bool _isDisposed; + + /// + /// Position in decompressed data + /// + private long _position; + + /// + /// Buffer with currently used chunk of decompressed data + /// + private byte[] _outBuffer; + + /// + /// Position in buffer of decompressed data + /// + private int _outPosition; + + /// + /// Initializates a stream that decompresses ADC data on the fly + /// + /// Stream that contains the compressed data + /// Must be set to because compression is not implemented + public ADCStream(Stream stream, CompressionMode compressionMode = CompressionMode.Decompress) { - /// - /// This stream holds the compressed data - /// - private readonly Stream _stream; - - /// - /// Is this instance disposed? - /// - private bool _isDisposed; - - /// - /// Position in decompressed data - /// - private long _position; - - /// - /// Buffer with currently used chunk of decompressed data - /// - private byte[] _outBuffer; - - /// - /// Position in buffer of decompressed data - /// - private int _outPosition; - - /// - /// Initializates a stream that decompresses ADC data on the fly - /// - /// Stream that contains the compressed data - /// Must be set to because compression is not implemented - public ADCStream(Stream stream, CompressionMode compressionMode = CompressionMode.Decompress) - { - if (compressionMode == CompressionMode.Compress) - { - throw new NotSupportedException(); - } - - _stream = stream; - } - - public override bool CanRead => _stream.CanRead; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => _position; set => throw new NotSupportedException(); } - - public override void Flush() - { - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count == 0) - { - return 0; - } - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - if (offset < buffer.GetLowerBound(0)) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if ((offset + count) > buffer.GetLength(0)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - int size = -1; - - if (_outBuffer is null) - { - size = ADCBase.Decompress(_stream, out _outBuffer); - _outPosition = 0; - } - - int inPosition = offset; - int toCopy = count; - int copied = 0; - - while (_outPosition + toCopy >= _outBuffer.Length) - { - int piece = _outBuffer.Length - _outPosition; - Array.Copy(_outBuffer, _outPosition, buffer, inPosition, piece); - inPosition += piece; - copied += piece; - _position += piece; - toCopy -= piece; - size = ADCBase.Decompress(_stream, out _outBuffer); - _outPosition = 0; - if (size == 0 || _outBuffer is null || _outBuffer.Length == 0) - { - return copied; - } - } - - Array.Copy(_outBuffer, _outPosition, buffer, inPosition, toCopy); - _outPosition += toCopy; - _position += toCopy; - copied += toCopy; - return copied; - } - - public override long Seek(long offset, SeekOrigin origin) + if (compressionMode == CompressionMode.Compress) { throw new NotSupportedException(); } - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + _stream = stream; } -} \ No newline at end of file + + public override bool CanRead => _stream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override void Flush() { } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + base.Dispose(disposing); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count == 0) + { + return 0; + } + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (offset < buffer.GetLowerBound(0)) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if ((offset + count) > buffer.GetLength(0)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var size = -1; + + if (_outBuffer is null) + { + size = ADCBase.Decompress(_stream, out _outBuffer); + _outPosition = 0; + } + + var inPosition = offset; + var toCopy = count; + var copied = 0; + + while (_outPosition + toCopy >= _outBuffer.Length) + { + var piece = _outBuffer.Length - _outPosition; + Array.Copy(_outBuffer, _outPosition, buffer, inPosition, piece); + inPosition += piece; + copied += piece; + _position += piece; + toCopy -= piece; + size = ADCBase.Decompress(_stream, out _outBuffer); + _outPosition = 0; + if (size == 0 || _outBuffer is null || _outBuffer.Length == 0) + { + return copied; + } + } + + Array.Copy(_outBuffer, _outPosition, buffer, inPosition, toCopy); + _outPosition += toCopy; + _position += toCopy; + copied += toCopy; + return copied; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Constants.cs b/src/SharpCompress/Compressors/BZip2/BZip2Constants.cs index ed506844..cfeb02e4 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Constants.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Constants.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2001,2004-2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,82 +20,541 @@ * great code. */ -namespace SharpCompress.Compressors.BZip2 +namespace SharpCompress.Compressors.BZip2; + +/** +* Base class for both the compress and decompress classes. +* Holds common arrays, and static data. +* +* @author Keiron Liddle +*/ + +internal class BZip2Constants { - /** - * Base class for both the compress and decompress classes. - * Holds common arrays, and static data. - * - * @author Keiron Liddle - */ + public const int baseBlockSize = 100000; + public const int MAX_ALPHA_SIZE = 258; + public const int MAX_CODE_LEN = 23; + public const int RUNA = 0; + public const int RUNB = 1; + public const int N_GROUPS = 6; + public const int G_SIZE = 50; + public const int N_ITERS = 4; + public const int MAX_SELECTORS = (2 + (900000 / G_SIZE)); + public const int NUM_OVERSHOOT_BYTES = 20; - internal class BZip2Constants + public static int[] rNums = { - public const int baseBlockSize = 100000; - public const int MAX_ALPHA_SIZE = 258; - public const int MAX_CODE_LEN = 23; - public const int RUNA = 0; - public const int RUNB = 1; - public const int N_GROUPS = 6; - public const int G_SIZE = 50; - public const int N_ITERS = 4; - public const int MAX_SELECTORS = (2 + (900000 / G_SIZE)); - public const int NUM_OVERSHOOT_BYTES = 20; - - public static int[] rNums = - { - 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, - 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, - 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, - 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, - 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, - 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, - 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, - 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, - 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, - 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, - 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, - 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, - 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, - 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, - 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, - 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, - 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, - 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, - 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, - 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, - 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, - 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, - 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, - 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, - 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, - 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, - 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, - 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, - 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, - 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, - 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, - 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, - 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, - 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, - 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, - 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, - 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, - 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, - 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, - 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, - 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, - 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, - 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, - 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, - 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, - 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, - 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, - 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, - 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, - 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, - 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, - 936, 638 - }; - } -} \ No newline at end of file + 619, + 720, + 127, + 481, + 931, + 816, + 813, + 233, + 566, + 247, + 985, + 724, + 205, + 454, + 863, + 491, + 741, + 242, + 949, + 214, + 733, + 859, + 335, + 708, + 621, + 574, + 73, + 654, + 730, + 472, + 419, + 436, + 278, + 496, + 867, + 210, + 399, + 680, + 480, + 51, + 878, + 465, + 811, + 169, + 869, + 675, + 611, + 697, + 867, + 561, + 862, + 687, + 507, + 283, + 482, + 129, + 807, + 591, + 733, + 623, + 150, + 238, + 59, + 379, + 684, + 877, + 625, + 169, + 643, + 105, + 170, + 607, + 520, + 932, + 727, + 476, + 693, + 425, + 174, + 647, + 73, + 122, + 335, + 530, + 442, + 853, + 695, + 249, + 445, + 515, + 909, + 545, + 703, + 919, + 874, + 474, + 882, + 500, + 594, + 612, + 641, + 801, + 220, + 162, + 819, + 984, + 589, + 513, + 495, + 799, + 161, + 604, + 958, + 533, + 221, + 400, + 386, + 867, + 600, + 782, + 382, + 596, + 414, + 171, + 516, + 375, + 682, + 485, + 911, + 276, + 98, + 553, + 163, + 354, + 666, + 933, + 424, + 341, + 533, + 870, + 227, + 730, + 475, + 186, + 263, + 647, + 537, + 686, + 600, + 224, + 469, + 68, + 770, + 919, + 190, + 373, + 294, + 822, + 808, + 206, + 184, + 943, + 795, + 384, + 383, + 461, + 404, + 758, + 839, + 887, + 715, + 67, + 618, + 276, + 204, + 918, + 873, + 777, + 604, + 560, + 951, + 160, + 578, + 722, + 79, + 804, + 96, + 409, + 713, + 940, + 652, + 934, + 970, + 447, + 318, + 353, + 859, + 672, + 112, + 785, + 645, + 863, + 803, + 350, + 139, + 93, + 354, + 99, + 820, + 908, + 609, + 772, + 154, + 274, + 580, + 184, + 79, + 626, + 630, + 742, + 653, + 282, + 762, + 623, + 680, + 81, + 927, + 626, + 789, + 125, + 411, + 521, + 938, + 300, + 821, + 78, + 343, + 175, + 128, + 250, + 170, + 774, + 972, + 275, + 999, + 639, + 495, + 78, + 352, + 126, + 857, + 956, + 358, + 619, + 580, + 124, + 737, + 594, + 701, + 612, + 669, + 112, + 134, + 694, + 363, + 992, + 809, + 743, + 168, + 974, + 944, + 375, + 748, + 52, + 600, + 747, + 642, + 182, + 862, + 81, + 344, + 805, + 988, + 739, + 511, + 655, + 814, + 334, + 249, + 515, + 897, + 955, + 664, + 981, + 649, + 113, + 974, + 459, + 893, + 228, + 433, + 837, + 553, + 268, + 926, + 240, + 102, + 654, + 459, + 51, + 686, + 754, + 806, + 760, + 493, + 403, + 415, + 394, + 687, + 700, + 946, + 670, + 656, + 610, + 738, + 392, + 760, + 799, + 887, + 653, + 978, + 321, + 576, + 617, + 626, + 502, + 894, + 679, + 243, + 440, + 680, + 879, + 194, + 572, + 640, + 724, + 926, + 56, + 204, + 700, + 707, + 151, + 457, + 449, + 797, + 195, + 791, + 558, + 945, + 679, + 297, + 59, + 87, + 824, + 713, + 663, + 412, + 693, + 342, + 606, + 134, + 108, + 571, + 364, + 631, + 212, + 174, + 643, + 304, + 329, + 343, + 97, + 430, + 751, + 497, + 314, + 983, + 374, + 822, + 928, + 140, + 206, + 73, + 263, + 980, + 736, + 876, + 478, + 430, + 305, + 170, + 514, + 364, + 692, + 829, + 82, + 855, + 953, + 676, + 246, + 369, + 970, + 294, + 750, + 807, + 827, + 150, + 790, + 288, + 923, + 804, + 378, + 215, + 828, + 592, + 281, + 565, + 555, + 710, + 82, + 896, + 831, + 547, + 261, + 524, + 462, + 293, + 465, + 502, + 56, + 661, + 821, + 976, + 991, + 658, + 869, + 905, + 758, + 745, + 193, + 768, + 550, + 608, + 933, + 378, + 286, + 215, + 979, + 792, + 961, + 61, + 688, + 793, + 644, + 986, + 403, + 106, + 366, + 905, + 644, + 372, + 567, + 466, + 434, + 645, + 210, + 389, + 550, + 919, + 135, + 780, + 773, + 635, + 389, + 707, + 100, + 626, + 958, + 165, + 504, + 920, + 176, + 193, + 713, + 857, + 265, + 203, + 50, + 668, + 108, + 645, + 990, + 626, + 197, + 510, + 357, + 358, + 850, + 858, + 364, + 936, + 638 + }; +} diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs index a65ca51f..feea12d1 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs @@ -1,126 +1,99 @@ using System; using System.IO; -namespace SharpCompress.Compressors.BZip2 +namespace SharpCompress.Compressors.BZip2; + +public sealed class BZip2Stream : Stream { - public sealed class BZip2Stream : Stream + private readonly Stream stream; + private bool isDisposed; + + /// + /// Create a BZip2Stream + /// + /// The stream to read from + /// Compression Mode + /// Decompress Concatenated + public BZip2Stream(Stream stream, CompressionMode compressionMode, bool decompressConcatenated) { - private readonly Stream stream; - private bool isDisposed; - - /// - /// Create a BZip2Stream - /// - /// The stream to read from - /// Compression Mode - /// Decompress Concatenated - public BZip2Stream(Stream stream, CompressionMode compressionMode, - bool decompressConcatenated) + Mode = compressionMode; + if (Mode == CompressionMode.Compress) { - Mode = compressionMode; - if (Mode == CompressionMode.Compress) - { - this.stream = new CBZip2OutputStream(stream); - } - else - { - this.stream = new CBZip2InputStream(stream, decompressConcatenated); - } + this.stream = new CBZip2OutputStream(stream); } - - public void Finish() + else { - (stream as CBZip2OutputStream)?.Finish(); + this.stream = new CBZip2InputStream(stream, decompressConcatenated); } + } - protected override void Dispose(bool disposing) + public void Finish() => (stream as CBZip2OutputStream)?.Finish(); + + protected override void Dispose(bool disposing) + { + if (isDisposed) { - if (isDisposed) - { - return; - } - isDisposed = true; - if (disposing) - { - stream.Dispose(); - } + return; } - - public CompressionMode Mode { get; } - - public override bool CanRead => stream.CanRead; - - public override bool CanSeek => stream.CanSeek; - - public override bool CanWrite => stream.CanWrite; - - public override void Flush() + isDisposed = true; + if (disposing) { - stream.Flush(); + stream.Dispose(); } + } - public override long Length => stream.Length; + public CompressionMode Mode { get; } - public override long Position { get => stream.Position; set => stream.Position = value; } + public override bool CanRead => stream.CanRead; - public override int Read(byte[] buffer, int offset, int count) - { - return stream.Read(buffer, offset, count); - } + public override bool CanSeek => stream.CanSeek; - public override int ReadByte() - { - return stream.ReadByte(); - } + public override bool CanWrite => stream.CanWrite; - public override long Seek(long offset, SeekOrigin origin) - { - return stream.Seek(offset, origin); - } + public override void Flush() => stream.Flush(); - public override void SetLength(long value) - { - stream.SetLength(value); - } + public override long Length => stream.Length; + + public override long Position + { + get => stream.Position; + set => stream.Position = value; + } + + public override int Read(byte[] buffer, int offset, int count) => + stream.Read(buffer, offset, count); + + public override int ReadByte() => stream.ReadByte(); + + public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + + public override void SetLength(long value) => stream.SetLength(value); #if !NETFRAMEWORK && !NETSTANDARD2_0 - public override int Read(Span buffer) - { - return stream.Read(buffer); - } - - public override void Write(ReadOnlySpan buffer) - { - stream.Write(buffer); - } + public override int Read(Span buffer) => stream.Read(buffer); + public override void Write(ReadOnlySpan buffer) => stream.Write(buffer); #endif - public override void Write(byte[] buffer, int offset, int count) - { - stream.Write(buffer, offset, count); - } + public override void Write(byte[] buffer, int offset, int count) => + stream.Write(buffer, offset, count); - public override void WriteByte(byte value) - { - stream.WriteByte(value); - } + public override void WriteByte(byte value) => stream.WriteByte(value); - /// - /// Consumes two bytes to test if there is a BZip2 header - /// - /// - /// - public static bool IsBZip2(Stream stream) + /// + /// Consumes two bytes to test if there is a BZip2 header + /// + /// + /// + public static bool IsBZip2(Stream stream) + { + var br = new BinaryReader(stream); + var chars = br.ReadBytes(2); + if (chars.Length < 2 || chars[0] != 'B' || chars[1] != 'Z') { - BinaryReader br = new BinaryReader(stream); - byte[] chars = br.ReadBytes(2); - if (chars.Length < 2 || chars[0] != 'B' || chars[1] != 'Z') - { - return false; - } - return true; + return false; } + return true; } } diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs index 62c928ab..e03a5096 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs @@ -25,773 +25,702 @@ using System.IO; * great code. */ -namespace SharpCompress.Compressors.BZip2 +namespace SharpCompress.Compressors.BZip2; + +/** + * An input stream that decompresses from the BZip2 format (with the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + * + * NB: note this class has been modified to read the leading BZ from the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ + +internal class CBZip2InputStream : Stream { - /** - * An input stream that decompresses from the BZip2 format (with the file - * header chars) to be read as any other stream. - * - * @author Keiron Liddle - * - * NB: note this class has been modified to read the leading BZ from the - * start of the BZIP2 stream to make it compatible with other PGP programs. - */ - - internal class CBZip2InputStream : Stream + private static void Cadvise() { - private static void Cadvise() - { - //System.out.Println("CRC Error"); - //throw new CCoruptionError(); - } + //System.out.Println("CRC Error"); + //throw new CCoruptionError(); + } - private static void BadBGLengths() - { - Cadvise(); - } + private static void BadBGLengths() => Cadvise(); - private static void BitStreamEOF() - { - Cadvise(); - } + private static void BitStreamEOF() => Cadvise(); - private static void CompressedStreamEOF() - { - Cadvise(); - } + private static void CompressedStreamEOF() => Cadvise(); - private void MakeMaps() + private void MakeMaps() + { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { - int i; - nInUse = 0; - for (i = 0; i < 256; i++) + if (inUse[i]) { - if (inUse[i]) - { - seqToUnseq[nInUse] = (char)i; - unseqToSeq[i] = (char)nInUse; - nInUse++; - } + seqToUnseq[nInUse] = (char)i; + unseqToSeq[i] = (char)nInUse; + nInUse++; } } + } - /* - index of the last char in the block, so - the block size == last + 1. - */ - private int last; + /* + index of the last char in the block, so + the block size == last + 1. + */ + private int last; - /* - index in zptr[] of original string after sorting. - */ - private int origPtr; + /* + index in zptr[] of original string after sorting. + */ + private int origPtr; - /* - always: in the range 0 .. 9. - The current block size is 100000 * this number. - */ - private int blockSize100k; + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + private int blockSize100k; - private bool blockRandomised; + private bool blockRandomised; - private int bsBuff; - private int bsLive; - private readonly CRC mCrc = new CRC(); + private int bsBuff; + private int bsLive; + private readonly CRC mCrc = new CRC(); - private readonly bool[] inUse = new bool[256]; - private int nInUse; + private readonly bool[] inUse = new bool[256]; + private int nInUse; - private readonly char[] seqToUnseq = new char[256]; - private readonly char[] unseqToSeq = new char[256]; + private readonly char[] seqToUnseq = new char[256]; + private readonly char[] unseqToSeq = new char[256]; - private readonly char[] selector = new char[BZip2Constants.MAX_SELECTORS]; - private readonly char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; + private readonly char[] selector = new char[BZip2Constants.MAX_SELECTORS]; + private readonly char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; - private int[] tt; - private char[] ll8; + private int[] tt; + private char[] ll8; - /* - freq table collected to save a pass over the data - during decompression. - */ - private readonly int[] unzftab = new int[256]; + /* + freq table collected to save a pass over the data + during decompression. + */ + private readonly int[] unzftab = new int[256]; - private readonly int[][] limit = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - private readonly int[][] basev = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - private readonly int[][] perm = InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - private readonly int[] minLens = new int[BZip2Constants.N_GROUPS]; + private readonly int[][] limit = InitIntArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + private readonly int[][] basev = InitIntArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + private readonly int[][] perm = InitIntArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + private readonly int[] minLens = new int[BZip2Constants.N_GROUPS]; - private Stream bsStream; + private Stream bsStream; - private bool streamEnd; + private bool streamEnd; - private int currentChar = -1; + private int currentChar = -1; - private const int START_BLOCK_STATE = 1; - private const int RAND_PART_A_STATE = 2; - private const int RAND_PART_B_STATE = 3; - private const int RAND_PART_C_STATE = 4; - private const int NO_RAND_PART_A_STATE = 5; - private const int NO_RAND_PART_B_STATE = 6; - private const int NO_RAND_PART_C_STATE = 7; + private const int START_BLOCK_STATE = 1; + private const int RAND_PART_A_STATE = 2; + private const int RAND_PART_B_STATE = 3; + private const int RAND_PART_C_STATE = 4; + private const int NO_RAND_PART_A_STATE = 5; + private const int NO_RAND_PART_B_STATE = 6; + private const int NO_RAND_PART_C_STATE = 7; - private int currentState = START_BLOCK_STATE; + private int currentState = START_BLOCK_STATE; - private int storedBlockCRC, storedCombinedCRC; - private int computedBlockCRC, computedCombinedCRC; - private readonly bool decompressConcatenated; + private int storedBlockCRC, + storedCombinedCRC; + private int computedBlockCRC, + computedCombinedCRC; + private readonly bool decompressConcatenated; - private int i2, count, chPrev, ch2; - private int i, tPos; - private int rNToGo; - private int rTPos; - private int j2; - private char z; - private bool isDisposed; + private int i2, + count, + chPrev, + ch2; + private int i, + tPos; + private int rNToGo; + private int rTPos; + private int j2; + private char z; + private bool isDisposed; - public CBZip2InputStream(Stream zStream, bool decompressConcatenated) + public CBZip2InputStream(Stream zStream, bool decompressConcatenated) + { + this.decompressConcatenated = decompressConcatenated; + ll8 = null; + tt = null; + BsSetStream(zStream); + Initialize(true); + InitBlock(); + SetupBlock(); + } + + protected override void Dispose(bool disposing) + { + if (isDisposed) { - this.decompressConcatenated = decompressConcatenated; - ll8 = null; - tt = null; - BsSetStream(zStream); - Initialize(true); - InitBlock(); - SetupBlock(); + return; + } + isDisposed = true; + base.Dispose(disposing); + bsStream?.Dispose(); + } + + internal static int[][] InitIntArray(int n1, int n2) + { + var a = new int[n1][]; + for (var k = 0; k < n1; ++k) + { + a[k] = new int[n2]; + } + return a; + } + + internal static char[][] InitCharArray(int n1, int n2) + { + var a = new char[n1][]; + for (var k = 0; k < n1; ++k) + { + a[k] = new char[n2]; + } + return a; + } + + public override int ReadByte() + { + if (streamEnd) + { + return -1; + } + var retChar = currentChar; + switch (currentState) + { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + SetupRandPartB(); + break; + case RAND_PART_C_STATE: + SetupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + SetupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + SetupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + + private bool Initialize(bool isFirstStream) + { + var magic0 = bsStream.ReadByte(); + var magic1 = bsStream.ReadByte(); + var magic2 = bsStream.ReadByte(); + if (magic0 == -1 && !isFirstStream) + { + return false; + } + if (magic0 != 'B' || magic1 != 'Z' || magic2 != 'h') + { + throw new IOException("Not a BZIP2 marked stream"); + } + var magic3 = bsStream.ReadByte(); + if (magic3 < '1' || magic3 > '9') + { + BsFinishedWithStream(); + streamEnd = true; + return false; } - protected override void Dispose(bool disposing) + SetDecompressStructureSizes(magic3 - '0'); + bsLive = 0; + computedCombinedCRC = 0; + return true; + } + + private void InitBlock() + { + char magic1, + magic2, + magic3, + magic4; + char magic5, + magic6; + + while (true) { - if (isDisposed) + magic1 = BsGetUChar(); + magic2 = BsGetUChar(); + magic3 = BsGetUChar(); + magic4 = BsGetUChar(); + magic5 = BsGetUChar(); + magic6 = BsGetUChar(); + if ( + magic1 != 0x17 + || magic2 != 0x72 + || magic3 != 0x45 + || magic4 != 0x38 + || magic5 != 0x50 + || magic6 != 0x90 + ) + { + break; + } + + if (Complete()) { return; } - isDisposed = true; - base.Dispose(disposing); - if (bsStream != null) - { - bsStream.Dispose(); - } } - internal static int[][] InitIntArray(int n1, int n2) + if ( + magic1 != 0x31 + || magic2 != 0x41 + || magic3 != 0x59 + || magic4 != 0x26 + || magic5 != 0x53 + || magic6 != 0x59 + ) { - int[][] a = new int[n1][]; - for (int k = 0; k < n1; ++k) - { - a[k] = new int[n2]; - } - return a; + BadBlockHeader(); + streamEnd = true; + return; } - internal static char[][] InitCharArray(int n1, int n2) + storedBlockCRC = BsGetInt32(); + + if (BsR(1) == 1) { - char[][] a = new char[n1][]; - for (int k = 0; k < n1; ++k) - { - a[k] = new char[n2]; - } - return a; + blockRandomised = true; + } + else + { + blockRandomised = false; } - public override int ReadByte() + // currBlockNo++; + GetAndMoveToFrontDecode(); + + mCrc.InitialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void EndBlock() + { + computedBlockCRC = mCrc.GetFinalCRC(); + /* A bad CRC is considered a fatal error. */ + if (storedBlockCRC != computedBlockCRC) { - if (streamEnd) - { - return -1; - } - int retChar = currentChar; - switch (currentState) - { - case START_BLOCK_STATE: - break; - case RAND_PART_A_STATE: - break; - case RAND_PART_B_STATE: - SetupRandPartB(); - break; - case RAND_PART_C_STATE: - SetupRandPartC(); - break; - case NO_RAND_PART_A_STATE: - break; - case NO_RAND_PART_B_STATE: - SetupNoRandPartB(); - break; - case NO_RAND_PART_C_STATE: - SetupNoRandPartC(); - break; - default: - break; - } - return retChar; + CrcError(); } - private bool Initialize(bool isFirstStream) - { - int magic0 = bsStream.ReadByte(); - int magic1 = bsStream.ReadByte(); - int magic2 = bsStream.ReadByte(); - if (magic0 == -1 && !isFirstStream) - { - return false; - } - if (magic0 != 'B' || magic1 != 'Z' || magic2 != 'h') - { - throw new IOException("Not a BZIP2 marked stream"); - } - int magic3 = bsStream.ReadByte(); - if (magic3 < '1' || magic3 > '9') - { - BsFinishedWithStream(); - streamEnd = true; - return false; - } + computedCombinedCRC = (computedCombinedCRC << 1) | (int)(((uint)computedCombinedCRC) >> 31); + computedCombinedCRC ^= computedBlockCRC; + } - SetDecompressStructureSizes(magic3 - '0'); - bsLive = 0; - computedCombinedCRC = 0; - return true; + private bool Complete() + { + storedCombinedCRC = BsGetInt32(); + if (storedCombinedCRC != computedCombinedCRC) + { + CrcError(); } - private void InitBlock() + var complete = !decompressConcatenated || !Initialize(false); + if (complete) { - char magic1, magic2, magic3, magic4; - char magic5, magic6; + BsFinishedWithStream(); + streamEnd = true; + } - while (true) + // Look for the next .bz2 stream if decompressing + // concatenated files. + return complete; + } + + private static void BlockOverrun() => Cadvise(); + + private static void BadBlockHeader() => Cadvise(); + + private static void CrcError() => Cadvise(); + + private void BsFinishedWithStream() + { + bsStream?.Dispose(); + bsStream = null; + } + + private void BsSetStream(Stream f) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + } + + private int BsR(int n) + { + int v; + while (bsLive < n) + { + int zzi; + int thech = '\0'; + try { - magic1 = BsGetUChar(); - magic2 = BsGetUChar(); - magic3 = BsGetUChar(); - magic4 = BsGetUChar(); - magic5 = BsGetUChar(); - magic6 = BsGetUChar(); - if (magic1 != 0x17 || magic2 != 0x72 || magic3 != 0x45 - || magic4 != 0x38 || magic5 != 0x50 || magic6 != 0x90) + thech = (char)bsStream.ReadByte(); + } + catch (IOException) + { + CompressedStreamEOF(); + } + if (thech == '\uffff') + { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + + v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + private char BsGetUChar() => (char)BsR(8); + + private int BsGetint() + { + var u = 0; + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + u = (u << 8) | BsR(8); + return u; + } + + private int BsGetIntVS(int numBits) => BsR(numBits); + + private int BsGetInt32() => BsGetint(); + + private void HbCreateDecodeTables( + int[] limit, + int[] basev, + int[] perm, + char[] length, + int minLen, + int maxLen, + int alphaSize + ) + { + int pp, + i, + j, + vec; + + pp = 0; + for (i = minLen; i <= maxLen; i++) + { + for (j = 0; j < alphaSize; j++) + { + if (length[j] == i) { - break; - } - - if (Complete()) - { - return; + perm[pp] = j; + pp++; } } + } - if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 - || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) - { - BadBlockHeader(); - streamEnd = true; - return; - } + for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) + { + basev[i] = 0; + } + for (i = 0; i < alphaSize; i++) + { + basev[length[i] + 1]++; + } - storedBlockCRC = BsGetInt32(); + for (i = 1; i < BZip2Constants.MAX_CODE_LEN; i++) + { + basev[i] += basev[i - 1]; + } + for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) + { + limit[i] = 0; + } + vec = 0; + + for (i = minLen; i <= maxLen; i++) + { + vec += (basev[i + 1] - basev[i]); + limit[i] = vec - 1; + vec <<= 1; + } + for (i = minLen + 1; i <= maxLen; i++) + { + basev[i] = ((limit[i - 1] + 1) << 1) - basev[i]; + } + } + + private void RecvDecodingTables() + { + var len = InitCharArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); + int i, + j, + t, + nGroups, + nSelectors, + alphaSize; + int minLen, + maxLen; + var inUse16 = new bool[16]; + + /* Receive the mapping table */ + for (i = 0; i < 16; i++) + { if (BsR(1) == 1) { - blockRandomised = true; + inUse16[i] = true; } else { - blockRandomised = false; + inUse16[i] = false; } - - // currBlockNo++; - GetAndMoveToFrontDecode(); - - mCrc.InitialiseCRC(); - currentState = START_BLOCK_STATE; } - private void EndBlock() + for (i = 0; i < 256; i++) { - computedBlockCRC = mCrc.GetFinalCRC(); - /* A bad CRC is considered a fatal error. */ - if (storedBlockCRC != computedBlockCRC) + inUse[i] = false; + } + + for (i = 0; i < 16; i++) + { + if (inUse16[i]) { - CrcError(); - } - - computedCombinedCRC = (computedCombinedCRC << 1) - | (int)(((uint)computedCombinedCRC) >> 31); - computedCombinedCRC ^= computedBlockCRC; - } - - private bool Complete() - { - storedCombinedCRC = BsGetInt32(); - if (storedCombinedCRC != computedCombinedCRC) - { - CrcError(); - } - - bool complete = !decompressConcatenated || !Initialize(false); - if (complete) - { - BsFinishedWithStream(); - streamEnd = true; - } - - // Look for the next .bz2 stream if decompressing - // concatenated files. - return complete; - } - - private static void BlockOverrun() - { - Cadvise(); - } - - private static void BadBlockHeader() - { - Cadvise(); - } - - private static void CrcError() - { - Cadvise(); - } - - private void BsFinishedWithStream() - { - bsStream?.Dispose(); - bsStream = null; - } - - private void BsSetStream(Stream f) - { - bsStream = f; - bsLive = 0; - bsBuff = 0; - } - - private int BsR(int n) - { - int v; - while (bsLive < n) - { - int zzi; - int thech = '\0'; - try + for (j = 0; j < 16; j++) { - thech = (char)bsStream.ReadByte(); - } - catch (IOException) - { - CompressedStreamEOF(); - } - if (thech == '\uffff') - { - CompressedStreamEOF(); - } - zzi = thech; - bsBuff = (bsBuff << 8) | (zzi & 0xff); - bsLive += 8; - } - - v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); - bsLive -= n; - return v; - } - - private char BsGetUChar() - { - return (char)BsR(8); - } - - private int BsGetint() - { - int u = 0; - u = (u << 8) | BsR(8); - u = (u << 8) | BsR(8); - u = (u << 8) | BsR(8); - u = (u << 8) | BsR(8); - return u; - } - - private int BsGetIntVS(int numBits) - { - return BsR(numBits); - } - - private int BsGetInt32() - { - return BsGetint(); - } - - private void HbCreateDecodeTables(int[] limit, int[] basev, - int[] perm, char[] length, - int minLen, int maxLen, int alphaSize) - { - int pp, i, j, vec; - - pp = 0; - for (i = minLen; i <= maxLen; i++) - { - for (j = 0; j < alphaSize; j++) - { - if (length[j] == i) + if (BsR(1) == 1) { - perm[pp] = j; - pp++; + inUse[(i * 16) + j] = true; } } } - - for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) - { - basev[i] = 0; - } - for (i = 0; i < alphaSize; i++) - { - basev[length[i] + 1]++; - } - - for (i = 1; i < BZip2Constants.MAX_CODE_LEN; i++) - { - basev[i] += basev[i - 1]; - } - - for (i = 0; i < BZip2Constants.MAX_CODE_LEN; i++) - { - limit[i] = 0; - } - vec = 0; - - for (i = minLen; i <= maxLen; i++) - { - vec += (basev[i + 1] - basev[i]); - limit[i] = vec - 1; - vec <<= 1; - } - for (i = minLen + 1; i <= maxLen; i++) - { - basev[i] = ((limit[i - 1] + 1) << 1) - basev[i]; - } } - private void RecvDecodingTables() + MakeMaps(); + alphaSize = nInUse + 2; + + /* Now the selectors */ + nGroups = BsR(3); + nSelectors = BsR(15); + for (i = 0; i < nSelectors; i++) { - char[][] len = InitCharArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - int i, j, t, nGroups, nSelectors, alphaSize; - int minLen, maxLen; - bool[] inUse16 = new bool[16]; - - /* Receive the mapping table */ - for (i = 0; i < 16; i++) + j = 0; + while (BsR(1) == 1) { - if (BsR(1) == 1) - { - inUse16[i] = true; - } - else - { - inUse16[i] = false; - } + j++; + } + selectorMtf[i] = (char)j; + } + + /* Undo the MTF values for the selectors. */ + { + var pos = new char[BZip2Constants.N_GROUPS]; + char tmp, + v; + for (v = '\0'; v < nGroups; v++) + { + pos[v] = v; } - for (i = 0; i < 256; i++) - { - inUse[i] = false; - } - - for (i = 0; i < 16; i++) - { - if (inUse16[i]) - { - for (j = 0; j < 16; j++) - { - if (BsR(1) == 1) - { - inUse[i * 16 + j] = true; - } - } - } - } - - MakeMaps(); - alphaSize = nInUse + 2; - - /* Now the selectors */ - nGroups = BsR(3); - nSelectors = BsR(15); for (i = 0; i < nSelectors; i++) { - j = 0; - while (BsR(1) == 1) + v = selectorMtf[i]; + tmp = pos[v]; + while (v > 0) { - j++; + pos[v] = pos[v - 1]; + v--; } - selectorMtf[i] = (char)j; - } - - /* Undo the MTF values for the selectors. */ - { - char[] pos = new char[BZip2Constants.N_GROUPS]; - char tmp, v; - for (v = '\0'; v < nGroups; v++) - { - pos[v] = v; - } - - for (i = 0; i < nSelectors; i++) - { - v = selectorMtf[i]; - tmp = pos[v]; - while (v > 0) - { - pos[v] = pos[v - 1]; - v--; - } - pos[0] = tmp; - selector[i] = tmp; - } - } - - /* Now the coding tables */ - for (t = 0; t < nGroups; t++) - { - int curr = BsR(5); - for (i = 0; i < alphaSize; i++) - { - while (BsR(1) == 1) - { - if (BsR(1) == 0) - { - curr++; - } - else - { - curr--; - } - } - len[t][i] = (char)curr; - } - } - - /* Create the Huffman decoding tables */ - for (t = 0; t < nGroups; t++) - { - minLen = 32; - maxLen = 0; - for (i = 0; i < alphaSize; i++) - { - if (len[t][i] > maxLen) - { - maxLen = len[t][i]; - } - if (len[t][i] < minLen) - { - minLen = len[t][i]; - } - } - HbCreateDecodeTables(limit[t], basev[t], perm[t], len[t], minLen, - maxLen, alphaSize); - minLens[t] = minLen; + pos[0] = tmp; + selector[i] = tmp; } } - private void GetAndMoveToFrontDecode() + /* Now the coding tables */ + for (t = 0; t < nGroups; t++) { - char[] yy = new char[256]; - int i, j, nextSym, limitLast; - int EOB, groupNo, groupPos; - - limitLast = BZip2Constants.baseBlockSize * blockSize100k; - origPtr = BsGetIntVS(24); - - RecvDecodingTables(); - EOB = nInUse + 1; - groupNo = -1; - groupPos = 0; - - /* - Setting up the unzftab entries here is not strictly - necessary, but it does save having to do it later - in a separate pass, and so saves a block's worth of - cache misses. - */ - for (i = 0; i <= 255; i++) + var curr = BsR(5); + for (i = 0; i < alphaSize; i++) { - unzftab[i] = 0; + while (BsR(1) == 1) + { + if (BsR(1) == 0) + { + curr++; + } + else + { + curr--; + } + } + len[t][i] = (char)curr; + } + } + + /* Create the Huffman decoding tables */ + for (t = 0; t < nGroups; t++) + { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) + { + if (len[t][i] > maxLen) + { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) + { + minLen = len[t][i]; + } + } + HbCreateDecodeTables(limit[t], basev[t], perm[t], len[t], minLen, maxLen, alphaSize); + minLens[t] = minLen; + } + } + + private void GetAndMoveToFrontDecode() + { + var yy = new char[256]; + int i, + j, + nextSym, + limitLast; + int EOB, + groupNo, + groupPos; + + limitLast = BZip2Constants.baseBlockSize * blockSize100k; + origPtr = BsGetIntVS(24); + + RecvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + */ + for (i = 0; i <= 255; i++) + { + unzftab[i] = 0; + } + + for (i = 0; i <= 255; i++) + { + yy[i] = (char)i; + } + + last = -1; + + { + int zt, + zn, + zvec, + zj; + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) + { + zn++; + { + { + while (bsLive < 1) + { + int zzi; + var thech = '\0'; + try + { + thech = (char)bsStream.ReadByte(); + } + catch (IOException) + { + CompressedStreamEOF(); + } + if (thech == '\uffff') + { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - basev[zt][zn]]; + } + + while (true) + { + if (nextSym == EOB) + { + break; } - for (i = 0; i <= 255; i++) + if (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB) { - yy[i] = (char)i; - } - - last = -1; - - { - int zt, zn, zvec, zj; - if (groupPos == 0) + char ch; + var s = -1; + var N = 1; + do { - groupNo++; - groupPos = BZip2Constants.G_SIZE; - } - groupPos--; - zt = selector[groupNo]; - zn = minLens[zt]; - zvec = BsR(zn); - while (zvec > limit[zt][zn]) - { - zn++; + if (nextSym == BZip2Constants.RUNA) { - { - while (bsLive < 1) - { - int zzi; - char thech = '\0'; - try - { - thech = (char)bsStream.ReadByte(); - } - catch (IOException) - { - CompressedStreamEOF(); - } - if (thech == '\uffff') - { - CompressedStreamEOF(); - } - zzi = thech; - bsBuff = (bsBuff << 8) | (zzi & 0xff); - bsLive += 8; - } - } - zj = (bsBuff >> (bsLive - 1)) & 1; - bsLive--; + s += (0 + 1) * N; } - zvec = (zvec << 1) | zj; - } - nextSym = perm[zt][zvec - basev[zt][zn]]; - } - - while (true) - { - if (nextSym == EOB) - { - break; - } - - if (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB) - { - char ch; - int s = -1; - int N = 1; - do + else if (nextSym == BZip2Constants.RUNB) { - if (nextSym == BZip2Constants.RUNA) - { - s = s + (0 + 1) * N; - } - else if (nextSym == BZip2Constants.RUNB) - { - s = s + (1 + 1) * N; - } - N = N * 2; - { - int zt, zn, zvec, zj; - if (groupPos == 0) - { - groupNo++; - groupPos = BZip2Constants.G_SIZE; - } - groupPos--; - zt = selector[groupNo]; - zn = minLens[zt]; - zvec = BsR(zn); - while (zvec > limit[zt][zn]) - { - zn++; - { - { - while (bsLive < 1) - { - int zzi; - char thech = '\0'; - try - { - thech = (char)bsStream.ReadByte(); - } - catch (IOException) - { - CompressedStreamEOF(); - } - if (thech == '\uffff') - { - CompressedStreamEOF(); - } - zzi = thech; - bsBuff = (bsBuff << 8) | (zzi & 0xff); - bsLive += 8; - } - } - zj = (bsBuff >> (bsLive - 1)) & 1; - bsLive--; - } - zvec = (zvec << 1) | zj; - } - nextSym = perm[zt][zvec - basev[zt][zn]]; - } + s += (1 + 1) * N; } - while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB); - - s++; - ch = seqToUnseq[yy[0]]; - unzftab[ch] += s; - - while (s > 0) + N *= 2; { - last++; - ll8[last] = ch; - s--; - } - - if (last >= limitLast) - { - BlockOverrun(); - } - } - else - { - char tmp; - last++; - if (last >= limitLast) - { - BlockOverrun(); - } - - tmp = yy[nextSym - 1]; - unzftab[seqToUnseq[tmp]]++; - ll8[last] = seqToUnseq[tmp]; - - /* - This loop is hammered during decompression, - hence the unrolling. - - for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; - */ - - j = nextSym - 1; - for (; j > 3; j -= 4) - { - yy[j] = yy[j - 1]; - yy[j - 1] = yy[j - 2]; - yy[j - 2] = yy[j - 3]; - yy[j - 3] = yy[j - 4]; - } - for (; j > 0; j--) - { - yy[j] = yy[j - 1]; - } - - yy[0] = tmp; - { - int zt, zn, zvec, zj; + int zt, + zn, + zvec, + zj; if (groupPos == 0) { groupNo++; @@ -809,7 +738,7 @@ namespace SharpCompress.Compressors.BZip2 while (bsLive < 1) { int zzi; - char thech = '\0'; + var thech = '\0'; try { thech = (char)bsStream.ReadByte(); @@ -818,6 +747,10 @@ namespace SharpCompress.Compressors.BZip2 { CompressedStreamEOF(); } + if (thech == '\uffff') + { + CompressedStreamEOF(); + } zzi = thech; bsBuff = (bsBuff << 8) | (zzi & 0xff); bsLive += 8; @@ -830,56 +763,212 @@ namespace SharpCompress.Compressors.BZip2 } nextSym = perm[zt][zvec - basev[zt][zn]]; } + } while (nextSym == BZip2Constants.RUNA || nextSym == BZip2Constants.RUNB); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) + { + last++; + ll8[last] = ch; + s--; } - } - } - private void SetupBlock() - { - Span cftab = stackalloc int[257]; - char ch; - - cftab[0] = 0; - for (i = 1; i <= 256; i++) - { - cftab[i] = unzftab[i - 1]; - } - for (i = 1; i <= 256; i++) - { - cftab[i] += cftab[i - 1]; - } - - for (i = 0; i <= last; i++) - { - ch = ll8[i]; - tt[cftab[ch]] = i; - cftab[ch]++; - } - - tPos = tt[origPtr]; - - count = 0; - i2 = 0; - ch2 = 256; /* not a char and not EOF */ - - if (blockRandomised) - { - rNToGo = 0; - rTPos = 0; - SetupRandPartA(); + if (last >= limitLast) + { + BlockOverrun(); + } } else { - SetupNoRandPartA(); + char tmp; + last++; + if (last >= limitLast) + { + BlockOverrun(); + } + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + This loop is hammered during decompression, + hence the unrolling. + + for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + + j = nextSym - 1; + for (; j > 3; j -= 4) + { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for (; j > 0; j--) + { + yy[j] = yy[j - 1]; + } + + yy[0] = tmp; + { + int zt, + zn, + zvec, + zj; + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) + { + zn++; + { + { + while (bsLive < 1) + { + int zzi; + var thech = '\0'; + try + { + thech = (char)bsStream.ReadByte(); + } + catch (IOException) + { + CompressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - basev[zt][zn]]; + } } } + } - private void SetupRandPartA() + private void SetupBlock() + { + Span cftab = stackalloc int[257]; + char ch; + + cftab[0] = 0; + for (i = 1; i <= 256; i++) { - if (i2 <= last) + cftab[i] = unzftab[i - 1]; + } + for (i = 1; i <= 256; i++) + { + cftab[i] += cftab[i - 1]; + } + + for (i = 0; i <= last; i++) + { + ch = ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /* not a char and not EOF */ + + if (blockRandomised) + { + rNToGo = 0; + rTPos = 0; + SetupRandPartA(); + } + else + { + SetupNoRandPartA(); + } + } + + private void SetupRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { - chPrev = ch2; - ch2 = ll8[tPos]; + rNToGo = BZip2Constants.rNums[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (rNToGo == 1) ? 1 : 0; + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.UpdateCRC(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupNoRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.UpdateCRC(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupRandPartB() + { + if (ch2 != chPrev) + { + currentState = RAND_PART_A_STATE; + count = 1; + SetupRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; tPos = tt[tPos]; if (rNToGo == 0) { @@ -891,206 +980,136 @@ namespace SharpCompress.Compressors.BZip2 } } rNToGo--; - ch2 ^= (rNToGo == 1) ? 1 : 0; - i2++; - - currentChar = ch2; - currentState = RAND_PART_B_STATE; - mCrc.UpdateCRC(ch2); - } - else - { - EndBlock(); - InitBlock(); - SetupBlock(); - } - } - - private void SetupNoRandPartA() - { - if (i2 <= last) - { - chPrev = ch2; - ch2 = ll8[tPos]; - tPos = tt[tPos]; - i2++; - - currentChar = ch2; - currentState = NO_RAND_PART_B_STATE; - mCrc.UpdateCRC(ch2); - } - else - { - EndBlock(); - InitBlock(); - SetupBlock(); - } - } - - private void SetupRandPartB() - { - if (ch2 != chPrev) - { - currentState = RAND_PART_A_STATE; - count = 1; - SetupRandPartA(); - } - else - { - count++; - if (count >= 4) - { - z = ll8[tPos]; - tPos = tt[tPos]; - if (rNToGo == 0) - { - rNToGo = BZip2Constants.rNums[rTPos]; - rTPos++; - if (rTPos == 512) - { - rTPos = 0; - } - } - rNToGo--; - z ^= (char)((rNToGo == 1) ? 1 : 0); - j2 = 0; - currentState = RAND_PART_C_STATE; - SetupRandPartC(); - } - else - { - currentState = RAND_PART_A_STATE; - SetupRandPartA(); - } - } - } - - private void SetupRandPartC() - { - if (j2 < z) - { - currentChar = ch2; - mCrc.UpdateCRC(ch2); - j2++; + z ^= (char)((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + SetupRandPartC(); } else { currentState = RAND_PART_A_STATE; - i2++; - count = 0; SetupRandPartA(); } } - - private void SetupNoRandPartB() - { - if (ch2 != chPrev) - { - currentState = NO_RAND_PART_A_STATE; - count = 1; - SetupNoRandPartA(); - } - else - { - count++; - if (count >= 4) - { - z = ll8[tPos]; - tPos = tt[tPos]; - currentState = NO_RAND_PART_C_STATE; - j2 = 0; - SetupNoRandPartC(); - } - else - { - currentState = NO_RAND_PART_A_STATE; - SetupNoRandPartA(); - } - } - } - - private void SetupNoRandPartC() - { - if (j2 < z) - { - currentChar = ch2; - mCrc.UpdateCRC(ch2); - j2++; - } - else - { - currentState = NO_RAND_PART_A_STATE; - i2++; - count = 0; - SetupNoRandPartA(); - } - } - - private void SetDecompressStructureSizes(int newSize100k) - { - if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k - && blockSize100k <= 9)) - { - // throw new IOException("Invalid block size"); - } - - blockSize100k = newSize100k; - - if (newSize100k == 0) - { - return; - } - - int n = BZip2Constants.baseBlockSize * newSize100k; - ll8 = new char[n]; - tt = new int[n]; - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int offset, int count) - { - int c = -1; - int k; - for (k = 0; k < count; ++k) - { - c = ReadByte(); - if (c == -1) - { - break; - } - buffer[k + offset] = (byte)c; - } - return k; - } - - public override long Seek(long offset, SeekOrigin origin) - { - return 0; - } - - public override void SetLength(long value) - { - } - - public override void Write(byte[] buffer, int offset, int count) - { - } - - public override void WriteByte(byte value) - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => 0; - - public override long Position { get { return 0; } set { } } } -} \ No newline at end of file + + private void SetupRandPartC() + { + if (j2 < z) + { + currentChar = ch2; + mCrc.UpdateCRC(ch2); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + SetupRandPartA(); + } + } + + private void SetupNoRandPartB() + { + if (ch2 != chPrev) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + SetupNoRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + SetupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + SetupNoRandPartA(); + } + } + } + + private void SetupNoRandPartC() + { + if (j2 < z) + { + currentChar = ch2; + mCrc.UpdateCRC(ch2); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + SetupNoRandPartA(); + } + } + + private void SetDecompressStructureSizes(int newSize100k) + { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) + { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) + { + return; + } + + var n = BZip2Constants.baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + public override void Flush() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var c = -1; + int k; + for (k = 0; k < count; ++k) + { + c = ReadByte(); + if (c == -1) + { + break; + } + buffer[k + offset] = (byte)c; + } + return k; + } + + public override long Seek(long offset, SeekOrigin origin) => 0; + + public override void SetLength(long value) { } + + public override void Write(byte[] buffer, int offset, int count) { } + + public override void WriteByte(byte value) { } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => 0; + + public override long Position + { + get => 0; + set { } + } +} diff --git a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs index b86695bd..4f8a240b 100644 --- a/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs +++ b/src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; /* @@ -25,986 +25,763 @@ using System.IO; #nullable disable -namespace SharpCompress.Compressors.BZip2 +namespace SharpCompress.Compressors.BZip2; + +/** + * An output stream that compresses into the BZip2 format (with the file + * header chars) into another stream. + * + * @author Keiron Liddle + * + * TODO: Update to BZip2 1.0.1 + * NB: note this class has been modified to add a leading BZ to the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ + +internal sealed class CBZip2OutputStream : Stream { - /** - * An output stream that compresses into the BZip2 format (with the file - * header chars) into another stream. - * - * @author Keiron Liddle - * - * TODO: Update to BZip2 1.0.1 - * NB: note this class has been modified to add a leading BZ to the - * start of the BZIP2 stream to make it compatible with other PGP programs. - */ + private const int SETMASK = (1 << 21); + private const int CLEARMASK = (~SETMASK); + private const int GREATER_ICOST = 15; + private const int LESSER_ICOST = 0; + private const int SMALL_THRESH = 20; + private const int DEPTH_THRESH = 10; - internal sealed class CBZip2OutputStream : Stream + /* + If you are ever unlucky/improbable enough + to get a stack overflow whilst sorting, + increase the following constant and try + again. In practice I have never seen the + stack go above 27 elems, so the following + limit seems very generous. + */ + private const int QSORT_STACK_SIZE = 1000; + private bool finished; + + private static void Panic() { - private const int SETMASK = (1 << 21); - private const int CLEARMASK = (~SETMASK); - private const int GREATER_ICOST = 15; - private const int LESSER_ICOST = 0; - private const int SMALL_THRESH = 20; - private const int DEPTH_THRESH = 10; + //System.out.Println("panic"); + //throw new CError(); + } - /* - If you are ever unlucky/improbable enough - to get a stack overflow whilst sorting, - increase the following constant and try - again. In practice I have never seen the - stack go above 27 elems, so the following - limit seems very generous. - */ - private const int QSORT_STACK_SIZE = 1000; - private bool finished; - - private static void Panic() + private void MakeMaps() + { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { - //System.out.Println("panic"); - //throw new CError(); + if (inUse[i]) + { + seqToUnseq[nInUse] = (char)i; + unseqToSeq[i] = (char)nInUse; + nInUse++; + } + } + } + + private static void HbMakeCodeLengths(char[] len, int[] freq, int alphaSize, int maxLen) + { + /* + Nodes and heap entries run from 1. Entry 0 + for both the heap and nodes is a sentinel. + */ + int nNodes, + nHeap, + n1, + n2, + i, + j, + k; + bool tooLong; + + Span heap = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE + 2]; // 1040 bytes + Span weight = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE * 2]; // 1040 bytes + Span parent = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE * 2]; // 1040 bytes + + for (i = 0; i < alphaSize; i++) + { + weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; } - private void MakeMaps() + while (true) { - int i; - nInUse = 0; - for (i = 0; i < 256; i++) + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for (i = 1; i <= alphaSize; i++) { - if (inUse[i]) + parent[i] = -1; + nHeap++; + heap[nHeap] = i; { - seqToUnseq[nInUse] = (char)i; - unseqToSeq[i] = (char)nInUse; - nInUse++; + int zz, + tmp; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; } } - } - - private static void HbMakeCodeLengths(char[] len, int[] freq, - int alphaSize, int maxLen) - { - /* - Nodes and heap entries run from 1. Entry 0 - for both the heap and nodes is a sentinel. - */ - int nNodes, nHeap, n1, n2, i, j, k; - bool tooLong; - - Span heap = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE + 2]; // 1040 bytes - Span weight = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE * 2]; // 1040 bytes - Span parent = stackalloc int[BZip2Constants.MAX_ALPHA_SIZE * 2]; // 1040 bytes - - for (i = 0; i < alphaSize; i++) + if (!(nHeap < (BZip2Constants.MAX_ALPHA_SIZE + 2))) { - weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; + Panic(); } - while (true) + while (nHeap > 1) { - nNodes = alphaSize; - nHeap = 0; - - heap[0] = 0; - weight[0] = 0; - parent[0] = -2; - - for (i = 1; i <= alphaSize; i++) + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; { - parent[i] = -1; - nHeap++; - heap[nHeap] = i; + int zz = 0, + yy = 0, + tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { - int zz, tmp; - zz = nHeap; - tmp = heap[zz]; - while (weight[tmp] < weight[heap[zz >> 1]]) + yy = zz << 1; + if (yy > nHeap) { - heap[zz] = heap[zz >> 1]; - zz >>= 1; + break; } - heap[zz] = tmp; - } - } - if (!(nHeap < (BZip2Constants.MAX_ALPHA_SIZE + 2))) - { - Panic(); - } - - while (nHeap > 1) - { - n1 = heap[1]; - heap[1] = heap[nHeap]; - nHeap--; - { - int zz = 0, yy = 0, tmp = 0; - zz = 1; - tmp = heap[zz]; - while (true) + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) { - yy = zz << 1; - if (yy > nHeap) - { - break; - } - if (yy < nHeap - && weight[heap[yy + 1]] < weight[heap[yy]]) - { - yy++; - } - if (weight[tmp] < weight[heap[yy]]) - { - break; - } - heap[zz] = heap[yy]; - zz = yy; + yy++; } - heap[zz] = tmp; - } - n2 = heap[1]; - heap[1] = heap[nHeap]; - nHeap--; - { - int zz = 0, yy = 0, tmp = 0; - zz = 1; - tmp = heap[zz]; - while (true) + if (weight[tmp] < weight[heap[yy]]) { - yy = zz << 1; - if (yy > nHeap) - { - break; - } - if (yy < nHeap - && weight[heap[yy + 1]] < weight[heap[yy]]) - { - yy++; - } - if (weight[tmp] < weight[heap[yy]]) - { - break; - } - heap[zz] = heap[yy]; - zz = yy; + break; } - heap[zz] = tmp; + heap[zz] = heap[yy]; + zz = yy; } - nNodes++; - parent[n1] = parent[n2] = nNodes; - - weight[nNodes] = (int)((uint)((weight[n1] & 0xffffff00) - + (weight[n2] & 0xffffff00)) - | (uint)(1 + (((weight[n1] & 0x000000ff) > - (weight[n2] & 0x000000ff)) - ? (weight[n1] & 0x000000ff) - : (weight[n2] & 0x000000ff)))); - - parent[nNodes] = -1; - nHeap++; - heap[nHeap] = nNodes; + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0, + yy = 0, + tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { - int zz = 0, tmp = 0; - zz = nHeap; - tmp = heap[zz]; - while (weight[tmp] < weight[heap[zz >> 1]]) + yy = zz << 1; + if (yy > nHeap) { - heap[zz] = heap[zz >> 1]; - zz >>= 1; + break; } - heap[zz] = tmp; + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) + { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) + { + break; + } + heap[zz] = heap[yy]; + zz = yy; } + heap[zz] = tmp; } - if (!(nNodes < (BZip2Constants.MAX_ALPHA_SIZE * 2))) - { - Panic(); - } + nNodes++; + parent[n1] = parent[n2] = nNodes; - tooLong = false; - for (i = 1; i <= alphaSize; i++) + weight[nNodes] = (int)( + (uint)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) + | (uint)( + 1 + + ( + ((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) + ? (weight[n1] & 0x000000ff) + : (weight[n2] & 0x000000ff) + ) + ) + ); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; { - j = 0; - k = i; - while (parent[k] >= 0) + int zz = 0, + tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) { - k = parent[k]; - j++; + heap[zz] = heap[zz >> 1]; + zz >>= 1; } - len[i - 1] = (char)j; - if (j > maxLen) - { - tooLong = true; - } - } - - if (!tooLong) - { - break; - } - - for (i = 1; i < alphaSize; i++) - { - j = weight[i] >> 8; - j = 1 + (j / 2); - weight[i] = j << 8; + heap[zz] = tmp; } } - } - - /* - index of the last char in the block, so - the block size == last + 1. - */ - private int last; - - /* - index in zptr[] of original string after sorting. - */ - private int origPtr; - - /* - always: in the range 0 .. 9. - The current block size is 100000 * this number. - */ - private readonly int blockSize100k; - - private bool blockRandomised; - - private int bytesOut; - private int bsBuff; - private int bsLive; - private readonly CRC mCrc = new CRC(); - - private readonly bool[] inUse = new bool[256]; - private int nInUse; - - private readonly char[] seqToUnseq = new char[256]; - private readonly char[] unseqToSeq = new char[256]; - - private readonly char[] selector = new char[BZip2Constants.MAX_SELECTORS]; - private readonly char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; - - private char[] block; - private int[] quadrant; - private int[] zptr; - private short[] szptr; - private int[] ftab; - - private int nMTF; - - private readonly int[] mtfFreq = new int[BZip2Constants.MAX_ALPHA_SIZE]; - - /* - * Used when sorting. If too many long comparisons - * happen, we stop sorting, randomise the block - * slightly, and try again. - */ - private readonly int workFactor; - private int workDone; - private int workLimit; - private bool firstAttempt; - private int nBlocksRandomised; - - private int currentChar = -1; - private int runLength; - - public CBZip2OutputStream(Stream inStream) - : this(inStream, 9) - { - } - - public CBZip2OutputStream(Stream inStream, int inBlockSize) - { - block = null; - quadrant = null; - zptr = null; - ftab = null; - - inStream.WriteByte((byte)'B'); - inStream.WriteByte((byte)'Z'); - - BsSetStream(inStream); - - workFactor = 50; - if (inBlockSize > 9) + if (!(nNodes < (BZip2Constants.MAX_ALPHA_SIZE * 2))) { - inBlockSize = 9; + Panic(); } - if (inBlockSize < 1) - { - inBlockSize = 1; - } - blockSize100k = inBlockSize; - AllocateCompressStructures(); - Initialize(); - InitBlock(); - } - /** - * - * modified by Oliver Merkel, 010128 - * - */ - - public override void WriteByte(byte bv) - { - int b = (256 + bv) % 256; - if (currentChar != -1) + tooLong = false; + for (i = 1; i <= alphaSize; i++) { - if (currentChar == b) + j = 0; + k = i; + while (parent[k] >= 0) { - runLength++; - if (runLength > 254) - { - WriteRun(); - currentChar = -1; - runLength = 0; - } + k = parent[k]; + j++; } - else + len[i - 1] = (char)j; + if (j > maxLen) + { + tooLong = true; + } + } + + if (!tooLong) + { + break; + } + + for (i = 1; i < alphaSize; i++) + { + j = weight[i] >> 8; + j = 1 + (j / 2); + weight[i] = j << 8; + } + } + } + + /* + index of the last char in the block, so + the block size == last + 1. + */ + private int last; + + /* + index in zptr[] of original string after sorting. + */ + private int origPtr; + + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + private readonly int blockSize100k; + + private bool blockRandomised; + + private int bytesOut; + private int bsBuff; + private int bsLive; + private readonly CRC mCrc = new CRC(); + + private readonly bool[] inUse = new bool[256]; + private int nInUse; + + private readonly char[] seqToUnseq = new char[256]; + private readonly char[] unseqToSeq = new char[256]; + + private readonly char[] selector = new char[BZip2Constants.MAX_SELECTORS]; + private readonly char[] selectorMtf = new char[BZip2Constants.MAX_SELECTORS]; + + private char[] block; + private int[] quadrant; + private int[] zptr; + private short[] szptr; + private int[] ftab; + + private int nMTF; + + private readonly int[] mtfFreq = new int[BZip2Constants.MAX_ALPHA_SIZE]; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private readonly int workFactor; + private int workDone; + private int workLimit; + private bool firstAttempt; + private int nBlocksRandomised; + + private int currentChar = -1; + private int runLength; + + public CBZip2OutputStream(Stream inStream) : this(inStream, 9) { } + + public CBZip2OutputStream(Stream inStream, int inBlockSize) + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + inStream.WriteByte((byte)'B'); + inStream.WriteByte((byte)'Z'); + + BsSetStream(inStream); + + workFactor = 50; + if (inBlockSize > 9) + { + inBlockSize = 9; + } + if (inBlockSize < 1) + { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + AllocateCompressStructures(); + Initialize(); + InitBlock(); + } + + /** + * + * modified by Oliver Merkel, 010128 + * + */ + + public override void WriteByte(byte bv) + { + var b = (256 + bv) % 256; + if (currentChar != -1) + { + if (currentChar == b) + { + runLength++; + if (runLength > 254) { WriteRun(); - runLength = 1; - currentChar = b; + currentChar = -1; + runLength = 0; } } else { - currentChar = b; - runLength++; - } - } - - private void WriteRun() - { - if (last < allowableBlockSize) - { - inUse[currentChar] = true; - for (int i = 0; i < runLength; i++) - { - mCrc.UpdateCRC((char)currentChar); - } - switch (runLength) - { - case 1: - last++; - block[last + 1] = (char)currentChar; - break; - case 2: - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - break; - case 3: - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - break; - default: - inUse[runLength - 4] = true; - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)currentChar; - last++; - block[last + 1] = (char)(runLength - 4); - break; - } - } - else - { - EndBlock(); - InitBlock(); WriteRun(); + runLength = 1; + currentChar = b; } } - - private bool disposed; - - protected override void Dispose(bool disposing) + else { - if (disposing) + currentChar = b; + runLength++; + } + } + + private void WriteRun() + { + if (last < allowableBlockSize) + { + inUse[currentChar] = true; + for (var i = 0; i < runLength; i++) { - if (disposed) - { - return; - } - - Finish(); - - disposed = true; - base.Dispose(); - bsStream?.Dispose(); - bsStream = null; + mCrc.UpdateCRC((char)currentChar); + } + switch (runLength) + { + case 1: + last++; + block[last + 1] = (char)currentChar; + break; + case 2: + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + break; + case 3: + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)currentChar; + last++; + block[last + 1] = (char)(runLength - 4); + break; } } - - public void Finish() + else { - if (finished) + EndBlock(); + InitBlock(); + WriteRun(); + } + } + + private bool disposed; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (disposed) { return; } - if (runLength > 0) + Finish(); + + disposed = true; + Dispose(); + bsStream?.Dispose(); + bsStream = null; + } + } + + public void Finish() + { + if (finished) + { + return; + } + + if (runLength > 0) + { + WriteRun(); + } + currentChar = -1; + EndBlock(); + EndCompression(); + finished = true; + Flush(); + } + + public override void Flush() => bsStream.Flush(); + + private int blockCRC, + combinedCRC; + + private void Initialize() + { + bytesOut = 0; + nBlocksRandomised = 0; + + /* Write `magic' bytes h indicating file-format == huffmanised, + followed by a digit indicating blockSize100k. + */ + BsPutUChar('h'); + BsPutUChar('0' + blockSize100k); + + combinedCRC = 0; + } + + private int allowableBlockSize; + + private void InitBlock() + { + // blockNo++; + mCrc.InitialiseCRC(); + last = -1; + + // ch = 0; + + for (var i = 0; i < 256; i++) + { + inUse[i] = false; + } + + /* 20 is just a paranoia constant */ + allowableBlockSize = (BZip2Constants.baseBlockSize * blockSize100k) - 20; + } + + private void EndBlock() + { + blockCRC = mCrc.GetFinalCRC(); + combinedCRC = (combinedCRC << 1) | (int)(((uint)combinedCRC) >> 31); + combinedCRC ^= blockCRC; + + /* sort the block and establish posn of original string */ + DoReversibleTransformation(); + + /* + A 6-byte block header, the value chosen arbitrarily + as 0x314159265359 :-). A 32 bit value does not really + give a strong enough guarantee that the value will not + appear by chance in the compressed datastream. Worst-case + probability of this event, for a 900k block, is about + 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + For a compressed file of size 100Gb -- about 100000 blocks -- + only a 48-bit marker will do. NB: normal compression/ + decompression do *not* rely on these statistical properties. + They are only important when trying to recover blocks from + damaged files. + */ + BsPutUChar(0x31); + BsPutUChar(0x41); + BsPutUChar(0x59); + BsPutUChar(0x26); + BsPutUChar(0x53); + BsPutUChar(0x59); + + /* Now the block's CRC, so it is in a known place. */ + BsPutint(blockCRC); + + /* Now a single bit indicating randomisation. */ + if (blockRandomised) + { + BsW(1, 1); + nBlocksRandomised++; + } + else + { + BsW(1, 0); + } + + /* Finally, block's contents proper. */ + MoveToFrontCodeAndSend(); + } + + private void EndCompression() + { + /* + Now another magic 48-bit number, 0x177245385090, to + indicate the end of the last block. (Sqrt(pi), if + you want to know. I did want to use e, but it contains + too much repetition -- 27 18 28 18 28 46 -- for me + to feel statistically comfortable. Call me paranoid.) + */ + BsPutUChar(0x17); + BsPutUChar(0x72); + BsPutUChar(0x45); + BsPutUChar(0x38); + BsPutUChar(0x50); + BsPutUChar(0x90); + + BsPutint(combinedCRC); + + BsFinishedWithStream(); + } + + private void HbAssignCodes(int[] code, char[] length, int minLen, int maxLen, int alphaSize) + { + int n, + vec, + i; + + vec = 0; + for (n = minLen; n <= maxLen; n++) + { + for (i = 0; i < alphaSize; i++) { - WriteRun(); - } - currentChar = -1; - EndBlock(); - EndCompression(); - finished = true; - Flush(); - } - - public override void Flush() - { - bsStream.Flush(); - } - - private int blockCRC, combinedCRC; - - private void Initialize() - { - bytesOut = 0; - nBlocksRandomised = 0; - - /* Write `magic' bytes h indicating file-format == huffmanised, - followed by a digit indicating blockSize100k. - */ - BsPutUChar('h'); - BsPutUChar('0' + blockSize100k); - - combinedCRC = 0; - } - - private int allowableBlockSize; - - private void InitBlock() - { - // blockNo++; - mCrc.InitialiseCRC(); - last = -1; - - // ch = 0; - - for (int i = 0; i < 256; i++) - { - inUse[i] = false; - } - - /* 20 is just a paranoia constant */ - allowableBlockSize = BZip2Constants.baseBlockSize * blockSize100k - 20; - } - - private void EndBlock() - { - blockCRC = mCrc.GetFinalCRC(); - combinedCRC = (combinedCRC << 1) | (int)(((uint)combinedCRC) >> 31); - combinedCRC ^= blockCRC; - - /* sort the block and establish posn of original string */ - DoReversibleTransformation(); - - /* - A 6-byte block header, the value chosen arbitrarily - as 0x314159265359 :-). A 32 bit value does not really - give a strong enough guarantee that the value will not - appear by chance in the compressed datastream. Worst-case - probability of this event, for a 900k block, is about - 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. - For a compressed file of size 100Gb -- about 100000 blocks -- - only a 48-bit marker will do. NB: normal compression/ - decompression do *not* rely on these statistical properties. - They are only important when trying to recover blocks from - damaged files. - */ - BsPutUChar(0x31); - BsPutUChar(0x41); - BsPutUChar(0x59); - BsPutUChar(0x26); - BsPutUChar(0x53); - BsPutUChar(0x59); - - /* Now the block's CRC, so it is in a known place. */ - BsPutint(blockCRC); - - /* Now a single bit indicating randomisation. */ - if (blockRandomised) - { - BsW(1, 1); - nBlocksRandomised++; - } - else - { - BsW(1, 0); - } - - /* Finally, block's contents proper. */ - MoveToFrontCodeAndSend(); - } - - private void EndCompression() - { - /* - Now another magic 48-bit number, 0x177245385090, to - indicate the end of the last block. (Sqrt(pi), if - you want to know. I did want to use e, but it contains - too much repetition -- 27 18 28 18 28 46 -- for me - to feel statistically comfortable. Call me paranoid.) - */ - BsPutUChar(0x17); - BsPutUChar(0x72); - BsPutUChar(0x45); - BsPutUChar(0x38); - BsPutUChar(0x50); - BsPutUChar(0x90); - - BsPutint(combinedCRC); - - BsFinishedWithStream(); - } - - private void HbAssignCodes(int[] code, char[] length, int minLen, - int maxLen, int alphaSize) - { - int n, vec, i; - - vec = 0; - for (n = minLen; n <= maxLen; n++) - { - for (i = 0; i < alphaSize; i++) + if (length[i] == n) { - if (length[i] == n) + code[i] = vec; + vec++; + } + } + ; + vec <<= 1; + } + } + + private void BsSetStream(Stream f) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + } + + private void BsFinishedWithStream() + { + while (bsLive > 0) + { + var ch = (bsBuff >> 24); + bsStream.WriteByte((byte)ch); // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void BsW(int n, int v) + { + while (bsLive >= 8) + { + var ch = (bsBuff >> 24); + bsStream.WriteByte((byte)ch); // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= (v << (32 - bsLive - n)); + bsLive += n; + } + + private void BsPutUChar(int c) => BsW(8, c); + + private void BsPutint(int u) + { + BsW(8, (u >> 24) & 0xff); + BsW(8, (u >> 16) & 0xff); + BsW(8, (u >> 8) & 0xff); + BsW(8, u & 0xff); + } + + private void BsPutIntVS(int numBits, int c) => BsW(numBits, c); + + private void SendMTFValues() + { + var len = CBZip2InputStream.InitCharArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + + int v, + t, + i, + j, + gs, + ge, + totc, + bt, + bc, + iter; + int nSelectors = 0, + alphaSize, + minLen, + maxLen, + selCtr; + int nGroups; //, nBytes; + + alphaSize = nInUse + 2; + for (t = 0; t < BZip2Constants.N_GROUPS; t++) + { + for (v = 0; v < alphaSize; v++) + { + len[t][v] = (char)GREATER_ICOST; + } + } + + /* Decide how many coding tables to use */ + if (nMTF <= 0) + { + Panic(); + } + + if (nMTF < 200) + { + nGroups = 2; + } + else if (nMTF < 600) + { + nGroups = 3; + } + else if (nMTF < 1200) + { + nGroups = 4; + } + else if (nMTF < 2400) + { + nGroups = 5; + } + else + { + nGroups = 6; + } + + /* Generate an initial set of coding tables */ + { + int nPart, + remF, + tFreq, + aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while (nPart > 0) + { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while (aFreq < tFreq && ge < alphaSize - 1) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1)) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for (v = 0; v < alphaSize; v++) + { + if (v >= gs && v <= ge) { - code[i] = vec; - vec++; + len[nPart - 1][v] = (char)LESSER_ICOST; + } + else + { + len[nPart - 1][v] = (char)GREATER_ICOST; } } - ; - vec <<= 1; + + nPart--; + gs = ge + 1; + remF -= aFreq; } } - private void BsSetStream(Stream f) + var rfreq = CBZip2InputStream.InitIntArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + var fave = new int[BZip2Constants.N_GROUPS]; + var cost = new short[BZip2Constants.N_GROUPS]; + /* + Iterate up to N_ITERS times to improve the tables. + */ + for (iter = 0; iter < BZip2Constants.N_ITERS; iter++) { - bsStream = f; - bsLive = 0; - bsBuff = 0; - bytesOut = 0; - } - - private void BsFinishedWithStream() - { - while (bsLive > 0) + for (t = 0; t < nGroups; t++) { - int ch = (bsBuff >> 24); - bsStream.WriteByte((byte)ch); // write 8-bit - bsBuff <<= 8; - bsLive -= 8; - bytesOut++; + fave[t] = 0; } - } - private void BsW(int n, int v) - { - while (bsLive >= 8) - { - int ch = (bsBuff >> 24); - bsStream.WriteByte((byte)ch); // write 8-bit - bsBuff <<= 8; - bsLive -= 8; - bytesOut++; - } - bsBuff |= (v << (32 - bsLive - n)); - bsLive += n; - } - - private void BsPutUChar(int c) - { - BsW(8, c); - } - - private void BsPutint(int u) - { - BsW(8, (u >> 24) & 0xff); - BsW(8, (u >> 16) & 0xff); - BsW(8, (u >> 8) & 0xff); - BsW(8, u & 0xff); - } - - private void BsPutIntVS(int numBits, int c) - { - BsW(numBits, c); - } - - private void SendMTFValues() - { - char[][] len = CBZip2InputStream.InitCharArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - - int v, t, i, j, gs, ge, totc, bt, bc, iter; - int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; - int nGroups; //, nBytes; - - alphaSize = nInUse + 2; - for (t = 0; t < BZip2Constants.N_GROUPS; t++) + for (t = 0; t < nGroups; t++) { for (v = 0; v < alphaSize; v++) { - len[t][v] = (char)GREATER_ICOST; + rfreq[t][v] = 0; } } - /* Decide how many coding tables to use */ - if (nMTF <= 0) - { - Panic(); - } - - if (nMTF < 200) - { - nGroups = 2; - } - else if (nMTF < 600) - { - nGroups = 3; - } - else if (nMTF < 1200) - { - nGroups = 4; - } - else if (nMTF < 2400) - { - nGroups = 5; - } - else - { - nGroups = 6; - } - - /* Generate an initial set of coding tables */ - { - int nPart, remF, tFreq, aFreq; - - nPart = nGroups; - remF = nMTF; - gs = 0; - while (nPart > 0) - { - tFreq = remF / nPart; - ge = gs - 1; - aFreq = 0; - while (aFreq < tFreq && ge < alphaSize - 1) - { - ge++; - aFreq += mtfFreq[ge]; - } - - if (ge > gs && nPart != nGroups && nPart != 1 - && ((nGroups - nPart) % 2 == 1)) - { - aFreq -= mtfFreq[ge]; - ge--; - } - - for (v = 0; v < alphaSize; v++) - { - if (v >= gs && v <= ge) - { - len[nPart - 1][v] = (char)LESSER_ICOST; - } - else - { - len[nPart - 1][v] = (char)GREATER_ICOST; - } - } - - nPart--; - gs = ge + 1; - remF -= aFreq; - } - } - - int[][] rfreq = CBZip2InputStream.InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - int[] fave = new int[BZip2Constants.N_GROUPS]; - short[] cost = new short[BZip2Constants.N_GROUPS]; - /* - Iterate up to N_ITERS times to improve the tables. - */ - for (iter = 0; iter < BZip2Constants.N_ITERS; iter++) - { - for (t = 0; t < nGroups; t++) - { - fave[t] = 0; - } - - for (t = 0; t < nGroups; t++) - { - for (v = 0; v < alphaSize; v++) - { - rfreq[t][v] = 0; - } - } - - nSelectors = 0; - totc = 0; - gs = 0; - while (true) - { - /* Set group start & end marks. */ - if (gs >= nMTF) - { - break; - } - ge = gs + BZip2Constants.G_SIZE - 1; - if (ge >= nMTF) - { - ge = nMTF - 1; - } - - /* - Calculate the cost of this group as coded - by each of the coding tables. - */ - for (t = 0; t < nGroups; t++) - { - cost[t] = 0; - } - - if (nGroups == 6) - { - short cost0, cost1, cost2, cost3, cost4, cost5; - cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; - for (i = gs; i <= ge; i++) - { - short icv = szptr[i]; - cost0 += (short)len[0][icv]; - cost1 += (short)len[1][icv]; - cost2 += (short)len[2][icv]; - cost3 += (short)len[3][icv]; - cost4 += (short)len[4][icv]; - cost5 += (short)len[5][icv]; - } - cost[0] = cost0; - cost[1] = cost1; - cost[2] = cost2; - cost[3] = cost3; - cost[4] = cost4; - cost[5] = cost5; - } - else - { - for (i = gs; i <= ge; i++) - { - short icv = szptr[i]; - for (t = 0; t < nGroups; t++) - { - cost[t] += (short)len[t][icv]; - } - } - } - - /* - Find the coding table which is best for this group, - and record its identity in the selector table. - */ - bc = 999999999; - bt = -1; - for (t = 0; t < nGroups; t++) - { - if (cost[t] < bc) - { - bc = cost[t]; - bt = t; - } - } - ; - totc += bc; - fave[bt]++; - selector[nSelectors] = (char)bt; - nSelectors++; - - /* - Increment the symbol frequencies for the selected table. - */ - for (i = gs; i <= ge; i++) - { - rfreq[bt][szptr[i]]++; - } - - gs = ge + 1; - } - - /* - Recompute the tables based on the accumulated frequencies. - */ - for (t = 0; t < nGroups; t++) - { - HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); - } - } - - rfreq = null; - fave = null; - cost = null; - - if (!(nGroups < 8)) - { - Panic(); - } - if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.G_SIZE)))) - { - Panic(); - } - - /* Compute MTF values for the selectors. */ - { - char[] pos = new char[BZip2Constants.N_GROUPS]; - char ll_i, tmp2, tmp; - for (i = 0; i < nGroups; i++) - { - pos[i] = (char)i; - } - for (i = 0; i < nSelectors; i++) - { - ll_i = selector[i]; - j = 0; - tmp = pos[j]; - while (ll_i != tmp) - { - j++; - tmp2 = tmp; - tmp = pos[j]; - pos[j] = tmp2; - } - pos[0] = tmp; - selectorMtf[i] = (char)j; - } - } - - int[][] code = CBZip2InputStream.InitIntArray(BZip2Constants.N_GROUPS, BZip2Constants.MAX_ALPHA_SIZE); - - /* Assign actual codes for the tables. */ - for (t = 0; t < nGroups; t++) - { - minLen = 32; - maxLen = 0; - for (i = 0; i < alphaSize; i++) - { - if (len[t][i] > maxLen) - { - maxLen = len[t][i]; - } - if (len[t][i] < minLen) - { - minLen = len[t][i]; - } - } - if (maxLen > 20) - { - Panic(); - } - if (minLen < 1) - { - Panic(); - } - HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); - } - - /* Transmit the mapping table. */ - { - bool[] inUse16 = new bool[16]; - for (i = 0; i < 16; i++) - { - inUse16[i] = false; - for (j = 0; j < 16; j++) - { - if (inUse[i * 16 + j]) - { - inUse16[i] = true; - } - } - } - - //nBytes = bytesOut; - for (i = 0; i < 16; i++) - { - if (inUse16[i]) - { - BsW(1, 1); - } - else - { - BsW(1, 0); - } - } - - for (i = 0; i < 16; i++) - { - if (inUse16[i]) - { - for (j = 0; j < 16; j++) - { - if (inUse[i * 16 + j]) - { - BsW(1, 1); - } - else - { - BsW(1, 0); - } - } - } - } - } - - /* Now the selectors. */ - //nBytes = bytesOut; - BsW(3, nGroups); - BsW(15, nSelectors); - for (i = 0; i < nSelectors; i++) - { - for (j = 0; j < selectorMtf[i]; j++) - { - BsW(1, 1); - } - BsW(1, 0); - } - - /* Now the coding tables. */ - //nBytes = bytesOut; - - for (t = 0; t < nGroups; t++) - { - int curr = len[t][0]; - BsW(5, curr); - for (i = 0; i < alphaSize; i++) - { - while (curr < len[t][i]) - { - BsW(2, 2); - curr++; /* 10 */ - } - while (curr > len[t][i]) - { - BsW(2, 3); - curr--; /* 11 */ - } - BsW(1, 0); - } - } - - /* And finally, the block data proper */ - //nBytes = bytesOut; - selCtr = 0; + nSelectors = 0; + totc = 0; gs = 0; while (true) { + /* Set group start & end marks. */ if (gs >= nMTF) { break; @@ -1014,939 +791,1219 @@ namespace SharpCompress.Compressors.BZip2 { ge = nMTF - 1; } + + /* + Calculate the cost of this group as coded + by each of the coding tables. + */ + for (t = 0; t < nGroups; t++) + { + cost[t] = 0; + } + + if (nGroups == 6) + { + short cost0, + cost1, + cost2, + cost3, + cost4, + cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for (i = gs; i <= ge; i++) + { + var icv = szptr[i]; + cost0 += (short)len[0][icv]; + cost1 += (short)len[1][icv]; + cost2 += (short)len[2][icv]; + cost3 += (short)len[3][icv]; + cost4 += (short)len[4][icv]; + cost5 += (short)len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for (i = gs; i <= ge; i++) + { + var icv = szptr[i]; + for (t = 0; t < nGroups; t++) + { + cost[t] += (short)len[t][icv]; + } + } + } + + /* + Find the coding table which is best for this group, + and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for (t = 0; t < nGroups; t++) + { + if (cost[t] < bc) + { + bc = cost[t]; + bt = t; + } + } + ; + totc += bc; + fave[bt]++; + selector[nSelectors] = (char)bt; + nSelectors++; + + /* + Increment the symbol frequencies for the selected table. + */ for (i = gs; i <= ge; i++) { - BsW(len[selector[selCtr]][szptr[i]], - code[selector[selCtr]][szptr[i]]); + rfreq[bt][szptr[i]]++; } gs = ge + 1; - selCtr++; - } - if (!(selCtr == nSelectors)) - { - Panic(); - } - } - - private void MoveToFrontCodeAndSend() - { - BsPutIntVS(24, origPtr); - GenerateMTFValues(); - SendMTFValues(); - } - - private Stream bsStream; - - private void SimpleSort(int lo, int hi, int d) - { - int i, j, h, bigN, hp; - int v; - - bigN = hi - lo + 1; - if (bigN < 2) - { - return; - } - - hp = 0; - while (incs[hp] < bigN) - { - hp++; - } - hp--; - - for (; hp >= 0; hp--) - { - h = incs[hp]; - - i = lo + h; - while (true) - { - /* copy 1 */ - if (i > hi) - { - break; - } - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - { - break; - } - } - zptr[j] = v; - i++; - - /* copy 2 */ - if (i > hi) - { - break; - } - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - { - break; - } - } - zptr[j] = v; - i++; - - /* copy 3 */ - if (i > hi) - { - break; - } - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - { - break; - } - } - zptr[j] = v; - i++; - - if (workDone > workLimit && firstAttempt) - { - return; - } - } - } - } - - private void Vswap(int p1, int p2, int n) - { - int temp = 0; - while (n > 0) - { - temp = zptr[p1]; - zptr[p1] = zptr[p2]; - zptr[p2] = temp; - p1++; - p2++; - n--; - } - } - - private char Med3(char a, char b, char c) - { - char t; - if (a > b) - { - t = a; - a = b; - b = t; - } - if (b > c) - { - t = b; - b = c; - c = t; - } - if (a > b) - { - b = a; - } - return b; - } - - internal class StackElem - { - internal int ll; - internal int hh; - internal int dd; - } - - private void QSort3(int loSt, int hiSt, int dSt) - { - int unLo, unHi, ltLo, gtHi, med, n, m; - int sp, lo, hi, d; - StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; - for (int count = 0; count < QSORT_STACK_SIZE; count++) - { - stack[count] = new StackElem(); - } - - sp = 0; - - stack[sp].ll = loSt; - stack[sp].hh = hiSt; - stack[sp].dd = dSt; - sp++; - - while (sp > 0) - { - if (sp >= QSORT_STACK_SIZE) - { - Panic(); - } - - sp--; - lo = stack[sp].ll; - hi = stack[sp].hh; - d = stack[sp].dd; - - if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) - { - SimpleSort(lo, hi, d); - if (workDone > workLimit && firstAttempt) - { - return; - } - continue; - } - - med = Med3(block[zptr[lo] + d + 1], - block[zptr[hi] + d + 1], - block[zptr[(lo + hi) >> 1] + d + 1]); - - unLo = ltLo = lo; - unHi = gtHi = hi; - - while (true) - { - while (true) - { - if (unLo > unHi) - { - break; - } - n = block[zptr[unLo] + d + 1] - med; - if (n == 0) - { - int temp = 0; - temp = zptr[unLo]; - zptr[unLo] = zptr[ltLo]; - zptr[ltLo] = temp; - ltLo++; - unLo++; - continue; - } - ; - if (n > 0) - { - break; - } - unLo++; - } - while (true) - { - if (unLo > unHi) - { - break; - } - n = block[zptr[unHi] + d + 1] - med; - if (n == 0) - { - int temp = 0; - temp = zptr[unHi]; - zptr[unHi] = zptr[gtHi]; - zptr[gtHi] = temp; - gtHi--; - unHi--; - continue; - } - ; - if (n < 0) - { - break; - } - unHi--; - } - if (unLo > unHi) - { - break; - } - int tempx = zptr[unLo]; - zptr[unLo] = zptr[unHi]; - zptr[unHi] = tempx; - unLo++; - unHi--; - } - - if (gtHi < ltLo) - { - stack[sp].ll = lo; - stack[sp].hh = hi; - stack[sp].dd = d + 1; - sp++; - continue; - } - - n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); - Vswap(lo, unLo - n, n); - m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); - Vswap(unLo, hi - m + 1, m); - - n = lo + unLo - ltLo - 1; - m = hi - (gtHi - unHi) + 1; - - stack[sp].ll = lo; - stack[sp].hh = n; - stack[sp].dd = d; - sp++; - - stack[sp].ll = n + 1; - stack[sp].hh = m - 1; - stack[sp].dd = d + 1; - sp++; - - stack[sp].ll = m; - stack[sp].hh = hi; - stack[sp].dd = d; - sp++; - } - } - - private void MainSort() - { - int i, j, ss, sb; - Span runningOrder = stackalloc int[256]; - Span copy = stackalloc int[256]; - bool[] bigDone = new bool[256]; - int c1, c2; - int numQSorted; - - /* - In the various block-sized structures, live data runs - from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, - set up the overshoot area for block. - */ - - // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); - for (i = 0; i < BZip2Constants.NUM_OVERSHOOT_BYTES; i++) - { - block[last + i + 2] = block[(i % (last + 1)) + 1]; - } - for (i = 0; i <= last + BZip2Constants.NUM_OVERSHOOT_BYTES; i++) - { - quadrant[i] = 0; - } - - block[0] = block[last + 1]; - - if (last < 4000) - { - /* - Use SimpleSort(), since the full sorting mechanism - has quite a large constant overhead. - */ - for (i = 0; i <= last; i++) - { - zptr[i] = i; - } - firstAttempt = false; - workDone = workLimit = 0; - SimpleSort(0, last, 0); - } - else - { - numQSorted = 0; - for (i = 0; i <= 255; i++) - { - bigDone[i] = false; - } - - for (i = 0; i <= 65536; i++) - { - ftab[i] = 0; - } - - c1 = block[0]; - for (i = 0; i <= last; i++) - { - c2 = block[i + 1]; - ftab[(c1 << 8) + c2]++; - c1 = c2; - } - - for (i = 1; i <= 65536; i++) - { - ftab[i] += ftab[i - 1]; - } - - c1 = block[1]; - for (i = 0; i < last; i++) - { - c2 = block[i + 2]; - j = (c1 << 8) + c2; - c1 = c2; - ftab[j]--; - zptr[ftab[j]] = i; - } - - j = ((block[last + 1]) << 8) + (block[1]); - ftab[j]--; - zptr[ftab[j]] = last; - - /* - Now ftab contains the first loc of every small bucket. - Calculate the running order, from smallest to largest - big bucket. - */ - - for (i = 0; i <= 255; i++) - { - runningOrder[i] = i; - } - - { - int vv; - int h = 1; - do - { - h = 3 * h + 1; - } - while (h <= 256); - do - { - h = h / 3; - for (i = h; i <= 255; i++) - { - vv = runningOrder[i]; - j = i; - while ((ftab[((runningOrder[j - h]) + 1) << 8] - - ftab[(runningOrder[j - h]) << 8]) > - (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) - { - runningOrder[j] = runningOrder[j - h]; - j = j - h; - if (j <= (h - 1)) - { - break; - } - } - runningOrder[j] = vv; - } - } - while (h != 1); - } - - /* - The main sorting loop. - */ - for (i = 0; i <= 255; i++) - { - /* - Process big buckets, starting with the least full. - */ - ss = runningOrder[i]; - - /* - Complete the big bucket [ss] by quicksorting - any unsorted small buckets [ss, j]. Hopefully - previous pointer-scanning phases have already - completed many of the small buckets [ss, j], so - we don't have to sort them at all. - */ - for (j = 0; j <= 255; j++) - { - sb = (ss << 8) + j; - if (!((ftab[sb] & SETMASK) == SETMASK)) - { - int lo = ftab[sb] & CLEARMASK; - int hi = (ftab[sb + 1] & CLEARMASK) - 1; - if (hi > lo) - { - QSort3(lo, hi, 2); - numQSorted += (hi - lo + 1); - if (workDone > workLimit && firstAttempt) - { - return; - } - } - ftab[sb] |= SETMASK; - } - } - - /* - The ss big bucket is now done. Record this fact, - and update the quadrant descriptors. Remember to - update quadrants in the overshoot area too, if - necessary. The "if (i < 255)" test merely skips - this updating for the last bucket processed, since - updating for the last bucket is pointless. - */ - bigDone[ss] = true; - - if (i < 255) - { - int bbStart = ftab[ss << 8] & CLEARMASK; - int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; - int shifts = 0; - - while ((bbSize >> shifts) > 65534) - { - shifts++; - } - - for (j = 0; j < bbSize; j++) - { - int a2update = zptr[bbStart + j]; - int qVal = (j >> shifts); - quadrant[a2update] = qVal; - if (a2update < BZip2Constants.NUM_OVERSHOOT_BYTES) - { - quadrant[a2update + last + 1] = qVal; - } - } - - if (!(((bbSize - 1) >> shifts) <= 65535)) - { - Panic(); - } - } - - /* - Now scan this big bucket so as to synthesise the - sorted order for small buckets [t, ss] for all t != ss. - */ - for (j = 0; j <= 255; j++) - { - copy[j] = ftab[(j << 8) + ss] & CLEARMASK; - } - - for (j = ftab[ss << 8] & CLEARMASK; - j < (ftab[(ss + 1) << 8] & CLEARMASK); - j++) - { - c1 = block[zptr[j]]; - if (!bigDone[c1]) - { - zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; - copy[c1]++; - } - } - - for (j = 0; j <= 255; j++) - { - ftab[(j << 8) + ss] |= SETMASK; - } - } - } - } - - private void RandomiseBlock() - { - int i; - int rNToGo = 0; - int rTPos = 0; - for (i = 0; i < 256; i++) - { - inUse[i] = false; - } - - for (i = 0; i <= last; i++) - { - if (rNToGo == 0) - { - rNToGo = (char)BZip2Constants.rNums[rTPos]; - rTPos++; - if (rTPos == 512) - { - rTPos = 0; - } - } - rNToGo--; - block[i + 1] ^= (char)((rNToGo == 1) ? 1 : 0); - - // handle 16 bit signed numbers - block[i + 1] &= (char)0xFF; - - inUse[block[i + 1]] = true; - } - } - - private void DoReversibleTransformation() - { - int i; - - workLimit = workFactor * last; - workDone = 0; - blockRandomised = false; - firstAttempt = true; - - MainSort(); - - if (workDone > workLimit && firstAttempt) - { - RandomiseBlock(); - workLimit = workDone = 0; - blockRandomised = true; - firstAttempt = false; - MainSort(); - } - - origPtr = -1; - for (i = 0; i <= last; i++) - { - if (zptr[i] == 0) - { - origPtr = i; - break; - } - } - ; - - if (origPtr == -1) - { - Panic(); - } - } - - private bool FullGtU(int i1, int i2) - { - int k; - char c1, c2; - int s1, s2; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - i1++; - i2++; - - k = last + 1; - - do - { - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return (s1 > s2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return (s1 > s2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return (s1 > s2); - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return (c1 > c2); - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return (s1 > s2); - } - i1++; - i2++; - - if (i1 > last) - { - i1 -= last; - i1--; - } - ; - if (i2 > last) - { - i2 -= last; - i2--; - } - ; - - k -= 4; - workDone++; - } - while (k >= 0); - - return false; - } - - /* - Knuth's increments seem to work better - than Incerpi-Sedgewick here. Possibly - because the number of elems to sort is - usually small, typically <= 20. - */ - - private readonly int[] incs = - { - 1, 4, 13, 40, 121, 364, 1093, 3280, - 9841, 29524, 88573, 265720, - 797161, 2391484 - }; - - private void AllocateCompressStructures() - { - int n = BZip2Constants.baseBlockSize * blockSize100k; - block = new char[(n + 1 + BZip2Constants.NUM_OVERSHOOT_BYTES)]; - quadrant = new int[(n + BZip2Constants.NUM_OVERSHOOT_BYTES)]; - zptr = new int[n]; - ftab = new int[65537]; - - if (block is null || quadrant is null || zptr is null || ftab is null) - { - //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; - //compressOutOfMemory ( totalDraw, n ); } /* - The back end needs a place to store the MTF values - whilst it calculates the coding tables. We could - put them in the zptr array. However, these values - will fit in a short, so we overlay szptr at the - start of zptr, in the hope of reducing the number - of cache misses induced by the multiple traversals - of the MTF values when calculating coding tables. - Seems to improve compression speed by about 1%. + Recompute the tables based on the accumulated frequencies. */ - // szptr = zptr; - - szptr = new short[2 * n]; + for (t = 0; t < nGroups; t++) + { + HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); + } } - private void GenerateMTFValues() + rfreq = null; + fave = null; + cost = null; + + if (!(nGroups < 8)) { - char[] yy = new char[256]; - int i, j; - char tmp; - char tmp2; - int zPend; - int wr; - int EOB; + Panic(); + } + if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.G_SIZE)))) + { + Panic(); + } - MakeMaps(); - EOB = nInUse + 1; - - for (i = 0; i <= EOB; i++) + /* Compute MTF values for the selectors. */ + { + var pos = new char[BZip2Constants.N_GROUPS]; + char ll_i, + tmp2, + tmp; + for (i = 0; i < nGroups; i++) { - mtfFreq[i] = 0; + pos[i] = (char)i; } - - wr = 0; - zPend = 0; - for (i = 0; i < nInUse; i++) + for (i = 0; i < nSelectors; i++) { - yy[i] = (char)i; - } - - for (i = 0; i <= last; i++) - { - char ll_i; - - ll_i = unseqToSeq[block[zptr[i]]]; - + ll_i = selector[i]; j = 0; - tmp = yy[j]; + tmp = pos[j]; while (ll_i != tmp) { j++; tmp2 = tmp; - tmp = yy[j]; - yy[j] = tmp2; + tmp = pos[j]; + pos[j] = tmp2; } - ; - yy[0] = tmp; + pos[0] = tmp; + selectorMtf[i] = (char)j; + } + } - if (j == 0) + var code = CBZip2InputStream.InitIntArray( + BZip2Constants.N_GROUPS, + BZip2Constants.MAX_ALPHA_SIZE + ); + + /* Assign actual codes for the tables. */ + for (t = 0; t < nGroups; t++) + { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) + { + if (len[t][i] > maxLen) { - zPend++; + maxLen = len[t][i]; + } + if (len[t][i] < minLen) + { + minLen = len[t][i]; + } + } + if (maxLen > 20) + { + Panic(); + } + if (minLen < 1) + { + Panic(); + } + HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); + } + + /* Transmit the mapping table. */ + { + var inUse16 = new bool[16]; + for (i = 0; i < 16; i++) + { + inUse16[i] = false; + for (j = 0; j < 16; j++) + { + if (inUse[(i * 16) + j]) + { + inUse16[i] = true; + } + } + } + + //nBytes = bytesOut; + for (i = 0; i < 16; i++) + { + if (inUse16[i]) + { + BsW(1, 1); } else { - if (zPend > 0) - { - zPend--; - while (true) - { - switch (zPend % 2) - { - case 0: - szptr[wr] = BZip2Constants.RUNA; - wr++; - mtfFreq[BZip2Constants.RUNA]++; - break; - case 1: - szptr[wr] = BZip2Constants.RUNB; - wr++; - mtfFreq[BZip2Constants.RUNB]++; - break; - } - ; - if (zPend < 2) - { - break; - } - zPend = (zPend - 2) / 2; - } - ; - zPend = 0; - } - szptr[wr] = (short)(j + 1); - wr++; - mtfFreq[j + 1]++; + BsW(1, 0); } } - if (zPend > 0) + for (i = 0; i < 16; i++) { - zPend--; - while (true) + if (inUse16[i]) { - switch (zPend % 2) + for (j = 0; j < 16; j++) { - case 0: - szptr[wr] = BZip2Constants.RUNA; - wr++; - mtfFreq[BZip2Constants.RUNA]++; - break; - case 1: - szptr[wr] = BZip2Constants.RUNB; - wr++; - mtfFreq[BZip2Constants.RUNB]++; - break; + if (inUse[(i * 16) + j]) + { + BsW(1, 1); + } + else + { + BsW(1, 0); + } } - if (zPend < 2) + } + } + } + + /* Now the selectors. */ + //nBytes = bytesOut; + BsW(3, nGroups); + BsW(15, nSelectors); + for (i = 0; i < nSelectors; i++) + { + for (j = 0; j < selectorMtf[i]; j++) + { + BsW(1, 1); + } + BsW(1, 0); + } + + /* Now the coding tables. */ + //nBytes = bytesOut; + + for (t = 0; t < nGroups; t++) + { + int curr = len[t][0]; + BsW(5, curr); + for (i = 0; i < alphaSize; i++) + { + while (curr < len[t][i]) + { + BsW(2, 2); + curr++; /* 10 */ + } + while (curr > len[t][i]) + { + BsW(2, 3); + curr--; /* 11 */ + } + BsW(1, 0); + } + } + + /* And finally, the block data proper */ + //nBytes = bytesOut; + selCtr = 0; + gs = 0; + while (true) + { + if (gs >= nMTF) + { + break; + } + ge = gs + BZip2Constants.G_SIZE - 1; + if (ge >= nMTF) + { + ge = nMTF - 1; + } + for (i = gs; i <= ge; i++) + { + BsW(len[selector[selCtr]][szptr[i]], code[selector[selCtr]][szptr[i]]); + } + + gs = ge + 1; + selCtr++; + } + if (!(selCtr == nSelectors)) + { + Panic(); + } + } + + private void MoveToFrontCodeAndSend() + { + BsPutIntVS(24, origPtr); + GenerateMTFValues(); + SendMTFValues(); + } + + private Stream bsStream; + + private void SimpleSort(int lo, int hi, int d) + { + int i, + j, + h, + bigN, + hp; + int v; + + bigN = hi - lo + 1; + if (bigN < 2) + { + return; + } + + hp = 0; + while (incs[hp] < bigN) + { + hp++; + } + hp--; + + for (; hp >= 0; hp--) + { + h = incs[hp]; + + i = lo + h; + while (true) + { + /* copy 1 */ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) { break; } - zPend = (zPend - 2) / 2; + } + zptr[j] = v; + i++; + + /* copy 2 */ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + /* copy 3 */ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + if (workDone > workLimit && firstAttempt) + { + return; } } + } + } - szptr[wr] = (short)EOB; - wr++; - mtfFreq[EOB]++; + private void Vswap(int p1, int p2, int n) + { + var temp = 0; + while (n > 0) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } - nMTF = wr; + private char Med3(char a, char b, char c) + { + char t; + if (a > b) + { + t = a; + a = b; + b = t; + } + if (b > c) + { + t = b; + b = c; + c = t; + } + if (a > b) + { + b = a; + } + return b; + } + + internal class StackElem + { + internal int ll; + internal int hh; + internal int dd; + } + + private void QSort3(int loSt, int hiSt, int dSt) + { + int unLo, + unHi, + ltLo, + gtHi, + med, + n, + m; + int sp, + lo, + hi, + d; + var stack = new StackElem[QSORT_STACK_SIZE]; + for (var count = 0; count < QSORT_STACK_SIZE; count++) + { + stack[count] = new StackElem(); } - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } + sp = 0; - public override int ReadByte() - { - return -1; - } + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; - public override long Seek(long offset, SeekOrigin origin) + while (sp > 0) { - return 0; - } - - public override void SetLength(long value) - { - } - - public override void Write(byte[] buffer, int offset, int count) - { - for (int k = 0; k < count; ++k) + if (sp >= QSORT_STACK_SIZE) { - WriteByte(buffer[k + offset]); + Panic(); + } + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) + { + SimpleSort(lo, hi, d); + if (workDone > workLimit && firstAttempt) + { + return; + } + continue; + } + + med = Med3( + block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[(lo + hi) >> 1] + d + 1] + ); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while (true) + { + while (true) + { + if (unLo > unHi) + { + break; + } + n = block[zptr[unLo] + d + 1] - med; + if (n == 0) + { + var temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + ; + if (n > 0) + { + break; + } + unLo++; + } + while (true) + { + if (unLo > unHi) + { + break; + } + n = block[zptr[unHi] + d + 1] - med; + if (n == 0) + { + var temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + ; + if (n < 0) + { + break; + } + unHi--; + } + if (unLo > unHi) + { + break; + } + var tempx = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = tempx; + unLo++; + unHi--; + } + + if (gtHi < ltLo) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); + Vswap(lo, unLo - n, n); + m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); + Vswap(unLo, hi - m + 1, m); + + n = lo + unLo - ltLo - 1; + m = hi - (gtHi - unHi) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void MainSort() + { + int i, + j, + ss, + sb; + Span runningOrder = stackalloc int[256]; + Span copy = stackalloc int[256]; + var bigDone = new bool[256]; + int c1, + c2; + int numQSorted; + + /* + In the various block-sized structures, live data runs + from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + set up the overshoot area for block. + */ + + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for (i = 0; i < BZip2Constants.NUM_OVERSHOOT_BYTES; i++) + { + block[last + i + 2] = block[(i % (last + 1)) + 1]; + } + for (i = 0; i <= last + BZip2Constants.NUM_OVERSHOOT_BYTES; i++) + { + quadrant[i] = 0; + } + + block[0] = block[last + 1]; + + if (last < 4000) + { + /* + Use SimpleSort(), since the full sorting mechanism + has quite a large constant overhead. + */ + for (i = 0; i <= last; i++) + { + zptr[i] = i; + } + firstAttempt = false; + workDone = workLimit = 0; + SimpleSort(0, last, 0); + } + else + { + numQSorted = 0; + for (i = 0; i <= 255; i++) + { + bigDone[i] = false; + } + + for (i = 0; i <= 65536; i++) + { + ftab[i] = 0; + } + + c1 = block[0]; + for (i = 0; i <= last; i++) + { + c2 = block[i + 1]; + ftab[(c1 << 8) + c2]++; + c1 = c2; + } + + for (i = 1; i <= 65536; i++) + { + ftab[i] += ftab[i - 1]; + } + + c1 = block[1]; + for (i = 0; i < last; i++) + { + c2 = block[i + 2]; + j = (c1 << 8) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ((block[last + 1]) << 8) + (block[1]); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + Now ftab contains the first loc of every small bucket. + Calculate the running order, from smallest to largest + big bucket. + */ + + for (i = 0; i <= 255; i++) + { + runningOrder[i] = i; + } + + { + int vv; + var h = 1; + do + { + h = (3 * h) + 1; + } while (h <= 256); + do + { + h /= 3; + for (i = h; i <= 255; i++) + { + vv = runningOrder[i]; + j = i; + while ( + ( + ftab[((runningOrder[j - h]) + 1) << 8] + - ftab[(runningOrder[j - h]) << 8] + ) > (ftab[((vv) + 1) << 8] - ftab[(vv) << 8]) + ) + { + runningOrder[j] = runningOrder[j - h]; + j -= h; + if (j <= (h - 1)) + { + break; + } + } + runningOrder[j] = vv; + } + } while (h != 1); + } + + /* + The main sorting loop. + */ + for (i = 0; i <= 255; i++) + { + /* + Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + Complete the big bucket [ss] by quicksorting + any unsorted small buckets [ss, j]. Hopefully + previous pointer-scanning phases have already + completed many of the small buckets [ss, j], so + we don't have to sort them at all. + */ + for (j = 0; j <= 255; j++) + { + sb = (ss << 8) + j; + if (!((ftab[sb] & SETMASK) == SETMASK)) + { + var lo = ftab[sb] & CLEARMASK; + var hi = (ftab[sb + 1] & CLEARMASK) - 1; + if (hi > lo) + { + QSort3(lo, hi, 2); + numQSorted += (hi - lo + 1); + if (workDone > workLimit && firstAttempt) + { + return; + } + } + ftab[sb] |= SETMASK; + } + } + + /* + The ss big bucket is now done. Record this fact, + and update the quadrant descriptors. Remember to + update quadrants in the overshoot area too, if + necessary. The "if (i < 255)" test merely skips + this updating for the last bucket processed, since + updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if (i < 255) + { + var bbStart = ftab[ss << 8] & CLEARMASK; + var bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; + var shifts = 0; + + while ((bbSize >> shifts) > 65534) + { + shifts++; + } + + for (j = 0; j < bbSize; j++) + { + var a2update = zptr[bbStart + j]; + var qVal = (j >> shifts); + quadrant[a2update] = qVal; + if (a2update < BZip2Constants.NUM_OVERSHOOT_BYTES) + { + quadrant[a2update + last + 1] = qVal; + } + } + + if (!(((bbSize - 1) >> shifts) <= 65535)) + { + Panic(); + } + } + + /* + Now scan this big bucket so as to synthesise the + sorted order for small buckets [t, ss] for all t != ss. + */ + for (j = 0; j <= 255; j++) + { + copy[j] = ftab[(j << 8) + ss] & CLEARMASK; + } + + for (j = ftab[ss << 8] & CLEARMASK; j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) + { + c1 = block[zptr[j]]; + if (!bigDone[c1]) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for (j = 0; j <= 255; j++) + { + ftab[(j << 8) + ss] |= SETMASK; + } + } + } + } + + private void RandomiseBlock() + { + int i; + var rNToGo = 0; + var rTPos = 0; + for (i = 0; i < 256; i++) + { + inUse[i] = false; + } + + for (i = 0; i <= last; i++) + { + if (rNToGo == 0) + { + rNToGo = (char)BZip2Constants.rNums[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + block[i + 1] ^= (char)((rNToGo == 1) ? 1 : 0); + + // handle 16 bit signed numbers + block[i + 1] &= (char)0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void DoReversibleTransformation() + { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + MainSort(); + + if (workDone > workLimit && firstAttempt) + { + RandomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + MainSort(); + } + + origPtr = -1; + for (i = 0; i <= last; i++) + { + if (zptr[i] == 0) + { + origPtr = i; + break; + } + } + ; + + if (origPtr == -1) + { + Panic(); + } + } + + private bool FullGtU(int i1, int i2) + { + int k; + char c1, + c2; + int s1, + s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return (s1 > s2); + } + i1++; + i2++; + + if (i1 > last) + { + i1 -= last; + i1--; + } + ; + if (i2 > last) + { + i2 -= last; + i2--; + } + ; + + k -= 4; + workDone++; + } while (k >= 0); + + return false; + } + + /* + Knuth's increments seem to work better + than Incerpi-Sedgewick here. Possibly + because the number of elems to sort is + usually small, typically <= 20. + */ + + private readonly int[] incs = + { + 1, + 4, + 13, + 40, + 121, + 364, + 1093, + 3280, + 9841, + 29524, + 88573, + 265720, + 797161, + 2391484 + }; + + private void AllocateCompressStructures() + { + var n = BZip2Constants.baseBlockSize * blockSize100k; + block = new char[(n + 1 + BZip2Constants.NUM_OVERSHOOT_BYTES)]; + quadrant = new int[(n + BZip2Constants.NUM_OVERSHOOT_BYTES)]; + zptr = new int[n]; + ftab = new int[65537]; + + if (block is null || quadrant is null || zptr is null || ftab is null) + { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + The back end needs a place to store the MTF values + whilst it calculates the coding tables. We could + put them in the zptr array. However, these values + will fit in a short, so we overlay szptr at the + start of zptr, in the hope of reducing the number + of cache misses induced by the multiple traversals + of the MTF values when calculating coding tables. + Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void GenerateMTFValues() + { + var yy = new char[256]; + int i, + j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + MakeMaps(); + EOB = nInUse + 1; + + for (i = 0; i <= EOB; i++) + { + mtfFreq[i] = 0; + } + + wr = 0; + zPend = 0; + for (i = 0; i < nInUse; i++) + { + yy[i] = (char)i; + } + + for (i = 0; i <= last; i++) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while (ll_i != tmp) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + ; + yy[0] = tmp; + + if (j == 0) + { + zPend++; + } + else + { + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = BZip2Constants.RUNA; + wr++; + mtfFreq[BZip2Constants.RUNA]++; + break; + case 1: + szptr[wr] = BZip2Constants.RUNB; + wr++; + mtfFreq[BZip2Constants.RUNB]++; + break; + } + ; + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + ; + zPend = 0; + } + szptr[wr] = (short)(j + 1); + wr++; + mtfFreq[j + 1]++; } } - public override bool CanRead => false; + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = BZip2Constants.RUNA; + wr++; + mtfFreq[BZip2Constants.RUNA]++; + break; + case 1: + szptr[wr] = BZip2Constants.RUNB; + wr++; + mtfFreq[BZip2Constants.RUNB]++; + break; + } + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + } - public override bool CanSeek => false; + szptr[wr] = (short)EOB; + wr++; + mtfFreq[EOB]++; - public override bool CanWrite => true; - - public override long Length => 0; - - public override long Position { get { return 0; } set { } } + nMTF = wr; } -} \ No newline at end of file + + public override int Read(byte[] buffer, int offset, int count) => 0; + + public override int ReadByte() => -1; + + public override long Seek(long offset, SeekOrigin origin) => 0; + + public override void SetLength(long value) { } + + public override void Write(byte[] buffer, int offset, int count) + { + for (var k = 0; k < count; ++k) + { + WriteByte(buffer[k + offset]); + } + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => 0; + + public override long Position + { + get => 0; + set { } + } +} diff --git a/src/SharpCompress/Compressors/BZip2/CRC.cs b/src/SharpCompress/Compressors/BZip2/CRC.cs index 7ac42130..9f593c40 100644 --- a/src/SharpCompress/Compressors/BZip2/CRC.cs +++ b/src/SharpCompress/Compressors/BZip2/CRC.cs @@ -20,184 +20,296 @@ * great code. */ -namespace SharpCompress.Compressors.BZip2 +namespace SharpCompress.Compressors.BZip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking + * of the data. + * + * @author Keiron Liddle + */ + +internal class CRC { - /** - * A simple class the hold and calculate the CRC for sanity checking - * of the data. - * - * @author Keiron Liddle - */ - - internal class CRC + public static int[] crc32Table = { - public static int[] crc32Table = - { - 0x00000000, 0x04c11db7, 0x09823b6e, - 0x0d4326d9, - 0x130476dc, 0x17c56b6b, 0x1a864db2, - 0x1e475005, - 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, - 0x2b4bcb61, - 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, - 0x384fbdbd, - 0x4c11db70, 0x48d0c6c7, 0x4593e01e, - 0x4152fda9, - 0x5f15adac, 0x5bd4b01b, 0x569796c2, - 0x52568b75, - 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, - 0x675a1011, - 0x791d4014, 0x7ddc5da3, 0x709f7b7a, - 0x745e66cd, - unchecked((int)0x9823b6e0), unchecked((int)0x9ce2ab57), unchecked((int)0x91a18d8e), - unchecked((int)0x95609039), - unchecked((int)0x8b27c03c), unchecked((int)0x8fe6dd8b), unchecked((int)0x82a5fb52), - unchecked((int)0x8664e6e5), - unchecked((int)0xbe2b5b58), unchecked((int)0xbaea46ef), unchecked((int)0xb7a96036), - unchecked((int)0xb3687d81), - unchecked((int)0xad2f2d84), unchecked((int)0xa9ee3033), unchecked((int)0xa4ad16ea), - unchecked((int)0xa06c0b5d), - unchecked((int)0xd4326d90), unchecked((int)0xd0f37027), unchecked((int)0xddb056fe), - unchecked((int)0xd9714b49), - unchecked((int)0xc7361b4c), unchecked((int)0xc3f706fb), unchecked((int)0xceb42022), - unchecked((int)0xca753d95), - unchecked((int)0xf23a8028), unchecked((int)0xf6fb9d9f), unchecked((int)0xfbb8bb46), - unchecked((int)0xff79a6f1), - unchecked((int)0xe13ef6f4), unchecked((int)0xe5ffeb43), unchecked((int)0xe8bccd9a), - unchecked((int)0xec7dd02d), - 0x34867077, 0x30476dc0, 0x3d044b19, - 0x39c556ae, - 0x278206ab, 0x23431b1c, 0x2e003dc5, - 0x2ac12072, - 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, - 0x1fcdbb16, - 0x018aeb13, 0x054bf6a4, 0x0808d07d, - 0x0cc9cdca, - 0x7897ab07, 0x7c56b6b0, 0x71159069, - 0x75d48dde, - 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, - 0x66d0fb02, - 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, - 0x53dc6066, - 0x4d9b3063, 0x495a2dd4, 0x44190b0d, - 0x40d816ba, - unchecked((int)0xaca5c697), unchecked((int)0xa864db20), unchecked((int)0xa527fdf9), - unchecked((int)0xa1e6e04e), - unchecked((int)0xbfa1b04b), unchecked((int)0xbb60adfc), unchecked((int)0xb6238b25), - unchecked((int)0xb2e29692), - unchecked((int)0x8aad2b2f), unchecked((int)0x8e6c3698), unchecked((int)0x832f1041), - unchecked((int)0x87ee0df6), - unchecked((int)0x99a95df3), unchecked((int)0x9d684044), unchecked((int)0x902b669d), - unchecked((int)0x94ea7b2a), - unchecked((int)0xe0b41de7), unchecked((int)0xe4750050), unchecked((int)0xe9362689), - unchecked((int)0xedf73b3e), - unchecked((int)0xf3b06b3b), unchecked((int)0xf771768c), unchecked((int)0xfa325055), - unchecked((int)0xfef34de2), - unchecked((int)0xc6bcf05f), unchecked((int)0xc27dede8), unchecked((int)0xcf3ecb31), - unchecked((int)0xcbffd686), - unchecked((int)0xd5b88683), unchecked((int)0xd1799b34), unchecked((int)0xdc3abded), - unchecked((int)0xd8fba05a), - 0x690ce0ee, 0x6dcdfd59, 0x608edb80, - 0x644fc637, - 0x7a089632, 0x7ec98b85, 0x738aad5c, - 0x774bb0eb, - 0x4f040d56, 0x4bc510e1, 0x46863638, - 0x42472b8f, - 0x5c007b8a, 0x58c1663d, 0x558240e4, - 0x51435d53, - 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, - 0x285e1d47, - 0x36194d42, 0x32d850f5, 0x3f9b762c, - 0x3b5a6b9b, - 0x0315d626, 0x07d4cb91, 0x0a97ed48, - 0x0e56f0ff, - 0x1011a0fa, 0x14d0bd4d, 0x19939b94, - 0x1d528623, - unchecked((int)0xf12f560e), unchecked((int)0xf5ee4bb9), unchecked((int)0xf8ad6d60), - unchecked((int)0xfc6c70d7), - unchecked((int)0xe22b20d2), unchecked((int)0xe6ea3d65), unchecked((int)0xeba91bbc), - unchecked((int)0xef68060b), - unchecked((int)0xd727bbb6), unchecked((int)0xd3e6a601), unchecked((int)0xdea580d8), - unchecked((int)0xda649d6f), - unchecked((int)0xc423cd6a), unchecked((int)0xc0e2d0dd), unchecked((int)0xcda1f604), - unchecked((int)0xc960ebb3), - unchecked((int)0xbd3e8d7e), unchecked((int)0xb9ff90c9), unchecked((int)0xb4bcb610), - unchecked((int)0xb07daba7), - unchecked((int)0xae3afba2), unchecked((int)0xaafbe615), unchecked((int)0xa7b8c0cc), - unchecked((int)0xa379dd7b), - unchecked((int)0x9b3660c6), unchecked((int)0x9ff77d71), unchecked((int)0x92b45ba8), - unchecked((int)0x9675461f), - unchecked((int)0x8832161a), unchecked((int)0x8cf30bad), unchecked((int)0x81b02d74), - unchecked((int)0x857130c3), - 0x5d8a9099, 0x594b8d2e, 0x5408abf7, - 0x50c9b640, - 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, - 0x43cdc09c, - 0x7b827d21, 0x7f436096, 0x7200464f, - 0x76c15bf8, - 0x68860bfd, 0x6c47164a, 0x61043093, - 0x65c52d24, - 0x119b4be9, 0x155a565e, 0x18197087, - 0x1cd86d30, - 0x029f3d35, 0x065e2082, 0x0b1d065b, - 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, - 0x3ad08088, - 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, - 0x29d4f654, - unchecked((int)0xc5a92679), unchecked((int)0xc1683bce), unchecked((int)0xcc2b1d17), - unchecked((int)0xc8ea00a0), - unchecked((int)0xd6ad50a5), unchecked((int)0xd26c4d12), unchecked((int)0xdf2f6bcb), - unchecked((int)0xdbee767c), - unchecked((int)0xe3a1cbc1), unchecked((int)0xe760d676), unchecked((int)0xea23f0af), - unchecked((int)0xeee2ed18), - unchecked((int)0xf0a5bd1d), unchecked((int)0xf464a0aa), unchecked((int)0xf9278673), - unchecked((int)0xfde69bc4), - unchecked((int)0x89b8fd09), unchecked((int)0x8d79e0be), unchecked((int)0x803ac667), - unchecked((int)0x84fbdbd0), - unchecked((int)0x9abc8bd5), unchecked((int)0x9e7d9662), unchecked((int)0x933eb0bb), - unchecked((int)0x97ffad0c), - unchecked((int)0xafb010b1), unchecked((int)0xab710d06), unchecked((int)0xa6322bdf), - unchecked((int)0xa2f33668), - unchecked((int)0xbcb4666d), unchecked((int)0xb8757bda), unchecked((int)0xb5365d03), - unchecked((int)0xb1f740b4) - }; + 0x00000000, + 0x04c11db7, + 0x09823b6e, + 0x0d4326d9, + 0x130476dc, + 0x17c56b6b, + 0x1a864db2, + 0x1e475005, + 0x2608edb8, + 0x22c9f00f, + 0x2f8ad6d6, + 0x2b4bcb61, + 0x350c9b64, + 0x31cd86d3, + 0x3c8ea00a, + 0x384fbdbd, + 0x4c11db70, + 0x48d0c6c7, + 0x4593e01e, + 0x4152fda9, + 0x5f15adac, + 0x5bd4b01b, + 0x569796c2, + 0x52568b75, + 0x6a1936c8, + 0x6ed82b7f, + 0x639b0da6, + 0x675a1011, + 0x791d4014, + 0x7ddc5da3, + 0x709f7b7a, + 0x745e66cd, + unchecked((int)0x9823b6e0), + unchecked((int)0x9ce2ab57), + unchecked((int)0x91a18d8e), + unchecked((int)0x95609039), + unchecked((int)0x8b27c03c), + unchecked((int)0x8fe6dd8b), + unchecked((int)0x82a5fb52), + unchecked((int)0x8664e6e5), + unchecked((int)0xbe2b5b58), + unchecked((int)0xbaea46ef), + unchecked((int)0xb7a96036), + unchecked((int)0xb3687d81), + unchecked((int)0xad2f2d84), + unchecked((int)0xa9ee3033), + unchecked((int)0xa4ad16ea), + unchecked((int)0xa06c0b5d), + unchecked((int)0xd4326d90), + unchecked((int)0xd0f37027), + unchecked((int)0xddb056fe), + unchecked((int)0xd9714b49), + unchecked((int)0xc7361b4c), + unchecked((int)0xc3f706fb), + unchecked((int)0xceb42022), + unchecked((int)0xca753d95), + unchecked((int)0xf23a8028), + unchecked((int)0xf6fb9d9f), + unchecked((int)0xfbb8bb46), + unchecked((int)0xff79a6f1), + unchecked((int)0xe13ef6f4), + unchecked((int)0xe5ffeb43), + unchecked((int)0xe8bccd9a), + unchecked((int)0xec7dd02d), + 0x34867077, + 0x30476dc0, + 0x3d044b19, + 0x39c556ae, + 0x278206ab, + 0x23431b1c, + 0x2e003dc5, + 0x2ac12072, + 0x128e9dcf, + 0x164f8078, + 0x1b0ca6a1, + 0x1fcdbb16, + 0x018aeb13, + 0x054bf6a4, + 0x0808d07d, + 0x0cc9cdca, + 0x7897ab07, + 0x7c56b6b0, + 0x71159069, + 0x75d48dde, + 0x6b93dddb, + 0x6f52c06c, + 0x6211e6b5, + 0x66d0fb02, + 0x5e9f46bf, + 0x5a5e5b08, + 0x571d7dd1, + 0x53dc6066, + 0x4d9b3063, + 0x495a2dd4, + 0x44190b0d, + 0x40d816ba, + unchecked((int)0xaca5c697), + unchecked((int)0xa864db20), + unchecked((int)0xa527fdf9), + unchecked((int)0xa1e6e04e), + unchecked((int)0xbfa1b04b), + unchecked((int)0xbb60adfc), + unchecked((int)0xb6238b25), + unchecked((int)0xb2e29692), + unchecked((int)0x8aad2b2f), + unchecked((int)0x8e6c3698), + unchecked((int)0x832f1041), + unchecked((int)0x87ee0df6), + unchecked((int)0x99a95df3), + unchecked((int)0x9d684044), + unchecked((int)0x902b669d), + unchecked((int)0x94ea7b2a), + unchecked((int)0xe0b41de7), + unchecked((int)0xe4750050), + unchecked((int)0xe9362689), + unchecked((int)0xedf73b3e), + unchecked((int)0xf3b06b3b), + unchecked((int)0xf771768c), + unchecked((int)0xfa325055), + unchecked((int)0xfef34de2), + unchecked((int)0xc6bcf05f), + unchecked((int)0xc27dede8), + unchecked((int)0xcf3ecb31), + unchecked((int)0xcbffd686), + unchecked((int)0xd5b88683), + unchecked((int)0xd1799b34), + unchecked((int)0xdc3abded), + unchecked((int)0xd8fba05a), + 0x690ce0ee, + 0x6dcdfd59, + 0x608edb80, + 0x644fc637, + 0x7a089632, + 0x7ec98b85, + 0x738aad5c, + 0x774bb0eb, + 0x4f040d56, + 0x4bc510e1, + 0x46863638, + 0x42472b8f, + 0x5c007b8a, + 0x58c1663d, + 0x558240e4, + 0x51435d53, + 0x251d3b9e, + 0x21dc2629, + 0x2c9f00f0, + 0x285e1d47, + 0x36194d42, + 0x32d850f5, + 0x3f9b762c, + 0x3b5a6b9b, + 0x0315d626, + 0x07d4cb91, + 0x0a97ed48, + 0x0e56f0ff, + 0x1011a0fa, + 0x14d0bd4d, + 0x19939b94, + 0x1d528623, + unchecked((int)0xf12f560e), + unchecked((int)0xf5ee4bb9), + unchecked((int)0xf8ad6d60), + unchecked((int)0xfc6c70d7), + unchecked((int)0xe22b20d2), + unchecked((int)0xe6ea3d65), + unchecked((int)0xeba91bbc), + unchecked((int)0xef68060b), + unchecked((int)0xd727bbb6), + unchecked((int)0xd3e6a601), + unchecked((int)0xdea580d8), + unchecked((int)0xda649d6f), + unchecked((int)0xc423cd6a), + unchecked((int)0xc0e2d0dd), + unchecked((int)0xcda1f604), + unchecked((int)0xc960ebb3), + unchecked((int)0xbd3e8d7e), + unchecked((int)0xb9ff90c9), + unchecked((int)0xb4bcb610), + unchecked((int)0xb07daba7), + unchecked((int)0xae3afba2), + unchecked((int)0xaafbe615), + unchecked((int)0xa7b8c0cc), + unchecked((int)0xa379dd7b), + unchecked((int)0x9b3660c6), + unchecked((int)0x9ff77d71), + unchecked((int)0x92b45ba8), + unchecked((int)0x9675461f), + unchecked((int)0x8832161a), + unchecked((int)0x8cf30bad), + unchecked((int)0x81b02d74), + unchecked((int)0x857130c3), + 0x5d8a9099, + 0x594b8d2e, + 0x5408abf7, + 0x50c9b640, + 0x4e8ee645, + 0x4a4ffbf2, + 0x470cdd2b, + 0x43cdc09c, + 0x7b827d21, + 0x7f436096, + 0x7200464f, + 0x76c15bf8, + 0x68860bfd, + 0x6c47164a, + 0x61043093, + 0x65c52d24, + 0x119b4be9, + 0x155a565e, + 0x18197087, + 0x1cd86d30, + 0x029f3d35, + 0x065e2082, + 0x0b1d065b, + 0x0fdc1bec, + 0x3793a651, + 0x3352bbe6, + 0x3e119d3f, + 0x3ad08088, + 0x2497d08d, + 0x2056cd3a, + 0x2d15ebe3, + 0x29d4f654, + unchecked((int)0xc5a92679), + unchecked((int)0xc1683bce), + unchecked((int)0xcc2b1d17), + unchecked((int)0xc8ea00a0), + unchecked((int)0xd6ad50a5), + unchecked((int)0xd26c4d12), + unchecked((int)0xdf2f6bcb), + unchecked((int)0xdbee767c), + unchecked((int)0xe3a1cbc1), + unchecked((int)0xe760d676), + unchecked((int)0xea23f0af), + unchecked((int)0xeee2ed18), + unchecked((int)0xf0a5bd1d), + unchecked((int)0xf464a0aa), + unchecked((int)0xf9278673), + unchecked((int)0xfde69bc4), + unchecked((int)0x89b8fd09), + unchecked((int)0x8d79e0be), + unchecked((int)0x803ac667), + unchecked((int)0x84fbdbd0), + unchecked((int)0x9abc8bd5), + unchecked((int)0x9e7d9662), + unchecked((int)0x933eb0bb), + unchecked((int)0x97ffad0c), + unchecked((int)0xafb010b1), + unchecked((int)0xab710d06), + unchecked((int)0xa6322bdf), + unchecked((int)0xa2f33668), + unchecked((int)0xbcb4666d), + unchecked((int)0xb8757bda), + unchecked((int)0xb5365d03), + unchecked((int)0xb1f740b4) + }; - public CRC() + public CRC() => InitialiseCRC(); + + internal void InitialiseCRC() => globalCrc = unchecked((int)0xffffffff); + + internal int GetFinalCRC() => ~globalCrc; + + internal int GetGlobalCRC() => globalCrc; + + internal void SetGlobalCRC(int newCrc) => globalCrc = newCrc; + + internal void UpdateCRC(int inCh) + { + var temp = (globalCrc >> 24) ^ inCh; + if (temp < 0) { - InitialiseCRC(); + temp = 256 + temp; } - - internal void InitialiseCRC() - { - globalCrc = unchecked((int)0xffffffff); - } - - internal int GetFinalCRC() - { - return ~globalCrc; - } - - internal int GetGlobalCRC() - { - return globalCrc; - } - - internal void SetGlobalCRC(int newCrc) - { - globalCrc = newCrc; - } - - internal void UpdateCRC(int inCh) - { - int temp = (globalCrc >> 24) ^ inCh; - if (temp < 0) - { - temp = 256 + temp; - } - globalCrc = (globalCrc << 8) ^ crc32Table[temp]; - } - - internal int globalCrc; + globalCrc = (globalCrc << 8) ^ crc32Table[temp]; } -} \ No newline at end of file + + internal int globalCrc; +} diff --git a/src/SharpCompress/Compressors/CompressionMode.cs b/src/SharpCompress/Compressors/CompressionMode.cs index 0d288bc5..4eba1121 100644 --- a/src/SharpCompress/Compressors/CompressionMode.cs +++ b/src/SharpCompress/Compressors/CompressionMode.cs @@ -1,8 +1,7 @@ -namespace SharpCompress.Compressors +namespace SharpCompress.Compressors; + +public enum CompressionMode { - public enum CompressionMode - { - Compress = 0, - Decompress = 1 - } -} \ No newline at end of file + Compress = 0, + Decompress = 1 +} diff --git a/src/SharpCompress/Compressors/Deflate/CRC32.cs b/src/SharpCompress/Compressors/Deflate/CRC32.cs index d5e650c0..892459fb 100644 --- a/src/SharpCompress/Compressors/Deflate/CRC32.cs +++ b/src/SharpCompress/Compressors/Deflate/CRC32.cs @@ -35,259 +35,245 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +/// +/// Calculates a 32bit Cyclic Redundancy Checksum (CRC) using the same polynomial +/// used by Zip. This type is used internally by DotNetZip; it is generally not used +/// directly by applications wishing to create, read, or manipulate zip archive +/// files. +/// +[CLSCompliant(false)] +public class CRC32 { - /// - /// Calculates a 32bit Cyclic Redundancy Checksum (CRC) using the same polynomial - /// used by Zip. This type is used internally by DotNetZip; it is generally not used - /// directly by applications wishing to create, read, or manipulate zip archive - /// files. - /// - internal class CRC32 + private const int BUFFER_SIZE = 8192; + private static readonly uint[] crc32Table; + private uint runningCrc32Result = 0xFFFFFFFF; + + static CRC32() { - private const int BUFFER_SIZE = 8192; - private static readonly UInt32[] crc32Table; - private UInt32 runningCrc32Result = 0xFFFFFFFF; - - static CRC32() + unchecked { - unchecked + // PKZip specifies CRC32 with a polynomial of 0xEDB88320; + // This is also the CRC-32 polynomial used bby Ethernet, FDDI, + // bzip2, gzip, and others. + // Often the polynomial is shown reversed as 0x04C11DB7. + // For more details, see http://en.wikipedia.org/wiki/Cyclic_redundancy_check + var dwPolynomial = 0xEDB88320; + uint i, + j; + + crc32Table = new uint[256]; + + uint dwCrc; + for (i = 0; i < 256; i++) { - // PKZip specifies CRC32 with a polynomial of 0xEDB88320; - // This is also the CRC-32 polynomial used bby Ethernet, FDDI, - // bzip2, gzip, and others. - // Often the polynomial is shown reversed as 0x04C11DB7. - // For more details, see http://en.wikipedia.org/wiki/Cyclic_redundancy_check - UInt32 dwPolynomial = 0xEDB88320; - UInt32 i, j; - - crc32Table = new UInt32[256]; - - UInt32 dwCrc; - for (i = 0; i < 256; i++) + dwCrc = i; + for (j = 8; j > 0; j--) { - dwCrc = i; - for (j = 8; j > 0; j--) + if ((dwCrc & 1) == 1) { - if ((dwCrc & 1) == 1) - { - dwCrc = (dwCrc >> 1) ^ dwPolynomial; - } - else - { - dwCrc >>= 1; - } + dwCrc = (dwCrc >> 1) ^ dwPolynomial; } - crc32Table[i] = dwCrc; - } - } - } - - /// - /// indicates the total number of bytes read on the CRC stream. - /// This is used when writing the ZipDirEntry when compressing files. - /// - public Int64 TotalBytesRead { get; private set; } - - /// - /// Indicates the current CRC for all blocks slurped in. - /// - public Int32 Crc32Result => unchecked((Int32)(~runningCrc32Result)); - - /// - /// Returns the CRC32 for the specified stream. - /// - /// The stream over which to calculate the CRC32 - /// the CRC32 calculation - public UInt32 GetCrc32(Stream input) - { - return GetCrc32AndCopy(input, null); - } - - /// - /// Returns the CRC32 for the specified stream, and writes the input into the - /// output stream. - /// - /// The stream over which to calculate the CRC32 - /// The stream into which to deflate the input - /// the CRC32 calculation - public UInt32 GetCrc32AndCopy(Stream input, Stream? output) - { - if (input is null) - { - throw new ZlibException("The input stream must not be null."); - } - - unchecked - { - //UInt32 crc32Result; - //crc32Result = 0xFFFFFFFF; - var buffer = new byte[BUFFER_SIZE]; - int readSize = BUFFER_SIZE; - - TotalBytesRead = 0; - int count = input.Read(buffer, 0, readSize); - if (output != null) - { - output.Write(buffer, 0, count); - } - TotalBytesRead += count; - while (count > 0) - { - SlurpBlock(buffer, 0, count); - count = input.Read(buffer, 0, readSize); - if (output != null) + else { - output.Write(buffer, 0, count); + dwCrc >>= 1; } - TotalBytesRead += count; } - - return ~runningCrc32Result; + crc32Table[i] = dwCrc; } } + } - /// - /// Get the CRC32 for the given (word,byte) combo. This is a computation - /// defined by PKzip. - /// - /// The word to start with. - /// The byte to combine it with. - /// The CRC-ized result. - public Int32 ComputeCrc32(Int32 W, byte B) + /// + /// indicates the total number of bytes read on the CRC stream. + /// This is used when writing the ZipDirEntry when compressing files. + /// + public long TotalBytesRead { get; private set; } + + /// + /// Indicates the current CRC for all blocks slurped in. + /// + public int Crc32Result => unchecked((int)(~runningCrc32Result)); + + /// + /// Returns the CRC32 for the specified stream. + /// + /// The stream over which to calculate the CRC32 + /// the CRC32 calculation + public uint GetCrc32(Stream input) => GetCrc32AndCopy(input, null); + + /// + /// Returns the CRC32 for the specified stream, and writes the input into the + /// output stream. + /// + /// The stream over which to calculate the CRC32 + /// The stream into which to deflate the input + /// the CRC32 calculation + public uint GetCrc32AndCopy(Stream input, Stream? output) + { + if (input is null) { - return _InternalComputeCrc32((UInt32)W, B); + throw new ZlibException("The input stream must not be null."); } - - internal int _InternalComputeCrc32(UInt32 W, byte B) + unchecked { - return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8)); - } + //UInt32 crc32Result; + //crc32Result = 0xFFFFFFFF; + var buffer = new byte[BUFFER_SIZE]; + var readSize = BUFFER_SIZE; - /// - /// Update the value for the running CRC32 using the given block of bytes. - /// This is useful when using the CRC32() class in a Stream. - /// - /// block of bytes to slurp - /// starting point in the block - /// how many bytes within the block to slurp - public void SlurpBlock(byte[] block, int offset, int count) - { - if (block is null) - { - throw new ZlibException("The data buffer must not be null."); - } - - for (int i = 0; i < count; i++) - { - int x = offset + i; - runningCrc32Result = ((runningCrc32Result) >> 8) ^ - crc32Table[(block[x]) ^ ((runningCrc32Result) & 0x000000FF)]; - } + TotalBytesRead = 0; + var count = input.Read(buffer, 0, readSize); + output?.Write(buffer, 0, count); TotalBytesRead += count; + while (count > 0) + { + SlurpBlock(buffer, 0, count); + count = input.Read(buffer, 0, readSize); + output?.Write(buffer, 0, count); + TotalBytesRead += count; + } + + return ~runningCrc32Result; + } + } + + /// + /// Get the CRC32 for the given (word,byte) combo. This is a computation + /// defined by PKzip. + /// + /// The word to start with. + /// The byte to combine it with. + /// The CRC-ized result. + public int ComputeCrc32(int W, byte B) => _InternalComputeCrc32((uint)W, B); + + internal int _InternalComputeCrc32(uint W, byte B) => + (int)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8)); + + /// + /// Update the value for the running CRC32 using the given block of bytes. + /// This is useful when using the CRC32() class in a Stream. + /// + /// block of bytes to slurp + /// starting point in the block + /// how many bytes within the block to slurp + public void SlurpBlock(byte[] block, int offset, int count) + { + if (block is null) + { + throw new ZlibException("The data buffer must not be null."); } - // pre-initialize the crc table for speed of lookup. - - private uint gf2_matrix_times(ReadOnlySpan matrix, uint vec) + for (var i = 0; i < count; i++) { - uint sum = 0; - int i = 0; - while (vec != 0) + var x = offset + i; + runningCrc32Result = + ((runningCrc32Result) >> 8) + ^ crc32Table[(block[x]) ^ ((runningCrc32Result) & 0x000000FF)]; + } + TotalBytesRead += count; + } + + // pre-initialize the crc table for speed of lookup. + + private uint gf2_matrix_times(ReadOnlySpan matrix, uint vec) + { + uint sum = 0; + var i = 0; + while (vec != 0) + { + if ((vec & 0x01) == 0x01) { - if ((vec & 0x01) == 0x01) - { - sum ^= matrix[i]; - } - vec >>= 1; - i++; + sum ^= matrix[i]; } - return sum; + vec >>= 1; + i++; + } + return sum; + } + + private void gf2_matrix_square(Span square, Span mat) + { + for (var i = 0; i < 32; i++) + { + square[i] = gf2_matrix_times(mat, mat[i]); + } + } + + /// + /// Combines the given CRC32 value with the current running total. + /// + /// + /// This is useful when using a divide-and-conquer approach to calculating a CRC. + /// Multiple threads can each calculate a CRC32 on a segment of the data, and then + /// combine the individual CRC32 values at the end. + /// + /// the crc value to be combined with this one + /// the length of data the CRC value was calculated on + public void Combine(int crc, int length) + { + Span even = stackalloc uint[32]; // even-power-of-two zeros operator + Span odd = stackalloc uint[32]; // odd-power-of-two zeros operator + + if (length == 0) + { + return; } - private void gf2_matrix_square(Span square, Span mat) + var crc1 = ~runningCrc32Result; + var crc2 = (uint)crc; + + // put operator for one zero bit in odd + odd[0] = 0xEDB88320; // the CRC-32 polynomial + uint row = 1; + for (var i = 1; i < 32; i++) { - for (int i = 0; i < 32; i++) - { - square[i] = gf2_matrix_times(mat, mat[i]); - } + odd[i] = row; + row <<= 1; } - /// - /// Combines the given CRC32 value with the current running total. - /// - /// - /// This is useful when using a divide-and-conquer approach to calculating a CRC. - /// Multiple threads can each calculate a CRC32 on a segment of the data, and then - /// combine the individual CRC32 values at the end. - /// - /// the crc value to be combined with this one - /// the length of data the CRC value was calculated on - public void Combine(int crc, int length) + // put operator for two zero bits in even + gf2_matrix_square(even, odd); + + // put operator for four zero bits in odd + gf2_matrix_square(odd, even); + + var len2 = (uint)length; + + // apply len2 zeros to crc1 (first square will put the operator for one + // zero byte, eight zero bits, in even) + do { - Span even = stackalloc uint[32]; // even-power-of-two zeros operator - Span odd = stackalloc uint[32]; // odd-power-of-two zeros operator - - if (length == 0) - { - return; - } - - uint crc1 = ~runningCrc32Result; - var crc2 = (uint)crc; - - // put operator for one zero bit in odd - odd[0] = 0xEDB88320; // the CRC-32 polynomial - uint row = 1; - for (int i = 1; i < 32; i++) - { - odd[i] = row; - row <<= 1; - } - - // put operator for two zero bits in even + // apply zeros operator for this bit of len2 gf2_matrix_square(even, odd); - // put operator for four zero bits in odd - gf2_matrix_square(odd, even); - - var len2 = (uint)length; - - // apply len2 zeros to crc1 (first square will put the operator for one - // zero byte, eight zero bits, in even) - do + if ((len2 & 1) == 1) { - // apply zeros operator for this bit of len2 - gf2_matrix_square(even, odd); - - if ((len2 & 1) == 1) - { - crc1 = gf2_matrix_times(even, crc1); - } - len2 >>= 1; - - if (len2 == 0) - { - break; - } - - // another iteration of the loop with odd and even swapped - gf2_matrix_square(odd, even); - if ((len2 & 1) == 1) - { - crc1 = gf2_matrix_times(odd, crc1); - } - len2 >>= 1; + crc1 = gf2_matrix_times(even, crc1); } - while (len2 != 0); + len2 >>= 1; - crc1 ^= crc2; + if (len2 == 0) + { + break; + } - runningCrc32Result = ~crc1; + // another iteration of the loop with odd and even swapped + gf2_matrix_square(odd, even); + if ((len2 & 1) == 1) + { + crc1 = gf2_matrix_times(odd, crc1); + } + len2 >>= 1; + } while (len2 != 0); - //return (int) crc1; - } + crc1 ^= crc2; - // private member vars + runningCrc32Result = ~crc1; + + //return (int) crc1; } -} \ No newline at end of file + + // private member vars +} diff --git a/src/SharpCompress/Compressors/Deflate/DeflateManager.cs b/src/SharpCompress/Compressors/Deflate/DeflateManager.cs index 0abc4564..27ad4018 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateManager.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateManager.cs @@ -3,52 +3,52 @@ // Deflate.cs // ------------------------------------------------------------------ // -// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // -// This code is licensed under the Microsoft Public License. +// This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // -// last saved (in emacs): +// last saved (in emacs): // Time-stamp: <2009-October-28 13:44:59> // // ------------------------------------------------------------------ // // This module defines logic for handling the Deflate or compression. // -// This code is based on multiple sources: +// This code is based on multiple sources: // - the original zlib v1.2.3 source, which is Copyright (C) 1995-2005 Jean-loup Gailly. -// - the original jzlib, which is Copyright (c) 2000-2003 ymnk, JCraft,Inc. +// - the original jzlib, which is Copyright (c) 2000-2003 ymnk, JCraft,Inc. // -// However, this code is significantly different from both. +// However, this code is significantly different from both. // The object model is not the same, and many of the behaviors are different. // -// In keeping with the license for these other works, the copyrights for +// In keeping with the license for these other works, the copyrights for // jzlib and zlib are here. // // ----------------------------------------------------------------------- // Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the distribution. -// +// // 3. The names of the authors may not be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, @@ -59,7 +59,7 @@ // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // ----------------------------------------------------------------------- // // This program is based on zlib-1.1.3; credit to authors @@ -72,1249 +72,1449 @@ using System; using SharpCompress.Algorithms; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +internal sealed partial class DeflateManager { - internal sealed partial class DeflateManager + // extra bits for each length code + internal static readonly int[] ExtraLengthBits = { - // extra bits for each length code - internal static readonly int[] ExtraLengthBits = - { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 - }; + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0 + }; - // extra bits for each distance code - internal static readonly int[] ExtraDistanceBits = - { - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 - }; + // extra bits for each distance code + internal static readonly int[] ExtraDistanceBits = + { + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13 + }; - internal enum BlockState + internal enum BlockState + { + NeedMore = 0, // block not completed, need more input or more output + BlockDone, // block flush performed + FinishStarted, // finish started, need only more output at next deflate + FinishDone // finish done, accept no more input or output + } + + internal enum DeflateFlavor + { + Store, + Fast, + Slow + } + + private const int MEM_LEVEL_MAX = 9; + private const int MEM_LEVEL_DEFAULT = 8; + + internal delegate BlockState CompressFunc(FlushType flush); + + internal class Config + { + // Use a faster search when the previous match is longer than this + internal int GoodLength; // reduce lazy search above this match length + + // Attempt to find a better match only when the current match is + // strictly smaller than this value. This mechanism is used only for + // compression levels >= 4. For levels 1,2,3: MaxLazy is actually + // MaxInsertLength. (See DeflateFast) + + internal int MaxLazy; // do not perform lazy search above this match length + + internal int NiceLength; // quit search above this match length + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + + internal int MaxChainLength; + + internal DeflateFlavor Flavor; + + private Config( + int goodLength, + int maxLazy, + int niceLength, + int maxChainLength, + DeflateFlavor flavor + ) { - NeedMore = 0, // block not completed, need more input or more output - BlockDone, // block flush performed - FinishStarted, // finish started, need only more output at next deflate - FinishDone // finish done, accept no more input or output + GoodLength = goodLength; + MaxLazy = maxLazy; + NiceLength = niceLength; + MaxChainLength = maxChainLength; + Flavor = flavor; } - internal enum DeflateFlavor - { - Store, - Fast, - Slow - } + public static Config Lookup(CompressionLevel level) => Table[(int)level]; - private const int MEM_LEVEL_MAX = 9; - private const int MEM_LEVEL_DEFAULT = 8; - - internal delegate BlockState CompressFunc(FlushType flush); - - internal class Config - { - // Use a faster search when the previous match is longer than this - internal int GoodLength; // reduce lazy search above this match length - - // Attempt to find a better match only when the current match is - // strictly smaller than this value. This mechanism is used only for - // compression levels >= 4. For levels 1,2,3: MaxLazy is actually - // MaxInsertLength. (See DeflateFast) - - internal int MaxLazy; // do not perform lazy search above this match length - - internal int NiceLength; // quit search above this match length - - // To speed up deflation, hash chains are never searched beyond this - // length. A higher limit improves compression ratio but degrades the speed. - - internal int MaxChainLength; - - internal DeflateFlavor Flavor; - - private Config(int goodLength, int maxLazy, int niceLength, int maxChainLength, DeflateFlavor flavor) + static Config() => + Table = new[] { - GoodLength = goodLength; - MaxLazy = maxLazy; - NiceLength = niceLength; - MaxChainLength = maxChainLength; - Flavor = flavor; + new Config(0, 0, 0, 0, DeflateFlavor.Store), + new Config(4, 4, 8, 4, DeflateFlavor.Fast), + new Config(4, 5, 16, 8, DeflateFlavor.Fast), + new Config(4, 6, 32, 32, DeflateFlavor.Fast), + new Config(4, 4, 16, 16, DeflateFlavor.Slow), + new Config(8, 16, 32, 32, DeflateFlavor.Slow), + new Config(8, 16, 128, 128, DeflateFlavor.Slow), + new Config(8, 32, 128, 256, DeflateFlavor.Slow), + new Config(32, 128, 258, 1024, DeflateFlavor.Slow), + new Config(32, 258, 258, 4096, DeflateFlavor.Slow) + }; + + private static readonly Config[] Table; + } + + private CompressFunc DeflateFunction; + + private static readonly string[] _ErrorMessage = + { + "need dictionary", + "stream end", + "", + "file error", + "stream error", + "data error", + "insufficient memory", + "buffer error", + "incompatible version", + "" + }; + + // preset dictionary flag in zlib header + private const int PRESET_DICT = 0x20; + + private const int INIT_STATE = 42; + private const int BUSY_STATE = 113; + private const int FINISH_STATE = 666; + + // The deflate compression method + private const int Z_DEFLATED = 8; + + private const int STORED_BLOCK = 0; + private const int STATIC_TREES = 1; + private const int DYN_TREES = 2; + + // The three kinds of block type + private const int Z_BINARY = 0; + private const int Z_ASCII = 1; + private const int Z_UNKNOWN = 2; + + private const int Buf_size = 8 * 2; + + private const int MIN_MATCH = 3; + private const int MAX_MATCH = 258; + + private const int MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + + private static readonly int HEAP_SIZE = ((2 * InternalConstants.L_CODES) + 1); + + private const int END_BLOCK = 256; + + internal ZlibCodec _codec; // the zlib encoder/decoder + internal int status; // as the name implies + internal byte[] pending; // output still pending - waiting to be compressed + internal int nextPending; // index of next pending byte to output to the stream + internal int pendingCount; // number of bytes in the pending buffer + + internal sbyte data_type; // UNKNOWN, BINARY or ASCII + internal int last_flush; // value of flush param for previous deflate call + + internal int w_size; // LZ77 window size (32K by default) + internal int w_bits; // log2(w_size) (8..16) + internal int w_mask; // w_size - 1 + + //internal byte[] dictionary; + internal byte[] window; + + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. + // + // To do: use the user input buffer as sliding window. + + internal int window_size; + + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + internal short[] prev; + + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + private short[] head; // Heads of the hash chains or NIL. + + private int ins_h; // hash index of string to be inserted + private int hash_size; // number of elements in hash table + private int hash_bits; // log2(hash_size) + private int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + private int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + private int blockStart; + + private Config config; + private int match_length; // length of best match + private int prev_match; // previous match + private int match_available; // set if previous match exists + private int strstart; // start of string to insert into.....???? + private int match_start; // start of matching string + private int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + private int prev_length; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + private CompressionLevel compressionLevel; // compression level (1..9) + private CompressionStrategy compressionStrategy; // favor or force Huffman coding + + private readonly short[] dyn_ltree; // literal and length tree + private readonly short[] dyn_dtree; // distance tree + private readonly short[] bl_tree; // Huffman tree for bit lengths + + private readonly Tree treeLiterals = new Tree(); // desc for literal tree + private readonly Tree treeDistances = new Tree(); // desc for distance tree + private readonly Tree treeBitLengths = new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + private readonly short[] bl_count = new short[InternalConstants.MAX_BITS + 1]; + + // heap used to build the Huffman trees + private readonly int[] heap = new int[(2 * InternalConstants.L_CODES) + 1]; + + private int heap_len; // number of elements in the heap + private int heap_max; // element of largest frequency + + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + private readonly sbyte[] depth = new sbyte[(2 * InternalConstants.L_CODES) + 1]; + + private int _lengthOffset; // index for literals or lengths + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + + internal int lit_bufsize; + + internal int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + internal int _distanceOffset; // index into pending; points to distance data?? + + internal int opt_len; // bit length of current block with optimal trees + internal int static_len; // bit length of current block with static trees + internal int matches; // number of string matches in current block + internal int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + internal short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + internal int bi_valid; + + internal DeflateManager() + { + dyn_ltree = new short[HEAP_SIZE * 2]; + dyn_dtree = new short[((2 * InternalConstants.D_CODES) + 1) * 2]; // distance tree + bl_tree = new short[((2 * InternalConstants.BL_CODES) + 1) * 2]; // Huffman tree for bit lengths + } + + // lm_init + private void _InitializeLazyMatch() + { + window_size = 2 * w_size; + + // clear the hash - workitem 9063 + Array.Clear(head, 0, hash_size); + + //for (int i = 0; i < hash_size; i++) head[i] = 0; + + config = Config.Lookup(compressionLevel); + SetDeflater(); + + strstart = 0; + blockStart = 0; + lookahead = 0; + match_length = prev_length = MIN_MATCH - 1; + match_available = 0; + ins_h = 0; + } + + // Initialize the tree data structures for a new zlib stream. + private void _InitializeTreeData() + { + treeLiterals.dyn_tree = dyn_ltree; + treeLiterals.staticTree = StaticTree.Literals; + + treeDistances.dyn_tree = dyn_dtree; + treeDistances.staticTree = StaticTree.Distances; + + treeBitLengths.dyn_tree = bl_tree; + treeBitLengths.staticTree = StaticTree.BitLengths; + + bi_buf = 0; + bi_valid = 0; + last_eob_len = 8; // enough lookahead for inflate + + // Initialize the first block of the first file: + _InitializeBlocks(); + } + + internal void _InitializeBlocks() + { + // Initialize the trees. + for (var i = 0; i < InternalConstants.L_CODES; i++) + { + dyn_ltree[i * 2] = 0; + } + for (var i = 0; i < InternalConstants.D_CODES; i++) + { + dyn_dtree[i * 2] = 0; + } + for (var i = 0; i < InternalConstants.BL_CODES; i++) + { + bl_tree[i * 2] = 0; + } + + dyn_ltree[END_BLOCK * 2] = 1; + opt_len = static_len = 0; + last_lit = matches = 0; + } + + // Restore the heap property by moving down the tree starting at node k, + // exchanging a node with the smallest of its two sons if necessary, stopping + // when the heap property is re-established (each father smaller than its + // two sons). + internal void pqdownheap(short[] tree, int k) + { + var v = heap[k]; + var j = k << 1; // left son of k + while (j <= heap_len) + { + // Set j to the smallest of the two sons: + if (j < heap_len && IsSmaller(tree, heap[j + 1], heap[j], depth)) + { + j++; } - public static Config Lookup(CompressionLevel level) + // Exit if v is smaller than both sons + if (IsSmaller(tree, v, heap[j], depth)) { - return Table[(int)level]; + break; } - static Config() + // Exchange v with the smallest son + heap[k] = heap[j]; + k = j; + + // And continue down the tree, setting j to the left son of k + j <<= 1; + } + heap[k] = v; + } + + internal static bool IsSmaller(short[] tree, int n, int m, sbyte[] depth) + { + var tn2 = tree[n * 2]; + var tm2 = tree[m * 2]; + return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m])); + } + + // Scan a literal or distance tree to determine the frequencies of the codes + // in the bit length tree. + internal void ScanTree(short[] tree, int maxCode) + { + int n; // iterates over all tree elements + var prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[(0 * 2) + 1]; // length of next code + var count = 0; // repeat count of the current code + var max_count = 7; // max repeat count + var min_count = 4; // min repeat count + + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + tree[((maxCode + 1) * 2) + 1] = 0x7fff; // guard //?? + + for (n = 0; n <= maxCode; n++) + { + curlen = nextlen; + nextlen = tree[((n + 1) * 2) + 1]; + if (++count < max_count && curlen == nextlen) { - Table = new[] - { - new Config(0, 0, 0, 0, DeflateFlavor.Store), - new Config(4, 4, 8, 4, DeflateFlavor.Fast), - new Config(4, 5, 16, 8, DeflateFlavor.Fast), - new Config(4, 6, 32, 32, DeflateFlavor.Fast), - new Config(4, 4, 16, 16, DeflateFlavor.Slow), - new Config(8, 16, 32, 32, DeflateFlavor.Slow), - new Config(8, 16, 128, 128, DeflateFlavor.Slow), - new Config(8, 32, 128, 256, DeflateFlavor.Slow), - new Config(32, 128, 258, 1024, DeflateFlavor.Slow), - new Config(32, 258, 258, 4096, DeflateFlavor.Slow) - }; + continue; } - - private static readonly Config[] Table; - } - - private CompressFunc DeflateFunction; - - private static readonly String[] _ErrorMessage = - { - "need dictionary", - "stream end", - "", - "file error", - "stream error", - "data error", - "insufficient memory", - "buffer error", - "incompatible version", - "" - }; - - // preset dictionary flag in zlib header - private const int PRESET_DICT = 0x20; - - private const int INIT_STATE = 42; - private const int BUSY_STATE = 113; - private const int FINISH_STATE = 666; - - // The deflate compression method - private const int Z_DEFLATED = 8; - - private const int STORED_BLOCK = 0; - private const int STATIC_TREES = 1; - private const int DYN_TREES = 2; - - // The three kinds of block type - private const int Z_BINARY = 0; - private const int Z_ASCII = 1; - private const int Z_UNKNOWN = 2; - - private const int Buf_size = 8 * 2; - - private const int MIN_MATCH = 3; - private const int MAX_MATCH = 258; - - private const int MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); - - private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1); - - private const int END_BLOCK = 256; - - internal ZlibCodec _codec; // the zlib encoder/decoder - internal int status; // as the name implies - internal byte[] pending; // output still pending - waiting to be compressed - internal int nextPending; // index of next pending byte to output to the stream - internal int pendingCount; // number of bytes in the pending buffer - - internal sbyte data_type; // UNKNOWN, BINARY or ASCII - internal int last_flush; // value of flush param for previous deflate call - - internal int w_size; // LZ77 window size (32K by default) - internal int w_bits; // log2(w_size) (8..16) - internal int w_mask; // w_size - 1 - - //internal byte[] dictionary; - internal byte[] window; - - // Sliding window. Input bytes are read into the second half of the window, - // and move to the first half later to keep a dictionary of at least wSize - // bytes. With this organization, matches are limited to a distance of - // wSize-MAX_MATCH bytes, but this ensures that IO is always - // performed with a length multiple of the block size. - // - // To do: use the user input buffer as sliding window. - - internal int window_size; - - // Actual size of window: 2*wSize, except when the user input buffer - // is directly used as sliding window. - - internal short[] prev; - - // Link to older string with same hash index. To limit the size of this - // array to 64K, this link is maintained only for the last 32K strings. - // An index in this array is thus a window index modulo 32K. - - private short[] head; // Heads of the hash chains or NIL. - - private int ins_h; // hash index of string to be inserted - private int hash_size; // number of elements in hash table - private int hash_bits; // log2(hash_size) - private int hash_mask; // hash_size-1 - - // Number of bits by which ins_h must be shifted at each input - // step. It must be such that after MIN_MATCH steps, the oldest - // byte no longer takes part in the hash key, that is: - // hash_shift * MIN_MATCH >= hash_bits - private int hash_shift; - - // Window position at the beginning of the current output block. Gets - // negative when the window is moved backwards. - - private int blockStart; - - private Config config; - private int match_length; // length of best match - private int prev_match; // previous match - private int match_available; // set if previous match exists - private int strstart; // start of string to insert into.....???? - private int match_start; // start of matching string - private int lookahead; // number of valid bytes ahead in window - - // Length of the best match at previous step. Matches not greater than this - // are discarded. This is used in the lazy match evaluation. - private int prev_length; - - // Insert new strings in the hash table only if the match length is not - // greater than this length. This saves time but degrades compression. - // max_insert_length is used only for compression levels <= 3. - - private CompressionLevel compressionLevel; // compression level (1..9) - private CompressionStrategy compressionStrategy; // favor or force Huffman coding - - private readonly short[] dyn_ltree; // literal and length tree - private readonly short[] dyn_dtree; // distance tree - private readonly short[] bl_tree; // Huffman tree for bit lengths - - private readonly Tree treeLiterals = new Tree(); // desc for literal tree - private readonly Tree treeDistances = new Tree(); // desc for distance tree - private readonly Tree treeBitLengths = new Tree(); // desc for bit length tree - - // number of codes at each bit length for an optimal tree - private readonly short[] bl_count = new short[InternalConstants.MAX_BITS + 1]; - - // heap used to build the Huffman trees - private readonly int[] heap = new int[2 * InternalConstants.L_CODES + 1]; - - private int heap_len; // number of elements in the heap - private int heap_max; // element of largest frequency - - // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. - // The same heap array is used to build all trees. - - // Depth of each subtree used as tie breaker for trees of equal frequency - private readonly sbyte[] depth = new sbyte[2 * InternalConstants.L_CODES + 1]; - - private int _lengthOffset; // index for literals or lengths - - // Size of match buffer for literals/lengths. There are 4 reasons for - // limiting lit_bufsize to 64K: - // - frequencies can be kept in 16 bit counters - // - if compression is not successful for the first block, all input - // data is still in the window so we can still emit a stored block even - // when input comes from standard input. (This can also be done for - // all blocks if lit_bufsize is not greater than 32K.) - // - if compression is not successful for a file smaller than 64K, we can - // even emit a stored file instead of a stored block (saving 5 bytes). - // This is applicable only for zip (not gzip or zlib). - // - creating new Huffman trees less frequently may not provide fast - // adaptation to changes in the input data statistics. (Take for - // example a binary file with poorly compressible code followed by - // a highly compressible string table.) Smaller buffer sizes give - // fast adaptation but have of course the overhead of transmitting - // trees more frequently. - - internal int lit_bufsize; - - internal int last_lit; // running index in l_buf - - // Buffer for distances. To simplify the code, d_buf and l_buf have - // the same number of elements. To use different lengths, an extra flag - // array would be necessary. - - internal int _distanceOffset; // index into pending; points to distance data?? - - internal int opt_len; // bit length of current block with optimal trees - internal int static_len; // bit length of current block with static trees - internal int matches; // number of string matches in current block - internal int last_eob_len; // bit length of EOB code for last block - - // Output buffer. bits are inserted starting at the bottom (least - // significant bits). - internal short bi_buf; - - // Number of valid bits in bi_buf. All bits above the last valid bit - // are always zero. - internal int bi_valid; - - internal DeflateManager() - { - dyn_ltree = new short[HEAP_SIZE * 2]; - dyn_dtree = new short[(2 * InternalConstants.D_CODES + 1) * 2]; // distance tree - bl_tree = new short[(2 * InternalConstants.BL_CODES + 1) * 2]; // Huffman tree for bit lengths - } - - // lm_init - private void _InitializeLazyMatch() - { - window_size = 2 * w_size; - - // clear the hash - workitem 9063 - Array.Clear(head, 0, hash_size); - - //for (int i = 0; i < hash_size; i++) head[i] = 0; - - config = Config.Lookup(compressionLevel); - SetDeflater(); - - strstart = 0; - blockStart = 0; - lookahead = 0; - match_length = prev_length = MIN_MATCH - 1; - match_available = 0; - ins_h = 0; - } - - // Initialize the tree data structures for a new zlib stream. - private void _InitializeTreeData() - { - treeLiterals.dyn_tree = dyn_ltree; - treeLiterals.staticTree = StaticTree.Literals; - - treeDistances.dyn_tree = dyn_dtree; - treeDistances.staticTree = StaticTree.Distances; - - treeBitLengths.dyn_tree = bl_tree; - treeBitLengths.staticTree = StaticTree.BitLengths; - - bi_buf = 0; - bi_valid = 0; - last_eob_len = 8; // enough lookahead for inflate - - // Initialize the first block of the first file: - _InitializeBlocks(); - } - - internal void _InitializeBlocks() - { - // Initialize the trees. - for (int i = 0; i < InternalConstants.L_CODES; i++) + if (count < min_count) { - dyn_ltree[i * 2] = 0; + bl_tree[curlen * 2] = (short)(bl_tree[curlen * 2] + count); } - for (int i = 0; i < InternalConstants.D_CODES; i++) + else if (curlen != 0) { - dyn_dtree[i * 2] = 0; + if (curlen != prevlen) + { + bl_tree[curlen * 2]++; + } + bl_tree[InternalConstants.REP_3_6 * 2]++; } - for (int i = 0; i < InternalConstants.BL_CODES; i++) + else if (count <= 10) { - bl_tree[i * 2] = 0; - } - - dyn_ltree[END_BLOCK * 2] = 1; - opt_len = static_len = 0; - last_lit = matches = 0; - } - - // Restore the heap property by moving down the tree starting at node k, - // exchanging a node with the smallest of its two sons if necessary, stopping - // when the heap property is re-established (each father smaller than its - // two sons). - internal void pqdownheap(short[] tree, int k) - { - int v = heap[k]; - int j = k << 1; // left son of k - while (j <= heap_len) - { - // Set j to the smallest of the two sons: - if (j < heap_len && IsSmaller(tree, heap[j + 1], heap[j], depth)) - { - j++; - } - - // Exit if v is smaller than both sons - if (IsSmaller(tree, v, heap[j], depth)) - { - break; - } - - // Exchange v with the smallest son - heap[k] = heap[j]; - k = j; - - // And continue down the tree, setting j to the left son of k - j <<= 1; - } - heap[k] = v; - } - - internal static bool IsSmaller(short[] tree, int n, int m, sbyte[] depth) - { - short tn2 = tree[n * 2]; - short tm2 = tree[m * 2]; - return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m])); - } - - // Scan a literal or distance tree to determine the frequencies of the codes - // in the bit length tree. - internal void ScanTree(short[] tree, int maxCode) - { - int n; // iterates over all tree elements - int prevlen = -1; // last emitted length - int curlen; // length of current code - int nextlen = tree[0 * 2 + 1]; // length of next code - int count = 0; // repeat count of the current code - int max_count = 7; // max repeat count - int min_count = 4; // min repeat count - - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - tree[(maxCode + 1) * 2 + 1] = 0x7fff; // guard //?? - - for (n = 0; n <= maxCode; n++) - { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]; - if (++count < max_count && curlen == nextlen) - { - continue; - } - if (count < min_count) - { - bl_tree[curlen * 2] = (short)(bl_tree[curlen * 2] + count); - } - else if (curlen != 0) - { - if (curlen != prevlen) - { - bl_tree[curlen * 2]++; - } - bl_tree[InternalConstants.REP_3_6 * 2]++; - } - else if (count <= 10) - { - bl_tree[InternalConstants.REPZ_3_10 * 2]++; - } - else - { - bl_tree[InternalConstants.REPZ_11_138 * 2]++; - } - count = 0; - prevlen = curlen; - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - else if (curlen == nextlen) - { - max_count = 6; - min_count = 3; - } - else - { - max_count = 7; - min_count = 4; - } - } - } - - // Construct the Huffman tree for the bit lengths and return the index in - // bl_order of the last bit length code to send. - internal int BuildBlTree() - { - int max_blindex; // index of last bit length code of non zero freq - - // Determine the bit length frequencies for literal and distance trees - ScanTree(dyn_ltree, treeLiterals.max_code); - ScanTree(dyn_dtree, treeDistances.max_code); - - // Build the bit length tree: - treeBitLengths.build_tree(this); - - // opt_len now includes the length of the tree representations, except - // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. - - // Determine the number of bit length codes to send. The pkzip format - // requires that at least 4 bit length codes be sent. (appnote.txt says - // 3 but the actual value used is 4.) - for (max_blindex = InternalConstants.BL_CODES - 1; max_blindex >= 3; max_blindex--) - { - if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] != 0) - { - break; - } - } - - // Update opt_len to include the bit length tree and counts - opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; - - return max_blindex; - } - - // Send the header for a block using dynamic Huffman trees: the counts, the - // lengths of the bit length codes, the literal tree and the distance tree. - // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. - internal void send_all_trees(int lcodes, int dcodes, int blcodes) - { - int rank; // index in bl_order - - send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt - send_bits(dcodes - 1, 5); - send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt - for (rank = 0; rank < blcodes; rank++) - { - send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3); - } - send_tree(dyn_ltree, lcodes - 1); // literal tree - send_tree(dyn_dtree, dcodes - 1); // distance tree - } - - // Send a literal or distance tree in compressed form, using the codes in - // bl_tree. - internal void send_tree(short[] tree, int max_code) - { - int n; // iterates over all tree elements - int prevlen = -1; // last emitted length - int curlen; // length of current code - int nextlen = tree[0 * 2 + 1]; // length of next code - int count = 0; // repeat count of the current code - int max_count = 7; // max repeat count - int min_count = 4; // min repeat count - - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - - for (n = 0; n <= max_code; n++) - { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]; - if (++count < max_count && curlen == nextlen) - { - continue; - } - if (count < min_count) - { - do - { - send_code(curlen, bl_tree); - } - while (--count != 0); - } - else if (curlen != 0) - { - if (curlen != prevlen) - { - send_code(curlen, bl_tree); - count--; - } - send_code(InternalConstants.REP_3_6, bl_tree); - send_bits(count - 3, 2); - } - else if (count <= 10) - { - send_code(InternalConstants.REPZ_3_10, bl_tree); - send_bits(count - 3, 3); - } - else - { - send_code(InternalConstants.REPZ_11_138, bl_tree); - send_bits(count - 11, 7); - } - count = 0; - prevlen = curlen; - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - else if (curlen == nextlen) - { - max_count = 6; - min_count = 3; - } - else - { - max_count = 7; - min_count = 4; - } - } - } - - // Output a block of bytes on the stream. - // IN assertion: there is enough room in pending_buf. - private void put_bytes(byte[] p, int start, int len) - { - Array.Copy(p, start, pending, pendingCount, len); - pendingCount += len; - } - -#if NOTNEEDED - private void put_byte(byte c) - { - pending[pendingCount++] = c; - } - internal void put_short(int b) - { - unchecked - { - pending[pendingCount++] = (byte)b; - pending[pendingCount++] = (byte)(b >> 8); - } - } - internal void putShortMSB(int b) - { - unchecked - { - pending[pendingCount++] = (byte)(b >> 8); - pending[pendingCount++] = (byte)b; - } - } -#endif - - internal void send_code(int c, short[] tree) - { - int c2 = c * 2; - send_bits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff)); - } - -#pragma warning disable 675 // workaround for Visual Studio 2015 compiler bug: https://github.com/dotnet/roslyn/issues/4027 - internal void send_bits(int value, int length) - { - int len = length; - unchecked - { - if (bi_valid > Buf_size - len) - { - //int val = value; - // bi_buf |= (val << bi_valid); - - bi_buf |= (short)((value << bi_valid) & 0xffff); - - //put_short(bi_buf); - pending[pendingCount++] = (byte)bi_buf; - pending[pendingCount++] = (byte)(bi_buf >> 8); - - bi_buf = (short)((uint)value >> (Buf_size - bi_valid)); - bi_valid += len - Buf_size; - } - else - { - // bi_buf |= (value) << bi_valid; - bi_buf |= (short)((value << bi_valid) & 0xffff); - bi_valid += len; - } - } - } -#pragma warning restore 675 - - // Send one empty static block to give enough lookahead for inflate. - // This takes 10 bits, of which 7 may remain in the bit buffer. - // The current inflate code requires 9 bits of lookahead. If the - // last two codes for the previous block (real code plus EOB) were coded - // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode - // the last real code. In this case we send two empty static blocks instead - // of one. (There are no problems if the previous block is stored or fixed.) - // To simplify the code, we assume the worst case of last real code encoded - // on one bit only. - internal void _tr_align() - { - send_bits(STATIC_TREES << 1, 3); - send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); - - bi_flush(); - - // Of the 10 bits for the empty block, we have already sent - // (10 - bi_valid) bits. The lookahead for the last real code (before - // the EOB of the previous block) was thus at least one plus the length - // of the EOB plus what we have just sent of the empty static block. - if (1 + last_eob_len + 10 - bi_valid < 9) - { - send_bits(STATIC_TREES << 1, 3); - send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); - bi_flush(); - } - last_eob_len = 7; - } - - // Save the match info and tally the frequency counts. Return true if - // the current block must be flushed. - internal bool _tr_tally(int dist, int lc) - { - pending[_distanceOffset + last_lit * 2] = unchecked((byte)((uint)dist >> 8)); - pending[_distanceOffset + last_lit * 2 + 1] = unchecked((byte)dist); - pending[_lengthOffset + last_lit] = unchecked((byte)lc); - last_lit++; - - if (dist == 0) - { - // lc is the unmatched char - dyn_ltree[lc * 2]++; + bl_tree[InternalConstants.REPZ_3_10 * 2]++; } else { - matches++; - - // Here, lc is the match length - MIN_MATCH - dist--; // dist = match distance - 1 - dyn_ltree[(Tree.LengthCode[lc] + InternalConstants.LITERALS + 1) * 2]++; - dyn_dtree[Tree.DistanceCode(dist) * 2]++; + bl_tree[InternalConstants.REPZ_11_138 * 2]++; } - - if ((last_lit & 0x1fff) == 0 && (int)compressionLevel > 2) + count = 0; + prevlen = curlen; + if (nextlen == 0) { - // Compute an upper bound for the compressed length - int out_length = last_lit << 3; - int in_length = strstart - blockStart; - int dcode; - for (dcode = 0; dcode < InternalConstants.D_CODES; dcode++) - { - out_length = - (int)(out_length + dyn_dtree[dcode * 2] * (5L + ExtraDistanceBits[dcode])); - } - out_length >>= 3; - if ((matches < (last_lit / 2)) && out_length < in_length / 2) - { - return true; - } + max_count = 138; + min_count = 3; } + else if (curlen == nextlen) + { + max_count = 6; + min_count = 3; + } + else + { + max_count = 7; + min_count = 4; + } + } + } - return (last_lit == lit_bufsize - 1) || (last_lit == lit_bufsize); + // Construct the Huffman tree for the bit lengths and return the index in + // bl_order of the last bit length code to send. + internal int BuildBlTree() + { + int max_blindex; // index of last bit length code of non zero freq - // dinoch - wraparound? - // We avoid equality with lit_bufsize because of wraparound at 64K - // on 16 bit machines and because stored blocks are restricted to - // 64K-1 bytes. + // Determine the bit length frequencies for literal and distance trees + ScanTree(dyn_ltree, treeLiterals.max_code); + ScanTree(dyn_dtree, treeDistances.max_code); + + // Build the bit length tree: + treeBitLengths.build_tree(this); + + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for (max_blindex = InternalConstants.BL_CODES - 1; max_blindex >= 3; max_blindex--) + { + if (bl_tree[(Tree.bl_order[max_blindex] * 2) + 1] != 0) + { + break; + } } - // Send the block data compressed using the given Huffman trees - internal void send_compressed_block(short[] ltree, short[] dtree) - { - int distance; // distance of matched string - int lc; // match length or unmatched char (if dist == 0) - int lx = 0; // running index in l_buf - int code; // the code to send - int extra; // number of extra bits to send + // Update opt_len to include the bit length tree and counts + opt_len += (3 * (max_blindex + 1)) + 5 + 5 + 4; - if (last_lit != 0) + return max_blindex; + } + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + internal void send_all_trees(int lcodes, int dcodes, int blcodes) + { + int rank; // index in bl_order + + send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes - 1, 5); + send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) + { + send_bits(bl_tree[(Tree.bl_order[rank] * 2) + 1], 3); + } + send_tree(dyn_ltree, lcodes - 1); // literal tree + send_tree(dyn_dtree, dcodes - 1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + internal void send_tree(short[] tree, int max_code) + { + int n; // iterates over all tree elements + var prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[(0 * 2) + 1]; // length of next code + var count = 0; // repeat count of the current code + var max_count = 7; // max repeat count + var min_count = 4; // min repeat count + + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) + { + curlen = nextlen; + nextlen = tree[((n + 1) * 2) + 1]; + if (++count < max_count && curlen == nextlen) + { + continue; + } + if (count < min_count) { do { - int ix = _distanceOffset + lx * 2; - distance = ((pending[ix] << 8) & 0xff00) | - (pending[ix + 1] & 0xff); - lc = (pending[_lengthOffset + lx]) & 0xff; - lx++; - - if (distance == 0) - { - send_code(lc, ltree); // send a literal byte - } - else - { - // literal or match pair - // Here, lc is the match length - MIN_MATCH - code = Tree.LengthCode[lc]; - - // send the length code - send_code(code + InternalConstants.LITERALS + 1, ltree); - extra = ExtraLengthBits[code]; - if (extra != 0) - { - // send the extra length bits - lc -= Tree.LengthBase[code]; - send_bits(lc, extra); - } - distance--; // dist is now the match distance - 1 - code = Tree.DistanceCode(distance); - - // send the distance code - send_code(code, dtree); - - extra = ExtraDistanceBits[code]; - if (extra != 0) - { - // send the extra distance bits - distance -= Tree.DistanceBase[code]; - send_bits(distance, extra); - } - } - - // Check that the overlay between pending and d_buf+l_buf is ok: - } - while (lx < last_lit); + send_code(curlen, bl_tree); + } while (--count != 0); } - - send_code(END_BLOCK, ltree); - last_eob_len = ltree[END_BLOCK * 2 + 1]; - } - - // Set the data type to ASCII or BINARY, using a crude approximation: - // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. - // IN assertion: the fields freq of dyn_ltree are set and the total of all - // frequencies does not exceed 64K (to fit in an int on 16 bit machines). - internal void set_data_type() - { - int n = 0; - int ascii_freq = 0; - int bin_freq = 0; - while (n < 7) + else if (curlen != 0) { - bin_freq += dyn_ltree[n * 2]; - n++; - } - while (n < 128) - { - ascii_freq += dyn_ltree[n * 2]; - n++; - } - while (n < InternalConstants.LITERALS) - { - bin_freq += dyn_ltree[n * 2]; - n++; - } - data_type = (sbyte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); - } - - // Flush the bit buffer, keeping at most 7 bits in it. - internal void bi_flush() - { - if (bi_valid == 16) - { - pending[pendingCount++] = (byte)bi_buf; - pending[pendingCount++] = (byte)(bi_buf >> 8); - bi_buf = 0; - bi_valid = 0; - } - else if (bi_valid >= 8) - { - //put_byte((byte)bi_buf); - pending[pendingCount++] = (byte)bi_buf; - bi_buf >>= 8; - bi_valid -= 8; - } - } - - // Flush the bit buffer and align the output on a byte boundary - internal void bi_windup() - { - if (bi_valid > 8) - { - pending[pendingCount++] = (byte)bi_buf; - pending[pendingCount++] = (byte)(bi_buf >> 8); - } - else if (bi_valid > 0) - { - //put_byte((byte)bi_buf); - pending[pendingCount++] = (byte)bi_buf; - } - bi_buf = 0; - bi_valid = 0; - } - - // Copy a stored block, storing first the length and its - // one's complement if requested. - internal void copy_block(int buf, int len, bool header) - { - bi_windup(); // align on byte boundary - last_eob_len = 8; // enough lookahead for inflate - - if (header) - { - unchecked + if (curlen != prevlen) { - //put_short((short)len); - pending[pendingCount++] = (byte)len; - pending[pendingCount++] = (byte)(len >> 8); - - //put_short((short)~len); - pending[pendingCount++] = (byte)~len; - pending[pendingCount++] = (byte)(~len >> 8); + send_code(curlen, bl_tree); + count--; } + send_code(InternalConstants.REP_3_6, bl_tree); + send_bits(count - 3, 2); } - - put_bytes(window, buf, len); - } - - internal void flush_block_only(bool eof) - { - _tr_flush_block(blockStart >= 0 ? blockStart : -1, strstart - blockStart, eof); - blockStart = strstart; - _codec.flush_pending(); - } - - // Copy without compression as much as possible from the input stream, return - // the current block state. - // This function does not insert new strings in the dictionary since - // uncompressible data is probably not useful. This function is used - // only for the level=0 compression option. - // NOTE: this function should be optimized to avoid extra copying from - // window to pending_buf. - internal BlockState DeflateNone(FlushType flush) - { - // Stored blocks are limited to 0xffff bytes, pending is limited - // to pending_buf_size, and each stored block has a 5 byte header: - - int max_block_size = 0xffff; - int max_start; - - if (max_block_size > pending.Length - 5) + else if (count <= 10) { - max_block_size = pending.Length - 5; - } - - // Copy as much as possible from input to output: - while (true) - { - // Fill the window as much as possible: - if (lookahead <= 1) - { - _fillWindow(); - if (lookahead == 0 && flush == FlushType.None) - { - return BlockState.NeedMore; - } - if (lookahead == 0) - { - break; // flush the current block - } - } - - strstart += lookahead; - lookahead = 0; - - // Emit a stored block if pending will be full: - max_start = blockStart + max_block_size; - if (strstart == 0 || strstart >= max_start) - { - // strstart == 0 is possible when wraparound on 16-bit machine - lookahead = strstart - max_start; - strstart = max_start; - - flush_block_only(false); - if (_codec.AvailableBytesOut == 0) - { - return BlockState.NeedMore; - } - } - - // Flush if we may have to slide, otherwise block_start may become - // negative and the data will be gone: - if (strstart - blockStart >= w_size - MIN_LOOKAHEAD) - { - flush_block_only(false); - if (_codec.AvailableBytesOut == 0) - { - return BlockState.NeedMore; - } - } - } - - flush_block_only(flush == FlushType.Finish); - if (_codec.AvailableBytesOut == 0) - { - return (flush == FlushType.Finish) ? BlockState.FinishStarted : BlockState.NeedMore; - } - - return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; - } - - // Send a stored block - internal void _tr_stored_block(int buf, int stored_len, bool eof) - { - send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type - copy_block(buf, stored_len, true); // with header - } - - // Determine the best encoding for the current block: dynamic trees, static - // trees or store, and output the encoded block to the zip file. - internal void _tr_flush_block(int buf, int stored_len, bool eof) - { - int opt_lenb, static_lenb; // opt_len and static_len in bytes - int max_blindex = 0; // index of last bit length code of non zero freq - - // Build the Huffman trees unless a stored block is forced - if (compressionLevel > 0) - { - // Check if the file is ascii or binary - if (data_type == Z_UNKNOWN) - { - set_data_type(); - } - - // Construct the literal and distance trees - treeLiterals.build_tree(this); - - treeDistances.build_tree(this); - - // At this point, opt_len and static_len are the total bit lengths of - // the compressed block data, excluding the tree representations. - - // Build the bit length tree for the above two trees, and get the index - // in bl_order of the last bit length code to send. - max_blindex = BuildBlTree(); - - // Determine the best encoding. Compute first the block length in bytes - opt_lenb = (opt_len + 3 + 7) >> 3; - static_lenb = (static_len + 3 + 7) >> 3; - - if (static_lenb <= opt_lenb) - { - opt_lenb = static_lenb; - } + send_code(InternalConstants.REPZ_3_10, bl_tree); + send_bits(count - 3, 3); } else { - opt_lenb = static_lenb = stored_len + 5; // force a stored block + send_code(InternalConstants.REPZ_11_138, bl_tree); + send_bits(count - 11, 7); } - - if (stored_len + 4 <= opt_lenb && buf != -1) + count = 0; + prevlen = curlen; + if (nextlen == 0) { - // 4: two words for the lengths - // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. - // Otherwise we can't have processed more than WSIZE input bytes since - // the last block flush, because compression would have been - // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to - // transform a block into a stored block. - _tr_stored_block(buf, stored_len, eof); + max_count = 138; + min_count = 3; } - else if (static_lenb == opt_lenb) + else if (curlen == nextlen) { - send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3); - send_compressed_block(StaticTree.lengthAndLiteralsTreeCodes, StaticTree.distTreeCodes); + max_count = 6; + min_count = 3; } else { - send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3); - send_all_trees(treeLiterals.max_code + 1, treeDistances.max_code + 1, max_blindex + 1); - send_compressed_block(dyn_ltree, dyn_dtree); + max_count = 7; + min_count = 4; } + } + } - // The above check is made mod 2^32, for files larger than 512 MB - // and uLong implemented on 32 bits. + // Output a block of bytes on the stream. + // IN assertion: there is enough room in pending_buf. + private void put_bytes(byte[] p, int start, int len) + { + Array.Copy(p, start, pending, pendingCount, len); + pendingCount += len; + } - _InitializeBlocks(); +#if NOTNEEDED + private void put_byte(byte c) + { + pending[pendingCount++] = c; + } - if (eof) + internal void put_short(int b) + { + unchecked + { + pending[pendingCount++] = (byte)b; + pending[pendingCount++] = (byte)(b >> 8); + } + } + + internal void putShortMSB(int b) + { + unchecked + { + pending[pendingCount++] = (byte)(b >> 8); + pending[pendingCount++] = (byte)b; + } + } +#endif + + internal void send_code(int c, short[] tree) + { + var c2 = c * 2; + send_bits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff)); + } + +#pragma warning disable 675 // workaround for Visual Studio 2015 compiler bug: https://github.com/dotnet/roslyn/issues/4027 + internal void send_bits(int value, int length) + { + var len = length; + unchecked + { + if (bi_valid > Buf_size - len) { - bi_windup(); + //int val = value; + // bi_buf |= (val << bi_valid); + + bi_buf |= (short)((value << bi_valid) & 0xffff); + + //put_short(bi_buf); + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + + bi_buf = (short)((uint)value >> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } + else + { + // bi_buf |= (value) << bi_valid; + bi_buf |= (short)((value << bi_valid) & 0xffff); + bi_valid += len; + } + } + } +#pragma warning restore 675 + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + internal void _tr_align() + { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) + { + send_bits(STATIC_TREES << 1, 3); + send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes); + bi_flush(); + } + last_eob_len = 7; + } + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + internal bool _tr_tally(int dist, int lc) + { + pending[_distanceOffset + (last_lit * 2)] = unchecked((byte)((uint)dist >> 8)); + pending[_distanceOffset + (last_lit * 2) + 1] = unchecked((byte)dist); + pending[_lengthOffset + last_lit] = unchecked((byte)lc); + last_lit++; + + if (dist == 0) + { + // lc is the unmatched char + dyn_ltree[lc * 2]++; + } + else + { + matches++; + + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree.LengthCode[lc] + InternalConstants.LITERALS + 1) * 2]++; + dyn_dtree[Tree.DistanceCode(dist) * 2]++; + } + + if ((last_lit & 0x1fff) == 0 && (int)compressionLevel > 2) + { + // Compute an upper bound for the compressed length + var out_length = last_lit << 3; + var in_length = strstart - blockStart; + int dcode; + for (dcode = 0; dcode < InternalConstants.D_CODES; dcode++) + { + out_length = (int)( + out_length + (dyn_dtree[dcode * 2] * (5L + ExtraDistanceBits[dcode])) + ); + } + out_length >>= 3; + if ((matches < (last_lit / 2)) && out_length < in_length / 2) + { + return true; } } - // Fill the window when the lookahead becomes insufficient. - // Updates strstart and lookahead. - // - // IN assertion: lookahead < MIN_LOOKAHEAD - // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - // At least one byte has been read, or avail_in == 0; reads are - // performed for at least two bytes (required for the zip translate_eol - // option -- not supported here). - private void _fillWindow() - { - int n, m; - int p; - int more; // Amount of free space at the end of the window. + return (last_lit == lit_bufsize - 1) || (last_lit == lit_bufsize); + // dinoch - wraparound? + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + internal void send_compressed_block(short[] ltree, short[] dtree) + { + int distance; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + var lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0) + { do { - more = (window_size - lookahead - strstart); + var ix = _distanceOffset + (lx * 2); + distance = ((pending[ix] << 8) & 0xff00) | (pending[ix + 1] & 0xff); + lc = (pending[_lengthOffset + lx]) & 0xff; + lx++; - // Deal with !@#$% 64K limit: - if (more == 0 && strstart == 0 && lookahead == 0) + if (distance == 0) { - more = w_size; - } - else if (more == -1) - { - // Very unlikely, but possible on 16 bit machine if strstart == 0 - // and lookahead == 1 (input done one byte at time) - more--; - - // If the window is almost full and there is insufficient lookahead, - // move the upper half to the lower one to make room in the upper half. - } - else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) - { - Array.Copy(window, w_size, window, 0, w_size); - match_start -= w_size; - strstart -= w_size; // we now have strstart >= MAX_DIST - blockStart -= w_size; - - // Slide the hash table (could be avoided with 32 bit values - // at the expense of memory usage). We slide even when level == 0 - // to keep the hash table consistent if we switch back to level > 0 - // later. (Using level 0 permanently is not an optimal usage of - // zlib, so we don't care about this pathological case.) - - n = hash_size; - p = n; - do - { - m = (head[--p] & 0xffff); - head[p] = (short)((m >= w_size) ? (m - w_size) : 0); - } - while (--n != 0); - - n = w_size; - p = n; - do - { - m = (prev[--p] & 0xffff); - prev[p] = (short)((m >= w_size) ? (m - w_size) : 0); - - // If n is not on any hash chain, prev[n] is garbage but - // its value will never be used. - } - while (--n != 0); - more += w_size; - } - - if (_codec.AvailableBytesIn == 0) - { - return; - } - - // If there was no sliding: - // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - // more == window_size - lookahead - strstart - // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - // => more >= window_size - 2*WSIZE + 2 - // In the BIG_MEM or MMAP case (not yet supported), - // window_size == input_size + MIN_LOOKAHEAD && - // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - // Otherwise, window_size == 2*WSIZE so more >= 2. - // If there was sliding, more >= WSIZE. So in all cases, more >= 2. - - n = _codec.read_buf(window, strstart + lookahead, more); - lookahead += n; - - // Initialize the hash value now that we have some input: - if (lookahead >= MIN_MATCH) - { - ins_h = window[strstart] & 0xff; - ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; - } - - // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - // but this is not important since only literal bytes will be emitted. - } - while (lookahead < MIN_LOOKAHEAD && _codec.AvailableBytesIn != 0); - } - - // Compress as much as possible from the input stream, return the current - // block state. - // This function does not perform lazy evaluation of matches and inserts - // new strings in the dictionary only for unmatched strings or for short - // matches. It is used only for the fast compression options. - internal BlockState DeflateFast(FlushType flush) - { - // short hash_head = 0; // head of the hash chain - int hash_head = 0; // head of the hash chain - bool bflush; // set if current block must be flushed - - while (true) - { - // Make sure that we always have enough lookahead, except - // at the end of the input file. We need MAX_MATCH bytes - // for the next match, plus MIN_MATCH bytes to insert the - // string following the next match. - if (lookahead < MIN_LOOKAHEAD) - { - _fillWindow(); - if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) - { - return BlockState.NeedMore; - } - if (lookahead == 0) - { - break; // flush the current block - } - } - - // Insert the string window[strstart .. strstart+2] in the - // dictionary, and set hash_head to the head of the hash chain: - if (lookahead >= MIN_MATCH) - { - ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; - - // prev[strstart&w_mask]=hash_head=head[ins_h]; - hash_head = (head[ins_h] & 0xffff); - prev[strstart & w_mask] = head[ins_h]; - head[ins_h] = unchecked((short)strstart); - } - - // Find the longest match, discarding those <= prev_length. - // At this point we have always match_length < MIN_MATCH - - if (hash_head != 0L && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) - { - // To simplify the code, we prevent matches with the string - // of window index 0 (in particular we have to avoid a match - // of the string with itself at the start of the input file). - if (compressionStrategy != CompressionStrategy.HuffmanOnly) - { - match_length = longest_match(hash_head); - } - - // longest_match() sets match_start - } - if (match_length >= MIN_MATCH) - { - // check_match(strstart, match_start, match_length); - - bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH); - - lookahead -= match_length; - - // Insert new strings in the hash table only if the match length - // is not too large. This saves time but degrades compression. - if (match_length <= config.MaxLazy && lookahead >= MIN_MATCH) - { - match_length--; // string at strstart already in hash table - do - { - strstart++; - - ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; - - // prev[strstart&w_mask]=hash_head=head[ins_h]; - hash_head = (head[ins_h] & 0xffff); - prev[strstart & w_mask] = head[ins_h]; - head[ins_h] = unchecked((short)strstart); - - // strstart never exceeds WSIZE-MAX_MATCH, so there are - // always MIN_MATCH bytes ahead. - } - while (--match_length != 0); - strstart++; - } - else - { - strstart += match_length; - match_length = 0; - ins_h = window[strstart] & 0xff; - - ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; - - // If lookahead < MIN_MATCH, ins_h is garbage, but it does not - // matter since it will be recomputed at next deflate call. - } + send_code(lc, ltree); // send a literal byte } else { - // No match, output a literal byte + // literal or match pair + // Here, lc is the match length - MIN_MATCH + code = Tree.LengthCode[lc]; - bflush = _tr_tally(0, window[strstart] & 0xff); - lookahead--; + // send the length code + send_code(code + InternalConstants.LITERALS + 1, ltree); + extra = ExtraLengthBits[code]; + if (extra != 0) + { + // send the extra length bits + lc -= Tree.LengthBase[code]; + send_bits(lc, extra); + } + distance--; // dist is now the match distance - 1 + code = Tree.DistanceCode(distance); + + // send the distance code + send_code(code, dtree); + + extra = ExtraDistanceBits[code]; + if (extra != 0) + { + // send the extra distance bits + distance -= Tree.DistanceBase[code]; + send_bits(distance, extra); + } + } + + // Check that the overlay between pending and d_buf+l_buf is ok: + } while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[(END_BLOCK * 2) + 1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + internal void set_data_type() + { + var n = 0; + var ascii_freq = 0; + var bin_freq = 0; + while (n < 7) + { + bin_freq += dyn_ltree[n * 2]; + n++; + } + while (n < 128) + { + ascii_freq += dyn_ltree[n * 2]; + n++; + } + while (n < InternalConstants.LITERALS) + { + bin_freq += dyn_ltree[n * 2]; + n++; + } + data_type = (sbyte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + internal void bi_flush() + { + if (bi_valid == 16) + { + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + bi_buf = 0; + bi_valid = 0; + } + else if (bi_valid >= 8) + { + //put_byte((byte)bi_buf); + pending[pendingCount++] = (byte)bi_buf; + bi_buf >>= 8; + bi_valid -= 8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + internal void bi_windup() + { + if (bi_valid > 8) + { + pending[pendingCount++] = (byte)bi_buf; + pending[pendingCount++] = (byte)(bi_buf >> 8); + } + else if (bi_valid > 0) + { + //put_byte((byte)bi_buf); + pending[pendingCount++] = (byte)bi_buf; + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + internal void copy_block(int buf, int len, bool header) + { + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) + { + unchecked + { + //put_short((short)len); + pending[pendingCount++] = (byte)len; + pending[pendingCount++] = (byte)(len >> 8); + + //put_short((short)~len); + pending[pendingCount++] = (byte)~len; + pending[pendingCount++] = (byte)(~len >> 8); + } + } + + put_bytes(window, buf, len); + } + + internal void flush_block_only(bool eof) + { + _tr_flush_block(blockStart >= 0 ? blockStart : -1, strstart - blockStart, eof); + blockStart = strstart; + _codec.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + internal BlockState DeflateNone(FlushType flush) + { + // Stored blocks are limited to 0xffff bytes, pending is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + var max_block_size = 0xffff; + int max_start; + + if (max_block_size > pending.Length - 5) + { + max_block_size = pending.Length - 5; + } + + // Copy as much as possible from input to output: + while (true) + { + // Fill the window as much as possible: + if (lookahead <= 1) + { + _fillWindow(); + if (lookahead == 0 && flush == FlushType.None) + { + return BlockState.NeedMore; + } + if (lookahead == 0) + { + break; // flush the current block + } + } + + strstart += lookahead; + lookahead = 0; + + // Emit a stored block if pending will be full: + max_start = blockStart + max_block_size; + if (strstart == 0 || strstart >= max_start) + { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = strstart - max_start; + strstart = max_start; + + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + { + return BlockState.NeedMore; + } + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if (strstart - blockStart >= w_size - MIN_LOOKAHEAD) + { + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + { + return BlockState.NeedMore; + } + } + } + + flush_block_only(flush == FlushType.Finish); + if (_codec.AvailableBytesOut == 0) + { + return (flush == FlushType.Finish) ? BlockState.FinishStarted : BlockState.NeedMore; + } + + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + // Send a stored block + internal void _tr_stored_block(int buf, int stored_len, bool eof) + { + send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + internal void _tr_flush_block(int buf, int stored_len, bool eof) + { + int opt_lenb, + static_lenb; // opt_len and static_len in bytes + var max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if (compressionLevel > 0) + { + // Check if the file is ascii or binary + if (data_type == Z_UNKNOWN) + { + set_data_type(); + } + + // Construct the literal and distance trees + treeLiterals.build_tree(this); + + treeDistances.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex = BuildBlTree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb = (opt_len + 3 + 7) >> 3; + static_lenb = (static_len + 3 + 7) >> 3; + + if (static_lenb <= opt_lenb) + { + opt_lenb = static_lenb; + } + } + else + { + opt_lenb = static_lenb = stored_len + 5; // force a stored block + } + + if (stored_len + 4 <= opt_lenb && buf != -1) + { + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if (static_lenb == opt_lenb) + { + send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3); + send_compressed_block(StaticTree.lengthAndLiteralsTreeCodes, StaticTree.distTreeCodes); + } + else + { + send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3); + send_all_trees(treeLiterals.max_code + 1, treeDistances.max_code + 1, max_blindex + 1); + send_compressed_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + _InitializeBlocks(); + + if (eof) + { + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + private void _fillWindow() + { + int n, + m; + int p; + int more; // Amount of free space at the end of the window. + + do + { + more = (window_size - lookahead - strstart); + + // Deal with !@#$% 64K limit: + if (more == 0 && strstart == 0 && lookahead == 0) + { + more = w_size; + } + else if (more == -1) + { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) + { + Array.Copy(window, w_size, window, 0, w_size); + match_start -= w_size; + strstart -= w_size; // we now have strstart >= MAX_DIST + blockStart -= w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p = n; + do + { + m = (head[--p] & 0xffff); + head[p] = (short)((m >= w_size) ? (m - w_size) : 0); + } while (--n != 0); + + n = w_size; + p = n; + do + { + m = (prev[--p] & 0xffff); + prev[p] = (short)((m >= w_size) ? (m - w_size) : 0); + + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } while (--n != 0); + more += w_size; + } + + if (_codec.AvailableBytesIn == 0) + { + return; + } + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = _codec.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if (lookahead >= MIN_MATCH) + { + ins_h = window[strstart] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + } + + // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + // but this is not important since only literal bytes will be emitted. + } while (lookahead < MIN_LOOKAHEAD && _codec.AvailableBytesIn != 0); + } + + // Compress as much as possible from the input stream, return the current + // block state. + // This function does not perform lazy evaluation of matches and inserts + // new strings in the dictionary only for unmatched strings or for short + // matches. It is used only for the fast compression options. + internal BlockState DeflateFast(FlushType flush) + { + // short hash_head = 0; // head of the hash chain + var hash_head = 0; // head of the hash chain + bool bflush; // set if current block must be flushed + + while (true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if (lookahead < MIN_LOOKAHEAD) + { + _fillWindow(); + if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) + { + return BlockState.NeedMore; + } + if (lookahead == 0) + { + break; // flush the current block + } + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if (lookahead >= MIN_MATCH) + { + ins_h = + (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) + & hash_mask; + + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + + if (hash_head != 0L && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + if (compressionStrategy != CompressionStrategy.HuffmanOnly) + { + match_length = longest_match(hash_head); + } + + // longest_match() sets match_start + } + if (match_length >= MIN_MATCH) + { + // check_match(strstart, match_start, match_length); + + bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if (match_length <= config.MaxLazy && lookahead >= MIN_MATCH) + { + match_length--; // string at strstart already in hash table + do + { + strstart++; + + ins_h = + ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) + & hash_mask; + + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + } while (--match_length != 0); strstart++; } + else + { + strstart += match_length; + match_length = 0; + ins_h = window[strstart] & 0xff; + + ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask; + + // If lookahead < MIN_MATCH, ins_h is garbage, but it does not + // matter since it will be recomputed at next deflate call. + } + } + else + { + // No match, output a literal byte + + bflush = _tr_tally(0, window[strstart] & 0xff); + lookahead--; + strstart++; + } + if (bflush) + { + flush_block_only(false); + if (_codec.AvailableBytesOut == 0) + { + return BlockState.NeedMore; + } + } + } + + flush_block_only(flush == FlushType.Finish); + if (_codec.AvailableBytesOut == 0) + { + if (flush == FlushType.Finish) + { + return BlockState.FinishStarted; + } + return BlockState.NeedMore; + } + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + // Same as above, but achieves better compression. We use a lazy + // evaluation for matches: a match is finally adopted only if there is + // no better match at the next window position. + internal BlockState DeflateSlow(FlushType flush) + { + // short hash_head = 0; // head of hash chain + var hash_head = 0; // head of hash chain + bool bflush; // set if current block must be flushed + + // Process the input block. + while (true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + + if (lookahead < MIN_LOOKAHEAD) + { + _fillWindow(); + if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) + { + return BlockState.NeedMore; + } + + if (lookahead == 0) + { + break; // flush the current block + } + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + + if (lookahead >= MIN_MATCH) + { + ins_h = + (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) + & hash_mask; + + // prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + + // Find the longest match, discarding those <= prev_length. + prev_length = match_length; + prev_match = match_start; + match_length = MIN_MATCH - 1; + + if ( + hash_head != 0 + && prev_length < config.MaxLazy + && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD + ) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + + if (compressionStrategy != CompressionStrategy.HuffmanOnly) + { + match_length = longest_match(hash_head); + } + + // longest_match() sets match_start + + if ( + match_length <= 5 + && ( + compressionStrategy == CompressionStrategy.Filtered + || (match_length == MIN_MATCH && strstart - match_start > 4096) + ) + ) + { + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH - 1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if (prev_length >= MIN_MATCH && match_length <= prev_length) + { + var max_insert = strstart + lookahead - MIN_MATCH; + + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= (prev_length - 1); + prev_length -= 2; + do + { + if (++strstart <= max_insert) + { + ins_h = + ( + ((ins_h) << hash_shift) + ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff) + ) & hash_mask; + + //prev[strstart&w_mask]=hash_head=head[ins_h]; + hash_head = (head[ins_h] & 0xffff); + prev[strstart & w_mask] = head[ins_h]; + head[ins_h] = unchecked((short)strstart); + } + } while (--prev_length != 0); + match_available = 0; + match_length = MIN_MATCH - 1; + strstart++; + if (bflush) { flush_block_only(false); @@ -1324,668 +1524,549 @@ namespace SharpCompress.Compressors.Deflate } } } - - flush_block_only(flush == FlushType.Finish); - if (_codec.AvailableBytesOut == 0) + else if (match_available != 0) { - if (flush == FlushType.Finish) + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + + if (bflush) { - return BlockState.FinishStarted; + flush_block_only(false); + } + strstart++; + lookahead--; + if (_codec.AvailableBytesOut == 0) + { + return BlockState.NeedMore; } - return BlockState.NeedMore; } - return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + else + { + // There is no previous match to compare with, wait for + // the next step to decide. + + match_available = 1; + strstart++; + lookahead--; + } } - // Same as above, but achieves better compression. We use a lazy - // evaluation for matches: a match is finally adopted only if there is - // no better match at the next window position. - internal BlockState DeflateSlow(FlushType flush) + if (match_available != 0) { - // short hash_head = 0; // head of hash chain - int hash_head = 0; // head of hash chain - bool bflush; // set if current block must be flushed + bflush = _tr_tally(0, window[strstart - 1] & 0xff); + match_available = 0; + } + flush_block_only(flush == FlushType.Finish); - // Process the input block. - while (true) + if (_codec.AvailableBytesOut == 0) + { + if (flush == FlushType.Finish) { - // Make sure that we always have enough lookahead, except - // at the end of the input file. We need MAX_MATCH bytes - // for the next match, plus MIN_MATCH bytes to insert the - // string following the next match. + return BlockState.FinishStarted; + } + return BlockState.NeedMore; + } - if (lookahead < MIN_LOOKAHEAD) + return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; + } + + internal int longest_match(int cur_match) + { + var chain_length = config.MaxChainLength; // max hash chain length + var scan = strstart; // current string + int match; // matched string + int len; // length of current match + var best_len = prev_length; // best match length so far + var limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0; + + var niceLength = config.NiceLength; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + var wmask = w_mask; + + var strend = strstart + MAX_MATCH; + var scan_end1 = window[scan + best_len - 1]; + var scan_end = window[scan + best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= config.GoodLength) + { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (niceLength > lookahead) + { + niceLength = lookahead; + } + + do + { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if ( + window[match + best_len] != scan_end + || window[match + best_len - 1] != scan_end1 + || window[match] != window[scan] + || window[++match] != window[scan + 1] + ) + { + continue; + } + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; + match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { } while ( + window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && scan < strend + ); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) + { + match_start = cur_match; + best_len = len; + if (len >= niceLength) { - _fillWindow(); - if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None) - { - return BlockState.NeedMore; - } - - if (lookahead == 0) - { - break; // flush the current block - } + break; } + scan_end1 = window[scan + best_len - 1]; + scan_end = window[scan + best_len]; + } + } while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length != 0); - // Insert the string window[strstart .. strstart+2] in the - // dictionary, and set hash_head to the head of the hash chain: + if (best_len <= lookahead) + { + return best_len; + } + return lookahead; + } - if (lookahead >= MIN_MATCH) + private bool Rfc1950BytesEmitted; + + internal bool WantRfc1950HeaderBytes { get; set; } = true; + + internal int Initialize(ZlibCodec codec, CompressionLevel level) => + Initialize(codec, level, ZlibConstants.WindowBitsMax); + + internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits) => + Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, CompressionStrategy.Default); + + internal int Initialize( + ZlibCodec codec, + CompressionLevel level, + int bits, + CompressionStrategy compressionStrategy + ) => Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, compressionStrategy); + + internal int Initialize( + ZlibCodec codec, + CompressionLevel level, + int windowBits, + int memLevel, + CompressionStrategy strategy + ) + { + _codec = codec; + _codec.Message = null; + + // validation + if (windowBits < 9 || windowBits > 15) + { + throw new ZlibException("windowBits must be in the range 9..15."); + } + + if (memLevel < 1 || memLevel > MEM_LEVEL_MAX) + { + throw new ZlibException( + string.Format("memLevel must be in the range 1.. {0}", MEM_LEVEL_MAX) + ); + } + + _codec.dstate = this; + + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + window = new byte[w_size * 2]; + prev = new short[w_size]; + head = new short[hash_size]; + + // for memLevel==8, this will be 16384, 16k + lit_bufsize = 1 << (memLevel + 6); + + // Use a single array as the buffer for data pending compression, + // the output distance codes, and the output length codes (aka tree). + // orig comment: This works just fine since the average + // output size for (length,distance) codes is <= 24 bits. + pending = new byte[lit_bufsize * 4]; + _distanceOffset = lit_bufsize; + _lengthOffset = (1 + 2) * lit_bufsize; + + // So, for memLevel 8, the length of the pending buffer is 65536. 64k. + // The first 16k are pending bytes. + // The middle slice, of 32k, is used for distance codes. + // The final 16k are length codes. + + compressionLevel = level; + compressionStrategy = strategy; + + Reset(); + return ZlibConstants.Z_OK; + } + + internal void Reset() + { + _codec.TotalBytesIn = _codec.TotalBytesOut = 0; + _codec.Message = null; + + //strm.data_type = Z_UNKNOWN; + + pendingCount = 0; + nextPending = 0; + + Rfc1950BytesEmitted = false; + + status = (WantRfc1950HeaderBytes) ? INIT_STATE : BUSY_STATE; + _codec._adler32 = 1; + + last_flush = (int)FlushType.None; + + _InitializeTreeData(); + _InitializeLazyMatch(); + } + + internal int End() + { + if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) + { + return ZlibConstants.Z_STREAM_ERROR; + } + + // Deallocate in reverse order of allocations: + pending = null; + head = null; + prev = null; + window = null; + + // free + // dstate=null; + return status == BUSY_STATE ? ZlibConstants.Z_DATA_ERROR : ZlibConstants.Z_OK; + } + + private void SetDeflater() + { + switch (config.Flavor) + { + case DeflateFlavor.Store: + DeflateFunction = DeflateNone; + break; + case DeflateFlavor.Fast: + DeflateFunction = DeflateFast; + break; + case DeflateFlavor.Slow: + DeflateFunction = DeflateSlow; + break; + } + } + + internal int SetParams(CompressionLevel level, CompressionStrategy strategy) + { + var result = ZlibConstants.Z_OK; + + if (compressionLevel != level) + { + var newConfig = Config.Lookup(level); + + // change in the deflate flavor (Fast vs slow vs none)? + if (newConfig.Flavor != config.Flavor && _codec.TotalBytesIn != 0) + { + // Flush the last buffer: + result = _codec.Deflate(FlushType.Partial); + } + + compressionLevel = level; + config = newConfig; + SetDeflater(); + } + + // no need to flush with change in strategy? Really? + compressionStrategy = strategy; + + return result; + } + + internal int SetDictionary(byte[] dictionary) + { + var length = dictionary.Length; + var index = 0; + + if (dictionary is null || status != INIT_STATE) + { + throw new ZlibException("Stream error."); + } + + _codec._adler32 = Adler32.Calculate(_codec._adler32, dictionary); + + if (length < MIN_MATCH) + { + return ZlibConstants.Z_OK; + } + if (length > w_size - MIN_LOOKAHEAD) + { + length = w_size - MIN_LOOKAHEAD; + index = dictionary.Length - length; // use the tail of the dictionary + } + Array.Copy(dictionary, index, window, 0, length); + strstart = length; + blockStart = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0] & 0xff; + ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask; + + for (var n = 0; n <= length - MIN_MATCH; n++) + { + ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; + prev[n & w_mask] = head[ins_h]; + head[ins_h] = (short)n; + } + return ZlibConstants.Z_OK; + } + + internal int Deflate(FlushType flush) + { + int old_flush; + + if ( + _codec.OutputBuffer is null + || (_codec.InputBuffer is null && _codec.AvailableBytesIn != 0) + || (status == FINISH_STATE && flush != FlushType.Finish) + ) + { + _codec.Message = _ErrorMessage[ + ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_STREAM_ERROR) + ]; + throw new ZlibException(string.Format("Something is fishy. [{0}]", _codec.Message)); + + //return ZlibConstants.Z_STREAM_ERROR; + } + if (_codec.AvailableBytesOut == 0) + { + _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + throw new ZlibException("OutputBuffer is full (AvailableBytesOut == 0)"); + + //return ZlibConstants.Z_BUF_ERROR; + } + + old_flush = last_flush; + last_flush = (int)flush; + + // Write the zlib (rfc1950) header bytes + if (status == INIT_STATE) + { + var header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8; + var level_flags = (((int)compressionLevel - 1) & 0xff) >> 1; + + if (level_flags > 3) + { + level_flags = 3; + } + header |= (level_flags << 6); + if (strstart != 0) + { + header |= PRESET_DICT; + } + header += 31 - (header % 31); + + status = BUSY_STATE; + //putShortMSB(header); + unchecked + { + pending[pendingCount++] = (byte)(header >> 8); + pending[pendingCount++] = (byte)header; + } + + // Save the adler32 of the preset dictionary: + if (strstart != 0) + { + ////putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16))); + //putShortMSB((int)((UInt64)_codec._Adler32 >> 16)); + //putShortMSB((int)(_codec._Adler32 & 0xffff)); + pending[pendingCount++] = (byte)((_codec._adler32 & 0xFF000000) >> 24); + pending[pendingCount++] = (byte)((_codec._adler32 & 0x00FF0000) >> 16); + pending[pendingCount++] = (byte)((_codec._adler32 & 0x0000FF00) >> 8); + pending[pendingCount++] = (byte)(_codec._adler32 & 0x000000FF); + } + _codec._adler32 = 1; + } + + // Flush as much pending output as possible + if (pendingCount != 0) + { + _codec.flush_pending(); + if (_codec.AvailableBytesOut == 0) + { + //System.out.println(" avail_out==0"); + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return ZlibConstants.Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if ( + _codec.AvailableBytesIn == 0 && (int)flush <= old_flush && flush != FlushType.Finish + ) + { + // workitem 8557 + // Not sure why this needs to be an error. + // pendingCount == 0, which means there's nothing to deflate. + // And the caller has not asked for a FlushType.Finish, but... + // that seems very non-fatal. We can just say "OK" and do nthing. + + // _codec.Message = z_errmsg[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + // throw new ZlibException("AvailableBytesIn == 0 && flush<=old_flush && flush != FlushType.Finish"); + + return ZlibConstants.Z_OK; + } + + // User must not provide more input after the first FINISH: + if (status == FINISH_STATE && _codec.AvailableBytesIn != 0) + { + _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; + throw new ZlibException("status == FINISH_STATE && _codec.AvailableBytesIn != 0"); + } + + // Start a new block or continue the current one. + if ( + _codec.AvailableBytesIn != 0 + || lookahead != 0 + || (flush != FlushType.None && status != FINISH_STATE) + ) + { + var bstate = DeflateFunction(flush); + + if (bstate == BlockState.FinishStarted || bstate == BlockState.FinishDone) + { + status = FINISH_STATE; + } + if (bstate == BlockState.NeedMore || bstate == BlockState.FinishStarted) + { + if (_codec.AvailableBytesOut == 0) { - ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; - - // prev[strstart&w_mask]=hash_head=head[ins_h]; - hash_head = (head[ins_h] & 0xffff); - prev[strstart & w_mask] = head[ins_h]; - head[ins_h] = unchecked((short)strstart); + last_flush = -1; // avoid BUF_ERROR next call, see above } + return ZlibConstants.Z_OK; - // Find the longest match, discarding those <= prev_length. - prev_length = match_length; - prev_match = match_start; - match_length = MIN_MATCH - 1; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } - if (hash_head != 0 && prev_length < config.MaxLazy && - ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) + if (bstate == BlockState.BlockDone) + { + if (flush == FlushType.Partial) { - // To simplify the code, we prevent matches with the string - // of window index 0 (in particular we have to avoid a match - // of the string with itself at the start of the input file). - - if (compressionStrategy != CompressionStrategy.HuffmanOnly) - { - match_length = longest_match(hash_head); - } - - // longest_match() sets match_start - - if (match_length <= 5 && (compressionStrategy == CompressionStrategy.Filtered || - (match_length == MIN_MATCH && strstart - match_start > 4096))) - { - // If prev_match is also MIN_MATCH, match_start is garbage - // but we will ignore the current match anyway. - match_length = MIN_MATCH - 1; - } - } - - // If there was a match at the previous step and the current - // match is not better, output the previous match: - if (prev_length >= MIN_MATCH && match_length <= prev_length) - { - int max_insert = strstart + lookahead - MIN_MATCH; - - // Do not insert strings in hash table beyond this. - - // check_match(strstart-1, prev_match, prev_length); - - bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); - - // Insert in hash table all strings up to the end of the match. - // strstart-1 and strstart are already inserted. If there is not - // enough lookahead, the last two strings are not inserted in - // the hash table. - lookahead -= (prev_length - 1); - prev_length -= 2; - do - { - if (++strstart <= max_insert) - { - ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & - hash_mask; - - //prev[strstart&w_mask]=hash_head=head[ins_h]; - hash_head = (head[ins_h] & 0xffff); - prev[strstart & w_mask] = head[ins_h]; - head[ins_h] = unchecked((short)strstart); - } - } - while (--prev_length != 0); - match_available = 0; - match_length = MIN_MATCH - 1; - strstart++; - - if (bflush) - { - flush_block_only(false); - if (_codec.AvailableBytesOut == 0) - { - return BlockState.NeedMore; - } - } - } - else if (match_available != 0) - { - // If there was no match at the previous position, output a - // single literal. If there was a match but the current match - // is longer, truncate the previous match to a single literal. - - bflush = _tr_tally(0, window[strstart - 1] & 0xff); - - if (bflush) - { - flush_block_only(false); - } - strstart++; - lookahead--; - if (_codec.AvailableBytesOut == 0) - { - return BlockState.NeedMore; - } + _tr_align(); } else { - // There is no previous match to compare with, wait for - // the next step to decide. + // FlushType.Full or FlushType.Sync + _tr_stored_block(0, 0, false); - match_available = 1; - strstart++; - lookahead--; - } - } - - if (match_available != 0) - { - bflush = _tr_tally(0, window[strstart - 1] & 0xff); - match_available = 0; - } - flush_block_only(flush == FlushType.Finish); - - if (_codec.AvailableBytesOut == 0) - { - if (flush == FlushType.Finish) - { - return BlockState.FinishStarted; - } - return BlockState.NeedMore; - } - - return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone; - } - - internal int longest_match(int cur_match) - { - int chain_length = config.MaxChainLength; // max hash chain length - int scan = strstart; // current string - int match; // matched string - int len; // length of current match - int best_len = prev_length; // best match length so far - int limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0; - - int niceLength = config.NiceLength; - - // Stop when cur_match becomes <= limit. To simplify the code, - // we prevent matches with the string of window index 0. - - int wmask = w_mask; - - int strend = strstart + MAX_MATCH; - byte scan_end1 = window[scan + best_len - 1]; - byte scan_end = window[scan + best_len]; - - // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. - // It is easy to get rid of this optimization if necessary. - - // Do not waste too much time if we already have a good match: - if (prev_length >= config.GoodLength) - { - chain_length >>= 2; - } - - // Do not look for matches beyond the end of the input. This is necessary - // to make deflate deterministic. - if (niceLength > lookahead) - { - niceLength = lookahead; - } - - do - { - match = cur_match; - - // Skip to next match if the match length cannot increase - // or if the match length is less than 2: - if (window[match + best_len] != scan_end || - window[match + best_len - 1] != scan_end1 || - window[match] != window[scan] || - window[++match] != window[scan + 1]) - { - continue; - } - - // The check at best_len-1 can be removed because it will be made - // again later. (This heuristic is not always a win.) - // It is not necessary to compare scan[2] and match[2] since they - // are always equal when the other bytes match, given that - // the hash keys are equal and that HASH_BITS >= 8. - scan += 2; - match++; - - // We check for insufficient lookahead only every 8th comparison; - // the 256th check will be made at strstart+258. - do - { - } - while (window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && - window[++scan] == window[++match] && scan < strend); - - len = MAX_MATCH - (strend - scan); - scan = strend - MAX_MATCH; - - if (len > best_len) - { - match_start = cur_match; - best_len = len; - if (len >= niceLength) + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if (flush == FlushType.Full) { - break; + // clear hash (forget the history) + for (var i = 0; i < hash_size; i++) + { + head[i] = 0; + } } - scan_end1 = window[scan + best_len - 1]; - scan_end = window[scan + best_len]; } - } - while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length != 0); - - if (best_len <= lookahead) - { - return best_len; - } - return lookahead; - } - - private bool Rfc1950BytesEmitted; - - internal bool WantRfc1950HeaderBytes { get; set; } = true; - - internal int Initialize(ZlibCodec codec, CompressionLevel level) - { - return Initialize(codec, level, ZlibConstants.WindowBitsMax); - } - - internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits) - { - return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, CompressionStrategy.Default); - } - - internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits, - CompressionStrategy compressionStrategy) - { - return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, compressionStrategy); - } - - internal int Initialize(ZlibCodec codec, CompressionLevel level, int windowBits, int memLevel, - CompressionStrategy strategy) - { - _codec = codec; - _codec.Message = null; - - // validation - if (windowBits < 9 || windowBits > 15) - { - throw new ZlibException("windowBits must be in the range 9..15."); - } - - if (memLevel < 1 || memLevel > MEM_LEVEL_MAX) - { - throw new ZlibException(String.Format("memLevel must be in the range 1.. {0}", MEM_LEVEL_MAX)); - } - - _codec.dstate = this; - - w_bits = windowBits; - w_size = 1 << w_bits; - w_mask = w_size - 1; - - hash_bits = memLevel + 7; - hash_size = 1 << hash_bits; - hash_mask = hash_size - 1; - hash_shift = ((hash_bits + MIN_MATCH - 1) / MIN_MATCH); - - window = new byte[w_size * 2]; - prev = new short[w_size]; - head = new short[hash_size]; - - // for memLevel==8, this will be 16384, 16k - lit_bufsize = 1 << (memLevel + 6); - - // Use a single array as the buffer for data pending compression, - // the output distance codes, and the output length codes (aka tree). - // orig comment: This works just fine since the average - // output size for (length,distance) codes is <= 24 bits. - pending = new byte[lit_bufsize * 4]; - _distanceOffset = lit_bufsize; - _lengthOffset = (1 + 2) * lit_bufsize; - - // So, for memLevel 8, the length of the pending buffer is 65536. 64k. - // The first 16k are pending bytes. - // The middle slice, of 32k, is used for distance codes. - // The final 16k are length codes. - - compressionLevel = level; - compressionStrategy = strategy; - - Reset(); - return ZlibConstants.Z_OK; - } - - internal void Reset() - { - _codec.TotalBytesIn = _codec.TotalBytesOut = 0; - _codec.Message = null; - - //strm.data_type = Z_UNKNOWN; - - pendingCount = 0; - nextPending = 0; - - Rfc1950BytesEmitted = false; - - status = (WantRfc1950HeaderBytes) ? INIT_STATE : BUSY_STATE; - _codec._adler32 = 1; - - last_flush = (int)FlushType.None; - - _InitializeTreeData(); - _InitializeLazyMatch(); - } - - internal int End() - { - if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) - { - return ZlibConstants.Z_STREAM_ERROR; - } - - // Deallocate in reverse order of allocations: - pending = null; - head = null; - prev = null; - window = null; - - // free - // dstate=null; - return status == BUSY_STATE ? ZlibConstants.Z_DATA_ERROR : ZlibConstants.Z_OK; - } - - private void SetDeflater() - { - switch (config.Flavor) - { - case DeflateFlavor.Store: - DeflateFunction = DeflateNone; - break; - case DeflateFlavor.Fast: - DeflateFunction = DeflateFast; - break; - case DeflateFlavor.Slow: - DeflateFunction = DeflateSlow; - break; - } - } - - internal int SetParams(CompressionLevel level, CompressionStrategy strategy) - { - int result = ZlibConstants.Z_OK; - - if (compressionLevel != level) - { - Config newConfig = Config.Lookup(level); - - // change in the deflate flavor (Fast vs slow vs none)? - if (newConfig.Flavor != config.Flavor && _codec.TotalBytesIn != 0) - { - // Flush the last buffer: - result = _codec.Deflate(FlushType.Partial); - } - - compressionLevel = level; - config = newConfig; - SetDeflater(); - } - - // no need to flush with change in strategy? Really? - compressionStrategy = strategy; - - return result; - } - - internal int SetDictionary(byte[] dictionary) - { - int length = dictionary.Length; - int index = 0; - - if (dictionary is null || status != INIT_STATE) - { - throw new ZlibException("Stream error."); - } - - _codec._adler32 = Adler32.Calculate(_codec._adler32, dictionary); - - if (length < MIN_MATCH) - { - return ZlibConstants.Z_OK; - } - if (length > w_size - MIN_LOOKAHEAD) - { - length = w_size - MIN_LOOKAHEAD; - index = dictionary.Length - length; // use the tail of the dictionary - } - Array.Copy(dictionary, index, window, 0, length); - strstart = length; - blockStart = length; - - // Insert all strings in the hash table (except for the last two bytes). - // s->lookahead stays null, so s->ins_h will be recomputed at the next - // call of fill_window. - - ins_h = window[0] & 0xff; - ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask; - - for (int n = 0; n <= length - MIN_MATCH; n++) - { - ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask; - prev[n & w_mask] = head[ins_h]; - head[ins_h] = (short)n; - } - return ZlibConstants.Z_OK; - } - - internal int Deflate(FlushType flush) - { - int old_flush; - - if (_codec.OutputBuffer is null || - (_codec.InputBuffer is null && _codec.AvailableBytesIn != 0) || - (status == FINISH_STATE && flush != FlushType.Finish)) - { - _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_STREAM_ERROR)]; - throw new ZlibException(String.Format("Something is fishy. [{0}]", _codec.Message)); - - //return ZlibConstants.Z_STREAM_ERROR; - } - if (_codec.AvailableBytesOut == 0) - { - _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; - throw new ZlibException("OutputBuffer is full (AvailableBytesOut == 0)"); - - //return ZlibConstants.Z_BUF_ERROR; - } - - old_flush = last_flush; - last_flush = (int)flush; - - // Write the zlib (rfc1950) header bytes - if (status == INIT_STATE) - { - int header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8; - int level_flags = (((int)compressionLevel - 1) & 0xff) >> 1; - - if (level_flags > 3) - { - level_flags = 3; - } - header |= (level_flags << 6); - if (strstart != 0) - { - header |= PRESET_DICT; - } - header += 31 - (header % 31); - - status = BUSY_STATE; - - //putShortMSB(header); - unchecked - { - pending[pendingCount++] = (byte)(header >> 8); - pending[pendingCount++] = (byte)header; - } - - // Save the adler32 of the preset dictionary: - if (strstart != 0) - { - ////putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16))); - //putShortMSB((int)((UInt64)_codec._Adler32 >> 16)); - //putShortMSB((int)(_codec._Adler32 & 0xffff)); - pending[pendingCount++] = (byte)((_codec._adler32 & 0xFF000000) >> 24); - pending[pendingCount++] = (byte)((_codec._adler32 & 0x00FF0000) >> 16); - pending[pendingCount++] = (byte)((_codec._adler32 & 0x0000FF00) >> 8); - pending[pendingCount++] = (byte)(_codec._adler32 & 0x000000FF); - } - _codec._adler32 = 1; - } - - // Flush as much pending output as possible - if (pendingCount != 0) - { _codec.flush_pending(); if (_codec.AvailableBytesOut == 0) { - //System.out.println(" avail_out==0"); - // Since avail_out is 0, deflate will be called again with - // more output space, but possibly with both pending and - // avail_in equal to zero. There won't be anything to do, - // but this is not an error situation so make sure we - // return OK instead of BUF_ERROR at next call of deflate: - last_flush = -1; + last_flush = -1; // avoid BUF_ERROR at next call, see above return ZlibConstants.Z_OK; } - - // Make sure there is something to do and avoid duplicate consecutive - // flushes. For repeated and useless calls with Z_FINISH, we keep - // returning Z_STREAM_END instead of Z_BUFF_ERROR. } - else if (_codec.AvailableBytesIn == 0 && - (int)flush <= old_flush && - flush != FlushType.Finish) - { - // workitem 8557 - // Not sure why this needs to be an error. - // pendingCount == 0, which means there's nothing to deflate. - // And the caller has not asked for a FlushType.Finish, but... - // that seems very non-fatal. We can just say "OK" and do nthing. - - // _codec.Message = z_errmsg[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; - // throw new ZlibException("AvailableBytesIn == 0 && flush<=old_flush && flush != FlushType.Finish"); - - return ZlibConstants.Z_OK; - } - - // User must not provide more input after the first FINISH: - if (status == FINISH_STATE && _codec.AvailableBytesIn != 0) - { - _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)]; - throw new ZlibException("status == FINISH_STATE && _codec.AvailableBytesIn != 0"); - } - - // Start a new block or continue the current one. - if (_codec.AvailableBytesIn != 0 || lookahead != 0 || (flush != FlushType.None && status != FINISH_STATE)) - { - BlockState bstate = DeflateFunction(flush); - - if (bstate == BlockState.FinishStarted || bstate == BlockState.FinishDone) - { - status = FINISH_STATE; - } - if (bstate == BlockState.NeedMore || bstate == BlockState.FinishStarted) - { - if (_codec.AvailableBytesOut == 0) - { - last_flush = -1; // avoid BUF_ERROR next call, see above - } - return ZlibConstants.Z_OK; - - // If flush != Z_NO_FLUSH && avail_out == 0, the next call - // of deflate should use the same flush parameter to make sure - // that the flush is complete. So we don't have to output an - // empty block here, this will be done at next call. This also - // ensures that for a very small output buffer, we emit at most - // one empty block. - } - - if (bstate == BlockState.BlockDone) - { - if (flush == FlushType.Partial) - { - _tr_align(); - } - else - { - // FlushType.Full or FlushType.Sync - _tr_stored_block(0, 0, false); - - // For a full flush, this empty block will be recognized - // as a special marker by inflate_sync(). - if (flush == FlushType.Full) - { - // clear hash (forget the history) - for (int i = 0; i < hash_size; i++) - { - head[i] = 0; - } - } - } - _codec.flush_pending(); - if (_codec.AvailableBytesOut == 0) - { - last_flush = -1; // avoid BUF_ERROR at next call, see above - return ZlibConstants.Z_OK; - } - } - } - - if (flush != FlushType.Finish) - { - return ZlibConstants.Z_OK; - } - - if (!WantRfc1950HeaderBytes || Rfc1950BytesEmitted) - { - return ZlibConstants.Z_STREAM_END; - } - - // Write the zlib trailer (adler32) - pending[pendingCount++] = (byte)((_codec._adler32 & 0xFF000000) >> 24); - pending[pendingCount++] = (byte)((_codec._adler32 & 0x00FF0000) >> 16); - pending[pendingCount++] = (byte)((_codec._adler32 & 0x0000FF00) >> 8); - pending[pendingCount++] = (byte)(_codec._adler32 & 0x000000FF); - - //putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16))); - //putShortMSB((int)(_codec._Adler32 & 0xffff)); - - _codec.flush_pending(); - - // If avail_out is zero, the application will call deflate again - // to flush the rest. - - Rfc1950BytesEmitted = true; // write the trailer only once! - - return pendingCount != 0 ? ZlibConstants.Z_OK : ZlibConstants.Z_STREAM_END; } + + if (flush != FlushType.Finish) + { + return ZlibConstants.Z_OK; + } + + if (!WantRfc1950HeaderBytes || Rfc1950BytesEmitted) + { + return ZlibConstants.Z_STREAM_END; + } + + // Write the zlib trailer (adler32) + pending[pendingCount++] = (byte)((_codec._adler32 & 0xFF000000) >> 24); + pending[pendingCount++] = (byte)((_codec._adler32 & 0x00FF0000) >> 16); + pending[pendingCount++] = (byte)((_codec._adler32 & 0x0000FF00) >> 8); + pending[pendingCount++] = (byte)(_codec._adler32 & 0x000000FF); + + //putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16))); + //putShortMSB((int)(_codec._Adler32 & 0xffff)); + + _codec.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + + Rfc1950BytesEmitted = true; // write the trailer only once! + + return pendingCount != 0 ? ZlibConstants.Z_OK : ZlibConstants.Z_STREAM_END; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs index e3b18f6a..05003d7f 100644 --- a/src/SharpCompress/Compressors/Deflate/DeflateStream.cs +++ b/src/SharpCompress/Compressors/Deflate/DeflateStream.cs @@ -28,339 +28,347 @@ using System; using System.IO; using System.Text; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +public class DeflateStream : Stream { - public class DeflateStream : Stream + private readonly ZlibBaseStream _baseStream; + private bool _disposed; + + public DeflateStream( + Stream stream, + CompressionMode mode, + CompressionLevel level = CompressionLevel.Default, + Encoding? forceEncoding = null + ) => + _baseStream = new ZlibBaseStream( + stream, + mode, + level, + ZlibStreamFlavor.DEFLATE, + forceEncoding + ); + + #region Zlib properties + + /// + /// This property sets the flush behavior on the stream. + /// + /// See the ZLIB documentation for the meaning of the flush behavior. + /// + public virtual FlushType FlushMode { - private readonly ZlibBaseStream _baseStream; - private bool _disposed; - - public DeflateStream(Stream stream, CompressionMode mode, - CompressionLevel level = CompressionLevel.Default, - Encoding? forceEncoding = null) - { - _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, forceEncoding); - } - - #region Zlib properties - - /// - /// This property sets the flush behavior on the stream. - /// - /// See the ZLIB documentation for the meaning of the flush behavior. - /// - public virtual FlushType FlushMode - { - get => (_baseStream._flushMode); - set - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream._flushMode = value; - } - } - - /// - /// The size of the working buffer for the compression codec. - /// - /// - /// - /// - /// The working buffer is used for all stream operations. The default size is - /// 1024 bytes. The minimum size is 128 bytes. You may get better performance - /// with a larger buffer. Then again, you might not. You would have to test - /// it. - /// - /// - /// - /// Set this before the first call to Read() or Write() on the - /// stream. If you try to set it afterwards, it will throw. - /// - /// - public int BufferSize - { - get => _baseStream._bufferSize; - set - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - if (_baseStream._workingBuffer != null) - { - throw new ZlibException("The working buffer is already set."); - } - if (value < ZlibConstants.WorkingBufferSizeMin) - { - throw new ZlibException( - String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, - ZlibConstants.WorkingBufferSizeMin)); - } - _baseStream._bufferSize = value; - } - } - - /// - /// The ZLIB strategy to be used during compression. - /// - /// - /// - /// By tweaking this parameter, you may be able to optimize the compression for - /// data with particular characteristics. - /// - public CompressionStrategy Strategy - { - get => _baseStream.Strategy; - set - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream.Strategy = value; - } - } - - /// Returns the total number of bytes input so far. - public virtual long TotalIn => _baseStream._z.TotalBytesIn; - - /// Returns the total number of bytes output so far. - public virtual long TotalOut => _baseStream._z.TotalBytesOut; - - #endregion - - #region System.IO.Stream methods - - /// - /// Indicates whether the stream can be read. - /// - /// - /// The return value depends on whether the captive stream supports reading. - /// - public override bool CanRead - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - return _baseStream._stream.CanRead; - } - } - - /// - /// Indicates whether the stream supports Seek operations. - /// - /// - /// Always returns false. - /// - public override bool CanSeek => false; - - /// - /// Indicates whether the stream can be written. - /// - /// - /// The return value depends on whether the captive stream supports writing. - /// - public override bool CanWrite - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - return _baseStream._stream.CanWrite; - } - } - - /// - /// Reading this property always throws a . - /// - public override long Length => throw new NotSupportedException(); - - /// - /// The position of the stream pointer. - /// - /// - /// - /// Setting this property always throws a . Reading will return the total bytes - /// written out, if used in writing, or the total bytes read in, if used in - /// reading. The count may refer to compressed bytes or uncompressed bytes, - /// depending on how you've used the stream. - /// - public override long Position - { - get - { - if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Writer) - { - return _baseStream._z.TotalBytesOut; - } - if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Reader) - { - return _baseStream._z.TotalBytesIn; - } - return 0; - } - set => throw new NotSupportedException(); - } - - /// - /// Dispose the stream. - /// - /// - /// This may or may not result in a Close() call on the captive stream. - /// - protected override void Dispose(bool disposing) - { - try - { - if (!_disposed) - { - if (disposing) - { - _baseStream?.Dispose(); - } - _disposed = true; - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Flush the stream. - /// - public override void Flush() + get => (_baseStream._flushMode); + set { if (_disposed) { throw new ObjectDisposedException("DeflateStream"); } - _baseStream.Flush(); + _baseStream._flushMode = value; } - - /// - /// Read data from the stream. - /// - /// - /// - /// - /// If you wish to use the DeflateStream to compress data while - /// reading, you can create a DeflateStream with - /// CompressionMode.Compress, providing an uncompressed data stream. - /// Then call Read() on that DeflateStream, and the data read will be - /// compressed as you read. If you wish to use the DeflateStream to - /// decompress data while reading, you can create a DeflateStream with - /// CompressionMode.Decompress, providing a readable compressed data - /// stream. Then call Read() on that DeflateStream, and the data read - /// will be decompressed as you read. - /// - /// - /// - /// A DeflateStream can be used for Read() or Write(), but not both. - /// - /// - /// - /// The buffer into which the read data should be placed. - /// the offset within that data array to put the first byte read. - /// the number of bytes to read. - /// the number of bytes actually read - public override int Read(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - return _baseStream.Read(buffer, offset, count); - } - - public override int ReadByte() - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - return _baseStream.ReadByte(); - } - - /// - /// Calling this method always throws a . - /// - /// this is irrelevant, since it will always throw! - /// this is irrelevant, since it will always throw! - /// irrelevant! - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - /// Calling this method always throws a . - /// - /// this is irrelevant, since it will always throw! - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - /// Write data to the stream. - /// - /// - /// - /// - /// If you wish to use the DeflateStream to compress data while - /// writing, you can create a DeflateStream with - /// CompressionMode.Compress, and a writable output stream. Then call - /// Write() on that DeflateStream, providing uncompressed data - /// as input. The data sent to the output stream will be the compressed form - /// of the data written. If you wish to use the DeflateStream to - /// decompress data while writing, you can create a DeflateStream with - /// CompressionMode.Decompress, and a writable output stream. Then - /// call Write() on that stream, providing previously compressed - /// data. The data sent to the output stream will be the decompressed form of - /// the data written. - /// - /// - /// - /// A DeflateStream can be used for Read() or Write(), - /// but not both. - /// - /// - /// - /// - /// The buffer holding data to write to the stream. - /// the offset within that data array to find the first byte to write. - /// the number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream.Write(buffer, offset, count); - } - - public override void WriteByte(byte value) - { - if (_disposed) - { - throw new ObjectDisposedException("DeflateStream"); - } - _baseStream.WriteByte(value); - } - - #endregion - - public MemoryStream InputBuffer => new MemoryStream(_baseStream._z.InputBuffer, _baseStream._z.NextIn, - _baseStream._z.AvailableBytesIn); } -} \ No newline at end of file + + /// + /// The size of the working buffer for the compression codec. + /// + /// + /// + /// + /// The working buffer is used for all stream operations. The default size is + /// 1024 bytes. The minimum size is 128 bytes. You may get better performance + /// with a larger buffer. Then again, you might not. You would have to test + /// it. + /// + /// + /// + /// Set this before the first call to Read() or Write() on the + /// stream. If you try to set it afterwards, it will throw. + /// + /// + public int BufferSize + { + get => _baseStream._bufferSize; + set + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + if (_baseStream._workingBuffer != null) + { + throw new ZlibException("The working buffer is already set."); + } + if (value < ZlibConstants.WorkingBufferSizeMin) + { + throw new ZlibException( + string.Format( + "Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", + value, + ZlibConstants.WorkingBufferSizeMin + ) + ); + } + _baseStream._bufferSize = value; + } + } + + /// + /// The ZLIB strategy to be used during compression. + /// + /// + /// + /// By tweaking this parameter, you may be able to optimize the compression for + /// data with particular characteristics. + /// + public CompressionStrategy Strategy + { + get => _baseStream.Strategy; + set + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + _baseStream.Strategy = value; + } + } + + /// Returns the total number of bytes input so far. + public virtual long TotalIn => _baseStream._z.TotalBytesIn; + + /// Returns the total number of bytes output so far. + public virtual long TotalOut => _baseStream._z.TotalBytesOut; + + #endregion + + #region System.IO.Stream methods + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + return _baseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek => false; + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + return _baseStream._stream.CanWrite; + } + } + + /// + /// Reading this property always throws a . + /// + public override long Length => throw new NotSupportedException(); + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Writer) + { + return _baseStream._z.TotalBytesOut; + } + if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Reader) + { + return _baseStream._z.TotalBytesIn; + } + return 0; + } + set => throw new NotSupportedException(); + } + + /// + /// Dispose the stream. + /// + /// + /// This may or may not result in a Close() call on the captive stream. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing) + { + _baseStream?.Dispose(); + } + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + _baseStream.Flush(); + } + + /// + /// Read data from the stream. + /// + /// + /// + /// + /// If you wish to use the DeflateStream to compress data while + /// reading, you can create a DeflateStream with + /// CompressionMode.Compress, providing an uncompressed data stream. + /// Then call Read() on that DeflateStream, and the data read will be + /// compressed as you read. If you wish to use the DeflateStream to + /// decompress data while reading, you can create a DeflateStream with + /// CompressionMode.Decompress, providing a readable compressed data + /// stream. Then call Read() on that DeflateStream, and the data read + /// will be decompressed as you read. + /// + /// + /// + /// A DeflateStream can be used for Read() or Write(), but not both. + /// + /// + /// + /// The buffer into which the read data should be placed. + /// the offset within that data array to put the first byte read. + /// the number of bytes to read. + /// the number of bytes actually read + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + return _baseStream.Read(buffer, offset, count); + } + + public override int ReadByte() + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + return _baseStream.ReadByte(); + } + + /// + /// Calling this method always throws a . + /// + /// this is irrelevant, since it will always throw! + /// this is irrelevant, since it will always throw! + /// irrelevant! + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + /// Calling this method always throws a . + /// + /// this is irrelevant, since it will always throw! + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// If you wish to use the DeflateStream to compress data while + /// writing, you can create a DeflateStream with + /// CompressionMode.Compress, and a writable output stream. Then call + /// Write() on that DeflateStream, providing uncompressed data + /// as input. The data sent to the output stream will be the compressed form + /// of the data written. If you wish to use the DeflateStream to + /// decompress data while writing, you can create a DeflateStream with + /// CompressionMode.Decompress, and a writable output stream. Then + /// call Write() on that stream, providing previously compressed + /// data. The data sent to the output stream will be the decompressed form of + /// the data written. + /// + /// + /// + /// A DeflateStream can be used for Read() or Write(), + /// but not both. + /// + /// + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + _baseStream.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + if (_disposed) + { + throw new ObjectDisposedException("DeflateStream"); + } + _baseStream.WriteByte(value); + } + + #endregion + + public MemoryStream InputBuffer => + new MemoryStream( + _baseStream._z.InputBuffer, + _baseStream._z.NextIn, + _baseStream._z.AvailableBytesIn + ); +} diff --git a/src/SharpCompress/Compressors/Deflate/FlushType.cs b/src/SharpCompress/Compressors/Deflate/FlushType.cs index 9a3cc774..bcf7fe10 100644 --- a/src/SharpCompress/Compressors/Deflate/FlushType.cs +++ b/src/SharpCompress/Compressors/Deflate/FlushType.cs @@ -1,44 +1,43 @@ -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +/// +/// Describes how to flush the current deflate operation. +/// +/// +/// The different FlushType values are useful when using a Deflate in a streaming application. +/// +public enum FlushType { - /// - /// Describes how to flush the current deflate operation. + /// No flush at all. + None = 0, + + /// Closes the current block, but doesn't flush it to + /// the output. Used internally only in hypothetical + /// scenarios. This was supposed to be removed by Zlib, but it is + /// still in use in some edge cases. /// - /// - /// The different FlushType values are useful when using a Deflate in a streaming application. - /// - public enum FlushType - { - /// No flush at all. - None = 0, + Partial, - /// Closes the current block, but doesn't flush it to - /// the output. Used internally only in hypothetical - /// scenarios. This was supposed to be removed by Zlib, but it is - /// still in use in some edge cases. - /// - Partial, + /// + /// Use this during compression to specify that all pending output should be + /// flushed to the output buffer and the output should be aligned on a byte + /// boundary. You might use this in a streaming communication scenario, so that + /// the decompressor can get all input data available so far. When using this + /// with a ZlibCodec, AvailableBytesIn will be zero after the call if + /// enough output space has been provided before the call. Flushing will + /// degrade compression and so it should be used only when necessary. + /// + Sync, - /// - /// Use this during compression to specify that all pending output should be - /// flushed to the output buffer and the output should be aligned on a byte - /// boundary. You might use this in a streaming communication scenario, so that - /// the decompressor can get all input data available so far. When using this - /// with a ZlibCodec, AvailableBytesIn will be zero after the call if - /// enough output space has been provided before the call. Flushing will - /// degrade compression and so it should be used only when necessary. - /// - Sync, + /// + /// Use this during compression to specify that all output should be flushed, as + /// with FlushType.Sync, but also, the compression state should be reset + /// so that decompression can restart from this point if previous compressed + /// data has been damaged or if random access is desired. Using + /// FlushType.Full too often can significantly degrade the compression. + /// + Full, - /// - /// Use this during compression to specify that all output should be flushed, as - /// with FlushType.Sync, but also, the compression state should be reset - /// so that decompression can restart from this point if previous compressed - /// data has been damaged or if random access is desired. Using - /// FlushType.Full too often can significantly degrade the compression. - /// - Full, - - /// Signals the end of the compression/decompression stream. - Finish - } -} \ No newline at end of file + /// Signals the end of the compression/decompression stream. + Finish +} diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.cs index ffc4759d..c547df14 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -31,452 +31,451 @@ using System.Buffers.Binary; using System.IO; using System.Text; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +public class GZipStream : Stream { - public class GZipStream : Stream + internal static readonly DateTime UNIX_EPOCH = new DateTime( + 1970, + 1, + 1, + 0, + 0, + 0, + DateTimeKind.Utc + ); + + private string? _comment; + private string? _fileName; + private DateTime? _lastModified; + + internal ZlibBaseStream BaseStream; + private bool _disposed; + private bool _firstReadDone; + private int _headerByteCount; + + private readonly Encoding _encoding; + + public GZipStream(Stream stream, CompressionMode mode) + : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) { } + + public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) + : this(stream, mode, level, Encoding.UTF8) { } + + public GZipStream( + Stream stream, + CompressionMode mode, + CompressionLevel level, + Encoding encoding + ) { - internal static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); + _encoding = encoding; + } - private string? _comment; - private string? _fileName; - private DateTime? _lastModified; + #region Zlib properties - internal ZlibBaseStream BaseStream; - private bool _disposed; - private bool _firstReadDone; - private int _headerByteCount; - - private readonly Encoding _encoding; - - public GZipStream(Stream stream, CompressionMode mode) - : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) - { - } - - public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) - : this(stream, mode, level, Encoding.UTF8) - { - } - - public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, Encoding encoding) - { - BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); - _encoding = encoding; - } - - #region Zlib properties - - public virtual FlushType FlushMode - { - get => (BaseStream._flushMode); - set - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - BaseStream._flushMode = value; - } - } - - public int BufferSize - { - get => BaseStream._bufferSize; - set - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - if (BaseStream._workingBuffer != null) - { - throw new ZlibException("The working buffer is already set."); - } - if (value < ZlibConstants.WorkingBufferSizeMin) - { - throw new ZlibException( - String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, - ZlibConstants.WorkingBufferSizeMin)); - } - BaseStream._bufferSize = value; - } - } - - internal virtual long TotalIn => BaseStream._z.TotalBytesIn; - - internal virtual long TotalOut => BaseStream._z.TotalBytesOut; - - #endregion - - #region Stream methods - - /// - /// Indicates whether the stream can be read. - /// - /// - /// The return value depends on whether the captive stream supports reading. - /// - public override bool CanRead - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - return BaseStream._stream.CanRead; - } - } - - /// - /// Indicates whether the stream supports Seek operations. - /// - /// - /// Always returns false. - /// - public override bool CanSeek => false; - - /// - /// Indicates whether the stream can be written. - /// - /// - /// The return value depends on whether the captive stream supports writing. - /// - public override bool CanWrite - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - return BaseStream._stream.CanWrite; - } - } - - /// - /// Reading this property always throws a . - /// - public override long Length => throw new NotSupportedException(); - - /// - /// The position of the stream pointer. - /// - /// - /// - /// Setting this property always throws a . Reading will return the total bytes - /// written out, if used in writing, or the total bytes read in, if used in - /// reading. The count may refer to compressed bytes or uncompressed bytes, - /// depending on how you've used the stream. - /// - public override long Position - { - get - { - if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Writer) - { - return BaseStream._z.TotalBytesOut + _headerByteCount; - } - if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Reader) - { - return BaseStream._z.TotalBytesIn + BaseStream._gzipHeaderByteCount; - } - return 0; - } - - set => throw new NotSupportedException(); - } - - /// - /// Dispose the stream. - /// - /// - /// This may or may not result in a Close() call on the captive stream. - /// - protected override void Dispose(bool disposing) - { - try - { - if (!_disposed) - { - if (disposing && (BaseStream != null)) - { - BaseStream.Dispose(); - Crc32 = BaseStream.Crc32; - } - _disposed = true; - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Flush the stream. - /// - public override void Flush() + public virtual FlushType FlushMode + { + get => (BaseStream._flushMode); + set { if (_disposed) { throw new ObjectDisposedException("GZipStream"); } - BaseStream.Flush(); - } - - /// - /// Read and decompress data from the source stream. - /// - /// - /// - /// With a GZipStream, decompression is done through reading. - /// - /// - /// - /// - /// byte[] working = new byte[WORKING_BUFFER_SIZE]; - /// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile)) - /// { - /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) - /// { - /// using (var output = System.IO.File.Create(_DecompressedFile)) - /// { - /// int n; - /// while ((n= decompressor.Read(working, 0, working.Length)) !=0) - /// { - /// output.Write(working, 0, n); - /// } - /// } - /// } - /// } - /// - /// - /// The buffer into which the decompressed data should be placed. - /// the offset within that data array to put the first byte read. - /// the number of bytes to read. - /// the number of bytes actually read - public override int Read(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - int n = BaseStream.Read(buffer, offset, count); - - // Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n); - // Console.WriteLine( Util.FormatByteArray(buffer, offset, n) ); - - if (!_firstReadDone) - { - _firstReadDone = true; - FileName = BaseStream._GzipFileName; - Comment = BaseStream._GzipComment; - LastModified = BaseStream._GzipMtime; - } - return n; - } - - /// - /// Calling this method always throws a . - /// - /// irrelevant; it will always throw! - /// irrelevant; it will always throw! - /// irrelevant! - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - /// Calling this method always throws a . - /// - /// irrelevant; this method will always throw! - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - /// Write data to the stream. - /// - /// - /// - /// - /// If you wish to use the GZipStream to compress data while writing, - /// you can create a GZipStream with CompressionMode.Compress, and a - /// writable output stream. Then call Write() on that GZipStream, - /// providing uncompressed data as input. The data sent to the output stream - /// will be the compressed form of the data written. - /// - /// - /// - /// A GZipStream can be used for Read() or Write(), but not - /// both. Writing implies compression. Reading implies decompression. - /// - /// - /// - /// The buffer holding data to write to the stream. - /// the offset within that data array to find the first byte to write. - /// the number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("GZipStream"); - } - if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined) - { - //Console.WriteLine("GZipStream: First write"); - if (BaseStream._wantCompress) - { - // first write in compression, therefore, emit the GZIP header - _headerByteCount = EmitHeader(); - } - else - { - throw new InvalidOperationException(); - } - } - - BaseStream.Write(buffer, offset, count); - } - - #endregion Stream methods - - public string? Comment - { - get => _comment; - set - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(GZipStream)); - } - _comment = value; - } - } - - - public DateTime? LastModified - { - get => _lastModified; - set - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(GZipStream)); - } - _lastModified = value; - } - } - - public string? FileName - { - get => _fileName; - set - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(GZipStream)); - } - _fileName = value; - if (_fileName is null) - { - return; - } - if (_fileName.Contains('/')) - { - _fileName = _fileName.Replace('/', '\\'); - } - if (_fileName.EndsWith('\\')) - { - throw new InvalidOperationException("Illegal filename"); - } - - if (_fileName.Contains('\\')) - { - // trim any leading path - _fileName = Path.GetFileName(_fileName); - } - } - } - - public int Crc32 { get; private set; } - - private int EmitHeader() - { - byte[]? commentBytes = (Comment is null) ? null - : _encoding.GetBytes(Comment); - byte[]? filenameBytes = (FileName is null) ? null - : _encoding.GetBytes(FileName); - - int cbLength = commentBytes?.Length + 1 ?? 0; - int fnLength = filenameBytes?.Length + 1 ?? 0; - - int bufferLength = 10 + cbLength + fnLength; - var header = new byte[bufferLength]; - int i = 0; - - // ID - header[i++] = 0x1F; - header[i++] = 0x8B; - - // compression method - header[i++] = 8; - byte flag = 0; - if (Comment != null) - { - flag ^= 0x10; - } - if (FileName != null) - { - flag ^= 0x8; - } - - // flag - header[i++] = flag; - - // mtime - if (LastModified is null) - { - LastModified = DateTime.Now; - } - TimeSpan delta = LastModified.Value - UNIX_EPOCH; - var timet = (int)delta.TotalSeconds; - BinaryPrimitives.WriteInt32LittleEndian(header.AsSpan(i), timet); - i += 4; - - // xflg - header[i++] = 0; // this field is totally useless - - // OS - header[i++] = 0xFF; // 0xFF == unspecified - - // extra field length - only if FEXTRA is set, which it is not. - //header[i++]= 0; - //header[i++]= 0; - - // filename - if (fnLength != 0) - { - Array.Copy(filenameBytes!, 0, header, i, fnLength - 1); - i += fnLength - 1; - header[i++] = 0; // terminate - } - - // comment - if (cbLength != 0) - { - Array.Copy(commentBytes!, 0, header, i, cbLength - 1); - i += cbLength - 1; - header[i++] = 0; // terminate - } - - BaseStream._stream.Write(header, 0, header.Length); - - return header.Length; // bytes written + BaseStream._flushMode = value; } } + + public int BufferSize + { + get => BaseStream._bufferSize; + set + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + if (BaseStream._workingBuffer != null) + { + throw new ZlibException("The working buffer is already set."); + } + if (value < ZlibConstants.WorkingBufferSizeMin) + { + throw new ZlibException( + string.Format( + "Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", + value, + ZlibConstants.WorkingBufferSizeMin + ) + ); + } + BaseStream._bufferSize = value; + } + } + + internal virtual long TotalIn => BaseStream._z.TotalBytesIn; + + internal virtual long TotalOut => BaseStream._z.TotalBytesOut; + + #endregion + + #region Stream methods + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + return BaseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek => false; + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + return BaseStream._stream.CanWrite; + } + } + + /// + /// Reading this property always throws a . + /// + public override long Length => throw new NotSupportedException(); + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Writer) + { + return BaseStream._z.TotalBytesOut + _headerByteCount; + } + if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Reader) + { + return BaseStream._z.TotalBytesIn + BaseStream._gzipHeaderByteCount; + } + return 0; + } + set => throw new NotSupportedException(); + } + + /// + /// Dispose the stream. + /// + /// + /// This may or may not result in a Close() call on the captive stream. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing && (BaseStream != null)) + { + BaseStream.Dispose(); + Crc32 = BaseStream.Crc32; + } + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + BaseStream.Flush(); + } + + /// + /// Read and decompress data from the source stream. + /// + /// + /// + /// With a GZipStream, decompression is done through reading. + /// + /// + /// + /// + /// byte[] working = new byte[WORKING_BUFFER_SIZE]; + /// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile)) + /// { + /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) + /// { + /// using (var output = System.IO.File.Create(_DecompressedFile)) + /// { + /// int n; + /// while ((n= decompressor.Read(working, 0, working.Length)) !=0) + /// { + /// output.Write(working, 0, n); + /// } + /// } + /// } + /// } + /// + /// + /// The buffer into which the decompressed data should be placed. + /// the offset within that data array to put the first byte read. + /// the number of bytes to read. + /// the number of bytes actually read + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + var n = BaseStream.Read(buffer, offset, count); + + // Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n); + // Console.WriteLine( Util.FormatByteArray(buffer, offset, n) ); + + if (!_firstReadDone) + { + _firstReadDone = true; + FileName = BaseStream._GzipFileName; + Comment = BaseStream._GzipComment; + LastModified = BaseStream._GzipMtime; + } + return n; + } + + /// + /// Calling this method always throws a . + /// + /// irrelevant; it will always throw! + /// irrelevant; it will always throw! + /// irrelevant! + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + /// Calling this method always throws a . + /// + /// irrelevant; this method will always throw! + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// If you wish to use the GZipStream to compress data while writing, + /// you can create a GZipStream with CompressionMode.Compress, and a + /// writable output stream. Then call Write() on that GZipStream, + /// providing uncompressed data as input. The data sent to the output stream + /// will be the compressed form of the data written. + /// + /// + /// + /// A GZipStream can be used for Read() or Write(), but not + /// both. Writing implies compression. Reading implies decompression. + /// + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("GZipStream"); + } + if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined) + { + //Console.WriteLine("GZipStream: First write"); + if (BaseStream._wantCompress) + { + // first write in compression, therefore, emit the GZIP header + _headerByteCount = EmitHeader(); + } + else + { + throw new InvalidOperationException(); + } + } + + BaseStream.Write(buffer, offset, count); + } + + #endregion Stream methods + + public string? Comment + { + get => _comment; + set + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GZipStream)); + } + _comment = value; + } + } + + public DateTime? LastModified + { + get => _lastModified; + set + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GZipStream)); + } + _lastModified = value; + } + } + + public string? FileName + { + get => _fileName; + set + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GZipStream)); + } + _fileName = value; + if (_fileName is null) + { + return; + } + if (_fileName.Contains('/')) + { + _fileName = _fileName.Replace('/', '\\'); + } + if (_fileName.EndsWith('\\')) + { + throw new InvalidOperationException("Illegal filename"); + } + + if (_fileName.Contains('\\')) + { + // trim any leading path + _fileName = Path.GetFileName(_fileName); + } + } + } + + public int Crc32 { get; private set; } + + private int EmitHeader() + { + var commentBytes = (Comment is null) ? null : _encoding.GetBytes(Comment); + var filenameBytes = (FileName is null) ? null : _encoding.GetBytes(FileName); + + var cbLength = commentBytes?.Length + 1 ?? 0; + var fnLength = filenameBytes?.Length + 1 ?? 0; + + var bufferLength = 10 + cbLength + fnLength; + var header = new byte[bufferLength]; + var i = 0; + + // ID + header[i++] = 0x1F; + header[i++] = 0x8B; + + // compression method + header[i++] = 8; + byte flag = 0; + if (Comment != null) + { + flag ^= 0x10; + } + if (FileName != null) + { + flag ^= 0x8; + } + + // flag + header[i++] = flag; + + // mtime + LastModified ??= DateTime.Now; + var delta = LastModified.Value - UNIX_EPOCH; + var timet = (int)delta.TotalSeconds; + BinaryPrimitives.WriteInt32LittleEndian(header.AsSpan(i), timet); + i += 4; + + // xflg + header[i++] = 0; // this field is totally useless + + // OS + header[i++] = 0xFF; // 0xFF == unspecified + + // extra field length - only if FEXTRA is set, which it is not. + //header[i++]= 0; + //header[i++]= 0; + + // filename + if (fnLength != 0) + { + Array.Copy(filenameBytes!, 0, header, i, fnLength - 1); + i += fnLength - 1; + header[i++] = 0; // terminate + } + + // comment + if (cbLength != 0) + { + Array.Copy(commentBytes!, 0, header, i, cbLength - 1); + i += cbLength - 1; + header[i++] = 0; // terminate + } + + BaseStream._stream.Write(header, 0, header.Length); + + return header.Length; // bytes written + } } diff --git a/src/SharpCompress/Compressors/Deflate/InfTree.cs b/src/SharpCompress/Compressors/Deflate/InfTree.cs index ccbc37bc..74f4069d 100644 --- a/src/SharpCompress/Compressors/Deflate/InfTree.cs +++ b/src/SharpCompress/Compressors/Deflate/InfTree.cs @@ -1,45 +1,45 @@ // Inftree.cs // ------------------------------------------------------------------ // -// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // -// This code is licensed under the Microsoft Public License. +// This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // -// last saved (in emacs): +// last saved (in emacs): // Time-stamp: <2009-October-28 12:43:54> // // ------------------------------------------------------------------ // // This module defines classes used in decompression. This code is derived -// from the jzlib implementation of zlib. In keeping with the license for jzlib, +// from the jzlib implementation of zlib. In keeping with the license for jzlib, // the copyright to that code is below. // // ------------------------------------------------------------------ -// +// // Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the distribution. -// +// // 3. The names of the authors may not be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, @@ -50,7 +50,7 @@ // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // ----------------------------------------------------------------------- // // This program is based on zlib-1.1.3; credit to authors @@ -63,516 +63,2169 @@ using System; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +internal sealed class InfTree { - internal sealed class InfTree + private const int MANY = 1440; + + private const int Z_OK = 0; + private const int Z_DATA_ERROR = -3; + private const int Z_MEM_ERROR = -4; + private const int Z_BUF_ERROR = -5; + internal const int fixed_bl = 9; + internal const int fixed_bd = 5; + internal const int BMAX = 15; // maximum bit length of any code + + //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] fixed_tl = { - private const int MANY = 1440; + 96, + 7, + 256, + 0, + 8, + 80, + 0, + 8, + 16, + 84, + 8, + 115, + 82, + 7, + 31, + 0, + 8, + 112, + 0, + 8, + 48, + 0, + 9, + 192, + 80, + 7, + 10, + 0, + 8, + 96, + 0, + 8, + 32, + 0, + 9, + 160, + 0, + 8, + 0, + 0, + 8, + 128, + 0, + 8, + 64, + 0, + 9, + 224, + 80, + 7, + 6, + 0, + 8, + 88, + 0, + 8, + 24, + 0, + 9, + 144, + 83, + 7, + 59, + 0, + 8, + 120, + 0, + 8, + 56, + 0, + 9, + 208, + 81, + 7, + 17, + 0, + 8, + 104, + 0, + 8, + 40, + 0, + 9, + 176, + 0, + 8, + 8, + 0, + 8, + 136, + 0, + 8, + 72, + 0, + 9, + 240, + 80, + 7, + 4, + 0, + 8, + 84, + 0, + 8, + 20, + 85, + 8, + 227, + 83, + 7, + 43, + 0, + 8, + 116, + 0, + 8, + 52, + 0, + 9, + 200, + 81, + 7, + 13, + 0, + 8, + 100, + 0, + 8, + 36, + 0, + 9, + 168, + 0, + 8, + 4, + 0, + 8, + 132, + 0, + 8, + 68, + 0, + 9, + 232, + 80, + 7, + 8, + 0, + 8, + 92, + 0, + 8, + 28, + 0, + 9, + 152, + 84, + 7, + 83, + 0, + 8, + 124, + 0, + 8, + 60, + 0, + 9, + 216, + 82, + 7, + 23, + 0, + 8, + 108, + 0, + 8, + 44, + 0, + 9, + 184, + 0, + 8, + 12, + 0, + 8, + 140, + 0, + 8, + 76, + 0, + 9, + 248, + 80, + 7, + 3, + 0, + 8, + 82, + 0, + 8, + 18, + 85, + 8, + 163, + 83, + 7, + 35, + 0, + 8, + 114, + 0, + 8, + 50, + 0, + 9, + 196, + 81, + 7, + 11, + 0, + 8, + 98, + 0, + 8, + 34, + 0, + 9, + 164, + 0, + 8, + 2, + 0, + 8, + 130, + 0, + 8, + 66, + 0, + 9, + 228, + 80, + 7, + 7, + 0, + 8, + 90, + 0, + 8, + 26, + 0, + 9, + 148, + 84, + 7, + 67, + 0, + 8, + 122, + 0, + 8, + 58, + 0, + 9, + 212, + 82, + 7, + 19, + 0, + 8, + 106, + 0, + 8, + 42, + 0, + 9, + 180, + 0, + 8, + 10, + 0, + 8, + 138, + 0, + 8, + 74, + 0, + 9, + 244, + 80, + 7, + 5, + 0, + 8, + 86, + 0, + 8, + 22, + 192, + 8, + 0, + 83, + 7, + 51, + 0, + 8, + 118, + 0, + 8, + 54, + 0, + 9, + 204, + 81, + 7, + 15, + 0, + 8, + 102, + 0, + 8, + 38, + 0, + 9, + 172, + 0, + 8, + 6, + 0, + 8, + 134, + 0, + 8, + 70, + 0, + 9, + 236, + 80, + 7, + 9, + 0, + 8, + 94, + 0, + 8, + 30, + 0, + 9, + 156, + 84, + 7, + 99, + 0, + 8, + 126, + 0, + 8, + 62, + 0, + 9, + 220, + 82, + 7, + 27, + 0, + 8, + 110, + 0, + 8, + 46, + 0, + 9, + 188, + 0, + 8, + 14, + 0, + 8, + 142, + 0, + 8, + 78, + 0, + 9, + 252, + 96, + 7, + 256, + 0, + 8, + 81, + 0, + 8, + 17, + 85, + 8, + 131, + 82, + 7, + 31, + 0, + 8, + 113, + 0, + 8, + 49, + 0, + 9, + 194, + 80, + 7, + 10, + 0, + 8, + 97, + 0, + 8, + 33, + 0, + 9, + 162, + 0, + 8, + 1, + 0, + 8, + 129, + 0, + 8, + 65, + 0, + 9, + 226, + 80, + 7, + 6, + 0, + 8, + 89, + 0, + 8, + 25, + 0, + 9, + 146, + 83, + 7, + 59, + 0, + 8, + 121, + 0, + 8, + 57, + 0, + 9, + 210, + 81, + 7, + 17, + 0, + 8, + 105, + 0, + 8, + 41, + 0, + 9, + 178, + 0, + 8, + 9, + 0, + 8, + 137, + 0, + 8, + 73, + 0, + 9, + 242, + 80, + 7, + 4, + 0, + 8, + 85, + 0, + 8, + 21, + 80, + 8, + 258, + 83, + 7, + 43, + 0, + 8, + 117, + 0, + 8, + 53, + 0, + 9, + 202, + 81, + 7, + 13, + 0, + 8, + 101, + 0, + 8, + 37, + 0, + 9, + 170, + 0, + 8, + 5, + 0, + 8, + 133, + 0, + 8, + 69, + 0, + 9, + 234, + 80, + 7, + 8, + 0, + 8, + 93, + 0, + 8, + 29, + 0, + 9, + 154, + 84, + 7, + 83, + 0, + 8, + 125, + 0, + 8, + 61, + 0, + 9, + 218, + 82, + 7, + 23, + 0, + 8, + 109, + 0, + 8, + 45, + 0, + 9, + 186, + 0, + 8, + 13, + 0, + 8, + 141, + 0, + 8, + 77, + 0, + 9, + 250, + 80, + 7, + 3, + 0, + 8, + 83, + 0, + 8, + 19, + 85, + 8, + 195, + 83, + 7, + 35, + 0, + 8, + 115, + 0, + 8, + 51, + 0, + 9, + 198, + 81, + 7, + 11, + 0, + 8, + 99, + 0, + 8, + 35, + 0, + 9, + 166, + 0, + 8, + 3, + 0, + 8, + 131, + 0, + 8, + 67, + 0, + 9, + 230, + 80, + 7, + 7, + 0, + 8, + 91, + 0, + 8, + 27, + 0, + 9, + 150, + 84, + 7, + 67, + 0, + 8, + 123, + 0, + 8, + 59, + 0, + 9, + 214, + 82, + 7, + 19, + 0, + 8, + 107, + 0, + 8, + 43, + 0, + 9, + 182, + 0, + 8, + 11, + 0, + 8, + 139, + 0, + 8, + 75, + 0, + 9, + 246, + 80, + 7, + 5, + 0, + 8, + 87, + 0, + 8, + 23, + 192, + 8, + 0, + 83, + 7, + 51, + 0, + 8, + 119, + 0, + 8, + 55, + 0, + 9, + 206, + 81, + 7, + 15, + 0, + 8, + 103, + 0, + 8, + 39, + 0, + 9, + 174, + 0, + 8, + 7, + 0, + 8, + 135, + 0, + 8, + 71, + 0, + 9, + 238, + 80, + 7, + 9, + 0, + 8, + 95, + 0, + 8, + 31, + 0, + 9, + 158, + 84, + 7, + 99, + 0, + 8, + 127, + 0, + 8, + 63, + 0, + 9, + 222, + 82, + 7, + 27, + 0, + 8, + 111, + 0, + 8, + 47, + 0, + 9, + 190, + 0, + 8, + 15, + 0, + 8, + 143, + 0, + 8, + 79, + 0, + 9, + 254, + 96, + 7, + 256, + 0, + 8, + 80, + 0, + 8, + 16, + 84, + 8, + 115, + 82, + 7, + 31, + 0, + 8, + 112, + 0, + 8, + 48, + 0, + 9, + 193, + 80, + 7, + 10, + 0, + 8, + 96, + 0, + 8, + 32, + 0, + 9, + 161, + 0, + 8, + 0, + 0, + 8, + 128, + 0, + 8, + 64, + 0, + 9, + 225, + 80, + 7, + 6, + 0, + 8, + 88, + 0, + 8, + 24, + 0, + 9, + 145, + 83, + 7, + 59, + 0, + 8, + 120, + 0, + 8, + 56, + 0, + 9, + 209, + 81, + 7, + 17, + 0, + 8, + 104, + 0, + 8, + 40, + 0, + 9, + 177, + 0, + 8, + 8, + 0, + 8, + 136, + 0, + 8, + 72, + 0, + 9, + 241, + 80, + 7, + 4, + 0, + 8, + 84, + 0, + 8, + 20, + 85, + 8, + 227, + 83, + 7, + 43, + 0, + 8, + 116, + 0, + 8, + 52, + 0, + 9, + 201, + 81, + 7, + 13, + 0, + 8, + 100, + 0, + 8, + 36, + 0, + 9, + 169, + 0, + 8, + 4, + 0, + 8, + 132, + 0, + 8, + 68, + 0, + 9, + 233, + 80, + 7, + 8, + 0, + 8, + 92, + 0, + 8, + 28, + 0, + 9, + 153, + 84, + 7, + 83, + 0, + 8, + 124, + 0, + 8, + 60, + 0, + 9, + 217, + 82, + 7, + 23, + 0, + 8, + 108, + 0, + 8, + 44, + 0, + 9, + 185, + 0, + 8, + 12, + 0, + 8, + 140, + 0, + 8, + 76, + 0, + 9, + 249, + 80, + 7, + 3, + 0, + 8, + 82, + 0, + 8, + 18, + 85, + 8, + 163, + 83, + 7, + 35, + 0, + 8, + 114, + 0, + 8, + 50, + 0, + 9, + 197, + 81, + 7, + 11, + 0, + 8, + 98, + 0, + 8, + 34, + 0, + 9, + 165, + 0, + 8, + 2, + 0, + 8, + 130, + 0, + 8, + 66, + 0, + 9, + 229, + 80, + 7, + 7, + 0, + 8, + 90, + 0, + 8, + 26, + 0, + 9, + 149, + 84, + 7, + 67, + 0, + 8, + 122, + 0, + 8, + 58, + 0, + 9, + 213, + 82, + 7, + 19, + 0, + 8, + 106, + 0, + 8, + 42, + 0, + 9, + 181, + 0, + 8, + 10, + 0, + 8, + 138, + 0, + 8, + 74, + 0, + 9, + 245, + 80, + 7, + 5, + 0, + 8, + 86, + 0, + 8, + 22, + 192, + 8, + 0, + 83, + 7, + 51, + 0, + 8, + 118, + 0, + 8, + 54, + 0, + 9, + 205, + 81, + 7, + 15, + 0, + 8, + 102, + 0, + 8, + 38, + 0, + 9, + 173, + 0, + 8, + 6, + 0, + 8, + 134, + 0, + 8, + 70, + 0, + 9, + 237, + 80, + 7, + 9, + 0, + 8, + 94, + 0, + 8, + 30, + 0, + 9, + 157, + 84, + 7, + 99, + 0, + 8, + 126, + 0, + 8, + 62, + 0, + 9, + 221, + 82, + 7, + 27, + 0, + 8, + 110, + 0, + 8, + 46, + 0, + 9, + 189, + 0, + 8, + 14, + 0, + 8, + 142, + 0, + 8, + 78, + 0, + 9, + 253, + 96, + 7, + 256, + 0, + 8, + 81, + 0, + 8, + 17, + 85, + 8, + 131, + 82, + 7, + 31, + 0, + 8, + 113, + 0, + 8, + 49, + 0, + 9, + 195, + 80, + 7, + 10, + 0, + 8, + 97, + 0, + 8, + 33, + 0, + 9, + 163, + 0, + 8, + 1, + 0, + 8, + 129, + 0, + 8, + 65, + 0, + 9, + 227, + 80, + 7, + 6, + 0, + 8, + 89, + 0, + 8, + 25, + 0, + 9, + 147, + 83, + 7, + 59, + 0, + 8, + 121, + 0, + 8, + 57, + 0, + 9, + 211, + 81, + 7, + 17, + 0, + 8, + 105, + 0, + 8, + 41, + 0, + 9, + 179, + 0, + 8, + 9, + 0, + 8, + 137, + 0, + 8, + 73, + 0, + 9, + 243, + 80, + 7, + 4, + 0, + 8, + 85, + 0, + 8, + 21, + 80, + 8, + 258, + 83, + 7, + 43, + 0, + 8, + 117, + 0, + 8, + 53, + 0, + 9, + 203, + 81, + 7, + 13, + 0, + 8, + 101, + 0, + 8, + 37, + 0, + 9, + 171, + 0, + 8, + 5, + 0, + 8, + 133, + 0, + 8, + 69, + 0, + 9, + 235, + 80, + 7, + 8, + 0, + 8, + 93, + 0, + 8, + 29, + 0, + 9, + 155, + 84, + 7, + 83, + 0, + 8, + 125, + 0, + 8, + 61, + 0, + 9, + 219, + 82, + 7, + 23, + 0, + 8, + 109, + 0, + 8, + 45, + 0, + 9, + 187, + 0, + 8, + 13, + 0, + 8, + 141, + 0, + 8, + 77, + 0, + 9, + 251, + 80, + 7, + 3, + 0, + 8, + 83, + 0, + 8, + 19, + 85, + 8, + 195, + 83, + 7, + 35, + 0, + 8, + 115, + 0, + 8, + 51, + 0, + 9, + 199, + 81, + 7, + 11, + 0, + 8, + 99, + 0, + 8, + 35, + 0, + 9, + 167, + 0, + 8, + 3, + 0, + 8, + 131, + 0, + 8, + 67, + 0, + 9, + 231, + 80, + 7, + 7, + 0, + 8, + 91, + 0, + 8, + 27, + 0, + 9, + 151, + 84, + 7, + 67, + 0, + 8, + 123, + 0, + 8, + 59, + 0, + 9, + 215, + 82, + 7, + 19, + 0, + 8, + 107, + 0, + 8, + 43, + 0, + 9, + 183, + 0, + 8, + 11, + 0, + 8, + 139, + 0, + 8, + 75, + 0, + 9, + 247, + 80, + 7, + 5, + 0, + 8, + 87, + 0, + 8, + 23, + 192, + 8, + 0, + 83, + 7, + 51, + 0, + 8, + 119, + 0, + 8, + 55, + 0, + 9, + 207, + 81, + 7, + 15, + 0, + 8, + 103, + 0, + 8, + 39, + 0, + 9, + 175, + 0, + 8, + 7, + 0, + 8, + 135, + 0, + 8, + 71, + 0, + 9, + 239, + 80, + 7, + 9, + 0, + 8, + 95, + 0, + 8, + 31, + 0, + 9, + 159, + 84, + 7, + 99, + 0, + 8, + 127, + 0, + 8, + 63, + 0, + 9, + 223, + 82, + 7, + 27, + 0, + 8, + 111, + 0, + 8, + 47, + 0, + 9, + 191, + 0, + 8, + 15, + 0, + 8, + 143, + 0, + 8, + 79, + 0, + 9, + 255 + }; - private const int Z_OK = 0; - private const int Z_STREAM_END = 1; - private const int Z_NEED_DICT = 2; - private const int Z_ERRNO = -1; - private const int Z_STREAM_ERROR = -2; - private const int Z_DATA_ERROR = -3; - private const int Z_MEM_ERROR = -4; - private const int Z_BUF_ERROR = -5; - private const int Z_VERSION_ERROR = -6; + //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] fixed_td = + { + 80, + 5, + 1, + 87, + 5, + 257, + 83, + 5, + 17, + 91, + 5, + 4097, + 81, + 5, + 5, + 89, + 5, + 1025, + 85, + 5, + 65, + 93, + 5, + 16385, + 80, + 5, + 3, + 88, + 5, + 513, + 84, + 5, + 33, + 92, + 5, + 8193, + 82, + 5, + 9, + 90, + 5, + 2049, + 86, + 5, + 129, + 192, + 5, + 24577, + 80, + 5, + 2, + 87, + 5, + 385, + 83, + 5, + 25, + 91, + 5, + 6145, + 81, + 5, + 7, + 89, + 5, + 1537, + 85, + 5, + 97, + 93, + 5, + 24577, + 80, + 5, + 4, + 88, + 5, + 769, + 84, + 5, + 49, + 92, + 5, + 12289, + 82, + 5, + 13, + 90, + 5, + 3073, + 86, + 5, + 193, + 192, + 5, + 24577 + }; - internal const int fixed_bl = 9; - internal const int fixed_bd = 5; - internal const int BMAX = 15; // maximum bit length of any code + // Tables for deflate from PKZIP's appnote.txt. + //UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cplens = + { + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 15, + 17, + 19, + 23, + 27, + 31, + 35, + 43, + 51, + 59, + 67, + 83, + 99, + 115, + 131, + 163, + 195, + 227, + 258, + 0, + 0 + }; - //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] fixed_tl = + // see note #13 above about 258 + //UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cplext = + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0, + 112, + 112 + }; + + //UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cpdist = + { + 1, + 2, + 3, + 4, + 5, + 7, + 9, + 13, + 17, + 25, + 33, + 49, + 65, + 97, + 129, + 193, + 257, + 385, + 513, + 769, + 1025, + 1537, + 2049, + 3073, + 4097, + 6145, + 8193, + 12289, + 16385, + 24577 + }; + + //UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal static readonly int[] cpdext = + { + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13 + }; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + internal int[] c; // bit length count table + internal int[] hn; // hufts used in space + internal int[] r; // table entry for structure assignment + internal int[] u; // table stack + internal int[] v; // work area for huft_build + internal int[] x; // bit offsets, then code stack + + private int huft_build( + int[] b, + int bindex, + int n, + int s, + int[] d, + int[] e, + int[] t, + int[] m, + int[] hp, + int[] hn, + int[] v + ) + { + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; + i = n; + do { - 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, - 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, - 160, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, - 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, - 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176, 0, 8, 8, 0, 8, - 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, - 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, 0, - 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, - 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, - 8, 124, 0, 8, 60, 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0 - , 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, - 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50 - , 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2 - , 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, 26 - , 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, - 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, - 74, 0, 9, 244, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, - 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8 - , 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236, 80, - 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, - 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0 - , 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, - 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, - 194, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, - 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, - 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, 81, 7, 17, 0, - 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, - 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0 - , 8, 117, 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, - 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, - 0, 8, 93, 0, 8, 29, 0, 9, 154, 84, 7, 83, 0, 8, 125, 0, 8, 61, - 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186, - 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, - 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, - 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, - 131, 0, 8, 67, 0, 9, 230, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, - 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, - 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, - 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, - 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0 - , 9, 174, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0 - , 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, - 0, 9, 222, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190, 0, 8, 15 - , 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, - 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193, 80, - 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8 - , 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, - 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0 - , 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, - 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116 - , 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, - 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, - 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, - 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8 - , 140, 0, 8, 76, 0, 9, 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, - 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, - 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0 - , 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, - 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42 - , 0, 9, 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, - 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, - 54, 0, 9, 205, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173, 0, 8 - , 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, - 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221, 82, - 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8, - 14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8 - , 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195, - 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, - 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147, - 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, - 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, - 243, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8 - , 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, - 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235, 80, 7, 8, 0, - 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, - 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0 - , 8, 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, - 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, - 11, 0, 8, 99, 0, 8, 35, 0, 9, 167, 0, 8, 3, 0, 8, 131, 0, 8, - 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, - 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, 107, 0, 8 - , 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, - 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, - 8, 55, 0, 9, 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0 - , 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, - 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223, - 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143 - , 0, 8, 79, 0, 9, 255 - }; + c[b[bindex + p]]++; + p++; + i--; // assume all entries <= BMAX + } while (i != 0); - //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] fixed_td = + if (c[0] == n) { - 80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, - 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33 - , - 92, 5, 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, - 80 - , 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, - 1537 - , 85, 5, 97, 93, 5, 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92 - , 5 - , 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577 - }; + // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } - // Tables for deflate from PKZIP's appnote.txt. - //UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] cplens = + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) { - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, - 51 - , 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 - }; - - // see note #13 above about 258 - //UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] cplext = + if (c[j] != 0) + { + break; + } + } + k = j; // minimum code length + if (l < j) { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4 - , 4 - , 4, 5, 5, 5, 5, 0, 112, 112 - }; - - //UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] cpdist = + l = j; + } + for (i = BMAX; i != 0; i--) { - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, - 385 - , 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, - 16385, - 24577 - }; - - //UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal static readonly int[] cpdext = + if (c[i] != 0) + { + break; + } + } + g = i; // maximum code length + if (l > i) { - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 - , - 10, 10, 11, 11, 12, 12, 13, 13 - }; + l = i; + } + m[0] = l; - // If BMAX needs to be larger than 16, then h and x[] should be uLong. - internal int[] c; // bit length count table - internal int[] hn; // hufts used in space - internal int[] r; // table entry for structure assignment - internal int[] u; // table stack - internal int[] v; // work area for huft_build - internal int[] x; // bit offsets, then code stack - - private int huft_build(int[] b, int bindex, int n, int s, int[] d, int[] e, int[] t, int[] m, int[] hp, int[] hn, - int[] v) + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1) { - // Given a list of code lengths and a maximum table size, make a set of - // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR - // if the given code set is incomplete (the tables are still built in this - // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of - // lengths), or Z_MEM_ERROR if not enough memory. - - int a; // counter for codes of length k - int f; // i repeats in table every f entries - int g; // maximum code length - int h; // table level - int i; // counter, current code - int j; // counter - int k; // number of bits in current code - int l; // bits per table (returned in m) - int mask; // (1 << w) - 1, to avoid cc -O bug on HP - int p; // pointer into c[], b[], or v[] - int q; // points to current table - int w; // bits before this table == (l * h) - int xp; // pointer into x - int y; // number of dummy codes added - int z; // number of entries in current table - - // Generate counts for each bit length - - p = 0; - i = n; - do - { - c[b[bindex + p]]++; - p++; - i--; // assume all entries <= BMAX - } - while (i != 0); - - if (c[0] == n) - { - // null input--all zero length codes - t[0] = -1; - m[0] = 0; - return Z_OK; - } - - // Find minimum and maximum length, bound *m by those - l = m[0]; - for (j = 1; j <= BMAX; j++) - { - if (c[j] != 0) - { - break; - } - } - k = j; // minimum code length - if (l < j) - { - l = j; - } - for (i = BMAX; i != 0; i--) - { - if (c[i] != 0) - { - break; - } - } - g = i; // maximum code length - if (l > i) - { - l = i; - } - m[0] = l; - - // Adjust last length count to fill out codes, if needed - for (y = 1 << j; j < i; j++, y <<= 1) - { - if ((y -= c[j]) < 0) - { - return Z_DATA_ERROR; - } - } - if ((y -= c[i]) < 0) + if ((y -= c[j]) < 0) { return Z_DATA_ERROR; } - c[i] += y; + } + if ((y -= c[i]) < 0) + { + return Z_DATA_ERROR; + } + c[i] += y; - // Generate starting offsets into the value table for each length - x[1] = j = 0; - p = 1; - xp = 2; - while (--i != 0) + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; + xp = 2; + while (--i != 0) + { + // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; + p = 0; + do + { + if ((j = b[bindex + p]) != 0) { - // note that i == g from above - x[xp] = (j += c[p]); - xp++; - p++; + v[x[j]++] = i; } + p++; + } while (++i < n); + n = x[g]; // set n to length of v - // Make a table of values in order of bit lengths - i = 0; - p = 0; - do + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++) + { + a = c[k]; + while (a-- != 0) { - if ((j = b[bindex + p]) != 0) + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l) { - v[x[j]++] = i; - } - p++; - } - while (++i < n); - n = x[g]; // set n to length of v + h++; + w += l; // previous table always l bits - // Generate the Huffman codes and for each, make the table entries - x[0] = i = 0; // first Huffman code is zero - p = 0; // grab values in bit order - h = -1; // no tables yet--level -1 - w = -l; // bits decoded == (l * h) - u[0] = 0; // just to keep compilers happy - q = 0; // ditto - z = 0; // ditto - - // go through the bit lengths (k already is bits in shortest code) - for (; k <= g; k++) - { - a = c[k]; - while (a-- != 0) - { - // here i is the Huffman code of length k bits for value *p - // make tables up to required level - while (k > w + l) + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if ((f = 1 << (j = k - w)) > a + 1) { - h++; - w += l; // previous table always l bits - - // compute minimum size table less than or equal to l bits - z = g - w; - z = (z > l) ? l : z; // table size upper limit - if ((f = 1 << (j = k - w)) > a + 1) + // try a k-w bit table + // too few codes for k-w bit table + f -= (a + 1); // deduct codes from patterns left + xp = k; + if (j < z) { - // try a k-w bit table - // too few codes for k-w bit table - f -= (a + 1); // deduct codes from patterns left - xp = k; - if (j < z) + while (++j < z) { - while (++j < z) + // try smaller tables up to z bits + if ((f <<= 1) <= c[++xp]) { - // try smaller tables up to z bits - if ((f <<= 1) <= c[++xp]) - { - break; // enough codes to use up j bits - } - f -= c[xp]; // else deduct codes from patterns + break; // enough codes to use up j bits } + f -= c[xp]; // else deduct codes from patterns } } - z = 1 << j; // table entries for j-bit table - - // allocate new table - if (hn[0] + z > MANY) - { - // (note: doesn't matter for fixed) - return Z_DATA_ERROR; // overflow of MANY - } - u[h] = q = hn[0]; // DEBUG - hn[0] += z; - - // connect to last table, if there is one - if (h != 0) - { - x[h] = i; // save pattern for backing up - r[0] = (sbyte)j; // bits in this table - r[1] = (sbyte)l; // bits to dump before this table - j = SharedUtils.URShift(i, (w - l)); - r[2] = (q - u[h - 1] - j); // offset to this table - Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table - } - else - { - t[0] = q; // first table is returned result - } } + z = 1 << j; // table entries for j-bit table - // set up table entry in r - r[1] = (sbyte)(k - w); - if (p >= n) + // allocate new table + if (hn[0] + z > MANY) { - r[0] = 128 + 64; // out of values--invalid code + // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY } - else if (v[p] < s) + u[h] = q = hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if (h != 0) { - r[0] = (sbyte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block - r[2] = v[p++]; // simple code is just the value + x[h] = i; // save pattern for backing up + r[0] = (sbyte)j; // bits in this table + r[1] = (sbyte)l; // bits to dump before this table + j = SharedUtils.URShift(i, (w - l)); + r[2] = (q - u[h - 1] - j); // offset to this table + Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table } else { - r[0] = (sbyte)(e[v[p] - s] + 16 + 64); // non-simple--look up in lists - r[2] = d[v[p++] - s]; - } - - // fill code-like entries with r - f = 1 << (k - w); - for (j = SharedUtils.URShift(i, w); j < z; j += f) - { - Array.Copy(r, 0, hp, (q + j) * 3, 3); - } - - // backwards increment the k-bit code i - for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1)) - { - i ^= j; - } - i ^= j; - - // backup over finished tables - mask = (1 << w) - 1; // needed on HP, cc -O bug - while ((i & mask) != x[h]) - { - h--; // don't need to update q - w -= l; - mask = (1 << w) - 1; + t[0] = q; // first table is returned result } } - } - // Return Z_BUF_ERROR if we were given an incomplete table - return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + // set up table entry in r + r[1] = (sbyte)(k - w); + if (p >= n) + { + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s) + { + r[0] = (sbyte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else + { + r[0] = (sbyte)(e[v[p] - s] + 16 + 64); // non-simple--look up in lists + r[2] = d[v[p++] - s]; + } + + // fill code-like entries with r + f = 1 << (k - w); + for (j = SharedUtils.URShift(i, w); j < z; j += f) + { + Array.Copy(r, 0, hp, (q + j) * 3, 3); + } + + // backwards increment the k-bit code i + for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1)) + { + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]) + { + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } } - internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z) - { - int result; - initWorkArea(19); - hn[0] = 0; - result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z) + { + int result; + initWorkArea(19); + hn[0] = 0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if (result == Z_DATA_ERROR) + { + z.Message = "oversubscribed dynamic bit lengths tree"; + } + else if (result == Z_BUF_ERROR || bb[0] == 0) + { + z.Message = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + internal int inflate_trees_dynamic( + int nl, + int nd, + int[] c, + int[] bl, + int[] bd, + int[] tl, + int[] td, + int[] hp, + ZlibCodec z + ) + { + int result; + + // build literal/length tree + initWorkArea(288); + hn[0] = 0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0) + { if (result == Z_DATA_ERROR) { - z.Message = "oversubscribed dynamic bit lengths tree"; + z.Message = "oversubscribed literal/length tree"; } - else if (result == Z_BUF_ERROR || bb[0] == 0) + else if (result != Z_MEM_ERROR) { - z.Message = "incomplete dynamic bit lengths tree"; + z.Message = "incomplete literal/length tree"; result = Z_DATA_ERROR; } return result; } - internal int inflate_trees_dynamic(int nl, int nd, int[] c, int[] bl, int[] bd, int[] tl, int[] td, int[] hp, - ZlibCodec z) + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)) { - int result; - - // build literal/length tree - initWorkArea(288); - hn[0] = 0; - result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); - if (result != Z_OK || bl[0] == 0) + if (result == Z_DATA_ERROR) { - if (result == Z_DATA_ERROR) - { - z.Message = "oversubscribed literal/length tree"; - } - else if (result != Z_MEM_ERROR) - { - z.Message = "incomplete literal/length tree"; - result = Z_DATA_ERROR; - } - return result; + z.Message = "oversubscribed distance tree"; } - - // build distance tree - initWorkArea(288); - result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); - - if (result != Z_OK || (bd[0] == 0 && nl > 257)) + else if (result == Z_BUF_ERROR) { - if (result == Z_DATA_ERROR) - { - z.Message = "oversubscribed distance tree"; - } - else if (result == Z_BUF_ERROR) - { - z.Message = "incomplete distance tree"; - result = Z_DATA_ERROR; - } - else if (result != Z_MEM_ERROR) - { - z.Message = "empty distance tree with lengths"; - result = Z_DATA_ERROR; - } - return result; + z.Message = "incomplete distance tree"; + result = Z_DATA_ERROR; } - - return Z_OK; + else if (result != Z_MEM_ERROR) + { + z.Message = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; } - internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z) - { - bl[0] = fixed_bl; - bd[0] = fixed_bd; - tl[0] = fixed_tl; - td[0] = fixed_td; - return Z_OK; - } + return Z_OK; + } - private void initWorkArea(int vsize) + internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z) + { + bl[0] = fixed_bl; + bd[0] = fixed_bd; + tl[0] = fixed_tl; + td[0] = fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize) + { + if (hn is null) { - if (hn is null) + hn = new int[1]; + v = new int[vsize]; + c = new int[BMAX + 1]; + r = new int[3]; + u = new int[BMAX]; + x = new int[BMAX + 1]; + } + else + { + if (v.Length < vsize) { - hn = new int[1]; v = new int[vsize]; - c = new int[BMAX + 1]; - r = new int[3]; - u = new int[BMAX]; - x = new int[BMAX + 1]; } - else - { - if (v.Length < vsize) - { - v = new int[vsize]; - } - Array.Clear(v, 0, vsize); - Array.Clear(c, 0, BMAX + 1); - r[0] = 0; - r[1] = 0; - r[2] = 0; + Array.Clear(v, 0, vsize); + Array.Clear(c, 0, BMAX + 1); + r[0] = 0; + r[1] = 0; + r[2] = 0; - // for(int i=0; i> 1) + { + case 0: // stored + b >>= 3; + k -= (3); + t = k & 7; // go to byte boundary + b >>= t; + k -= t; + mode = InflateBlockMode.LENS; // get length of stored block + break; - q = writeAt; - m = (q < readAt ? readAt - q - 1 : end - q); + case 1: // fixed + var bl = new int[1]; + var bd = new int[1]; + var tl = new int[1][]; + var td = new int[1][]; + InfTree.inflate_trees_fixed(bl, bd, tl, td, _codec); + codes.Init(bl[0], bd[0], tl[0], 0, td[0], 0); + b >>= 3; + k -= 3; + mode = InflateBlockMode.CODES; + break; - // process input based on current state - while (true) - { - switch (mode) - { - case InflateBlockMode.TYPE: + case 2: // dynamic + b >>= 3; + k -= 3; + mode = InflateBlockMode.TABLE; + break; + case 3: // illegal + b >>= 3; + k -= 3; + mode = InflateBlockMode.BAD; + _codec.Message = "invalid block type"; + r = ZlibConstants.Z_DATA_ERROR; + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + break; + + case InflateBlockMode.LENS: + + while (k < (32)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + ; + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + { + mode = InflateBlockMode.BAD; + _codec.Message = "invalid stored block lengths"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = + left != 0 + ? InflateBlockMode.STORED + : (last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE); + break; + + case InflateBlockMode.STORED: + if (n == 0) + { + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + if (m == 0) + { + if (q == end && readAt != 0) + { + q = 0; + m = (q < readAt ? readAt - q - 1 : end - q); + } + if (m == 0) + { + writeAt = q; + r = Flush(r); + q = writeAt; + m = (q < readAt ? readAt - q - 1 : end - q); + if (q == end && readAt != 0) + { + q = 0; + m = (q < readAt ? readAt - q - 1 : end - q); + } + if (m == 0) + { + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + } + } + r = ZlibConstants.Z_OK; + + t = left; + if (t > n) + { + t = n; + } + if (t > m) + { + t = m; + } + Array.Copy(_codec.InputBuffer, p, window, q, t); + p += t; + n -= t; + q += t; + m -= t; + if ((left -= t) != 0) + { + break; + } + mode = last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE; + break; + + case InflateBlockMode.TABLE: + + while (k < (14)) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + n--; + b |= (_codec.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + table = t = (b & 0x3fff); + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + mode = InflateBlockMode.BAD; + _codec.Message = "too many length or distance symbols"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if (blens is null || blens.Length < t) + { + blens = new int[t]; + } + else + { + Array.Clear(blens, 0, t); + + // for (int i = 0; i < t; i++) + // { + // blens[i] = 0; + // } + } + + b >>= 14; + k -= 14; + + index = 0; + mode = InflateBlockMode.BTREE; + goto case InflateBlockMode.BTREE; + + case InflateBlockMode.BTREE: + while (index < 4 + (table >> 10)) + { while (k < (3)) { if (n != 0) @@ -173,167 +438,58 @@ namespace SharpCompress.Compressors.Deflate b |= (_codec.InputBuffer[p++] & 0xff) << k; k += 8; } - t = (b & 7); - last = t & 1; - switch ((uint)t >> 1) - { - case 0: // stored - b >>= 3; - k -= (3); - t = k & 7; // go to byte boundary - b >>= t; - k -= t; - mode = InflateBlockMode.LENS; // get length of stored block - break; - - case 1: // fixed - var bl = new int[1]; - var bd = new int[1]; - var tl = new int[1][]; - var td = new int[1][]; - InfTree.inflate_trees_fixed(bl, bd, tl, td, _codec); - codes.Init(bl[0], bd[0], tl[0], 0, td[0], 0); - b >>= 3; - k -= 3; - mode = InflateBlockMode.CODES; - break; - - case 2: // dynamic - b >>= 3; - k -= 3; - mode = InflateBlockMode.TABLE; - break; - - case 3: // illegal - b >>= 3; - k -= 3; - mode = InflateBlockMode.BAD; - _codec.Message = "invalid block type"; - r = ZlibConstants.Z_DATA_ERROR; - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - break; - - case InflateBlockMode.LENS: - - while (k < (32)) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - ; - n--; - b |= (_codec.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + blens[border[index++]] = b & 7; + + b >>= 3; + k -= 3; + } + + while (index < 19) + { + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, _codec); + if (t != ZlibConstants.Z_OK) + { + r = t; + if (r == ZlibConstants.Z_DATA_ERROR) { + blens = null; mode = InflateBlockMode.BAD; - _codec.Message = "invalid stored block lengths"; - r = ZlibConstants.Z_DATA_ERROR; - - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - left = (b & 0xffff); - b = k = 0; // dump bits - mode = left != 0 - ? InflateBlockMode.STORED - : (last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE); - break; - - case InflateBlockMode.STORED: - if (n == 0) - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); } - if (m == 0) - { - if (q == end && readAt != 0) - { - q = 0; - m = (q < readAt ? readAt - q - 1 : end - q); - } - if (m == 0) - { - writeAt = q; - r = Flush(r); - q = writeAt; - m = (q < readAt ? readAt - q - 1 : end - q); - if (q == end && readAt != 0) - { - q = 0; - m = (q < readAt ? readAt - q - 1 : end - q); - } - if (m == 0) - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - } - } - r = ZlibConstants.Z_OK; + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } - t = left; - if (t > n) - { - t = n; - } - if (t > m) - { - t = m; - } - Array.Copy(_codec.InputBuffer, p, window, q, t); - p += t; - n -= t; - q += t; - m -= t; - if ((left -= t) != 0) + index = 0; + mode = InflateBlockMode.DTREE; + goto case InflateBlockMode.DTREE; + + case InflateBlockMode.DTREE: + while (true) + { + t = table; + if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))) { break; } - mode = last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE; - break; - case InflateBlockMode.TABLE: + int i, + j, + c; - while (k < (14)) + t = bb[0]; + + while (k < t) { if (n != 0) { @@ -355,47 +511,26 @@ namespace SharpCompress.Compressors.Deflate k += 8; } - table = t = (b & 0x3fff); - if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) - { - mode = InflateBlockMode.BAD; - _codec.Message = "too many length or distance symbols"; - r = ZlibConstants.Z_DATA_ERROR; + t = hufts[ + ((tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3) + 1 + ]; + c = hufts[ + ((tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3) + 2 + ]; - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); - if (blens is null || blens.Length < t) + if (c < 16) { - blens = new int[t]; + b >>= t; + k -= t; + blens[index++] = c; } else { - Array.Clear(blens, 0, t); + // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; - // for (int i = 0; i < t; i++) - // { - // blens[i] = 0; - // } - } - - b >>= 14; - k -= 14; - - index = 0; - mode = InflateBlockMode.BTREE; - goto case InflateBlockMode.BTREE; - - case InflateBlockMode.BTREE: - while (index < 4 + (table >> 10)) - { - while (k < (3)) + while (k < (t + i)) { if (n != 0) { @@ -417,27 +552,70 @@ namespace SharpCompress.Compressors.Deflate k += 8; } - blens[border[index++]] = b & 7; + b >>= t; + k -= t; - b >>= 3; - k -= 3; + j += (b & InternalInflateConstants.InflateMask[i]); + + b >>= i; + k -= i; + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) + { + blens = null; + mode = InflateBlockMode.BAD; + _codec.Message = "invalid bit length repeat"; + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + + c = (c == 16) ? blens[i - 1] : 0; + do + { + blens[i++] = c; + } while (--j != 0); + index = i; } + } - while (index < 19) - { - blens[border[index++]] = 0; - } + tb[0] = -1; + + { + var bl = new[] { 9 }; // must be <= 9 for lookahead assumptions + var bd = new[] { 6 }; // must be <= 9 for lookahead assumptions + var tl = new int[1]; + var td = new int[1]; + + t = table; + t = inftree.inflate_trees_dynamic( + 257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, + bl, + bd, + tl, + td, + hufts, + _codec + ); - bb[0] = 7; - t = inftree.inflate_trees_bits(blens, bb, tb, hufts, _codec); if (t != ZlibConstants.Z_OK) { - r = t; - if (r == ZlibConstants.Z_DATA_ERROR) + if (t == ZlibConstants.Z_DATA_ERROR) { blens = null; mode = InflateBlockMode.BAD; } + r = t; bitb = b; bitk = k; @@ -447,435 +625,431 @@ namespace SharpCompress.Compressors.Deflate writeAt = q; return Flush(r); } + codes.Init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); + } + mode = InflateBlockMode.CODES; + goto case InflateBlockMode.CODES; - index = 0; - mode = InflateBlockMode.DTREE; - goto case InflateBlockMode.DTREE; + case InflateBlockMode.CODES: + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; - case InflateBlockMode.DTREE: - while (true) + r = codes.Process(this, r); + if (r != ZlibConstants.Z_STREAM_END) + { + return Flush(r); + } + + r = ZlibConstants.Z_OK; + p = _codec.NextIn; + n = _codec.AvailableBytesIn; + b = bitb; + k = bitk; + q = writeAt; + m = (q < readAt ? readAt - q - 1 : end - q); + + if (last == 0) + { + mode = InflateBlockMode.TYPE; + break; + } + mode = InflateBlockMode.DRY; + goto case InflateBlockMode.DRY; + + case InflateBlockMode.DRY: + writeAt = q; + r = Flush(r); + q = writeAt; + m = (q < readAt ? readAt - q - 1 : end - q); + if (readAt != writeAt) + { + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + mode = InflateBlockMode.DONE; + goto case InflateBlockMode.DONE; + + case InflateBlockMode.DONE: + r = ZlibConstants.Z_STREAM_END; + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + + case InflateBlockMode.BAD: + r = ZlibConstants.Z_DATA_ERROR; + + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + + default: + r = ZlibConstants.Z_STREAM_ERROR; + + bitb = b; + bitk = k; + _codec.AvailableBytesIn = n; + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + writeAt = q; + return Flush(r); + } + } + } + + internal void Free() + { + Reset(); + window = null; + hufts = null; + } + + internal void SetDictionary(byte[] d, int start, int n) + { + Array.Copy(d, start, window, 0, n); + readAt = writeAt = n; + } + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. + internal int SyncPoint() => mode == InflateBlockMode.LENS ? 1 : 0; + + // copy as much as possible from the sliding window to the output area + internal int Flush(int r) + { + int nBytes; + + for (var pass = 0; pass < 2; pass++) + { + if (pass == 0) + { + // compute number of bytes to copy as far as end of window + nBytes = ((readAt <= writeAt ? writeAt : end) - readAt); + } + else + { + // compute bytes to copy + nBytes = writeAt - readAt; + } + + // workitem 8870 + if (nBytes == 0) + { + if (r == ZlibConstants.Z_BUF_ERROR) + { + r = ZlibConstants.Z_OK; + } + return r; + } + + if (nBytes > _codec.AvailableBytesOut) + { + nBytes = _codec.AvailableBytesOut; + } + + if (nBytes != 0 && r == ZlibConstants.Z_BUF_ERROR) + { + r = ZlibConstants.Z_OK; + } + + // update counters + _codec.AvailableBytesOut -= nBytes; + _codec.TotalBytesOut += nBytes; + + // update check information + if (checkfn != null) + { + _codec._adler32 = check = Adler32.Calculate(check, window.AsSpan(readAt, nBytes)); + } + + // copy as far as end of window + Array.Copy(window, readAt, _codec.OutputBuffer, _codec.NextOut, nBytes); + _codec.NextOut += nBytes; + readAt += nBytes; + + // see if more to copy at beginning of window + if (readAt == end && pass == 0) + { + // wrap pointers + readAt = 0; + if (writeAt == end) + { + writeAt = 0; + } + } + else + { + pass++; + } + } + + // done + return r; + } + + #region Nested type: InflateBlockMode + + private enum InflateBlockMode + { + TYPE = 0, // get type bits (3, including end bit) + LENS = 1, // get lengths for stored + STORED = 2, // processing stored block + TABLE = 3, // get table lengths + BTREE = 4, // get bit lengths tree for a dynamic block + DTREE = 5, // get length, distance trees for a dynamic block + CODES = 6, // processing fixed or dynamic block + DRY = 7, // output remaining window bytes + DONE = 8, // finished last block, done + BAD = 9 // ot a data error--stuck here + } + + #endregion +} + +internal static class InternalInflateConstants +{ + // And'ing with mask[n] masks the lower n bits + internal static readonly int[] InflateMask = + { + 0x00000000, + 0x00000001, + 0x00000003, + 0x00000007, + 0x0000000f, + 0x0000001f, + 0x0000003f, + 0x0000007f, + 0x000000ff, + 0x000001ff, + 0x000003ff, + 0x000007ff, + 0x00000fff, + 0x00001fff, + 0x00003fff, + 0x00007fff, + 0x0000ffff + }; +} + +internal sealed class InflateCodes +{ + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + private const int START = 0; // x: set up for LEN + private const int LEN = 1; // i: get length/literal/eob next + private const int LENEXT = 2; // i: getting length extra (have base) + private const int DIST = 3; // i: get distance next + private const int DISTEXT = 4; // i: getting distance extra + private const int COPY = 5; // o: copying bytes in window, waiting for space + private const int LIT = 6; // o: got literal, waiting for output space + private const int WASH = 7; // o: got eob, possibly still output waiting + private const int END = 8; // x: got eob and all data flushed + private const int BADCODE = 9; // x: got error + + // if EXT or COPY, where and how much + internal int bitsToGet; // bits to get for extra + internal byte dbits; // dtree bits decoder per branch + internal int dist; // distance back to copy from + internal int[] dtree; // distance tree + internal int dtree_index; // distance tree + + internal byte lbits; // ltree bits decoded per branch + internal int len; + internal int lit; + internal int[] ltree; // literal/length/eob tree + internal int ltree_index; // literal/length/eob tree + internal int mode; // current inflate_codes mode + internal int need; // bits needed + internal int[] tree; // pointer into tree + internal int tree_index; + + internal void Init(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index) + { + mode = START; + lbits = (byte)bl; + dbits = (byte)bd; + ltree = tl; + ltree_index = tl_index; + dtree = td; + dtree_index = td_index; + tree = null; + } + + internal int Process(InflateBlocks blocks, int r) + { + int j; // temporary storage + int tindex; // temporary pointer + int e; // extra bits or operation + var b = 0; // bit buffer + var k = 0; // bits in bit buffer + var p = 0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + var z = blocks._codec; + + // copy input/output information to locals (UPDATE macro restores) + p = z.NextIn; + n = z.AvailableBytesIn; + b = blocks.bitb; + k = blocks.bitk; + q = blocks.writeAt; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + // process input and output based on current state + while (true) + { + switch (mode) + { + // waiting for "i:"=input, "o:"=output, "x:"=nothing + case START: // x: set up for LEN + if (m >= 258 && n >= 10) + { + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + r = InflateFast( + lbits, + dbits, + ltree, + ltree_index, + dtree, + dtree_index, + blocks, + z + ); + + p = z.NextIn; + n = z.AvailableBytesIn; + b = blocks.bitb; + k = blocks.bitk; + q = blocks.writeAt; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + + if (r != ZlibConstants.Z_OK) { - t = table; - if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))) + mode = (r == ZlibConstants.Z_STREAM_END) ? WASH : BADCODE; + break; + } + } + need = lbits; + tree = ltree; + tree_index = ltree_index; + + mode = LEN; + goto case LEN; + + case LEN: // i: get length/literal/eob next + j = need; + + while (k < j) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + // Handling missing trailing bit(s) + var tmp_tindex = + (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3; + if (k >= tree[tmp_tindex + 1]) { break; } - int i, j, c; - - t = bb[0]; - - while (k < t) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - - n--; - b |= (_codec.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - t = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 1]; - c = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 2]; - - if (c < 16) - { - b >>= t; - k -= t; - blens[index++] = c; - } - else - { - // c == 16..18 - i = c == 18 ? 7 : c - 14; - j = c == 18 ? 11 : 3; - - while (k < (t + i)) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - - n--; - b |= (_codec.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - b >>= t; - k -= t; - - j += (b & InternalInflateConstants.InflateMask[i]); - - b >>= i; - k -= i; - - i = index; - t = table; - if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) - { - blens = null; - mode = InflateBlockMode.BAD; - _codec.Message = "invalid bit length repeat"; - r = ZlibConstants.Z_DATA_ERROR; - - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - - c = (c == 16) ? blens[i - 1] : 0; - do - { - blens[i++] = c; - } - while (--j != 0); - index = i; - } + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); } - - tb[0] = -1; - { - var bl = new[] { 9 }; // must be <= 9 for lookahead assumptions - var bd = new[] { 6 }; // must be <= 9 for lookahead assumptions - var tl = new int[1]; - var td = new int[1]; - - t = table; - t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), blens, bl, bd, tl, - td, hufts, _codec); - - if (t != ZlibConstants.Z_OK) - { - if (t == ZlibConstants.Z_DATA_ERROR) - { - blens = null; - mode = InflateBlockMode.BAD; - } - r = t; - - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - codes.Init(bl[0], bd[0], hufts, tl[0], hufts, td[0]); - } - mode = InflateBlockMode.CODES; - goto case InflateBlockMode.CODES; - - case InflateBlockMode.CODES: - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - - r = codes.Process(this, r); - if (r != ZlibConstants.Z_STREAM_END) - { - return Flush(r); - } - - r = ZlibConstants.Z_OK; - p = _codec.NextIn; - n = _codec.AvailableBytesIn; - b = bitb; - k = bitk; - q = writeAt; - m = (q < readAt ? readAt - q - 1 : end - q); - - if (last == 0) - { - mode = InflateBlockMode.TYPE; - break; - } - mode = InflateBlockMode.DRY; - goto case InflateBlockMode.DRY; - - case InflateBlockMode.DRY: - writeAt = q; - r = Flush(r); - q = writeAt; - m = (q < readAt ? readAt - q - 1 : end - q); - if (readAt != writeAt) - { - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - mode = InflateBlockMode.DONE; - goto case InflateBlockMode.DONE; - - case InflateBlockMode.DONE: - r = ZlibConstants.Z_STREAM_END; - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - - case InflateBlockMode.BAD: - r = ZlibConstants.Z_DATA_ERROR; - - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - - default: - r = ZlibConstants.Z_STREAM_ERROR; - - bitb = b; - bitk = k; - _codec.AvailableBytesIn = n; - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - writeAt = q; - return Flush(r); - } - } - } - - internal void Free() - { - Reset(); - window = null; - hufts = null; - } - - internal void SetDictionary(byte[] d, int start, int n) - { - Array.Copy(d, start, window, 0, n); - readAt = writeAt = n; - } - - // Returns true if inflate is currently at the end of a block generated - // by Z_SYNC_FLUSH or Z_FULL_FLUSH. - internal int SyncPoint() - { - return mode == InflateBlockMode.LENS ? 1 : 0; - } - - // copy as much as possible from the sliding window to the output area - internal int Flush(int r) - { - int nBytes; - - for (int pass = 0; pass < 2; pass++) - { - if (pass == 0) - { - // compute number of bytes to copy as far as end of window - nBytes = ((readAt <= writeAt ? writeAt : end) - readAt); - } - else - { - // compute bytes to copy - nBytes = writeAt - readAt; - } - - // workitem 8870 - if (nBytes == 0) - { - if (r == ZlibConstants.Z_BUF_ERROR) - { - r = ZlibConstants.Z_OK; + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; } - return r; - } - if (nBytes > _codec.AvailableBytesOut) - { - nBytes = _codec.AvailableBytesOut; - } + tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; - if (nBytes != 0 && r == ZlibConstants.Z_BUF_ERROR) - { - r = ZlibConstants.Z_OK; - } + b >>= (tree[tindex + 1]); + k -= (tree[tindex + 1]); - // update counters - _codec.AvailableBytesOut -= nBytes; - _codec.TotalBytesOut += nBytes; + e = tree[tindex]; - // update check information - if (checkfn != null) - { - _codec._adler32 = check = Adler32.Calculate(check, window.AsSpan(readAt, nBytes)); - } - - // copy as far as end of window - Array.Copy(window, readAt, _codec.OutputBuffer, _codec.NextOut, nBytes); - _codec.NextOut += nBytes; - readAt += nBytes; - - // see if more to copy at beginning of window - if (readAt == end && pass == 0) - { - // wrap pointers - readAt = 0; - if (writeAt == end) + if (e == 0) { - writeAt = 0; + // literal + lit = tree[tindex + 2]; + mode = LIT; + break; } - } - else - { - pass++; - } - } + if ((e & 16) != 0) + { + // length + bitsToGet = e & 15; + len = tree[tindex + 2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0) + { + // next table + need = e; + tree_index = (tindex / 3) + tree[tindex + 2]; + break; + } + if ((e & 32) != 0) + { + // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.Message = "invalid literal/length code"; + r = ZlibConstants.Z_DATA_ERROR; - // done - return r; - } + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); - #region Nested type: InflateBlockMode + case LENEXT: // i: getting length extra (have base) + j = bitsToGet; - private enum InflateBlockMode - { - TYPE = 0, // get type bits (3, including end bit) - LENS = 1, // get lengths for stored - STORED = 2, // processing stored block - TABLE = 3, // get table lengths - BTREE = 4, // get bit lengths tree for a dynamic block - DTREE = 5, // get length, distance trees for a dynamic block - CODES = 6, // processing fixed or dynamic block - DRY = 7, // output remaining window bytes - DONE = 8, // finished last block, done - BAD = 9 // ot a data error--stuck here - } - - #endregion - } - - internal static class InternalInflateConstants - { - // And'ing with mask[n] masks the lower n bits - internal static readonly int[] InflateMask = - { - 0x00000000, 0x00000001, 0x00000003, 0x00000007, - 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, - 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, - 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff - }; - } - - internal sealed class InflateCodes - { - // waiting for "i:"=input, - // "o:"=output, - // "x:"=nothing - private const int START = 0; // x: set up for LEN - private const int LEN = 1; // i: get length/literal/eob next - private const int LENEXT = 2; // i: getting length extra (have base) - private const int DIST = 3; // i: get distance next - private const int DISTEXT = 4; // i: getting distance extra - private const int COPY = 5; // o: copying bytes in window, waiting for space - private const int LIT = 6; // o: got literal, waiting for output space - private const int WASH = 7; // o: got eob, possibly still output waiting - private const int END = 8; // x: got eob and all data flushed - private const int BADCODE = 9; // x: got error - - // if EXT or COPY, where and how much - internal int bitsToGet; // bits to get for extra - internal byte dbits; // dtree bits decoder per branch - internal int dist; // distance back to copy from - internal int[] dtree; // distance tree - internal int dtree_index; // distance tree - - internal byte lbits; // ltree bits decoded per branch - internal int len; - internal int lit; - internal int[] ltree; // literal/length/eob tree - internal int ltree_index; // literal/length/eob tree - internal int mode; // current inflate_codes mode - internal int need; // bits needed - internal int[] tree; // pointer into tree - internal int tree_index; - - internal void Init(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index) - { - mode = START; - lbits = (byte)bl; - dbits = (byte)bd; - ltree = tl; - ltree_index = tl_index; - dtree = td; - dtree_index = td_index; - tree = null; - } - - internal int Process(InflateBlocks blocks, int r) - { - int j; // temporary storage - int tindex; // temporary pointer - int e; // extra bits or operation - int b = 0; // bit buffer - int k = 0; // bits in bit buffer - int p = 0; // input data pointer - int n; // bytes available there - int q; // output window write pointer - int m; // bytes to end of window or read pointer - int f; // pointer to copy strings from - - ZlibCodec z = blocks._codec; - - // copy input/output information to locals (UPDATE macro restores) - p = z.NextIn; - n = z.AvailableBytesIn; - b = blocks.bitb; - k = blocks.bitk; - q = blocks.writeAt; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - - // process input and output based on current state - while (true) - { - switch (mode) - { - // waiting for "i:"=input, "o:"=output, "x:"=nothing - case START: // x: set up for LEN - if (m >= 258 && n >= 10) + while (k < j) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else { blocks.bitb = b; blocks.bitk = k; @@ -883,292 +1057,130 @@ namespace SharpCompress.Compressors.Deflate z.TotalBytesIn += p - z.NextIn; z.NextIn = p; blocks.writeAt = q; - r = InflateFast(lbits, dbits, ltree, ltree_index, dtree, dtree_index, blocks, z); + return blocks.Flush(r); + } + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } - p = z.NextIn; - n = z.AvailableBytesIn; - b = blocks.bitb; - k = blocks.bitk; - q = blocks.writeAt; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; + len += (b & InternalInflateConstants.InflateMask[j]); - if (r != ZlibConstants.Z_OK) + b >>= j; + k -= j; + + need = dbits; + tree = dtree; + tree_index = dtree_index; + mode = DIST; + goto case DIST; + + case DIST: // i: get distance next + j = need; + + while (k < j) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + // Handling missing trailing bit(s) + var tmp_tindex = + (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3; + if (k >= tree[tmp_tindex + 1]) { - mode = (r == ZlibConstants.Z_STREAM_END) ? WASH : BADCODE; break; } + + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); } - need = lbits; - tree = ltree; - tree_index = ltree_index; + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } - mode = LEN; - goto case LEN; + tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; - case LEN: // i: get length/literal/eob next - j = need; + b >>= tree[tindex + 1]; + k -= tree[tindex + 1]; - while (k < j) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - // Handling missing trailing bit(s) - var tmp_tindex = (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3; - if (k >= tree[tmp_tindex + 1]) - { - break; - } - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; - - b >>= (tree[tindex + 1]); - k -= (tree[tindex + 1]); - - e = tree[tindex]; - - if (e == 0) - { - // literal - lit = tree[tindex + 2]; - mode = LIT; - break; - } - if ((e & 16) != 0) - { - // length - bitsToGet = e & 15; - len = tree[tindex + 2]; - mode = LENEXT; - break; - } - if ((e & 64) == 0) - { - // next table - need = e; - tree_index = tindex / 3 + tree[tindex + 2]; - break; - } - if ((e & 32) != 0) - { - // end of block - mode = WASH; - break; - } - mode = BADCODE; // invalid code - z.Message = "invalid literal/length code"; - r = ZlibConstants.Z_DATA_ERROR; - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - - case LENEXT: // i: getting length extra (have base) - j = bitsToGet; - - while (k < j) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - len += (b & InternalInflateConstants.InflateMask[j]); - - b >>= j; - k -= j; - - need = dbits; - tree = dtree; - tree_index = dtree_index; - mode = DIST; - goto case DIST; - - case DIST: // i: get distance next - j = need; - - while (k < j) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - // Handling missing trailing bit(s) - var tmp_tindex = (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3; - if (k >= tree[tmp_tindex + 1]) - { - break; - } - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3; - - b >>= tree[tindex + 1]; - k -= tree[tindex + 1]; - - e = (tree[tindex]); - if ((e & 0x10) != 0) - { - // distance - bitsToGet = e & 15; - dist = tree[tindex + 2]; - mode = DISTEXT; - break; - } - if ((e & 64) == 0) - { - // next table - need = e; - tree_index = tindex / 3 + tree[tindex + 2]; - break; - } - mode = BADCODE; // invalid code - z.Message = "invalid distance code"; - r = ZlibConstants.Z_DATA_ERROR; - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - - case DISTEXT: // i: getting distance extra - j = bitsToGet; - - while (k < j) - { - if (n != 0) - { - r = ZlibConstants.Z_OK; - } - else - { - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - dist += (b & InternalInflateConstants.InflateMask[j]); - - b >>= j; - k -= j; - - mode = COPY; - goto case COPY; - - case COPY: // o: copying bytes in window, waiting for space - f = q - dist; - while (f < 0) - { - // modulo window size-"while" instead - f += blocks.end; // of "if" handles invalid distances - } - while (len != 0) - { - if (m == 0) - { - if (q == blocks.end && blocks.readAt != 0) - { - q = 0; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - } - if (m == 0) - { - blocks.writeAt = q; - r = blocks.Flush(r); - q = blocks.writeAt; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - - if (q == blocks.end && blocks.readAt != 0) - { - q = 0; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - } - - if (m == 0) - { - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - } - } - - blocks.window[q++] = blocks.window[f++]; - m--; - - if (f == blocks.end) - { - f = 0; - } - len--; - } - mode = START; + e = (tree[tindex]); + if ((e & 0x10) != 0) + { + // distance + bitsToGet = e & 15; + dist = tree[tindex + 2]; + mode = DISTEXT; break; + } + if ((e & 64) == 0) + { + // next table + need = e; + tree_index = (tindex / 3) + tree[tindex + 2]; + break; + } + mode = BADCODE; // invalid code + z.Message = "invalid distance code"; + r = ZlibConstants.Z_DATA_ERROR; - case LIT: // o: got literal, waiting for output space + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + case DISTEXT: // i: getting distance extra + j = bitsToGet; + + while (k < j) + { + if (n != 0) + { + r = ZlibConstants.Z_OK; + } + else + { + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + } + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + dist += (b & InternalInflateConstants.InflateMask[j]); + + b >>= j; + k -= j; + + mode = COPY; + goto case COPY; + + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while (f < 0) + { + // modulo window size-"while" instead + f += blocks.end; // of "if" handles invalid distances + } + while (len != 0) + { if (m == 0) { if (q == blocks.end && blocks.readAt != 0) @@ -1188,6 +1200,7 @@ namespace SharpCompress.Compressors.Deflate q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; } + if (m == 0) { blocks.bitb = b; @@ -1200,794 +1213,850 @@ namespace SharpCompress.Compressors.Deflate } } } - r = ZlibConstants.Z_OK; - blocks.window[q++] = (byte)lit; + blocks.window[q++] = blocks.window[f++]; m--; - mode = START; - break; - - case WASH: // o: got eob, possibly more output - if (k > 7) + if (f == blocks.end) { - // return unused byte, if any - k -= 8; - n++; - p--; // can always return one + f = 0; } + len--; + } + mode = START; + break; - blocks.writeAt = q; - r = blocks.Flush(r); - q = blocks.writeAt; - m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - - if (blocks.readAt != blocks.writeAt) - { - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - mode = END; - goto case END; - - case END: - r = ZlibConstants.Z_STREAM_END; - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - - case BADCODE: // x: got error - - r = ZlibConstants.Z_DATA_ERROR; - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - - default: - r = ZlibConstants.Z_STREAM_ERROR; - - blocks.bitb = b; - blocks.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - blocks.writeAt = q; - return blocks.Flush(r); - } - } - } - - // Called with number of bytes left to write in window at least 258 - // (the maximum string length) and number of input bytes available - // at least ten. The ten bytes are six bytes for the longest length/ - // distance pair plus four bytes for overloading the bit buffer. - - internal int InflateFast(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index, InflateBlocks s, - ZlibCodec z) - { - int t; // temporary pointer - int[] tp; // temporary pointer - int tp_index; // temporary pointer - int e; // extra bits or operation - int b; // bit buffer - int k; // bits in bit buffer - int p; // input data pointer - int n; // bytes available there - int q; // output window write pointer - int m; // bytes to end of window or read pointer - int ml; // mask for literal/length tree - int md; // mask for distance tree - int c; // bytes to copy - int d; // distance back to copy from - int r; // copy source pointer - - int tp_index_t_3; // (tp_index+t)*3 - - // load input, output, bit values - p = z.NextIn; - n = z.AvailableBytesIn; - b = s.bitb; - k = s.bitk; - q = s.writeAt; - m = q < s.readAt ? s.readAt - q - 1 : s.end - q; - - // initialize masks - ml = InternalInflateConstants.InflateMask[bl]; - md = InternalInflateConstants.InflateMask[bd]; - - // do until not enough input or output space for fast loop - do - { - // assume called with m >= 258 && n >= 10 - // get literal/length code - while (k < (20)) - { - // max bits for literal/length code - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - t = b & ml; - tp = tl; - tp_index = tl_index; - tp_index_t_3 = (tp_index + t) * 3; - if ((e = tp[tp_index_t_3]) == 0) - { - b >>= (tp[tp_index_t_3 + 1]); - k -= (tp[tp_index_t_3 + 1]); - - s.window[q++] = (byte)tp[tp_index_t_3 + 2]; - m--; - continue; - } - do - { - b >>= (tp[tp_index_t_3 + 1]); - k -= (tp[tp_index_t_3 + 1]); - - if ((e & 16) != 0) + case LIT: // o: got literal, waiting for output space + if (m == 0) { - e &= 15; - c = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]); - - b >>= e; - k -= e; - - // decode distance base of block to copy - while (k < 15) + if (q == blocks.end && blocks.readAt != 0) { - // max bits for distance code - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; + q = 0; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; } - - t = b & md; - tp = td; - tp_index = td_index; - tp_index_t_3 = (tp_index + t) * 3; - e = tp[tp_index_t_3]; - - do + if (m == 0) { - b >>= (tp[tp_index_t_3 + 1]); - k -= (tp[tp_index_t_3 + 1]); + blocks.writeAt = q; + r = blocks.Flush(r); + q = blocks.writeAt; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - if ((e & 16) != 0) + if (q == blocks.end && blocks.readAt != 0) { - // get extra bits to add to distance base - e &= 15; - while (k < e) - { - // get extra bits (up to 13) - n--; - b |= (z.InputBuffer[p++] & 0xff) << k; - k += 8; - } - - d = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]); - - b >>= e; - k -= e; - - // do the copy - m -= c; - if (q >= d) - { - // offset before dest - // just copy - r = q - d; - if (q - r > 0 && 2 > (q - r)) - { - s.window[q++] = s.window[r++]; // minimum count is three, - s.window[q++] = s.window[r++]; // so unroll loop a little - c -= 2; - } - else - { - Array.Copy(s.window, r, s.window, q, 2); - q += 2; - r += 2; - c -= 2; - } - } - else - { - // else offset after destination - r = q - d; - do - { - r += s.end; // force pointer in window - } - while (r < 0); // covers invalid distances - e = s.end - r; - if (c > e) - { - // if source crosses, - c -= e; // wrapped copy - if (q - r > 0 && e > (q - r)) - { - do - { - s.window[q++] = s.window[r++]; - } - while (--e != 0); - } - else - { - Array.Copy(s.window, r, s.window, q, e); - q += e; - r += e; - e = 0; - } - r = 0; // copy rest from start of window - } - } - - // copy all or what's left - if (q - r > 0 && c > (q - r)) - { - do - { - s.window[q++] = s.window[r++]; - } - while (--c != 0); - } - else - { - Array.Copy(s.window, r, s.window, q, c); - q += c; - r += c; - c = 0; - } - break; + q = 0; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; } - if ((e & 64) == 0) + if (m == 0) { - t += tp[tp_index_t_3 + 2]; - t += (b & InternalInflateConstants.InflateMask[e]); - tp_index_t_3 = (tp_index + t) * 3; - e = tp[tp_index_t_3]; - } - else - { - z.Message = "invalid distance code"; - - c = z.AvailableBytesIn - n; - c = (k >> 3) < c ? k >> 3 : c; - n += c; - p -= c; - k -= (c << 3); - - s.bitb = b; - s.bitk = k; + blocks.bitb = b; + blocks.bitk = k; z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; - s.writeAt = q; - - return ZlibConstants.Z_DATA_ERROR; + blocks.writeAt = q; + return blocks.Flush(r); } } - while (true); - break; + } + r = ZlibConstants.Z_OK; + + blocks.window[q++] = (byte)lit; + m--; + + mode = START; + break; + + case WASH: // o: got eob, possibly more output + if (k > 7) + { + // return unused byte, if any + k -= 8; + n++; + p--; // can always return one } - if ((e & 64) == 0) - { - t += tp[tp_index_t_3 + 2]; - t += (b & InternalInflateConstants.InflateMask[e]); - tp_index_t_3 = (tp_index + t) * 3; - if ((e = tp[tp_index_t_3]) == 0) - { - b >>= (tp[tp_index_t_3 + 1]); - k -= (tp[tp_index_t_3 + 1]); - s.window[q++] = (byte)tp[tp_index_t_3 + 2]; - m--; - break; - } - } - else if ((e & 32) != 0) - { - c = z.AvailableBytesIn - n; - c = (k >> 3) < c ? k >> 3 : c; - n += c; - p -= c; - k -= (c << 3); + blocks.writeAt = q; + r = blocks.Flush(r); + q = blocks.writeAt; + m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q; - s.bitb = b; - s.bitk = k; + if (blocks.readAt != blocks.writeAt) + { + blocks.bitb = b; + blocks.bitk = k; z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p; - s.writeAt = q; - - return ZlibConstants.Z_STREAM_END; + blocks.writeAt = q; + return blocks.Flush(r); } - else - { - z.Message = "invalid literal/length code"; + mode = END; + goto case END; - c = z.AvailableBytesIn - n; - c = (k >> 3) < c ? k >> 3 : c; - n += c; - p -= c; - k -= (c << 3); + case END: + r = ZlibConstants.Z_STREAM_END; + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); - s.bitb = b; - s.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - s.writeAt = q; + case BADCODE: // x: got error - return ZlibConstants.Z_DATA_ERROR; - } - } - while (true); + r = ZlibConstants.Z_DATA_ERROR; + + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); + + default: + r = ZlibConstants.Z_STREAM_ERROR; + + blocks.bitb = b; + blocks.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + blocks.writeAt = q; + return blocks.Flush(r); } - while (m >= 258 && n >= 10); - - // not enough input or output--restore pointers and return - c = z.AvailableBytesIn - n; - c = (k >> 3) < c ? k >> 3 : c; - n += c; - p -= c; - k -= (c << 3); - - s.bitb = b; - s.bitk = k; - z.AvailableBytesIn = n; - z.TotalBytesIn += p - z.NextIn; - z.NextIn = p; - s.writeAt = q; - - return ZlibConstants.Z_OK; } } - internal sealed class InflateManager + // Called with number of bytes left to write in window at least 258 + // (the maximum string length) and number of input bytes available + // at least ten. The ten bytes are six bytes for the longest length/ + // distance pair plus four bytes for overloading the bit buffer. + + internal int InflateFast( + int bl, + int bd, + int[] tl, + int tl_index, + int[] td, + int td_index, + InflateBlocks s, + ZlibCodec z + ) { - // preset dictionary flag in zlib header - private const int PRESET_DICT = 0x20; + int t; // temporary pointer + int[] tp; // temporary pointer + int tp_index; // temporary pointer + int e; // extra bits or operation + int b; // bit buffer + int k; // bits in bit buffer + int p; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int ml; // mask for literal/length tree + int md; // mask for distance tree + int c; // bytes to copy + int d; // distance back to copy from + int r; // copy source pointer - private const int Z_DEFLATED = 8; - private static readonly byte[] mark = { 0, 0, 0xff, 0xff }; + int tp_index_t_3; // (tp_index+t)*3 - internal ZlibCodec _codec; // pointer back to this zlib stream - internal InflateBlocks blocks; // current inflate_blocks state + // load input, output, bit values + p = z.NextIn; + n = z.AvailableBytesIn; + b = s.bitb; + k = s.bitk; + q = s.writeAt; + m = q < s.readAt ? s.readAt - q - 1 : s.end - q; - // mode dependent information + // initialize masks + ml = InternalInflateConstants.InflateMask[bl]; + md = InternalInflateConstants.InflateMask[bd]; - // if CHECK, check values to compare - internal uint computedCheck; // computed check value - internal uint expectedCheck; // stream check value - - // if BAD, inflateSync's marker bytes count - internal int marker; - internal int method; // if FLAGS, method byte - private InflateManagerMode mode; // current inflate mode - - // mode independent information - //internal int nowrap; // flag for no wrapper - - internal int wbits; // log2(window size) (8..15, defaults to 15) - - public InflateManager() + // do until not enough input or output space for fast loop + do { - } - - public InflateManager(bool expectRfc1950HeaderBytes) - { - HandleRfc1950HeaderBytes = expectRfc1950HeaderBytes; - } - - internal bool HandleRfc1950HeaderBytes { get; set; } = true; - - internal int Reset() - { - _codec.TotalBytesIn = _codec.TotalBytesOut = 0; - _codec.Message = null; - mode = HandleRfc1950HeaderBytes ? InflateManagerMode.METHOD : InflateManagerMode.BLOCKS; - blocks.Reset(); - return ZlibConstants.Z_OK; - } - - internal int End() - { - if (blocks != null) + // assume called with m >= 258 && n >= 10 + // get literal/length code + while (k < (20)) { - blocks.Free(); - } - blocks = null; - return ZlibConstants.Z_OK; - } - - internal int Initialize(ZlibCodec codec, int w) - { - _codec = codec; - _codec.Message = null; - blocks = null; - - // handle undocumented nowrap option (no zlib header or check) - //nowrap = 0; - //if (w < 0) - //{ - // w = - w; - // nowrap = 1; - //} - - // set window size - if (w < 8 || w > 15) - { - End(); - throw new ZlibException("Bad window size."); - - //return ZlibConstants.Z_STREAM_ERROR; - } - wbits = w; - - blocks = new InflateBlocks(codec, - HandleRfc1950HeaderBytes ? this : null, - 1 << w); - - // reset state - Reset(); - return ZlibConstants.Z_OK; - } - - internal int Inflate(FlushType flush) - { - int b; - - if (_codec.InputBuffer is null) - { - throw new ZlibException("InputBuffer is null. "); + // max bits for literal/length code + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; } - // int f = (flush == FlushType.Finish) - // ? ZlibConstants.Z_BUF_ERROR - // : ZlibConstants.Z_OK; - - // workitem 8870 - int f = ZlibConstants.Z_OK; - int r = ZlibConstants.Z_BUF_ERROR; - - while (true) + t = b & ml; + tp = tl; + tp_index = tl_index; + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) == 0) { - switch (mode) + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + s.window[q++] = (byte)tp[tp_index_t_3 + 2]; + m--; + continue; + } + do + { + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) != 0) { - case InflateManagerMode.METHOD: - if (_codec.AvailableBytesIn == 0) + e &= 15; + c = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]); + + b >>= e; + k -= e; + + // decode distance base of block to copy + while (k < 15) + { + // max bits for distance code + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + t = b & md; + tp = td; + tp_index = td_index; + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; + + do + { + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + + if ((e & 16) != 0) { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - if (((method = _codec.InputBuffer[_codec.NextIn++]) & 0xf) != Z_DEFLATED) - { - mode = InflateManagerMode.BAD; - _codec.Message = String.Format("unknown compression method (0x{0:X2})", method); - marker = 5; // can't try inflateSync + // get extra bits to add to distance base + e &= 15; + while (k < e) + { + // get extra bits (up to 13) + n--; + b |= (z.InputBuffer[p++] & 0xff) << k; + k += 8; + } + + d = + tp[tp_index_t_3 + 2] + + (b & InternalInflateConstants.InflateMask[e]); + + b >>= e; + k -= e; + + // do the copy + m -= c; + if (q >= d) + { + // offset before dest + // just copy + r = q - d; + if (q - r > 0 && 2 > (q - r)) + { + s.window[q++] = s.window[r++]; // minimum count is three, + s.window[q++] = s.window[r++]; // so unroll loop a little + c -= 2; + } + else + { + Array.Copy(s.window, r, s.window, q, 2); + q += 2; + r += 2; + c -= 2; + } + } + else + { + // else offset after destination + r = q - d; + do + { + r += s.end; // force pointer in window + } while (r < 0); // covers invalid distances + e = s.end - r; + if (c > e) + { + // if source crosses, + c -= e; // wrapped copy + if (q - r > 0 && e > (q - r)) + { + do + { + s.window[q++] = s.window[r++]; + } while (--e != 0); + } + else + { + Array.Copy(s.window, r, s.window, q, e); + q += e; + r += e; + e = 0; + } + r = 0; // copy rest from start of window + } + } + + // copy all or what's left + if (q - r > 0 && c > (q - r)) + { + do + { + s.window[q++] = s.window[r++]; + } while (--c != 0); + } + else + { + Array.Copy(s.window, r, s.window, q, c); + q += c; + r += c; + c = 0; + } break; } - if ((method >> 4) + 8 > wbits) + if ((e & 64) == 0) { - mode = InflateManagerMode.BAD; - _codec.Message = String.Format("invalid window size ({0})", (method >> 4) + 8); - marker = 5; // can't try inflateSync - break; + t += tp[tp_index_t_3 + 2]; + t += (b & InternalInflateConstants.InflateMask[e]); + tp_index_t_3 = (tp_index + t) * 3; + e = tp[tp_index_t_3]; } - mode = InflateManagerMode.FLAG; - break; - - case InflateManagerMode.FLAG: - if (_codec.AvailableBytesIn == 0) + else { - return r; + z.Message = "invalid distance code"; + + c = z.AvailableBytesIn - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= (c << 3); + + s.bitb = b; + s.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_DATA_ERROR; } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - b = (_codec.InputBuffer[_codec.NextIn++]) & 0xff; - - if ((((method << 8) + b) % 31) != 0) - { - mode = InflateManagerMode.BAD; - _codec.Message = "incorrect header check"; - marker = 5; // can't try inflateSync - break; - } - - mode = ((b & PRESET_DICT) == 0) - ? InflateManagerMode.BLOCKS - : InflateManagerMode.DICT4; - break; - - case InflateManagerMode.DICT4: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000); - mode = InflateManagerMode.DICT3; - break; - - case InflateManagerMode.DICT3: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000); - mode = InflateManagerMode.DICT2; - break; - - case InflateManagerMode.DICT2: - - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00); - mode = InflateManagerMode.DICT1; - break; - - case InflateManagerMode.DICT1: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); - _codec._adler32 = expectedCheck; - mode = InflateManagerMode.DICT0; - return ZlibConstants.Z_NEED_DICT; - - case InflateManagerMode.DICT0: - mode = InflateManagerMode.BAD; - _codec.Message = "need dictionary"; - marker = 0; // can try inflateSync - return ZlibConstants.Z_STREAM_ERROR; - - case InflateManagerMode.BLOCKS: - r = blocks.Process(r); - if (r == ZlibConstants.Z_DATA_ERROR) - { - mode = InflateManagerMode.BAD; - marker = 0; // can try inflateSync - break; - } - - if (r == ZlibConstants.Z_OK) - { - r = f; - } - - if (r != ZlibConstants.Z_STREAM_END) - { - return r; - } - - r = f; - computedCheck = blocks.Reset(); - if (!HandleRfc1950HeaderBytes) - { - mode = InflateManagerMode.DONE; - return ZlibConstants.Z_STREAM_END; - } - mode = InflateManagerMode.CHECK4; - break; - - case InflateManagerMode.CHECK4: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000); - mode = InflateManagerMode.CHECK3; - break; - - case InflateManagerMode.CHECK3: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000); - mode = InflateManagerMode.CHECK2; - break; - - case InflateManagerMode.CHECK2: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00); - mode = InflateManagerMode.CHECK1; - break; - - case InflateManagerMode.CHECK1: - if (_codec.AvailableBytesIn == 0) - { - return r; - } - r = f; - _codec.AvailableBytesIn--; - _codec.TotalBytesIn++; - expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); - if (computedCheck != expectedCheck) - { - mode = InflateManagerMode.BAD; - _codec.Message = "incorrect data check"; - marker = 5; // can't try inflateSync - break; - } - mode = InflateManagerMode.DONE; - return ZlibConstants.Z_STREAM_END; - - case InflateManagerMode.DONE: - return ZlibConstants.Z_STREAM_END; - - case InflateManagerMode.BAD: - throw new ZlibException(String.Format("Bad state ({0})", _codec.Message)); - - default: - throw new ZlibException("Stream error."); + } while (true); + break; } - } - } - internal int SetDictionary(byte[] dictionary) - { - int index = 0; - int length = dictionary.Length; - if (mode != InflateManagerMode.DICT0) - { - throw new ZlibException("Stream error."); - } - - if (Adler32.Calculate(1, dictionary) != _codec._adler32) - { - return ZlibConstants.Z_DATA_ERROR; - } - - _codec._adler32 = 1; - - if (length >= (1 << wbits)) - { - length = (1 << wbits) - 1; - index = dictionary.Length - length; - } - blocks.SetDictionary(dictionary, index, length); - mode = InflateManagerMode.BLOCKS; - return ZlibConstants.Z_OK; - } - - internal int Sync() - { - int n; // number of bytes to look at - int p; // pointer to bytes - int m; // number of marker bytes found in a row - long r, w; // temporaries to save total_in and total_out - - // set up - if (mode != InflateManagerMode.BAD) - { - mode = InflateManagerMode.BAD; - marker = 0; - } - if ((n = _codec.AvailableBytesIn) == 0) - { - return ZlibConstants.Z_BUF_ERROR; - } - p = _codec.NextIn; - m = marker; - - // search - while (n != 0 && m < 4) - { - if (_codec.InputBuffer[p] == mark[m]) + if ((e & 64) == 0) { - m++; + t += tp[tp_index_t_3 + 2]; + t += (b & InternalInflateConstants.InflateMask[e]); + tp_index_t_3 = (tp_index + t) * 3; + if ((e = tp[tp_index_t_3]) == 0) + { + b >>= (tp[tp_index_t_3 + 1]); + k -= (tp[tp_index_t_3 + 1]); + s.window[q++] = (byte)tp[tp_index_t_3 + 2]; + m--; + break; + } } - else if (_codec.InputBuffer[p] != 0) + else if ((e & 32) != 0) { - m = 0; + c = z.AvailableBytesIn - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= (c << 3); + + s.bitb = b; + s.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_STREAM_END; } else { - m = 4 - m; + z.Message = "invalid literal/length code"; + + c = z.AvailableBytesIn - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= (c << 3); + + s.bitb = b; + s.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + s.writeAt = q; + + return ZlibConstants.Z_DATA_ERROR; } - p++; - n--; - } + } while (true); + } while (m >= 258 && n >= 10); - // restore - _codec.TotalBytesIn += p - _codec.NextIn; - _codec.NextIn = p; - _codec.AvailableBytesIn = n; - marker = m; + // not enough input or output--restore pointers and return + c = z.AvailableBytesIn - n; + c = (k >> 3) < c ? k >> 3 : c; + n += c; + p -= c; + k -= (c << 3); - // return no joy or set up to restart on a new block - if (m != 4) - { - return ZlibConstants.Z_DATA_ERROR; - } - r = _codec.TotalBytesIn; - w = _codec.TotalBytesOut; - Reset(); - _codec.TotalBytesIn = r; - _codec.TotalBytesOut = w; - mode = InflateManagerMode.BLOCKS; - return ZlibConstants.Z_OK; - } + s.bitb = b; + s.bitk = k; + z.AvailableBytesIn = n; + z.TotalBytesIn += p - z.NextIn; + z.NextIn = p; + s.writeAt = q; - // Returns true if inflate is currently at the end of a block generated - // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP - // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH - // but removes the length bytes of the resulting empty stored block. When - // decompressing, PPP checks that at the end of input packet, inflate is - // waiting for these length bytes. - internal int SyncPoint(ZlibCodec z) - { - return blocks.SyncPoint(); - } - - #region Nested type: InflateManagerMode - - private enum InflateManagerMode - { - METHOD = 0, // waiting for method byte - FLAG = 1, // waiting for flag byte - DICT4 = 2, // four dictionary check bytes to go - DICT3 = 3, // three dictionary check bytes to go - DICT2 = 4, // two dictionary check bytes to go - DICT1 = 5, // one dictionary check byte to go - DICT0 = 6, // waiting for inflateSetDictionary - BLOCKS = 7, // decompressing blocks - CHECK4 = 8, // four check bytes to go - CHECK3 = 9, // three check bytes to go - CHECK2 = 10, // two check bytes to go - CHECK1 = 11, // one check byte to go - DONE = 12, // finished check, done - BAD = 13 // got an error--stay here - } - - #endregion + return ZlibConstants.Z_OK; } -} \ No newline at end of file +} + +internal sealed class InflateManager +{ + // preset dictionary flag in zlib header + private const int PRESET_DICT = 0x20; + + private const int Z_DEFLATED = 8; + private static readonly byte[] mark = { 0, 0, 0xff, 0xff }; + + internal ZlibCodec _codec; // pointer back to this zlib stream + internal InflateBlocks blocks; // current inflate_blocks state + + // mode dependent information + + // if CHECK, check values to compare + internal uint computedCheck; // computed check value + internal uint expectedCheck; // stream check value + + // if BAD, inflateSync's marker bytes count + internal int marker; + internal int method; // if FLAGS, method byte + private InflateManagerMode mode; // current inflate mode + + // mode independent information + //internal int nowrap; // flag for no wrapper + + internal int wbits; // log2(window size) (8..15, defaults to 15) + + public InflateManager() { } + + public InflateManager(bool expectRfc1950HeaderBytes) => + HandleRfc1950HeaderBytes = expectRfc1950HeaderBytes; + + internal bool HandleRfc1950HeaderBytes { get; set; } = true; + + internal int Reset() + { + _codec.TotalBytesIn = _codec.TotalBytesOut = 0; + _codec.Message = null; + mode = HandleRfc1950HeaderBytes ? InflateManagerMode.METHOD : InflateManagerMode.BLOCKS; + blocks.Reset(); + return ZlibConstants.Z_OK; + } + + internal int End() + { + blocks?.Free(); + blocks = null; + return ZlibConstants.Z_OK; + } + + internal int Initialize(ZlibCodec codec, int w) + { + _codec = codec; + _codec.Message = null; + blocks = null; + + // handle undocumented nowrap option (no zlib header or check) + //nowrap = 0; + //if (w < 0) + //{ + // w = - w; + // nowrap = 1; + //} + + // set window size + if (w < 8 || w > 15) + { + End(); + throw new ZlibException("Bad window size."); + + //return ZlibConstants.Z_STREAM_ERROR; + } + wbits = w; + + blocks = new InflateBlocks(codec, HandleRfc1950HeaderBytes ? this : null, 1 << w); + + // reset state + Reset(); + return ZlibConstants.Z_OK; + } + + internal int Inflate(FlushType flush) + { + int b; + + if (_codec.InputBuffer is null) + { + throw new ZlibException("InputBuffer is null. "); + } + + // int f = (flush == FlushType.Finish) + // ? ZlibConstants.Z_BUF_ERROR + // : ZlibConstants.Z_OK; + + // workitem 8870 + var f = ZlibConstants.Z_OK; + var r = ZlibConstants.Z_BUF_ERROR; + + while (true) + { + switch (mode) + { + case InflateManagerMode.METHOD: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + if (((method = _codec.InputBuffer[_codec.NextIn++]) & 0xf) != Z_DEFLATED) + { + mode = InflateManagerMode.BAD; + _codec.Message = string.Format( + "unknown compression method (0x{0:X2})", + method + ); + marker = 5; // can't try inflateSync + break; + } + if ((method >> 4) + 8 > wbits) + { + mode = InflateManagerMode.BAD; + _codec.Message = string.Format( + "invalid window size ({0})", + (method >> 4) + 8 + ); + marker = 5; // can't try inflateSync + break; + } + mode = InflateManagerMode.FLAG; + break; + + case InflateManagerMode.FLAG: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + b = (_codec.InputBuffer[_codec.NextIn++]) & 0xff; + + if ((((method << 8) + b) % 31) != 0) + { + mode = InflateManagerMode.BAD; + _codec.Message = "incorrect header check"; + marker = 5; // can't try inflateSync + break; + } + + mode = + ((b & PRESET_DICT) == 0) + ? InflateManagerMode.BLOCKS + : InflateManagerMode.DICT4; + break; + + case InflateManagerMode.DICT4: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck = (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000 + ); + mode = InflateManagerMode.DICT3; + break; + + case InflateManagerMode.DICT3: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000 + ); + mode = InflateManagerMode.DICT2; + break; + + case InflateManagerMode.DICT2: + + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00 + ); + mode = InflateManagerMode.DICT1; + break; + + case InflateManagerMode.DICT1: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); + _codec._adler32 = expectedCheck; + mode = InflateManagerMode.DICT0; + return ZlibConstants.Z_NEED_DICT; + + case InflateManagerMode.DICT0: + mode = InflateManagerMode.BAD; + _codec.Message = "need dictionary"; + marker = 0; // can try inflateSync + return ZlibConstants.Z_STREAM_ERROR; + + case InflateManagerMode.BLOCKS: + r = blocks.Process(r); + if (r == ZlibConstants.Z_DATA_ERROR) + { + mode = InflateManagerMode.BAD; + marker = 0; // can try inflateSync + break; + } + + if (r == ZlibConstants.Z_OK) + { + r = f; + } + + if (r != ZlibConstants.Z_STREAM_END) + { + return r; + } + + r = f; + computedCheck = blocks.Reset(); + if (!HandleRfc1950HeaderBytes) + { + mode = InflateManagerMode.DONE; + return ZlibConstants.Z_STREAM_END; + } + mode = InflateManagerMode.CHECK4; + break; + + case InflateManagerMode.CHECK4: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck = (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000 + ); + mode = InflateManagerMode.CHECK3; + break; + + case InflateManagerMode.CHECK3: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000 + ); + mode = InflateManagerMode.CHECK2; + break; + + case InflateManagerMode.CHECK2: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)( + (_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00 + ); + mode = InflateManagerMode.CHECK1; + break; + + case InflateManagerMode.CHECK1: + if (_codec.AvailableBytesIn == 0) + { + return r; + } + r = f; + _codec.AvailableBytesIn--; + _codec.TotalBytesIn++; + expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff); + if (computedCheck != expectedCheck) + { + mode = InflateManagerMode.BAD; + _codec.Message = "incorrect data check"; + marker = 5; // can't try inflateSync + break; + } + mode = InflateManagerMode.DONE; + return ZlibConstants.Z_STREAM_END; + + case InflateManagerMode.DONE: + return ZlibConstants.Z_STREAM_END; + + case InflateManagerMode.BAD: + throw new ZlibException(string.Format("Bad state ({0})", _codec.Message)); + + default: + throw new ZlibException("Stream error."); + } + } + } + + internal int SetDictionary(byte[] dictionary) + { + var index = 0; + var length = dictionary.Length; + if (mode != InflateManagerMode.DICT0) + { + throw new ZlibException("Stream error."); + } + + if (Adler32.Calculate(1, dictionary) != _codec._adler32) + { + return ZlibConstants.Z_DATA_ERROR; + } + + _codec._adler32 = 1; + + if (length >= (1 << wbits)) + { + length = (1 << wbits) - 1; + index = dictionary.Length - length; + } + blocks.SetDictionary(dictionary, index, length); + mode = InflateManagerMode.BLOCKS; + return ZlibConstants.Z_OK; + } + + internal int Sync() + { + int n; // number of bytes to look at + int p; // pointer to bytes + int m; // number of marker bytes found in a row + long r, + w; // temporaries to save total_in and total_out + + // set up + if (mode != InflateManagerMode.BAD) + { + mode = InflateManagerMode.BAD; + marker = 0; + } + if ((n = _codec.AvailableBytesIn) == 0) + { + return ZlibConstants.Z_BUF_ERROR; + } + p = _codec.NextIn; + m = marker; + + // search + while (n != 0 && m < 4) + { + if (_codec.InputBuffer[p] == mark[m]) + { + m++; + } + else if (_codec.InputBuffer[p] != 0) + { + m = 0; + } + else + { + m = 4 - m; + } + p++; + n--; + } + + // restore + _codec.TotalBytesIn += p - _codec.NextIn; + _codec.NextIn = p; + _codec.AvailableBytesIn = n; + marker = m; + + // return no joy or set up to restart on a new block + if (m != 4) + { + return ZlibConstants.Z_DATA_ERROR; + } + r = _codec.TotalBytesIn; + w = _codec.TotalBytesOut; + Reset(); + _codec.TotalBytesIn = r; + _codec.TotalBytesOut = w; + mode = InflateManagerMode.BLOCKS; + return ZlibConstants.Z_OK; + } + + // Returns true if inflate is currently at the end of a block generated + // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + // but removes the length bytes of the resulting empty stored block. When + // decompressing, PPP checks that at the end of input packet, inflate is + // waiting for these length bytes. + internal int SyncPoint(ZlibCodec z) => blocks.SyncPoint(); + + #region Nested type: InflateManagerMode + + private enum InflateManagerMode + { + METHOD = 0, // waiting for method byte + FLAG = 1, // waiting for flag byte + DICT4 = 2, // four dictionary check bytes to go + DICT3 = 3, // three dictionary check bytes to go + DICT2 = 4, // two dictionary check bytes to go + DICT1 = 5, // one dictionary check byte to go + DICT0 = 6, // waiting for inflateSetDictionary + BLOCKS = 7, // decompressing blocks + CHECK4 = 8, // four check bytes to go + CHECK3 = 9, // three check bytes to go + CHECK2 = 10, // two check bytes to go + CHECK1 = 11, // one check byte to go + DONE = 12, // finished check, done + BAD = 13 // got an error--stay here + } + + #endregion +} diff --git a/src/SharpCompress/Compressors/Deflate/Tree.cs b/src/SharpCompress/Compressors/Deflate/Tree.cs index 54c1620e..ec1eb51f 100644 --- a/src/SharpCompress/Compressors/Deflate/Tree.cs +++ b/src/SharpCompress/Compressors/Deflate/Tree.cs @@ -3,20 +3,20 @@ // Tree.cs // ------------------------------------------------------------------ // -// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // -// This code is licensed under the Microsoft Public License. +// This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // -// last saved (in emacs): +// last saved (in emacs): // Time-stamp: <2009-October-28 13:29:50> // // ------------------------------------------------------------------ @@ -27,22 +27,22 @@ // code is below. // // ------------------------------------------------------------------ -// +// // Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the distribution. -// +// // 3. The names of the authors may not be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, @@ -53,7 +53,7 @@ // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // ----------------------------------------------------------------------- // // This program is based on zlib-1.1.3; credit to authors @@ -64,426 +64,1166 @@ using System; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +internal sealed partial class DeflateManager { - internal sealed partial class DeflateManager + #region Nested type: Tree + + private sealed class Tree { - #region Nested type: Tree + internal const int Buf_size = 8 * 2; + private static readonly int HEAP_SIZE = ((2 * InternalConstants.L_CODES) + 1); - private sealed class Tree + internal static readonly sbyte[] bl_order = { - internal const int Buf_size = 8 * 2; - private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1); + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15 + }; - internal static readonly sbyte[] bl_order = + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit + // length codes. + + // see definition of array dist_code below + //internal const int DIST_CODE_LEN = 512; + + private static readonly sbyte[] _dist_code = + { + 0, + 1, + 2, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 0, + 0, + 16, + 17, + 18, + 18, + 19, + 19, + 20, + 20, + 20, + 20, + 21, + 21, + 21, + 21, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29, + 29 + }; + + internal static readonly sbyte[] LengthCode = + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 12, + 12, + 13, + 13, + 13, + 13, + 14, + 14, + 14, + 14, + 15, + 15, + 15, + 15, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 18, + 18, + 18, + 18, + 18, + 18, + 18, + 18, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 19, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 20, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 21, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 22, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 23, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 25, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 26, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 27, + 28 + }; + + internal static readonly int[] LengthBase = + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 12, + 14, + 16, + 20, + 24, + 28, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 160, + 192, + 224, + 0 + }; + + internal static readonly int[] DistanceBase = + { + 0, + 1, + 2, + 3, + 4, + 6, + 8, + 12, + 16, + 24, + 32, + 48, + 64, + 96, + 128, + 192, + 256, + 384, + 512, + 768, + 1024, + 1536, + 2048, + 3072, + 4096, + 6144, + 8192, + 12288, + 16384, + 24576 + }; + + internal short[] dyn_tree; // the dynamic tree + internal int max_code; // largest code with non zero frequency + internal StaticTree staticTree; // the corresponding static tree + + /// + /// Map from a distance to a distance code. + /// + /// + /// No side effects. _dist_code[256] and _dist_code[257] are never used. + /// + internal static int DistanceCode(int dist) => + (dist < 256) ? _dist_code[dist] : _dist_code[256 + SharedUtils.URShift(dist, 7)]; + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + internal void gen_bitlen(DeflateManager s) + { + var tree = dyn_tree; + var stree = staticTree.treeCodes; + var extra = staticTree.extraBits; + var base_Renamed = staticTree.extraBase; + var max_length = staticTree.maxLength; + int h; // heap index + int n, + m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + var overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++) { - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, - 14, - 1, 15 - }; - - // The lengths of the bit length codes are sent in order of decreasing - // probability, to avoid transmitting the lengths for unused bit - // length codes. - - // see definition of array dist_code below - //internal const int DIST_CODE_LEN = 512; - - private static readonly sbyte[] _dist_code = - { - 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 11, 11, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, - 0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, - 21, 21, - 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, - 23, 23, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29 - }; - - internal static readonly sbyte[] LengthCode = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, - 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15 - , 15, 15, - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17 - , 17, 17, - 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19 - , 19, 19, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 - , 20, 20, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21 - , 21, 21, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22 - , 22, 22, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23 - , 23, 23, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 - , 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 - , 24, 24, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25 - , 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25 - , 25, 25, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26 - , 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26 - , 26, 26, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 - , 27, 27, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 - , 27, 28 - }; - - internal static readonly int[] LengthBase = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, - 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0 - }; - - internal static readonly int[] DistanceBase = - { - 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, - 192, - 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144 - , 8192, 12288, 16384, 24576 - }; - - internal short[] dyn_tree; // the dynamic tree - internal int max_code; // largest code with non zero frequency - internal StaticTree staticTree; // the corresponding static tree - - /// - /// Map from a distance to a distance code. - /// - /// - /// No side effects. _dist_code[256] and _dist_code[257] are never used. - /// - internal static int DistanceCode(int dist) - { - return (dist < 256) - ? _dist_code[dist] - : _dist_code[256 + SharedUtils.URShift(dist, 7)]; + s.bl_count[bits] = 0; } - // Compute the optimal bit lengths for a tree and update the total bit length - // for the current block. - // IN assertion: the fields freq and dad are set, heap[heap_max] and - // above are the tree nodes sorted by increasing frequency. - // OUT assertions: the field len is set to the optimal bit length, the - // array bl_count contains the frequencies for each bit length. - // The length opt_len is updated; static_len is also updated if stree is - // not null. - internal void gen_bitlen(DeflateManager s) + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[(s.heap[s.heap_max] * 2) + 1] = 0; // root of the heap + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { - short[] tree = dyn_tree; - short[] stree = staticTree.treeCodes; - int[] extra = staticTree.extraBits; - int base_Renamed = staticTree.extraBase; - int max_length = staticTree.maxLength; - int h; // heap index - int n, m; // iterate over the tree elements - int bits; // bit length - int xbits; // extra bits - short f; // frequency - int overflow = 0; // number of elements with bit length too large - - for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++) + n = s.heap[h]; + bits = tree[(tree[(n * 2) + 1] * 2) + 1] + 1; + if (bits > max_length) { - s.bl_count[bits] = 0; + bits = max_length; + overflow++; + } + tree[(n * 2) + 1] = (short)bits; + + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) + { + continue; // not a leaf node } - // In a first pass, compute the optimal bit lengths (which may - // overflow in the case of the bit length tree). - tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap - - for (h = s.heap_max + 1; h < HEAP_SIZE; h++) + s.bl_count[bits]++; + xbits = 0; + if (n >= base_Renamed) { - n = s.heap[h]; - bits = tree[tree[n * 2 + 1] * 2 + 1] + 1; - if (bits > max_length) - { - bits = max_length; - overflow++; - } - tree[n * 2 + 1] = (short)bits; - - // We overwrite tree[n*2+1] which is no longer needed - - if (n > max_code) - { - continue; // not a leaf node - } - - s.bl_count[bits]++; - xbits = 0; - if (n >= base_Renamed) - { - xbits = extra[n - base_Renamed]; - } - f = tree[n * 2]; - s.opt_len += f * (bits + xbits); - if (stree != null) - { - s.static_len += f * (stree[n * 2 + 1] + xbits); - } + xbits = extra[n - base_Renamed]; } - if (overflow == 0) + f = tree[n * 2]; + s.opt_len += f * (bits + xbits); + if (stree != null) { - return; - } - - // This happens for example on obj2 and pic of the Calgary corpus - // Find the first bit length which could increase: - do - { - bits = max_length - 1; - while (s.bl_count[bits] == 0) - { - bits--; - } - s.bl_count[bits]--; // move one leaf down the tree - s.bl_count[bits + 1] = (short)(s.bl_count[bits + 1] + 2); // move one overflow item as its brother - s.bl_count[max_length]--; - - // The brother of the overflow item also moves one step up, - // but this does not affect bl_count[max_length] - overflow -= 2; - } - while (overflow > 0); - - for (bits = max_length; bits != 0; bits--) - { - n = s.bl_count[bits]; - while (n != 0) - { - m = s.heap[--h]; - if (m > max_code) - { - continue; - } - if (tree[m * 2 + 1] != bits) - { - s.opt_len = (int)(s.opt_len + (bits - (long)tree[m * 2 + 1]) * tree[m * 2]); - tree[m * 2 + 1] = (short)bits; - } - n--; - } + s.static_len += f * (stree[(n * 2) + 1] + xbits); } } - - // Construct one Huffman tree and assigns the code bit strings and lengths. - // Update the total bit length for the current block. - // IN assertion: the field freq is set for all tree elements. - // OUT assertions: the fields len and code are set to the optimal bit length - // and corresponding code. The length opt_len is updated; static_len is - // also updated if stree is not null. The field max_code is set. - internal void build_tree(DeflateManager s) + if (overflow == 0) { - short[] tree = dyn_tree; - short[] stree = staticTree.treeCodes; - int elems = staticTree.elems; - int n, m; // iterate over heap elements - int max_code = -1; // largest code with non zero frequency - int node; // new node being created - - // Construct the initial heap, with least frequent element in - // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. - // heap[0] is not used. - s.heap_len = 0; - s.heap_max = HEAP_SIZE; - - for (n = 0; n < elems; n++) - { - if (tree[n * 2] != 0) - { - s.heap[++s.heap_len] = max_code = n; - s.depth[n] = 0; - } - else - { - tree[n * 2 + 1] = 0; - } - } - - // The pkzip format requires that at least one distance code exists, - // and that at least one bit should be sent even if there is only one - // possible code. So to avoid special checks later on we force at least - // two codes of non zero frequency. - while (s.heap_len < 2) - { - node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); - tree[node * 2] = 1; - s.depth[node] = 0; - s.opt_len--; - if (stree != null) - { - s.static_len -= stree[node * 2 + 1]; - } - - // node is 0 or 1 so it does not have extra bits - } - this.max_code = max_code; - - // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, - // establish sub-heaps of increasing lengths: - - for (n = s.heap_len / 2; n >= 1; n--) - { - s.pqdownheap(tree, n); - } - - // Construct the Huffman tree by repeatedly combining the least two - // frequent nodes. - - node = elems; // next internal node of the tree - do - { - // n = node of least frequency - n = s.heap[1]; - s.heap[1] = s.heap[s.heap_len--]; - s.pqdownheap(tree, 1); - m = s.heap[1]; // m = node of next least frequency - - s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency - s.heap[--s.heap_max] = m; - - // Create a new node father of n and m - tree[node * 2] = unchecked((short)(tree[n * 2] + tree[m * 2])); - s.depth[node] = (sbyte)(Math.Max((byte)s.depth[n], (byte)s.depth[m]) + 1); - tree[n * 2 + 1] = tree[m * 2 + 1] = (short)node; - - // and insert the new node in the heap - s.heap[1] = node++; - s.pqdownheap(tree, 1); - } - while (s.heap_len >= 2); - - s.heap[--s.heap_max] = s.heap[1]; - - // At this point, the fields freq and dad are set. We can now - // generate the bit lengths. - - gen_bitlen(s); - - // The field len is now set, we can generate the bit codes - gen_codes(tree, max_code, s.bl_count); + return; } - // Generate the codes for a given tree and bit counts (which need not be - // optimal). - // IN assertion: the array bl_count contains the bit length statistics for - // the given tree and the field len is set for all tree elements. - // OUT assertion: the field code is set for all tree elements of non - // zero code length. - internal static void gen_codes(short[] tree, int max_code, short[] bl_count) + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { - var next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length - short code = 0; // running code value - int bits; // bit index - int n; // code index - - // The distribution counts are first used to generate the code values - // without bit reversal. - for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++) + bits = max_length - 1; + while (s.bl_count[bits] == 0) { - unchecked - { - next_code[bits] = code = (short)((code + bl_count[bits - 1]) << 1); - } + bits--; } + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits + 1] = (short)(s.bl_count[bits + 1] + 2); // move one overflow item as its brother + s.bl_count[max_length]--; - // Check that the bit counts in bl_count are consistent. The last code - // must be all ones. - //Assert (code + bl_count[MAX_BITS]-1 == (1< 0); - for (n = 0; n <= max_code; n++) + for (bits = max_length; bits != 0; bits--) + { + n = s.bl_count[bits]; + while (n != 0) { - int len = tree[n * 2 + 1]; - if (len == 0) + m = s.heap[--h]; + if (m > max_code) { continue; } - - // Now reverse the bits - tree[n * 2] = unchecked((short)(bi_reverse(next_code[len]++, len))); + if (tree[(m * 2) + 1] != bits) + { + s.opt_len = (int)( + s.opt_len + ((bits - (long)tree[(m * 2) + 1]) * tree[m * 2]) + ); + tree[(m * 2) + 1] = (short)bits; + } + n--; } } - - // Reverse the first len bits of a code, using straightforward code (a faster - // method would use a table) - // IN assertion: 1 <= len <= 15 - internal static int bi_reverse(int code, int len) - { - int res = 0; - do - { - res |= code & 1; - code >>= 1; //SharedUtils.URShift(code, 1); - res <<= 1; - } - while (--len > 0); - return res >> 1; - } } - #endregion + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + internal void build_tree(DeflateManager s) + { + var tree = dyn_tree; + var stree = staticTree.treeCodes; + var elems = staticTree.elems; + int n, + m; // iterate over heap elements + var max_code = -1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) + { + if (tree[n * 2] != 0) + { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + } + else + { + tree[(n * 2) + 1] = 0; + } + } + + // The pkzip format requires that at least one distance code exists, + // and that at least one bit should be sent even if there is only one + // possible code. So to avoid special checks later on we force at least + // two codes of non zero frequency. + while (s.heap_len < 2) + { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2] = 1; + s.depth[node] = 0; + s.opt_len--; + if (stree != null) + { + s.static_len -= stree[(node * 2) + 1]; + } + + // node is 0 or 1 so it does not have extra bits + } + this.max_code = max_code; + + // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + // establish sub-heaps of increasing lengths: + + for (n = s.heap_len / 2; n >= 1; n--) + { + s.pqdownheap(tree, n); + } + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node = elems; // next internal node of the tree + do + { + // n = node of least frequency + n = s.heap[1]; + s.heap[1] = s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m = s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node * 2] = unchecked((short)(tree[n * 2] + tree[m * 2])); + s.depth[node] = (sbyte)(Math.Max((byte)s.depth[n], (byte)s.depth[m]) + 1); + tree[(n * 2) + 1] = tree[(m * 2) + 1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + internal static void gen_codes(short[] tree, int max_code, short[] bl_count) + { + var next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++) + { + unchecked + { + next_code[bits] = code = (short)((code + bl_count[bits - 1]) << 1); + } + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>= 1; //SharedUtils.URShift(code, 1); + res <<= 1; + } while (--len > 0); + return res >> 1; + } } -} \ No newline at end of file + + #endregion +} diff --git a/src/SharpCompress/Compressors/Deflate/Zlib.cs b/src/SharpCompress/Compressors/Deflate/Zlib.cs index 036d65f5..1377b116 100644 --- a/src/SharpCompress/Compressors/Deflate/Zlib.cs +++ b/src/SharpCompress/Compressors/Deflate/Zlib.cs @@ -65,344 +65,937 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +/// +/// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress. +/// +public enum CompressionLevel { /// - /// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress. + /// None means that the data will be simply stored, with no change at all. + /// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None + /// cannot be opened with the default zip reader. Use a different CompressionLevel. /// - public enum CompressionLevel - { - /// - /// None means that the data will be simply stored, with no change at all. - /// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None - /// cannot be opened with the default zip reader. Use a different CompressionLevel. - /// - None = 0, - - /// - /// Same as None. - /// - Level0 = None, - - /// - /// The fastest but least effective compression. - /// - BestSpeed = 1, - - /// - /// A synonym for BestSpeed. - /// - Level1 = BestSpeed, - - /// - /// A little slower, but better, than level 1. - /// - Level2 = 2, - - /// - /// A little slower, but better, than level 2. - /// - Level3 = 3, - - /// - /// A little slower, but better, than level 3. - /// - Level4 = 4, - - /// - /// A little slower than level 4, but with better compression. - /// - Level5 = 5, - - /// - /// The default compression level, with a good balance of speed and compression efficiency. - /// - Default = 6, - - /// - /// A synonym for Default. - /// - Level6 = Default, - - /// - /// Pretty good compression! - /// - Level7 = 7, - - /// - /// Better compression than Level7! - /// - Level8 = 8, - - /// - /// The "best" compression, where best means greatest reduction in size of the input data stream. - /// This is also the slowest compression. - /// - BestCompression = 9, - - /// - /// A synonym for BestCompression. - /// - Level9 = BestCompression - } + None = 0, /// - /// Describes options for how the compression algorithm is executed. Different strategies - /// work better on different sorts of data. The strategy parameter can affect the compression - /// ratio and the speed of compression but not the correctness of the compresssion. + /// Same as None. /// - public enum CompressionStrategy - { - /// - /// The default strategy is probably the best for normal data. - /// - Default = 0, - - /// - /// The Filtered strategy is intended to be used most effectively with data produced by a - /// filter or predictor. By this definition, filtered data consists mostly of small - /// values with a somewhat random distribution. In this case, the compression algorithm - /// is tuned to compress them better. The effect of Filtered is to force more Huffman - /// coding and less string matching; it is a half-step between Default and HuffmanOnly. - /// - Filtered = 1, - - /// - /// Using HuffmanOnly will force the compressor to do Huffman encoding only, with no - /// string matching. - /// - HuffmanOnly = 2 - } + Level0 = None, /// - /// A general purpose exception class for exceptions in the Zlib library. + /// The fastest but least effective compression. /// - public class ZlibException : Exception - { - /// - /// The ZlibException class captures exception information generated - /// by the Zlib library. - /// - public ZlibException() - { - } + BestSpeed = 1, - /// - /// This ctor collects a message attached to the exception. - /// - /// - public ZlibException(String s) - : base(s) - { - } - } + /// + /// A synonym for BestSpeed. + /// + Level1 = BestSpeed, - internal class SharedUtils - { - /// - /// Performs an unsigned bitwise right shift with the specified number - /// - /// Number to operate on - /// Ammount of bits to shift - /// The resulting number from the shift operation - public static int URShift(int number, int bits) - { - return (int)((uint)number >> bits); - } + /// + /// A little slower, but better, than level 1. + /// + Level2 = 2, + + /// + /// A little slower, but better, than level 2. + /// + Level3 = 3, + + /// + /// A little slower, but better, than level 3. + /// + Level4 = 4, + + /// + /// A little slower than level 4, but with better compression. + /// + Level5 = 5, + + /// + /// The default compression level, with a good balance of speed and compression efficiency. + /// + Default = 6, + + /// + /// A synonym for Default. + /// + Level6 = Default, + + /// + /// Pretty good compression! + /// + Level7 = 7, + + /// + /// Better compression than Level7! + /// + Level8 = 8, + + /// + /// The "best" compression, where best means greatest reduction in size of the input data stream. + /// This is also the slowest compression. + /// + BestCompression = 9, + + /// + /// A synonym for BestCompression. + /// + Level9 = BestCompression +} + +/// +/// Describes options for how the compression algorithm is executed. Different strategies +/// work better on different sorts of data. The strategy parameter can affect the compression +/// ratio and the speed of compression but not the correctness of the compresssion. +/// +public enum CompressionStrategy +{ + /// + /// The default strategy is probably the best for normal data. + /// + Default = 0, + + /// + /// The Filtered strategy is intended to be used most effectively with data produced by a + /// filter or predictor. By this definition, filtered data consists mostly of small + /// values with a somewhat random distribution. In this case, the compression algorithm + /// is tuned to compress them better. The effect of Filtered is to force more Huffman + /// coding and less string matching; it is a half-step between Default and HuffmanOnly. + /// + Filtered = 1, + + /// + /// Using HuffmanOnly will force the compressor to do Huffman encoding only, with no + /// string matching. + /// + HuffmanOnly = 2 +} + +/// +/// A general purpose exception class for exceptions in the Zlib library. +/// +public class ZlibException : Exception +{ + /// + /// The ZlibException class captures exception information generated + /// by the Zlib library. + /// + public ZlibException() { } + + /// + /// This ctor collects a message attached to the exception. + /// + /// + public ZlibException(string s) : base(s) { } +} + +internal class SharedUtils +{ + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static int URShift(int number, int bits) => (int)((uint)number >> bits); #if NOT -/// -/// Performs an unsigned bitwise right shift with the specified number -/// -/// Number to operate on -/// Ammount of bits to shift -/// The resulting number from the shift operation - public static long URShift(long number, int bits) - { - return (long) ((UInt64)number >> bits); - } + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static long URShift(long number, int bits) + { + return (long)((UInt64)number >> bits); + } #endif - /// - /// Reads a number of characters from the current source TextReader and writes - /// the data to the target array at the specified index. - /// - /// - /// The source TextReader to read from - /// Contains the array of characteres read from the source TextReader. - /// The starting index of the target array. - /// The maximum number of characters to read from the source TextReader. - /// - /// - /// The number of characters read. The number will be less than or equal to - /// count depending on the data available in the source TextReader. Returns -1 - /// if the end of the stream is reached. - /// - public static Int32 ReadInput(TextReader sourceTextReader, byte[] target, int start, int count) - { - // Returns 0 bytes if not enough space in target - if (target.Length == 0) - { - return 0; - } - - char[] charArray = new char[target.Length]; - int bytesRead = sourceTextReader.Read(charArray, start, count); - - // Returns -1 if EOF - if (bytesRead == 0) - { - return -1; - } - - for (int index = start; index < start + bytesRead; index++) - { - target[index] = (byte)charArray[index]; - } - - return bytesRead; - } - } - - internal static class InternalConstants + /// + /// Reads a number of characters from the current source TextReader and writes + /// the data to the target array at the specified index. + /// + /// + /// The source TextReader to read from + /// Contains the array of characteres read from the source TextReader. + /// The starting index of the target array. + /// The maximum number of characters to read from the source TextReader. + /// + /// + /// The number of characters read. The number will be less than or equal to + /// count depending on the data available in the source TextReader. Returns -1 + /// if the end of the stream is reached. + /// + public static int ReadInput(TextReader sourceTextReader, byte[] target, int start, int count) { - internal static readonly int MAX_BITS = 15; - internal static readonly int BL_CODES = 19; - internal static readonly int D_CODES = 30; - internal static readonly int LITERALS = 256; - internal static readonly int LENGTH_CODES = 29; - internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES); - - // Bit length codes must not exceed MAX_BL_BITS bits - internal static readonly int MAX_BL_BITS = 7; - - // repeat previous bit length 3-6 times (2 bits of repeat count) - internal static readonly int REP_3_6 = 16; - - // repeat a zero length 3-10 times (3 bits of repeat count) - internal static readonly int REPZ_3_10 = 17; - - // repeat a zero length 11-138 times (7 bits of repeat count) - internal static readonly int REPZ_11_138 = 18; - } - - internal sealed class StaticTree - { - internal static readonly short[] lengthAndLiteralsTreeCodes = + // Returns 0 bytes if not enough space in target + if (target.Length == 0) { - 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, - 8, 108, 8, 236, 8, - 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, - 8, 124, 8, 252, 8, - 2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8 - , 98, 8, 226, 8, - 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, - 8, 114, 8, 242, 8, - 10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, - 8, 106, 8, 234, 8, - 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, - 8, 122, 8, 250, 8, - 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8 - , 102, 8, 230, 8, - 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, - 8, 118, 8, 246, 8, - 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, - 8, 110, 8, 238, 8, - 30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, - 8, 126, 8, 254, 8, - 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8 - , 97, 8, 225, 8, - 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, - 8, 113, 8, 241, 8, - 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8 - , 105, 8, 233, 8, - 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, - 8, 121, 8, 249, 8, - 5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8 - , 101, 8, 229, 8, - 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, - 8, 117, 8, 245, 8, - 13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, - 8, 109, 8, 237, 8, - 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, - 8, 125, 8, 253, 8, - 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, - 9, 211, 9, 467, 9, - 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371 - , 9, 243, 9, 499, 9, - 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, - 9, 203, 9, 459, 9, - 43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363 - , 9, 235, 9, 491, 9, - 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, - 9, 219, 9, 475, 9, - 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379 - , 9, 251, 9, 507, 9, - 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, - 9, 199, 9, 455, 9, - 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359 - , 9, 231, 9, 487, 9, - 23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, - 9, 215, 9, 471, 9, - 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375 - , 9, 247, 9, 503, 9, - 15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, - 9, 207, 9, 463, 9, - 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367 - , 9, 239, 9, 495, 9, - 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, - 9, 223, 9, 479, 9, - 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383 - , 9, 255, 9, 511, 9, - 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, - 48, 7, 112, 7, - 8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, - 56, 7, 120, 7, - 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, - 52, 7, 116, 7, - 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8 - , 99, 8, 227, 8 - }; - - internal static readonly short[] distTreeCodes = - { - 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, - 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, - 1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5, - 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 - }; - - // extra bits for each bit length code - internal static readonly int[] extra_blbits = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 }; - - internal static readonly StaticTree Literals; - internal static readonly StaticTree Distances; - internal static readonly StaticTree BitLengths; - - internal short[]? treeCodes; // static tree or null - internal int[]? extraBits; // extra bits for each code or null - internal int extraBase; // base index for extra_bits - internal int elems; // max number of elements in the tree - internal int maxLength; // max bit length for the codes - - private StaticTree(short[]? treeCodes, int[]? extraBits, int extraBase, int elems, int maxLength) - { - this.treeCodes = treeCodes; - this.extraBits = extraBits; - this.extraBase = extraBase; - this.elems = elems; - this.maxLength = maxLength; + return 0; } - static StaticTree() + var charArray = new char[target.Length]; + var bytesRead = sourceTextReader.Read(charArray, start, count); + + // Returns -1 if EOF + if (bytesRead == 0) { - Literals = new StaticTree(lengthAndLiteralsTreeCodes, DeflateManager.ExtraLengthBits, - InternalConstants.LITERALS + 1, InternalConstants.L_CODES, - InternalConstants.MAX_BITS); - Distances = new StaticTree(distTreeCodes, DeflateManager.ExtraDistanceBits, 0, InternalConstants.D_CODES, - InternalConstants.MAX_BITS); - BitLengths = new StaticTree(null, extra_blbits, 0, InternalConstants.BL_CODES, InternalConstants.MAX_BL_BITS); + return -1; } + + for (var index = start; index < start + bytesRead; index++) + { + target[index] = (byte)charArray[index]; + } + + return bytesRead; + } +} + +internal static class InternalConstants +{ + internal static readonly int MAX_BITS = 15; + internal static readonly int BL_CODES = 19; + internal static readonly int D_CODES = 30; + internal static readonly int LITERALS = 256; + internal static readonly int LENGTH_CODES = 29; + internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES); + + // Bit length codes must not exceed MAX_BL_BITS bits + internal static readonly int MAX_BL_BITS = 7; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + internal static readonly int REP_3_6 = 16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + internal static readonly int REPZ_3_10 = 17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + internal static readonly int REPZ_11_138 = 18; +} + +internal sealed class StaticTree +{ + internal static readonly short[] lengthAndLiteralsTreeCodes = + { + 12, + 8, + 140, + 8, + 76, + 8, + 204, + 8, + 44, + 8, + 172, + 8, + 108, + 8, + 236, + 8, + 28, + 8, + 156, + 8, + 92, + 8, + 220, + 8, + 60, + 8, + 188, + 8, + 124, + 8, + 252, + 8, + 2, + 8, + 130, + 8, + 66, + 8, + 194, + 8, + 34, + 8, + 162, + 8, + 98, + 8, + 226, + 8, + 18, + 8, + 146, + 8, + 82, + 8, + 210, + 8, + 50, + 8, + 178, + 8, + 114, + 8, + 242, + 8, + 10, + 8, + 138, + 8, + 74, + 8, + 202, + 8, + 42, + 8, + 170, + 8, + 106, + 8, + 234, + 8, + 26, + 8, + 154, + 8, + 90, + 8, + 218, + 8, + 58, + 8, + 186, + 8, + 122, + 8, + 250, + 8, + 6, + 8, + 134, + 8, + 70, + 8, + 198, + 8, + 38, + 8, + 166, + 8, + 102, + 8, + 230, + 8, + 22, + 8, + 150, + 8, + 86, + 8, + 214, + 8, + 54, + 8, + 182, + 8, + 118, + 8, + 246, + 8, + 14, + 8, + 142, + 8, + 78, + 8, + 206, + 8, + 46, + 8, + 174, + 8, + 110, + 8, + 238, + 8, + 30, + 8, + 158, + 8, + 94, + 8, + 222, + 8, + 62, + 8, + 190, + 8, + 126, + 8, + 254, + 8, + 1, + 8, + 129, + 8, + 65, + 8, + 193, + 8, + 33, + 8, + 161, + 8, + 97, + 8, + 225, + 8, + 17, + 8, + 145, + 8, + 81, + 8, + 209, + 8, + 49, + 8, + 177, + 8, + 113, + 8, + 241, + 8, + 9, + 8, + 137, + 8, + 73, + 8, + 201, + 8, + 41, + 8, + 169, + 8, + 105, + 8, + 233, + 8, + 25, + 8, + 153, + 8, + 89, + 8, + 217, + 8, + 57, + 8, + 185, + 8, + 121, + 8, + 249, + 8, + 5, + 8, + 133, + 8, + 69, + 8, + 197, + 8, + 37, + 8, + 165, + 8, + 101, + 8, + 229, + 8, + 21, + 8, + 149, + 8, + 85, + 8, + 213, + 8, + 53, + 8, + 181, + 8, + 117, + 8, + 245, + 8, + 13, + 8, + 141, + 8, + 77, + 8, + 205, + 8, + 45, + 8, + 173, + 8, + 109, + 8, + 237, + 8, + 29, + 8, + 157, + 8, + 93, + 8, + 221, + 8, + 61, + 8, + 189, + 8, + 125, + 8, + 253, + 8, + 19, + 9, + 275, + 9, + 147, + 9, + 403, + 9, + 83, + 9, + 339, + 9, + 211, + 9, + 467, + 9, + 51, + 9, + 307, + 9, + 179, + 9, + 435, + 9, + 115, + 9, + 371, + 9, + 243, + 9, + 499, + 9, + 11, + 9, + 267, + 9, + 139, + 9, + 395, + 9, + 75, + 9, + 331, + 9, + 203, + 9, + 459, + 9, + 43, + 9, + 299, + 9, + 171, + 9, + 427, + 9, + 107, + 9, + 363, + 9, + 235, + 9, + 491, + 9, + 27, + 9, + 283, + 9, + 155, + 9, + 411, + 9, + 91, + 9, + 347, + 9, + 219, + 9, + 475, + 9, + 59, + 9, + 315, + 9, + 187, + 9, + 443, + 9, + 123, + 9, + 379, + 9, + 251, + 9, + 507, + 9, + 7, + 9, + 263, + 9, + 135, + 9, + 391, + 9, + 71, + 9, + 327, + 9, + 199, + 9, + 455, + 9, + 39, + 9, + 295, + 9, + 167, + 9, + 423, + 9, + 103, + 9, + 359, + 9, + 231, + 9, + 487, + 9, + 23, + 9, + 279, + 9, + 151, + 9, + 407, + 9, + 87, + 9, + 343, + 9, + 215, + 9, + 471, + 9, + 55, + 9, + 311, + 9, + 183, + 9, + 439, + 9, + 119, + 9, + 375, + 9, + 247, + 9, + 503, + 9, + 15, + 9, + 271, + 9, + 143, + 9, + 399, + 9, + 79, + 9, + 335, + 9, + 207, + 9, + 463, + 9, + 47, + 9, + 303, + 9, + 175, + 9, + 431, + 9, + 111, + 9, + 367, + 9, + 239, + 9, + 495, + 9, + 31, + 9, + 287, + 9, + 159, + 9, + 415, + 9, + 95, + 9, + 351, + 9, + 223, + 9, + 479, + 9, + 63, + 9, + 319, + 9, + 191, + 9, + 447, + 9, + 127, + 9, + 383, + 9, + 255, + 9, + 511, + 9, + 0, + 7, + 64, + 7, + 32, + 7, + 96, + 7, + 16, + 7, + 80, + 7, + 48, + 7, + 112, + 7, + 8, + 7, + 72, + 7, + 40, + 7, + 104, + 7, + 24, + 7, + 88, + 7, + 56, + 7, + 120, + 7, + 4, + 7, + 68, + 7, + 36, + 7, + 100, + 7, + 20, + 7, + 84, + 7, + 52, + 7, + 116, + 7, + 3, + 8, + 131, + 8, + 67, + 8, + 195, + 8, + 35, + 8, + 163, + 8, + 99, + 8, + 227, + 8 + }; + + internal static readonly short[] distTreeCodes = + { + 0, + 5, + 16, + 5, + 8, + 5, + 24, + 5, + 4, + 5, + 20, + 5, + 12, + 5, + 28, + 5, + 2, + 5, + 18, + 5, + 10, + 5, + 26, + 5, + 6, + 5, + 22, + 5, + 14, + 5, + 30, + 5, + 1, + 5, + 17, + 5, + 9, + 5, + 25, + 5, + 5, + 5, + 21, + 5, + 13, + 5, + 29, + 5, + 3, + 5, + 19, + 5, + 11, + 5, + 27, + 5, + 7, + 5, + 23, + 5 + }; + + // extra bits for each bit length code + internal static readonly int[] extra_blbits = + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 3, + 7 + }; + + internal static readonly StaticTree Literals; + internal static readonly StaticTree Distances; + internal static readonly StaticTree BitLengths; + + internal short[]? treeCodes; // static tree or null + internal int[]? extraBits; // extra bits for each code or null + internal int extraBase; // base index for extra_bits + internal int elems; // max number of elements in the tree + internal int maxLength; // max bit length for the codes + + private StaticTree( + short[]? treeCodes, + int[]? extraBits, + int extraBase, + int elems, + int maxLength + ) + { + this.treeCodes = treeCodes; + this.extraBits = extraBits; + this.extraBase = extraBase; + this.elems = elems; + this.maxLength = maxLength; + } + + static StaticTree() + { + Literals = new StaticTree( + lengthAndLiteralsTreeCodes, + DeflateManager.ExtraLengthBits, + InternalConstants.LITERALS + 1, + InternalConstants.L_CODES, + InternalConstants.MAX_BITS + ); + Distances = new StaticTree( + distTreeCodes, + DeflateManager.ExtraDistanceBits, + 0, + InternalConstants.D_CODES, + InternalConstants.MAX_BITS + ); + BitLengths = new StaticTree( + null, + extra_blbits, + 0, + InternalConstants.BL_CODES, + InternalConstants.MAX_BL_BITS + ); } } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index 45d5dfac..19a3c60a 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -33,137 +33,177 @@ using System.IO; using SharpCompress.Common.Tar.Headers; using System.Text; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +internal enum ZlibStreamFlavor { - internal enum ZlibStreamFlavor + ZLIB = 1950, + DEFLATE = 1951, + GZIP = 1952 +} + +internal class ZlibBaseStream : Stream +{ + protected internal ZlibCodec _z; // deferred init... new ZlibCodec(); + + protected internal StreamMode _streamMode = StreamMode.Undefined; + protected internal FlushType _flushMode; + protected internal ZlibStreamFlavor _flavor; + protected internal CompressionMode _compressionMode; + protected internal CompressionLevel _level; + protected internal byte[] _workingBuffer; + protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault; + protected internal byte[] _buf1 = new byte[1]; + + protected internal Stream _stream; + protected internal CompressionStrategy Strategy = CompressionStrategy.Default; + + // workitem 7159 + private readonly CRC32 crc; + protected internal string _GzipFileName; + protected internal string _GzipComment; + protected internal DateTime _GzipMtime; + protected internal int _gzipHeaderByteCount; + + private readonly Encoding _encoding; + + internal int Crc32 => crc?.Crc32Result ?? 0; + + public ZlibBaseStream( + Stream stream, + CompressionMode compressionMode, + CompressionLevel level, + ZlibStreamFlavor flavor, + Encoding encoding + ) { - ZLIB = 1950, - DEFLATE = 1951, - GZIP = 1952 - } + _flushMode = FlushType.None; - internal class ZlibBaseStream : Stream - { - protected internal ZlibCodec _z; // deferred init... new ZlibCodec(); + //this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT]; + _stream = stream; + _compressionMode = compressionMode; + _flavor = flavor; + _level = level; - protected internal StreamMode _streamMode = StreamMode.Undefined; - protected internal FlushType _flushMode; - protected internal ZlibStreamFlavor _flavor; - protected internal CompressionMode _compressionMode; - protected internal CompressionLevel _level; - protected internal byte[] _workingBuffer; - protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault; - protected internal byte[] _buf1 = new byte[1]; - - protected internal Stream _stream; - protected internal CompressionStrategy Strategy = CompressionStrategy.Default; + _encoding = encoding; // workitem 7159 - private readonly CRC32 crc; - protected internal string _GzipFileName; - protected internal string _GzipComment; - protected internal DateTime _GzipMtime; - protected internal int _gzipHeaderByteCount; - - private readonly Encoding _encoding; - - internal int Crc32 => crc?.Crc32Result ?? 0; - - public ZlibBaseStream(Stream stream, - CompressionMode compressionMode, - CompressionLevel level, - ZlibStreamFlavor flavor, - Encoding encoding) + if (flavor == ZlibStreamFlavor.GZIP) { - _flushMode = FlushType.None; - - //this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT]; - _stream = stream; - _compressionMode = compressionMode; - _flavor = flavor; - _level = level; - - _encoding = encoding; - - // workitem 7159 - if (flavor == ZlibStreamFlavor.GZIP) - { - crc = new CRC32(); - } + crc = new CRC32(); } + } - protected internal bool _wantCompress => (_compressionMode == CompressionMode.Compress); + protected internal bool _wantCompress => (_compressionMode == CompressionMode.Compress); - private ZlibCodec z + private ZlibCodec z + { + get { - get + if (_z is null) { - if (_z is null) + bool wantRfc1950Header = (_flavor == ZlibStreamFlavor.ZLIB); + _z = new ZlibCodec(); + if (_compressionMode == CompressionMode.Decompress) { - bool wantRfc1950Header = (_flavor == ZlibStreamFlavor.ZLIB); - _z = new ZlibCodec(); - if (_compressionMode == CompressionMode.Decompress) - { - _z.InitializeInflate(wantRfc1950Header); - } - else - { - _z.Strategy = Strategy; - _z.InitializeDeflate(_level, wantRfc1950Header); - } + _z.InitializeInflate(wantRfc1950Header); + } + else + { + _z.Strategy = Strategy; + _z.InitializeDeflate(_level, wantRfc1950Header); } - return _z; } + return _z; + } + } + + private byte[] workingBuffer => _workingBuffer ??= new byte[_bufferSize]; + + public override void Write(byte[] buffer, int offset, int count) + { + // workitem 7159 + // calculate the CRC on the unccompressed data (before writing) + if (crc != null) + { + crc.SlurpBlock(buffer, offset, count); } - private byte[] workingBuffer + if (_streamMode == StreamMode.Undefined) { - get => _workingBuffer ??= new byte[_bufferSize]; + _streamMode = StreamMode.Writer; + } + else if (_streamMode != StreamMode.Writer) + { + throw new ZlibException("Cannot Write after Reading."); } - public override void Write(byte[] buffer, int offset, int count) + if (count == 0) { - // workitem 7159 - // calculate the CRC on the unccompressed data (before writing) - if (crc != null) + return; + } + + // first reference of z property will initialize the private var _z + z.InputBuffer = buffer; + _z.NextIn = offset; + _z.AvailableBytesIn = count; + bool done = false; + do + { + _z.OutputBuffer = workingBuffer; + _z.NextOut = 0; + _z.AvailableBytesOut = _workingBuffer.Length; + int rc = (_wantCompress) ? _z.Deflate(_flushMode) : _z.Inflate(_flushMode); + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) { - crc.SlurpBlock(buffer, offset, count); + throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message); } - if (_streamMode == StreamMode.Undefined) - { - _streamMode = StreamMode.Writer; - } - else if (_streamMode != StreamMode.Writer) - { - throw new ZlibException("Cannot Write after Reading."); - } + //if (_workingBuffer.Length - _z.AvailableBytesOut > 0) + _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); - if (count == 0) - { - return; - } + done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; - // first reference of z property will initialize the private var _z - z.InputBuffer = buffer; - _z.NextIn = offset; - _z.AvailableBytesIn = count; + // If GZIP and de-compress, we're done when 8 bytes remain. + if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress) + { + done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); + } + } while (!done); + } + + private void finish() + { + if (_z is null) + { + return; + } + + if (_streamMode == StreamMode.Writer) + { bool done = false; do { _z.OutputBuffer = workingBuffer; _z.NextOut = 0; _z.AvailableBytesOut = _workingBuffer.Length; - int rc = (_wantCompress) - ? _z.Deflate(_flushMode) - : _z.Inflate(_flushMode); - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + int rc = + (_wantCompress) ? _z.Deflate(FlushType.Finish) : _z.Inflate(FlushType.Finish); + + if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) { - throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message); + string verb = (_wantCompress ? "de" : "in") + "flating"; + if (_z.Message is null) + { + throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc)); + } + throw new ZlibException(verb + ": " + _z.Message); } - //if (_workingBuffer.Length - _z.AvailableBytesOut > 0) - _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); + if (_workingBuffer.Length - _z.AvailableBytesOut > 0) + { + _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); + } done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; @@ -172,452 +212,314 @@ namespace SharpCompress.Compressors.Deflate { done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); } - } - while (!done); - } + } while (!done); - private void finish() - { - if (_z is null) - { - return; - } - - if (_streamMode == StreamMode.Writer) - { - bool done = false; - do - { - _z.OutputBuffer = workingBuffer; - _z.NextOut = 0; - _z.AvailableBytesOut = _workingBuffer.Length; - int rc = (_wantCompress) - ? _z.Deflate(FlushType.Finish) - : _z.Inflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) - { - string verb = (_wantCompress ? "de" : "in") + "flating"; - if (_z.Message is null) - { - throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc)); - } - throw new ZlibException(verb + ": " + _z.Message); - } - - if (_workingBuffer.Length - _z.AvailableBytesOut > 0) - { - _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut); - } - - done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0; - - // If GZIP and de-compress, we're done when 8 bytes remain. - if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress) - { - done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0); - } - } - while (!done); - - Flush(); - - // workitem 7159 - if (_flavor == ZlibStreamFlavor.GZIP) - { - if (_wantCompress) - { - // Emit the GZIP trailer: CRC32 and size mod 2^32 - Span intBuf = stackalloc byte[4]; - BinaryPrimitives.WriteInt32LittleEndian(intBuf, crc.Crc32Result); - _stream.Write(intBuf); - int c2 = (int)(crc.TotalBytesRead & 0x00000000FFFFFFFF); - BinaryPrimitives.WriteInt32LittleEndian(intBuf, c2); - _stream.Write(intBuf); - } - else - { - throw new ZlibException("Writing with decompression is not supported."); - } - } - } + Flush(); // workitem 7159 - else if (_streamMode == StreamMode.Reader) + if (_flavor == ZlibStreamFlavor.GZIP) { - if (_flavor == ZlibStreamFlavor.GZIP) + if (_wantCompress) { - if (!_wantCompress) - { - // workitem 8501: handle edge case (decompress empty stream) - if (_z.TotalBytesOut == 0L) - { - return; - } - - // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 - Span trailer = stackalloc byte[8]; - - // workitem 8679 - if (_z.AvailableBytesIn != 8) - { - // Make sure we have read to the end of the stream - _z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer); - int bytesNeeded = 8 - _z.AvailableBytesIn; - int bytesRead = _stream.Read(trailer.Slice(_z.AvailableBytesIn, bytesNeeded)); - if (bytesNeeded != bytesRead) - { - throw new ZlibException(String.Format( - "Protocol error. AvailableBytesIn={0}, expected 8", - _z.AvailableBytesIn + bytesRead)); - } - } - else - { - _z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer); - } - - Int32 crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer); - Int32 crc32_actual = crc.Crc32Result; - Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4)); - Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF); - - if (crc32_actual != crc32_expected) - { - throw new ZlibException( - String.Format("Bad CRC32 in GZIP stream. (actual({0:X8})!=expected({1:X8}))", - crc32_actual, crc32_expected)); - } - - if (isize_actual != isize_expected) - { - throw new ZlibException( - String.Format("Bad size in GZIP stream. (actual({0})!=expected({1}))", isize_actual, - isize_expected)); - } - } - else - { - throw new ZlibException("Reading with compression is not supported."); - } - } - } - } - - private void end() - { - if (z is null) - { - return; - } - if (_wantCompress) - { - _z.EndDeflate(); - } - else - { - _z.EndInflate(); - } - _z = null; - } - - protected override void Dispose(bool disposing) - { - if (isDisposed) - { - return; - } - isDisposed = true; - base.Dispose(disposing); - if (disposing) - { - if (_stream is null) - { - return; - } - try - { - finish(); - } - finally - { - end(); - _stream?.Dispose(); - _stream = null; - } - } - } - - public override void Flush() - { - _stream.Flush(); - } - - public override Int64 Seek(Int64 offset, SeekOrigin origin) - { - throw new NotSupportedException(); - - //_outStream.Seek(offset, origin); - } - - public override void SetLength(Int64 value) - { - _stream.SetLength(value); - } - -#if NOT - public int Read() - { - if (Read(_buf1, 0, 1) == 0) - return 0; - // calculate CRC after reading - if (crc!=null) - crc.SlurpBlock(_buf1,0,1); - return (_buf1[0] & 0xFF); - } -#endif - - private bool nomoreinput; - private bool isDisposed; - - private string ReadZeroTerminatedString() - { - var list = new List(); - bool done = false; - do - { - // workitem 7740 - int n = _stream.Read(_buf1, 0, 1); - if (n != 1) - { - throw new ZlibException("Unexpected EOF reading GZIP header."); - } - if (_buf1[0] == 0) - { - done = true; + // Emit the GZIP trailer: CRC32 and size mod 2^32 + Span intBuf = stackalloc byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(intBuf, crc.Crc32Result); + _stream.Write(intBuf); + int c2 = (int)(crc.TotalBytesRead & 0x00000000FFFFFFFF); + BinaryPrimitives.WriteInt32LittleEndian(intBuf, c2); + _stream.Write(intBuf); } else { - list.Add(_buf1[0]); + throw new ZlibException("Writing with decompression is not supported."); } } - while (!done); - byte[] buffer = list.ToArray(); - return _encoding.GetString(buffer, 0, buffer.Length); } - - private int _ReadAndValidateGzipHeader() + // workitem 7159 + else if (_streamMode == StreamMode.Reader) { - int totalBytesRead = 0; - - // read the header on the first read - Span header = stackalloc byte[10]; - int n = _stream.Read(header); - - // workitem 8501: handle edge case (decompress empty stream) - if (n == 0) + if (_flavor == ZlibStreamFlavor.GZIP) { - return 0; - } - - if (n != 10) - { - throw new ZlibException("Not a valid GZIP stream."); - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - throw new ZlibException("Bad GZIP header."); - } - - int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4)); - _GzipMtime = TarHeader.EPOCH.AddSeconds(timet); - totalBytesRead += n; - if ((header[3] & 0x04) == 0x04) - { - // read and discard extra field - n = _stream.Read(header.Slice(0, 2)); // 2-byte length field - totalBytesRead += n; - - short extraLength = (short)(header[0] + header[1] * 256); - byte[] extra = new byte[extraLength]; - n = _stream.Read(extra, 0, extra.Length); - if (n != extraLength) + if (!_wantCompress) { - throw new ZlibException("Unexpected end-of-file reading GZIP header."); - } - totalBytesRead += n; - } - if ((header[3] & 0x08) == 0x08) - { - _GzipFileName = ReadZeroTerminatedString(); - } - if ((header[3] & 0x10) == 0x010) - { - _GzipComment = ReadZeroTerminatedString(); - } - if ((header[3] & 0x02) == 0x02) - { - Read(_buf1, 0, 1); // CRC16, ignore - } - - return totalBytesRead; - } - - public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) - { - // According to MS documentation, any implementation of the IO.Stream.Read function must: - // (a) throw an exception if offset & count reference an invalid part of the buffer, - // or if count < 0, or if buffer is null - // (b) return 0 only upon EOF, or if count = 0 - // (c) if not EOF, then return at least 1 byte, up to bytes - - if (_streamMode == StreamMode.Undefined) - { - if (!_stream.CanRead) - { - throw new ZlibException("The stream is not readable."); - } - - // for the first read, set up some controls. - _streamMode = StreamMode.Reader; - - // (The first reference to _z goes through the private accessor which - // may initialize it.) - z.AvailableBytesIn = 0; - if (_flavor == ZlibStreamFlavor.GZIP) - { - _gzipHeaderByteCount = _ReadAndValidateGzipHeader(); - // workitem 8501: handle edge case (decompress empty stream) - if (_gzipHeaderByteCount == 0) + if (_z.TotalBytesOut == 0L) { - return 0; + return; + } + + // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 + Span trailer = stackalloc byte[8]; + + // workitem 8679 + if (_z.AvailableBytesIn != 8) + { + // Make sure we have read to the end of the stream + _z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer); + int bytesNeeded = 8 - _z.AvailableBytesIn; + int bytesRead = _stream.Read( + trailer.Slice(_z.AvailableBytesIn, bytesNeeded) + ); + if (bytesNeeded != bytesRead) + { + throw new ZlibException( + String.Format( + "Protocol error. AvailableBytesIn={0}, expected 8", + _z.AvailableBytesIn + bytesRead + ) + ); + } + } + else + { + _z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer); + } + + Int32 crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer); + Int32 crc32_actual = crc.Crc32Result; + Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4)); + Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF); + + if (crc32_actual != crc32_expected) + { + throw new ZlibException( + String.Format( + "Bad CRC32 in GZIP stream. (actual({0:X8})!=expected({1:X8}))", + crc32_actual, + crc32_expected + ) + ); + } + + if (isize_actual != isize_expected) + { + throw new ZlibException( + String.Format( + "Bad size in GZIP stream. (actual({0})!=expected({1}))", + isize_actual, + isize_expected + ) + ); } } - } - - if (_streamMode != StreamMode.Reader) - { - throw new ZlibException("Cannot Read after Writing."); - } - - int rc = 0; - - // set up the output of the deflate/inflate codec: - _z.OutputBuffer = buffer; - _z.NextOut = offset; - _z.AvailableBytesOut = count; - - if (count == 0) - { - return 0; - } - if (nomoreinput && _wantCompress) - { - // no more input data available; therefore we flush to - // try to complete the read - rc = _z.Deflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + else { - throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message)); + throw new ZlibException("Reading with compression is not supported."); } + } + } + } - rc = (count - _z.AvailableBytesOut); + private void end() + { + if (z is null) + { + return; + } + if (_wantCompress) + { + _z.EndDeflate(); + } + else + { + _z.EndInflate(); + } + _z = null; + } - // calculate CRC after reading - if (crc != null) - { - crc.SlurpBlock(buffer, offset, rc); - } + protected override void Dispose(bool disposing) + { + if (isDisposed) + { + return; + } + isDisposed = true; + base.Dispose(disposing); + if (disposing) + { + if (_stream is null) + { + return; + } + try + { + finish(); + } + finally + { + end(); + _stream?.Dispose(); + _stream = null; + } + } + } - return rc; - } - if (buffer is null) + public override void Flush() => _stream.Flush(); + + public override Int64 Seek(Int64 offset, SeekOrigin origin) => + throw new NotSupportedException(); + + //_outStream.Seek(offset, origin); + public override void SetLength(Int64 value) => _stream.SetLength(value); + +#if NOT + public int Read() + { + if (Read(_buf1, 0, 1) == 0) + return 0; + // calculate CRC after reading + if (crc != null) + crc.SlurpBlock(_buf1, 0, 1); + return (_buf1[0] & 0xFF); + } +#endif + + private bool nomoreinput; + private bool isDisposed; + + private string ReadZeroTerminatedString() + { + var list = new List(); + bool done = false; + do + { + // workitem 7740 + int n = _stream.Read(_buf1, 0, 1); + if (n != 1) { - throw new ArgumentNullException(nameof(buffer)); + throw new ZlibException("Unexpected EOF reading GZIP header."); } - if (count < 0) + if (_buf1[0] == 0) { - throw new ArgumentOutOfRangeException(nameof(count)); + done = true; } - if (offset < buffer.GetLowerBound(0)) + else { - throw new ArgumentOutOfRangeException(nameof(offset)); + list.Add(_buf1[0]); } - if ((offset + count) > buffer.GetLength(0)) + } while (!done); + byte[] buffer = list.ToArray(); + return _encoding.GetString(buffer, 0, buffer.Length); + } + + private int _ReadAndValidateGzipHeader() + { + int totalBytesRead = 0; + + // read the header on the first read + Span header = stackalloc byte[10]; + int n = _stream.Read(header); + + // workitem 8501: handle edge case (decompress empty stream) + if (n == 0) + { + return 0; + } + + if (n != 10) + { + throw new ZlibException("Not a valid GZIP stream."); + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + throw new ZlibException("Bad GZIP header."); + } + + int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4)); + _GzipMtime = TarHeader.EPOCH.AddSeconds(timet); + totalBytesRead += n; + if ((header[3] & 0x04) == 0x04) + { + // read and discard extra field + n = _stream.Read(header.Slice(0, 2)); // 2-byte length field + totalBytesRead += n; + + short extraLength = (short)(header[0] + header[1] * 256); + byte[] extra = new byte[extraLength]; + n = _stream.Read(extra, 0, extra.Length); + if (n != extraLength) { - throw new ArgumentOutOfRangeException(nameof(count)); + throw new ZlibException("Unexpected end-of-file reading GZIP header."); + } + totalBytesRead += n; + } + if ((header[3] & 0x08) == 0x08) + { + _GzipFileName = ReadZeroTerminatedString(); + } + if ((header[3] & 0x10) == 0x010) + { + _GzipComment = ReadZeroTerminatedString(); + } + if ((header[3] & 0x02) == 0x02) + { + Read(_buf1, 0, 1); // CRC16, ignore + } + + return totalBytesRead; + } + + public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) + { + // According to MS documentation, any implementation of the IO.Stream.Read function must: + // (a) throw an exception if offset & count reference an invalid part of the buffer, + // or if count < 0, or if buffer is null + // (b) return 0 only upon EOF, or if count = 0 + // (c) if not EOF, then return at least 1 byte, up to bytes + + if (_streamMode == StreamMode.Undefined) + { + if (!_stream.CanRead) + { + throw new ZlibException("The stream is not readable."); } - // This is necessary in case _workingBuffer has been resized. (new byte[]) - // (The first reference to _workingBuffer goes through the private accessor which + // for the first read, set up some controls. + _streamMode = StreamMode.Reader; + + // (The first reference to _z goes through the private accessor which // may initialize it.) - _z.InputBuffer = workingBuffer; - - do + z.AvailableBytesIn = 0; + if (_flavor == ZlibStreamFlavor.GZIP) { - // need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any. - if ((_z.AvailableBytesIn == 0) && (!nomoreinput)) - { - // No data available, so try to Read data from the captive stream. - _z.NextIn = 0; - _z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length); - if (_z.AvailableBytesIn == 0) - { - nomoreinput = true; - } - } + _gzipHeaderByteCount = _ReadAndValidateGzipHeader(); - // we have data in InputBuffer; now compress or decompress as appropriate - rc = (_wantCompress) - ? _z.Deflate(_flushMode) - : _z.Inflate(_flushMode); - - if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR)) + // workitem 8501: handle edge case (decompress empty stream) + if (_gzipHeaderByteCount == 0) { return 0; } + } + } - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"), - rc, _z.Message)); - } + if (_streamMode != StreamMode.Reader) + { + throw new ZlibException("Cannot Read after Writing."); + } - if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count)) - { - break; // nothing more to read - } - } //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK); - while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK); + int rc = 0; - // workitem 8557 - // is there more room in output? - if (_z.AvailableBytesOut > 0) + // set up the output of the deflate/inflate codec: + _z.OutputBuffer = buffer; + _z.NextOut = offset; + _z.AvailableBytesOut = count; + + if (count == 0) + { + return 0; + } + if (nomoreinput && _wantCompress) + { + // no more input data available; therefore we flush to + // try to complete the read + rc = _z.Deflate(FlushType.Finish); + + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) { - if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0) - { - // deferred - } - - // are we completely done reading? - if (nomoreinput) - { - // and in compression? - if (_wantCompress) - { - // no more input data available; therefore we flush to - // try to complete the read - rc = _z.Deflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - { - throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message)); - } - } - } + throw new ZlibException( + String.Format("Deflating: rc={0} msg={1}", rc, _z.Message) + ); } rc = (count - _z.AvailableBytesOut); @@ -630,22 +532,129 @@ namespace SharpCompress.Compressors.Deflate return rc; } - - public override Boolean CanRead => _stream.CanRead; - - public override Boolean CanSeek => _stream.CanSeek; - - public override Boolean CanWrite => _stream.CanWrite; - - public override Int64 Length => _stream.Length; - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - internal enum StreamMode + if (buffer is null) { - Writer, - Reader, - Undefined + throw new ArgumentNullException(nameof(buffer)); } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (offset < buffer.GetLowerBound(0)) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if ((offset + count) > buffer.GetLength(0)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + // This is necessary in case _workingBuffer has been resized. (new byte[]) + // (The first reference to _workingBuffer goes through the private accessor which + // may initialize it.) + _z.InputBuffer = workingBuffer; + + do + { + // need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any. + if ((_z.AvailableBytesIn == 0) && (!nomoreinput)) + { + // No data available, so try to Read data from the captive stream. + _z.NextIn = 0; + _z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length); + if (_z.AvailableBytesIn == 0) + { + nomoreinput = true; + } + } + + // we have data in InputBuffer; now compress or decompress as appropriate + rc = (_wantCompress) ? _z.Deflate(_flushMode) : _z.Inflate(_flushMode); + + if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR)) + { + return 0; + } + + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + { + throw new ZlibException( + String.Format( + "{0}flating: rc={1} msg={2}", + (_wantCompress ? "de" : "in"), + rc, + _z.Message + ) + ); + } + + if ( + (nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count) + ) + { + break; // nothing more to read + } + } //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK); + while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK); + + // workitem 8557 + // is there more room in output? + if (_z.AvailableBytesOut > 0) + { + if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0) + { + // deferred + } + + // are we completely done reading? + if (nomoreinput) + { + // and in compression? + if (_wantCompress) + { + // no more input data available; therefore we flush to + // try to complete the read + rc = _z.Deflate(FlushType.Finish); + + if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + { + throw new ZlibException( + String.Format("Deflating: rc={0} msg={1}", rc, _z.Message) + ); + } + } + } + } + + rc = (count - _z.AvailableBytesOut); + + // calculate CRC after reading + if (crc != null) + { + crc.SlurpBlock(buffer, offset, rc); + } + + return rc; + } + + public override Boolean CanRead => _stream.CanRead; + + public override Boolean CanSeek => _stream.CanSeek; + + public override Boolean CanWrite => _stream.CanWrite; + + public override Int64 Length => _stream.Length; + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + internal enum StreamMode + { + Writer, + Reader, + Undefined } } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibCodec.cs b/src/SharpCompress/Compressors/Deflate/ZlibCodec.cs index 8b995018..7c49c780 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibCodec.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibCodec.cs @@ -3,20 +3,20 @@ // ZlibCodec.cs // ------------------------------------------------------------------ // -// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // -// This code is licensed under the Microsoft Public License. +// This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // -// last saved (in emacs): +// last saved (in emacs): // Time-stamp: <2009-November-03 15:40:51> // // ------------------------------------------------------------------ @@ -30,22 +30,22 @@ // is included below. // // ------------------------------------------------------------------ -// +// // Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the distribution. -// +// // 3. The names of the authors may not be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, @@ -56,7 +56,7 @@ // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // ----------------------------------------------------------------------- // // This program is based on zlib-1.1.3; credit to authors @@ -67,682 +67,682 @@ using System; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +/// +/// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951). +/// +/// +/// +/// This class compresses and decompresses data according to the Deflate algorithm +/// and optionally, the ZLIB format, as documented in RFC 1950 - ZLIB and RFC 1951 - DEFLATE. +/// +internal sealed class ZlibCodec { /// - /// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951). + /// The buffer from which data is taken. + /// + public byte[] InputBuffer; + + /// + /// An index into the InputBuffer array, indicating where to start reading. + /// + public int NextIn; + + /// + /// The number of bytes available in the InputBuffer, starting at NextIn. + /// + /// + /// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call. + /// The class will update this number as calls to Inflate/Deflate are made. + /// + public int AvailableBytesIn; + + /// + /// Total number of bytes read so far, through all calls to Inflate()/Deflate(). + /// + public long TotalBytesIn; + + /// + /// Buffer to store output data. + /// + public byte[] OutputBuffer; + + /// + /// An index into the OutputBuffer array, indicating where to start writing. + /// + public int NextOut; + + /// + /// The number of bytes available in the OutputBuffer, starting at NextOut. + /// + /// + /// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call. + /// The class will update this number as calls to Inflate/Deflate are made. + /// + public int AvailableBytesOut; + + /// + /// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate(). + /// + public long TotalBytesOut; + + /// + /// used for diagnostics, when something goes wrong! + /// + public string Message; + + internal DeflateManager dstate; + internal InflateManager istate; + + internal uint _adler32; + + /// + /// The compression level to use in this codec. Useful only in compression mode. + /// + public CompressionLevel CompressLevel = CompressionLevel.Default; + + /// + /// The number of Window Bits to use. + /// + /// + /// This gauges the size of the sliding window, and hence the + /// compression effectiveness as well as memory consumption. It's best to just leave this + /// setting alone if you don't know what it is. The maximum value is 15 bits, which implies + /// a 32k window. + /// + public int WindowBits = ZlibConstants.WindowBitsDefault; + + /// + /// The compression strategy to use. + /// + /// + /// This is only effective in compression. The theory offered by ZLIB is that different + /// strategies could potentially produce significant differences in compression behavior + /// for different data sets. Unfortunately I don't have any good recommendations for how + /// to set it differently. When I tested changing the strategy I got minimally different + /// compression performance. It's best to leave this property alone if you don't have a + /// good feel for it. Or, you may want to produce a test harness that runs through the + /// different strategy options and evaluates them on different file types. If you do that, + /// let me know your results. + /// + public CompressionStrategy Strategy = CompressionStrategy.Default; + + /// + /// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this. + /// + public int Adler32 => (int)_adler32; + + /// + /// Create a ZlibCodec. + /// + /// + /// If you use this default constructor, you will later have to explicitly call + /// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress + /// or decompress. + /// + public ZlibCodec() { } + + /// + /// Create a ZlibCodec that either compresses or decompresses. + /// + /// + /// Indicates whether the codec should compress (deflate) or decompress (inflate). + /// + public ZlibCodec(CompressionMode mode) + { + if (mode == CompressionMode.Compress) + { + var rc = InitializeDeflate(); + if (rc != ZlibConstants.Z_OK) + { + throw new ZlibException("Cannot initialize for deflate."); + } + } + else if (mode == CompressionMode.Decompress) + { + var rc = InitializeInflate(); + if (rc != ZlibConstants.Z_OK) + { + throw new ZlibException("Cannot initialize for inflate."); + } + } + else + { + throw new ZlibException("Invalid ZlibStreamFlavor."); + } + } + + /// + /// Initialize the inflation state. + /// + /// + /// It is not necessary to call this before using the ZlibCodec to inflate data; + /// It is implicitly called when you call the constructor. + /// + /// Z_OK if everything goes well. + public int InitializeInflate() => InitializeInflate(WindowBits); + + /// + /// Initialize the inflation state with an explicit flag to + /// govern the handling of RFC1950 header bytes. /// /// /// - /// This class compresses and decompresses data according to the Deflate algorithm - /// and optionally, the ZLIB format, as documented in RFC 1950 - ZLIB and RFC 1951 - DEFLATE. + /// By default, the ZLIB header defined in RFC 1950 is expected. If + /// you want to read a zlib stream you should specify true for + /// expectRfc1950Header. If you have a deflate stream, you will want to specify + /// false. It is only necessary to invoke this initializer explicitly if you + /// want to specify false. /// - internal sealed class ZlibCodec + /// + /// whether to expect an RFC1950 header byte + /// pair when reading the stream of data to be inflated. + /// + /// Z_OK if everything goes well. + public int InitializeInflate(bool expectRfc1950Header) => + InitializeInflate(WindowBits, expectRfc1950Header); + + /// + /// Initialize the ZlibCodec for inflation, with the specified number of window bits. + /// + /// The number of window bits to use. If you need to ask what that is, + /// then you shouldn't be calling this initializer. + /// Z_OK if all goes well. + public int InitializeInflate(int windowBits) { - /// - /// The buffer from which data is taken. - /// - public byte[] InputBuffer; + WindowBits = windowBits; + return InitializeInflate(windowBits, true); + } - /// - /// An index into the InputBuffer array, indicating where to start reading. - /// - public int NextIn; - - /// - /// The number of bytes available in the InputBuffer, starting at NextIn. - /// - /// - /// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call. - /// The class will update this number as calls to Inflate/Deflate are made. - /// - public int AvailableBytesIn; - - /// - /// Total number of bytes read so far, through all calls to Inflate()/Deflate(). - /// - public long TotalBytesIn; - - /// - /// Buffer to store output data. - /// - public byte[] OutputBuffer; - - /// - /// An index into the OutputBuffer array, indicating where to start writing. - /// - public int NextOut; - - /// - /// The number of bytes available in the OutputBuffer, starting at NextOut. - /// - /// - /// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call. - /// The class will update this number as calls to Inflate/Deflate are made. - /// - public int AvailableBytesOut; - - /// - /// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate(). - /// - public long TotalBytesOut; - - /// - /// used for diagnostics, when something goes wrong! - /// - public string Message; - - internal DeflateManager dstate; - internal InflateManager istate; - - internal uint _adler32; - - /// - /// The compression level to use in this codec. Useful only in compression mode. - /// - public CompressionLevel CompressLevel = CompressionLevel.Default; - - /// - /// The number of Window Bits to use. - /// - /// - /// This gauges the size of the sliding window, and hence the - /// compression effectiveness as well as memory consumption. It's best to just leave this - /// setting alone if you don't know what it is. The maximum value is 15 bits, which implies - /// a 32k window. - /// - public int WindowBits = ZlibConstants.WindowBitsDefault; - - /// - /// The compression strategy to use. - /// - /// - /// This is only effective in compression. The theory offered by ZLIB is that different - /// strategies could potentially produce significant differences in compression behavior - /// for different data sets. Unfortunately I don't have any good recommendations for how - /// to set it differently. When I tested changing the strategy I got minimally different - /// compression performance. It's best to leave this property alone if you don't have a - /// good feel for it. Or, you may want to produce a test harness that runs through the - /// different strategy options and evaluates them on different file types. If you do that, - /// let me know your results. - /// - public CompressionStrategy Strategy = CompressionStrategy.Default; - - /// - /// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this. - /// - public int Adler32 => (int)_adler32; - - /// - /// Create a ZlibCodec. - /// - /// - /// If you use this default constructor, you will later have to explicitly call - /// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress - /// or decompress. - /// - public ZlibCodec() + /// + /// Initialize the inflation state with an explicit flag to govern the handling of + /// RFC1950 header bytes. + /// + /// + /// + /// If you want to read a zlib stream you should specify true for + /// expectRfc1950Header. In this case, the library will expect to find a ZLIB + /// header, as defined in RFC + /// 1950, in the compressed stream. If you will be reading a DEFLATE or + /// GZIP stream, which does not have such a header, you will want to specify + /// false. + /// + /// + /// whether to expect an RFC1950 header byte pair when reading + /// the stream of data to be inflated. + /// The number of window bits to use. If you need to ask what that is, + /// then you shouldn't be calling this initializer. + /// Z_OK if everything goes well. + public int InitializeInflate(int windowBits, bool expectRfc1950Header) + { + WindowBits = windowBits; + if (dstate != null) { + throw new ZlibException( + "You may not call InitializeInflate() after calling InitializeDeflate()." + ); + } + istate = new InflateManager(expectRfc1950Header); + return istate.Initialize(this, windowBits); + } + + /// + /// Inflate the data in the InputBuffer, placing the result in the OutputBuffer. + /// + /// + /// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and + /// AvailableBytesOut before calling this method. + /// + /// + /// + /// private void InflateBuffer() + /// { + /// int bufferSize = 1024; + /// byte[] buffer = new byte[bufferSize]; + /// ZlibCodec decompressor = new ZlibCodec(); + /// + /// Console.WriteLine("\n============================================"); + /// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length); + /// MemoryStream ms = new MemoryStream(DecompressedBytes); + /// + /// int rc = decompressor.InitializeInflate(); + /// + /// decompressor.InputBuffer = CompressedBytes; + /// decompressor.NextIn = 0; + /// decompressor.AvailableBytesIn = CompressedBytes.Length; + /// + /// decompressor.OutputBuffer = buffer; + /// + /// // pass 1: inflate + /// do + /// { + /// decompressor.NextOut = 0; + /// decompressor.AvailableBytesOut = buffer.Length; + /// rc = decompressor.Inflate(FlushType.None); + /// + /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + /// throw new Exception("inflating: " + decompressor.Message); + /// + /// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut); + /// } + /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); + /// + /// // pass 2: finish and flush + /// do + /// { + /// decompressor.NextOut = 0; + /// decompressor.AvailableBytesOut = buffer.Length; + /// rc = decompressor.Inflate(FlushType.Finish); + /// + /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) + /// throw new Exception("inflating: " + decompressor.Message); + /// + /// if (buffer.Length - decompressor.AvailableBytesOut > 0) + /// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut); + /// } + /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); + /// + /// decompressor.EndInflate(); + /// } + /// + /// + /// + /// The flush to use when inflating. + /// Z_OK if everything goes well. + public int Inflate(FlushType flush) + { + if (istate is null) + { + throw new ZlibException("No Inflate State!"); + } + return istate.Inflate(flush); + } + + /// + /// Ends an inflation session. + /// + /// + /// Call this after successively calling Inflate(). This will cause all buffers to be flushed. + /// After calling this you cannot call Inflate() without a intervening call to one of the + /// InitializeInflate() overloads. + /// + /// Z_OK if everything goes well. + public int EndInflate() + { + if (istate is null) + { + throw new ZlibException("No Inflate State!"); + } + var ret = istate.End(); + istate = null; + return ret; + } + + /// + /// I don't know what this does! + /// + /// Z_OK if everything goes well. + public int SyncInflate() + { + if (istate is null) + { + throw new ZlibException("No Inflate State!"); + } + return istate.Sync(); + } + + /// + /// Initialize the ZlibCodec for deflation operation. + /// + /// + /// The codec will use the MAX window bits and the default level of compression. + /// + /// + /// + /// int bufferSize = 40000; + /// byte[] CompressedBytes = new byte[bufferSize]; + /// byte[] DecompressedBytes = new byte[bufferSize]; + /// + /// ZlibCodec compressor = new ZlibCodec(); + /// + /// compressor.InitializeDeflate(CompressionLevel.Default); + /// + /// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress); + /// compressor.NextIn = 0; + /// compressor.AvailableBytesIn = compressor.InputBuffer.Length; + /// + /// compressor.OutputBuffer = CompressedBytes; + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = CompressedBytes.Length; + /// + /// while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize) + /// { + /// compressor.Deflate(FlushType.None); + /// } + /// + /// while (true) + /// { + /// int rc= compressor.Deflate(FlushType.Finish); + /// if (rc == ZlibConstants.Z_STREAM_END) break; + /// } + /// + /// compressor.EndDeflate(); + /// + /// + /// + /// Z_OK if all goes well. You generally don't need to check the return code. + public int InitializeDeflate() => _InternalInitializeDeflate(true); + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel. + /// + /// + /// The codec will use the maximum window bits (15) and the specified + /// CompressionLevel. It will emit a ZLIB stream as it compresses. + /// + /// The compression level for the codec. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level) + { + CompressLevel = level; + return _InternalInitializeDeflate(true); + } + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, + /// and the explicit flag governing whether to emit an RFC1950 header byte pair. + /// + /// + /// The codec will use the maximum window bits (15) and the specified CompressionLevel. + /// If you want to generate a zlib stream, you should specify true for + /// wantRfc1950Header. In this case, the library will emit a ZLIB + /// header, as defined in RFC + /// 1950, in the compressed stream. + /// + /// The compression level for the codec. + /// whether to emit an initial RFC1950 byte pair in the compressed stream. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header) + { + CompressLevel = level; + return _InternalInitializeDeflate(wantRfc1950Header); + } + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, + /// and the specified number of window bits. + /// + /// + /// The codec will use the specified number of window bits and the specified CompressionLevel. + /// + /// The compression level for the codec. + /// the number of window bits to use. If you don't know what this means, don't use this method. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, int bits) + { + CompressLevel = level; + WindowBits = bits; + return _InternalInitializeDeflate(true); + } + + /// + /// Initialize the ZlibCodec for deflation operation, using the specified + /// CompressionLevel, the specified number of window bits, and the explicit flag + /// governing whether to emit an RFC1950 header byte pair. + /// + /// + /// The compression level for the codec. + /// whether to emit an initial RFC1950 byte pair in the compressed stream. + /// the number of window bits to use. If you don't know what this means, don't use this method. + /// Z_OK if all goes well. + public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header) + { + CompressLevel = level; + WindowBits = bits; + return _InternalInitializeDeflate(wantRfc1950Header); + } + + private int _InternalInitializeDeflate(bool wantRfc1950Header) + { + if (istate != null) + { + throw new ZlibException( + "You may not call InitializeDeflate() after calling InitializeInflate()." + ); + } + dstate = new DeflateManager(); + dstate.WantRfc1950HeaderBytes = wantRfc1950Header; + + return dstate.Initialize(this, CompressLevel, WindowBits, Strategy); + } + + /// + /// Deflate one batch of data. + /// + /// + /// You must have set InputBuffer and OutputBuffer before calling this method. + /// + /// + /// + /// private void DeflateBuffer(CompressionLevel level) + /// { + /// int bufferSize = 1024; + /// byte[] buffer = new byte[bufferSize]; + /// ZlibCodec compressor = new ZlibCodec(); + /// + /// Console.WriteLine("\n============================================"); + /// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length); + /// MemoryStream ms = new MemoryStream(); + /// + /// int rc = compressor.InitializeDeflate(level); + /// + /// compressor.InputBuffer = UncompressedBytes; + /// compressor.NextIn = 0; + /// compressor.AvailableBytesIn = UncompressedBytes.Length; + /// + /// compressor.OutputBuffer = buffer; + /// + /// // pass 1: deflate + /// do + /// { + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = buffer.Length; + /// rc = compressor.Deflate(FlushType.None); + /// + /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) + /// throw new Exception("deflating: " + compressor.Message); + /// + /// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut); + /// } + /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); + /// + /// // pass 2: finish and flush + /// do + /// { + /// compressor.NextOut = 0; + /// compressor.AvailableBytesOut = buffer.Length; + /// rc = compressor.Deflate(FlushType.Finish); + /// + /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) + /// throw new Exception("deflating: " + compressor.Message); + /// + /// if (buffer.Length - compressor.AvailableBytesOut > 0) + /// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut); + /// } + /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); + /// + /// compressor.EndDeflate(); + /// + /// ms.Seek(0, SeekOrigin.Begin); + /// CompressedBytes = new byte[compressor.TotalBytesOut]; + /// ms.Read(CompressedBytes, 0, CompressedBytes.Length); + /// } + /// + /// + /// whether to flush all data as you deflate. Generally you will want to + /// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to + /// flush everything. + /// + /// Z_OK if all goes well. + public int Deflate(FlushType flush) + { + if (dstate is null) + { + throw new ZlibException("No Deflate State!"); + } + return dstate.Deflate(flush); + } + + /// + /// End a deflation session. + /// + /// + /// Call this after making a series of one or more calls to Deflate(). All buffers are flushed. + /// + /// Z_OK if all goes well. + public int EndDeflate() + { + if (dstate is null) + { + throw new ZlibException("No Deflate State!"); } - /// - /// Create a ZlibCodec that either compresses or decompresses. - /// - /// - /// Indicates whether the codec should compress (deflate) or decompress (inflate). - /// - public ZlibCodec(CompressionMode mode) + // TODO: dinoch Tue, 03 Nov 2009 15:39 (test this) + //int ret = dstate.End(); + dstate = null; + return ZlibConstants.Z_OK; //ret; + } + + /// + /// Reset a codec for another deflation session. + /// + /// + /// Call this to reset the deflation state. For example if a thread is deflating + /// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first + /// block and before the next Deflate(None) of the second block. + /// + /// Z_OK if all goes well. + public void ResetDeflate() + { + if (dstate is null) { - if (mode == CompressionMode.Compress) - { - int rc = InitializeDeflate(); - if (rc != ZlibConstants.Z_OK) - { - throw new ZlibException("Cannot initialize for deflate."); - } - } - else if (mode == CompressionMode.Decompress) - { - int rc = InitializeInflate(); - if (rc != ZlibConstants.Z_OK) - { - throw new ZlibException("Cannot initialize for inflate."); - } - } - else - { - throw new ZlibException("Invalid ZlibStreamFlavor."); - } + throw new ZlibException("No Deflate State!"); + } + dstate.Reset(); + } + + /// + /// Set the CompressionStrategy and CompressionLevel for a deflation session. + /// + /// the level of compression to use. + /// the strategy to use for compression. + /// Z_OK if all goes well. + public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy) + { + if (dstate is null) + { + throw new ZlibException("No Deflate State!"); + } + return dstate.SetParams(level, strategy); + } + + /// + /// Set the dictionary to be used for either Inflation or Deflation. + /// + /// The dictionary bytes to use. + /// Z_OK if all goes well. + public int SetDictionary(byte[] dictionary) + { + if (istate != null) + { + return istate.SetDictionary(dictionary); } - /// - /// Initialize the inflation state. - /// - /// - /// It is not necessary to call this before using the ZlibCodec to inflate data; - /// It is implicitly called when you call the constructor. - /// - /// Z_OK if everything goes well. - public int InitializeInflate() + if (dstate != null) { - return InitializeInflate(WindowBits); + return dstate.SetDictionary(dictionary); } - /// - /// Initialize the inflation state with an explicit flag to - /// govern the handling of RFC1950 header bytes. - /// - /// - /// - /// By default, the ZLIB header defined in RFC 1950 is expected. If - /// you want to read a zlib stream you should specify true for - /// expectRfc1950Header. If you have a deflate stream, you will want to specify - /// false. It is only necessary to invoke this initializer explicitly if you - /// want to specify false. - /// - /// - /// whether to expect an RFC1950 header byte - /// pair when reading the stream of data to be inflated. - /// - /// Z_OK if everything goes well. - public int InitializeInflate(bool expectRfc1950Header) + throw new ZlibException("No Inflate or Deflate state!"); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + internal void flush_pending() + { + var len = dstate.pendingCount; + + if (len > AvailableBytesOut) { - return InitializeInflate(WindowBits, expectRfc1950Header); + len = AvailableBytesOut; + } + if (len == 0) + { + return; } - /// - /// Initialize the ZlibCodec for inflation, with the specified number of window bits. - /// - /// The number of window bits to use. If you need to ask what that is, - /// then you shouldn't be calling this initializer. - /// Z_OK if all goes well. - public int InitializeInflate(int windowBits) + if ( + dstate.pending.Length <= dstate.nextPending + || OutputBuffer.Length <= NextOut + || dstate.pending.Length < (dstate.nextPending + len) + || OutputBuffer.Length < (NextOut + len) + ) { - WindowBits = windowBits; - return InitializeInflate(windowBits, true); + throw new ZlibException( + string.Format( + "Invalid State. (pending.Length={0}, pendingCount={1})", + dstate.pending.Length, + dstate.pendingCount + ) + ); } - /// - /// Initialize the inflation state with an explicit flag to govern the handling of - /// RFC1950 header bytes. - /// - /// - /// - /// If you want to read a zlib stream you should specify true for - /// expectRfc1950Header. In this case, the library will expect to find a ZLIB - /// header, as defined in RFC - /// 1950, in the compressed stream. If you will be reading a DEFLATE or - /// GZIP stream, which does not have such a header, you will want to specify - /// false. - /// - /// - /// whether to expect an RFC1950 header byte pair when reading - /// the stream of data to be inflated. - /// The number of window bits to use. If you need to ask what that is, - /// then you shouldn't be calling this initializer. - /// Z_OK if everything goes well. - public int InitializeInflate(int windowBits, bool expectRfc1950Header) + Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len); + + NextOut += len; + dstate.nextPending += len; + TotalBytesOut += len; + AvailableBytesOut -= len; + dstate.pendingCount -= len; + if (dstate.pendingCount == 0) { - WindowBits = windowBits; - if (dstate != null) - { - throw new ZlibException("You may not call InitializeInflate() after calling InitializeDeflate()."); - } - istate = new InflateManager(expectRfc1950Header); - return istate.Initialize(this, windowBits); - } - - /// - /// Inflate the data in the InputBuffer, placing the result in the OutputBuffer. - /// - /// - /// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and - /// AvailableBytesOut before calling this method. - /// - /// - /// - /// private void InflateBuffer() - /// { - /// int bufferSize = 1024; - /// byte[] buffer = new byte[bufferSize]; - /// ZlibCodec decompressor = new ZlibCodec(); - /// - /// Console.WriteLine("\n============================================"); - /// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length); - /// MemoryStream ms = new MemoryStream(DecompressedBytes); - /// - /// int rc = decompressor.InitializeInflate(); - /// - /// decompressor.InputBuffer = CompressedBytes; - /// decompressor.NextIn = 0; - /// decompressor.AvailableBytesIn = CompressedBytes.Length; - /// - /// decompressor.OutputBuffer = buffer; - /// - /// // pass 1: inflate - /// do - /// { - /// decompressor.NextOut = 0; - /// decompressor.AvailableBytesOut = buffer.Length; - /// rc = decompressor.Inflate(FlushType.None); - /// - /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - /// throw new Exception("inflating: " + decompressor.Message); - /// - /// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut); - /// } - /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); - /// - /// // pass 2: finish and flush - /// do - /// { - /// decompressor.NextOut = 0; - /// decompressor.AvailableBytesOut = buffer.Length; - /// rc = decompressor.Inflate(FlushType.Finish); - /// - /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) - /// throw new Exception("inflating: " + decompressor.Message); - /// - /// if (buffer.Length - decompressor.AvailableBytesOut > 0) - /// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut); - /// } - /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); - /// - /// decompressor.EndInflate(); - /// } - /// - /// - /// - /// The flush to use when inflating. - /// Z_OK if everything goes well. - public int Inflate(FlushType flush) - { - if (istate is null) - { - throw new ZlibException("No Inflate State!"); - } - return istate.Inflate(flush); - } - - /// - /// Ends an inflation session. - /// - /// - /// Call this after successively calling Inflate(). This will cause all buffers to be flushed. - /// After calling this you cannot call Inflate() without a intervening call to one of the - /// InitializeInflate() overloads. - /// - /// Z_OK if everything goes well. - public int EndInflate() - { - if (istate is null) - { - throw new ZlibException("No Inflate State!"); - } - int ret = istate.End(); - istate = null; - return ret; - } - - /// - /// I don't know what this does! - /// - /// Z_OK if everything goes well. - public int SyncInflate() - { - if (istate is null) - { - throw new ZlibException("No Inflate State!"); - } - return istate.Sync(); - } - - /// - /// Initialize the ZlibCodec for deflation operation. - /// - /// - /// The codec will use the MAX window bits and the default level of compression. - /// - /// - /// - /// int bufferSize = 40000; - /// byte[] CompressedBytes = new byte[bufferSize]; - /// byte[] DecompressedBytes = new byte[bufferSize]; - /// - /// ZlibCodec compressor = new ZlibCodec(); - /// - /// compressor.InitializeDeflate(CompressionLevel.Default); - /// - /// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress); - /// compressor.NextIn = 0; - /// compressor.AvailableBytesIn = compressor.InputBuffer.Length; - /// - /// compressor.OutputBuffer = CompressedBytes; - /// compressor.NextOut = 0; - /// compressor.AvailableBytesOut = CompressedBytes.Length; - /// - /// while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize) - /// { - /// compressor.Deflate(FlushType.None); - /// } - /// - /// while (true) - /// { - /// int rc= compressor.Deflate(FlushType.Finish); - /// if (rc == ZlibConstants.Z_STREAM_END) break; - /// } - /// - /// compressor.EndDeflate(); - /// - /// - /// - /// Z_OK if all goes well. You generally don't need to check the return code. - public int InitializeDeflate() - { - return _InternalInitializeDeflate(true); - } - - /// - /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel. - /// - /// - /// The codec will use the maximum window bits (15) and the specified - /// CompressionLevel. It will emit a ZLIB stream as it compresses. - /// - /// The compression level for the codec. - /// Z_OK if all goes well. - public int InitializeDeflate(CompressionLevel level) - { - CompressLevel = level; - return _InternalInitializeDeflate(true); - } - - /// - /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, - /// and the explicit flag governing whether to emit an RFC1950 header byte pair. - /// - /// - /// The codec will use the maximum window bits (15) and the specified CompressionLevel. - /// If you want to generate a zlib stream, you should specify true for - /// wantRfc1950Header. In this case, the library will emit a ZLIB - /// header, as defined in RFC - /// 1950, in the compressed stream. - /// - /// The compression level for the codec. - /// whether to emit an initial RFC1950 byte pair in the compressed stream. - /// Z_OK if all goes well. - public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header) - { - CompressLevel = level; - return _InternalInitializeDeflate(wantRfc1950Header); - } - - /// - /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, - /// and the specified number of window bits. - /// - /// - /// The codec will use the specified number of window bits and the specified CompressionLevel. - /// - /// The compression level for the codec. - /// the number of window bits to use. If you don't know what this means, don't use this method. - /// Z_OK if all goes well. - public int InitializeDeflate(CompressionLevel level, int bits) - { - CompressLevel = level; - WindowBits = bits; - return _InternalInitializeDeflate(true); - } - - /// - /// Initialize the ZlibCodec for deflation operation, using the specified - /// CompressionLevel, the specified number of window bits, and the explicit flag - /// governing whether to emit an RFC1950 header byte pair. - /// - /// - /// The compression level for the codec. - /// whether to emit an initial RFC1950 byte pair in the compressed stream. - /// the number of window bits to use. If you don't know what this means, don't use this method. - /// Z_OK if all goes well. - public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header) - { - CompressLevel = level; - WindowBits = bits; - return _InternalInitializeDeflate(wantRfc1950Header); - } - - private int _InternalInitializeDeflate(bool wantRfc1950Header) - { - if (istate != null) - { - throw new ZlibException("You may not call InitializeDeflate() after calling InitializeInflate()."); - } - dstate = new DeflateManager(); - dstate.WantRfc1950HeaderBytes = wantRfc1950Header; - - return dstate.Initialize(this, CompressLevel, WindowBits, Strategy); - } - - /// - /// Deflate one batch of data. - /// - /// - /// You must have set InputBuffer and OutputBuffer before calling this method. - /// - /// - /// - /// private void DeflateBuffer(CompressionLevel level) - /// { - /// int bufferSize = 1024; - /// byte[] buffer = new byte[bufferSize]; - /// ZlibCodec compressor = new ZlibCodec(); - /// - /// Console.WriteLine("\n============================================"); - /// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length); - /// MemoryStream ms = new MemoryStream(); - /// - /// int rc = compressor.InitializeDeflate(level); - /// - /// compressor.InputBuffer = UncompressedBytes; - /// compressor.NextIn = 0; - /// compressor.AvailableBytesIn = UncompressedBytes.Length; - /// - /// compressor.OutputBuffer = buffer; - /// - /// // pass 1: deflate - /// do - /// { - /// compressor.NextOut = 0; - /// compressor.AvailableBytesOut = buffer.Length; - /// rc = compressor.Deflate(FlushType.None); - /// - /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - /// throw new Exception("deflating: " + compressor.Message); - /// - /// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut); - /// } - /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); - /// - /// // pass 2: finish and flush - /// do - /// { - /// compressor.NextOut = 0; - /// compressor.AvailableBytesOut = buffer.Length; - /// rc = compressor.Deflate(FlushType.Finish); - /// - /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) - /// throw new Exception("deflating: " + compressor.Message); - /// - /// if (buffer.Length - compressor.AvailableBytesOut > 0) - /// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut); - /// } - /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); - /// - /// compressor.EndDeflate(); - /// - /// ms.Seek(0, SeekOrigin.Begin); - /// CompressedBytes = new byte[compressor.TotalBytesOut]; - /// ms.Read(CompressedBytes, 0, CompressedBytes.Length); - /// } - /// - /// - /// whether to flush all data as you deflate. Generally you will want to - /// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to - /// flush everything. - /// - /// Z_OK if all goes well. - public int Deflate(FlushType flush) - { - if (dstate is null) - { - throw new ZlibException("No Deflate State!"); - } - return dstate.Deflate(flush); - } - - /// - /// End a deflation session. - /// - /// - /// Call this after making a series of one or more calls to Deflate(). All buffers are flushed. - /// - /// Z_OK if all goes well. - public int EndDeflate() - { - if (dstate is null) - { - throw new ZlibException("No Deflate State!"); - } - - // TODO: dinoch Tue, 03 Nov 2009 15:39 (test this) - //int ret = dstate.End(); - dstate = null; - return ZlibConstants.Z_OK; //ret; - } - - /// - /// Reset a codec for another deflation session. - /// - /// - /// Call this to reset the deflation state. For example if a thread is deflating - /// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first - /// block and before the next Deflate(None) of the second block. - /// - /// Z_OK if all goes well. - public void ResetDeflate() - { - if (dstate is null) - { - throw new ZlibException("No Deflate State!"); - } - dstate.Reset(); - } - - /// - /// Set the CompressionStrategy and CompressionLevel for a deflation session. - /// - /// the level of compression to use. - /// the strategy to use for compression. - /// Z_OK if all goes well. - public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy) - { - if (dstate is null) - { - throw new ZlibException("No Deflate State!"); - } - return dstate.SetParams(level, strategy); - } - - /// - /// Set the dictionary to be used for either Inflation or Deflation. - /// - /// The dictionary bytes to use. - /// Z_OK if all goes well. - public int SetDictionary(byte[] dictionary) - { - if (istate != null) - { - return istate.SetDictionary(dictionary); - } - - if (dstate != null) - { - return dstate.SetDictionary(dictionary); - } - - throw new ZlibException("No Inflate or Deflate state!"); - } - - // Flush as much pending output as possible. All deflate() output goes - // through this function so some applications may wish to modify it - // to avoid allocating a large strm->next_out buffer and copying into it. - // (See also read_buf()). - internal void flush_pending() - { - int len = dstate.pendingCount; - - if (len > AvailableBytesOut) - { - len = AvailableBytesOut; - } - if (len == 0) - { - return; - } - - if (dstate.pending.Length <= dstate.nextPending || - OutputBuffer.Length <= NextOut || - dstate.pending.Length < (dstate.nextPending + len) || - OutputBuffer.Length < (NextOut + len)) - { - throw new ZlibException(String.Format("Invalid State. (pending.Length={0}, pendingCount={1})", - dstate.pending.Length, dstate.pendingCount)); - } - - Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len); - - NextOut += len; - dstate.nextPending += len; - TotalBytesOut += len; - AvailableBytesOut -= len; - dstate.pendingCount -= len; - if (dstate.pendingCount == 0) - { - dstate.nextPending = 0; - } - } - - // Read a new buffer from the current input stream, update the adler32 - // and total number of bytes read. All deflate() input goes through - // this function so some applications may wish to modify it to avoid - // allocating a large strm->next_in buffer and copying from it. - // (See also flush_pending()). - internal int read_buf(byte[] buf, int start, int size) - { - int len = AvailableBytesIn; - - if (len > size) - { - len = size; - } - if (len == 0) - { - return 0; - } - - AvailableBytesIn -= len; - - if (dstate.WantRfc1950HeaderBytes) - { - _adler32 = Algorithms.Adler32.Calculate(_adler32, InputBuffer.AsSpan(NextIn, len)); - } - Array.Copy(InputBuffer, NextIn, buf, start, len); - NextIn += len; - TotalBytesIn += len; - return len; + dstate.nextPending = 0; } } -} \ No newline at end of file + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + internal int read_buf(byte[] buf, int start, int size) + { + var len = AvailableBytesIn; + + if (len > size) + { + len = size; + } + if (len == 0) + { + return 0; + } + + AvailableBytesIn -= len; + + if (dstate.WantRfc1950HeaderBytes) + { + _adler32 = Algorithms.Adler32.Calculate(_adler32, InputBuffer.AsSpan(NextIn, len)); + } + Array.Copy(InputBuffer, NextIn, buf, start, len); + NextIn += len; + TotalBytesIn += len; + return len; + } +} diff --git a/src/SharpCompress/Compressors/Deflate/ZlibConstants.cs b/src/SharpCompress/Compressors/Deflate/ZlibConstants.cs index a6780614..1a372a31 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibConstants.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibConstants.cs @@ -1,20 +1,20 @@ // ZlibConstants.cs // ------------------------------------------------------------------ // -// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. +// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // -// This code is licensed under the Microsoft Public License. +// This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // -// last saved (in emacs): +// last saved (in emacs): // Time-stamp: <2009-November-03 18:50:19> // // ------------------------------------------------------------------ @@ -25,22 +25,22 @@ // copyright to that code is included here. // // ------------------------------------------------------------------ -// +// // Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in // the documentation and/or other materials provided with the distribution. -// +// // 3. The names of the authors may not be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, @@ -51,7 +51,7 @@ // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // ----------------------------------------------------------------------- // // This program is based on zlib-1.1.3; credit to authors @@ -61,65 +61,64 @@ // ----------------------------------------------------------------------- -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +/// +/// A bunch of constants used in the Zlib interface. +/// +internal static class ZlibConstants { /// - /// A bunch of constants used in the Zlib interface. + /// The maximum number of window bits for the Deflate algorithm. /// - internal static class ZlibConstants - { - /// - /// The maximum number of window bits for the Deflate algorithm. - /// - public const int WindowBitsMax = 15; // 32K LZ77 window + public const int WindowBitsMax = 15; // 32K LZ77 window - /// - /// The default number of window bits for the Deflate algorithm. - /// - public const int WindowBitsDefault = WindowBitsMax; + /// + /// The default number of window bits for the Deflate algorithm. + /// + public const int WindowBitsDefault = WindowBitsMax; - /// - /// indicates everything is A-OK - /// - public const int Z_OK = 0; + /// + /// indicates everything is A-OK + /// + public const int Z_OK = 0; - /// - /// Indicates that the last operation reached the end of the stream. - /// - public const int Z_STREAM_END = 1; + /// + /// Indicates that the last operation reached the end of the stream. + /// + public const int Z_STREAM_END = 1; - /// - /// The operation ended in need of a dictionary. - /// - public const int Z_NEED_DICT = 2; + /// + /// The operation ended in need of a dictionary. + /// + public const int Z_NEED_DICT = 2; - /// - /// There was an error with the stream - not enough data, not open and readable, etc. - /// - public const int Z_STREAM_ERROR = -2; + /// + /// There was an error with the stream - not enough data, not open and readable, etc. + /// + public const int Z_STREAM_ERROR = -2; - /// - /// There was an error with the data - not enough data, bad data, etc. - /// - public const int Z_DATA_ERROR = -3; + /// + /// There was an error with the data - not enough data, bad data, etc. + /// + public const int Z_DATA_ERROR = -3; - /// - /// There was an error with the working buffer. - /// - public const int Z_BUF_ERROR = -5; + /// + /// There was an error with the working buffer. + /// + public const int Z_BUF_ERROR = -5; - /// - /// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes. - /// + /// + /// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes. + /// #if NETCF - public const int WorkingBufferSizeDefault = 8192; + public const int WorkingBufferSizeDefault = 8192; #else - public const int WorkingBufferSizeDefault = 16384; + public const int WorkingBufferSizeDefault = 16384; #endif - /// - /// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes. - /// - public const int WorkingBufferSizeMin = 1024; - } -} \ No newline at end of file + /// + /// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes. + /// + public const int WorkingBufferSizeMin = 1024; +} diff --git a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs index c82040ef..e2c69b53 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibStream.cs @@ -29,316 +29,310 @@ using System; using System.IO; using System.Text; -namespace SharpCompress.Compressors.Deflate +namespace SharpCompress.Compressors.Deflate; + +public class ZlibStream : Stream { - public class ZlibStream : Stream + private readonly ZlibBaseStream _baseStream; + private bool _disposed; + + public ZlibStream(Stream stream, CompressionMode mode) + : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) { } + + public ZlibStream(Stream stream, CompressionMode mode, CompressionLevel level) + : this(stream, mode, level, Encoding.UTF8) { } + + public ZlibStream( + Stream stream, + CompressionMode mode, + CompressionLevel level, + Encoding encoding + ) => _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); + + #region Zlib properties + + /// + /// This property sets the flush behavior on the stream. + /// Sorry, though, not sure exactly how to describe all the various settings. + /// + public virtual FlushType FlushMode { - private readonly ZlibBaseStream _baseStream; - private bool _disposed; - - public ZlibStream(Stream stream, CompressionMode mode) - : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) - { - } - - public ZlibStream(Stream stream, CompressionMode mode, CompressionLevel level) - : this(stream, mode, level, Encoding.UTF8) - { - } - - public ZlibStream(Stream stream, CompressionMode mode, CompressionLevel level, Encoding encoding) - { - _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, encoding); - } - - #region Zlib properties - - /// - /// This property sets the flush behavior on the stream. - /// Sorry, though, not sure exactly how to describe all the various settings. - /// - public virtual FlushType FlushMode - { - get => (_baseStream._flushMode); - set - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - _baseStream._flushMode = value; - } - } - - /// - /// The size of the working buffer for the compression codec. - /// - /// - /// - /// - /// The working buffer is used for all stream operations. The default size is - /// 1024 bytes. The minimum size is 128 bytes. You may get better performance - /// with a larger buffer. Then again, you might not. You would have to test - /// it. - /// - /// - /// - /// Set this before the first call to Read() or Write() on the - /// stream. If you try to set it afterwards, it will throw. - /// - /// - public int BufferSize - { - get => _baseStream._bufferSize; - set - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - if (_baseStream._workingBuffer != null) - { - throw new ZlibException("The working buffer is already set."); - } - if (value < ZlibConstants.WorkingBufferSizeMin) - { - throw new ZlibException( - String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, - ZlibConstants.WorkingBufferSizeMin)); - } - _baseStream._bufferSize = value; - } - } - - /// Returns the total number of bytes input so far. - public virtual long TotalIn => _baseStream._z.TotalBytesIn; - - /// Returns the total number of bytes output so far. - public virtual long TotalOut => _baseStream._z.TotalBytesOut; - - #endregion - - #region System.IO.Stream methods - - /// - /// Indicates whether the stream can be read. - /// - /// - /// The return value depends on whether the captive stream supports reading. - /// - public override bool CanRead - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - return _baseStream._stream.CanRead; - } - } - - /// - /// Indicates whether the stream supports Seek operations. - /// - /// - /// Always returns false. - /// - public override bool CanSeek => false; - - /// - /// Indicates whether the stream can be written. - /// - /// - /// The return value depends on whether the captive stream supports writing. - /// - public override bool CanWrite - { - get - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - return _baseStream._stream.CanWrite; - } - } - - /// - /// Reading this property always throws a . - /// - public override long Length => throw new NotSupportedException(); - - /// - /// The position of the stream pointer. - /// - /// - /// - /// Setting this property always throws a . Reading will return the total bytes - /// written out, if used in writing, or the total bytes read in, if used in - /// reading. The count may refer to compressed bytes or uncompressed bytes, - /// depending on how you've used the stream. - /// - public override long Position - { - get - { - if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Writer) - { - return _baseStream._z.TotalBytesOut; - } - if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Reader) - { - return _baseStream._z.TotalBytesIn; - } - return 0; - } - - set => throw new NotSupportedException(); - } - - /// - /// Dispose the stream. - /// - /// - /// This may or may not result in a Close() call on the captive stream. - /// - protected override void Dispose(bool disposing) - { - try - { - if (!_disposed) - { - if (disposing) - { - _baseStream?.Dispose(); - } - _disposed = true; - } - } - finally - { - base.Dispose(disposing); - } - } - - /// - /// Flush the stream. - /// - public override void Flush() + get => (_baseStream._flushMode); + set { if (_disposed) { throw new ObjectDisposedException("ZlibStream"); } - _baseStream.Flush(); + _baseStream._flushMode = value; } - - /// - /// Read data from the stream. - /// - /// - /// - /// - /// - /// If you wish to use the ZlibStream to compress data while reading, - /// you can create a ZlibStream with CompressionMode.Compress, - /// providing an uncompressed data stream. Then call Read() on that - /// ZlibStream, and the data read will be compressed. If you wish to - /// use the ZlibStream to decompress data while reading, you can create - /// a ZlibStream with CompressionMode.Decompress, providing a - /// readable compressed data stream. Then call Read() on that - /// ZlibStream, and the data will be decompressed as it is read. - /// - /// - /// - /// A ZlibStream can be used for Read() or Write(), but - /// not both. - /// - /// - /// - /// The buffer into which the read data should be placed. - /// the offset within that data array to put the first byte read. - /// the number of bytes to read. - public override int Read(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - return _baseStream.Read(buffer, offset, count); - } - - public override int ReadByte() - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - return _baseStream.ReadByte(); - } - - /// - /// Calling this method always throws a . - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - /// Calling this method always throws a . - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - /// Write data to the stream. - /// - /// - /// - /// - /// - /// If you wish to use the ZlibStream to compress data while writing, - /// you can create a ZlibStream with CompressionMode.Compress, - /// and a writable output stream. Then call Write() on that - /// ZlibStream, providing uncompressed data as input. The data sent to - /// the output stream will be the compressed form of the data written. If you - /// wish to use the ZlibStream to decompress data while writing, you - /// can create a ZlibStream with CompressionMode.Decompress, and a - /// writable output stream. Then call Write() on that stream, - /// providing previously compressed data. The data sent to the output stream - /// will be the decompressed form of the data written. - /// - /// - /// - /// A ZlibStream can be used for Read() or Write(), but not both. - /// - /// - /// The buffer holding data to write to the stream. - /// the offset within that data array to find the first byte to write. - /// the number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - _baseStream.Write(buffer, offset, count); - } - - public override void WriteByte(byte value) - { - if (_disposed) - { - throw new ObjectDisposedException("ZlibStream"); - } - _baseStream.WriteByte(value); - } - - #endregion System.IO.Stream methods } -} \ No newline at end of file + + /// + /// The size of the working buffer for the compression codec. + /// + /// + /// + /// + /// The working buffer is used for all stream operations. The default size is + /// 1024 bytes. The minimum size is 128 bytes. You may get better performance + /// with a larger buffer. Then again, you might not. You would have to test + /// it. + /// + /// + /// + /// Set this before the first call to Read() or Write() on the + /// stream. If you try to set it afterwards, it will throw. + /// + /// + public int BufferSize + { + get => _baseStream._bufferSize; + set + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + if (_baseStream._workingBuffer != null) + { + throw new ZlibException("The working buffer is already set."); + } + if (value < ZlibConstants.WorkingBufferSizeMin) + { + throw new ZlibException( + string.Format( + "Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", + value, + ZlibConstants.WorkingBufferSizeMin + ) + ); + } + _baseStream._bufferSize = value; + } + } + + /// Returns the total number of bytes input so far. + public virtual long TotalIn => _baseStream._z.TotalBytesIn; + + /// Returns the total number of bytes output so far. + public virtual long TotalOut => _baseStream._z.TotalBytesOut; + + #endregion + + #region System.IO.Stream methods + + /// + /// Indicates whether the stream can be read. + /// + /// + /// The return value depends on whether the captive stream supports reading. + /// + public override bool CanRead + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + return _baseStream._stream.CanRead; + } + } + + /// + /// Indicates whether the stream supports Seek operations. + /// + /// + /// Always returns false. + /// + public override bool CanSeek => false; + + /// + /// Indicates whether the stream can be written. + /// + /// + /// The return value depends on whether the captive stream supports writing. + /// + public override bool CanWrite + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + return _baseStream._stream.CanWrite; + } + } + + /// + /// Reading this property always throws a . + /// + public override long Length => throw new NotSupportedException(); + + /// + /// The position of the stream pointer. + /// + /// + /// + /// Setting this property always throws a . Reading will return the total bytes + /// written out, if used in writing, or the total bytes read in, if used in + /// reading. The count may refer to compressed bytes or uncompressed bytes, + /// depending on how you've used the stream. + /// + public override long Position + { + get + { + if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Writer) + { + return _baseStream._z.TotalBytesOut; + } + if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Reader) + { + return _baseStream._z.TotalBytesIn; + } + return 0; + } + set => throw new NotSupportedException(); + } + + /// + /// Dispose the stream. + /// + /// + /// This may or may not result in a Close() call on the captive stream. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!_disposed) + { + if (disposing) + { + _baseStream?.Dispose(); + } + _disposed = true; + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// Flush the stream. + /// + public override void Flush() + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + _baseStream.Flush(); + } + + /// + /// Read data from the stream. + /// + /// + /// + /// + /// + /// If you wish to use the ZlibStream to compress data while reading, + /// you can create a ZlibStream with CompressionMode.Compress, + /// providing an uncompressed data stream. Then call Read() on that + /// ZlibStream, and the data read will be compressed. If you wish to + /// use the ZlibStream to decompress data while reading, you can create + /// a ZlibStream with CompressionMode.Decompress, providing a + /// readable compressed data stream. Then call Read() on that + /// ZlibStream, and the data will be decompressed as it is read. + /// + /// + /// + /// A ZlibStream can be used for Read() or Write(), but + /// not both. + /// + /// + /// + /// The buffer into which the read data should be placed. + /// the offset within that data array to put the first byte read. + /// the number of bytes to read. + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + return _baseStream.Read(buffer, offset, count); + } + + public override int ReadByte() + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + return _baseStream.ReadByte(); + } + + /// + /// Calling this method always throws a . + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + /// + /// Calling this method always throws a . + /// + public override void SetLength(long value) => throw new NotSupportedException(); + + /// + /// Write data to the stream. + /// + /// + /// + /// + /// + /// If you wish to use the ZlibStream to compress data while writing, + /// you can create a ZlibStream with CompressionMode.Compress, + /// and a writable output stream. Then call Write() on that + /// ZlibStream, providing uncompressed data as input. The data sent to + /// the output stream will be the compressed form of the data written. If you + /// wish to use the ZlibStream to decompress data while writing, you + /// can create a ZlibStream with CompressionMode.Decompress, and a + /// writable output stream. Then call Write() on that stream, + /// providing previously compressed data. The data sent to the output stream + /// will be the decompressed form of the data written. + /// + /// + /// + /// A ZlibStream can be used for Read() or Write(), but not both. + /// + /// + /// The buffer holding data to write to the stream. + /// the offset within that data array to find the first byte to write. + /// the number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + _baseStream.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + if (_disposed) + { + throw new ObjectDisposedException("ZlibStream"); + } + _baseStream.WriteByte(value); + } + + #endregion System.IO.Stream methods +} diff --git a/src/SharpCompress/Compressors/Deflate64/BlockType.cs b/src/SharpCompress/Compressors/Deflate64/BlockType.cs index c8938079..5bd9eb83 100644 --- a/src/SharpCompress/Compressors/Deflate64/BlockType.cs +++ b/src/SharpCompress/Compressors/Deflate64/BlockType.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +internal enum BlockType { - internal enum BlockType - { - Uncompressed = 0, - Static = 1, - Dynamic = 2 - } + Uncompressed = 0, + Static = 1, + Dynamic = 2 } diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs index c05f0c2a..149f7c92 100644 --- a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -10,273 +10,264 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +public sealed class Deflate64Stream : Stream { - public sealed class Deflate64Stream : Stream + private const int DEFAULT_BUFFER_SIZE = 8192; + + private Stream _stream; + private CompressionMode _mode; + private InflaterManaged _inflater; + private byte[] _buffer; + + public Deflate64Stream(Stream stream, CompressionMode mode) { - private const int DEFAULT_BUFFER_SIZE = 8192; - - private Stream _stream; - private CompressionMode _mode; - private InflaterManaged _inflater; - private byte[] _buffer; - - public Deflate64Stream(Stream stream, CompressionMode mode) + if (stream is null) { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (mode != CompressionMode.Decompress) - { - throw new NotImplementedException("Deflate64: this implementation only supports decompression"); - } - - if (!stream.CanRead) - { - throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); - } - - InitializeInflater(stream, ZipCompressionMethod.Deflate64); + throw new ArgumentNullException(nameof(stream)); } - /// - /// Sets up this DeflateManagedStream to be used for Inflation/Decompression - /// - private void InitializeInflater(Stream stream, ZipCompressionMethod method = ZipCompressionMethod.Deflate) + if (mode != CompressionMode.Decompress) { - Debug.Assert(stream != null); - Debug.Assert(method == ZipCompressionMethod.Deflate || method == ZipCompressionMethod.Deflate64); - if (!stream.CanRead) - { - throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); - } - - _inflater = new InflaterManaged(method == ZipCompressionMethod.Deflate64); - - _stream = stream; - _mode = CompressionMode.Decompress; - _buffer = new byte[DEFAULT_BUFFER_SIZE]; + throw new NotImplementedException( + "Deflate64: this implementation only supports decompression" + ); } - public override bool CanRead + if (!stream.CanRead) { - get - { - if (_stream is null) - { - return false; - } - - return (_mode == CompressionMode.Decompress && _stream.CanRead); - } + throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); } - public override bool CanWrite - { - get - { - if (_stream is null) - { - return false; - } + InitializeInflater(stream, ZipCompressionMethod.Deflate64); + } - return (_mode == CompressionMode.Compress && _stream.CanWrite); - } + /// + /// Sets up this DeflateManagedStream to be used for Inflation/Decompression + /// + private void InitializeInflater( + Stream stream, + ZipCompressionMethod method = ZipCompressionMethod.Deflate + ) + { + Debug.Assert(stream != null); + Debug.Assert( + method == ZipCompressionMethod.Deflate || method == ZipCompressionMethod.Deflate64 + ); + if (!stream.CanRead) + { + throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); } - public override bool CanSeek => false; + _inflater = new InflaterManaged(method == ZipCompressionMethod.Deflate64); - public override long Length - { - get { throw new NotSupportedException("Deflate64: not supported"); } - } + _stream = stream; + _mode = CompressionMode.Decompress; + _buffer = new byte[DEFAULT_BUFFER_SIZE]; + } - public override long Position - { - get { throw new NotSupportedException("Deflate64: not supported"); } - set { throw new NotSupportedException("Deflate64: not supported"); } - } - - public override void Flush() - { - EnsureNotDisposed(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("Deflate64: not supported"); - } - - public override void SetLength(long value) - { - throw new NotSupportedException("Deflate64: not supported"); - } - - public override int Read(byte[] array, int offset, int count) - { - EnsureDecompressionMode(); - ValidateParameters(array, offset, count); - EnsureNotDisposed(); - - int bytesRead; - int currentOffset = offset; - int remainingCount = count; - - while (true) - { - bytesRead = _inflater.Inflate(array, currentOffset, remainingCount); - currentOffset += bytesRead; - remainingCount -= bytesRead; - - if (remainingCount == 0) - { - break; - } - - if (_inflater.Finished()) - { - // if we finished decompressing, we can't have anything left in the outputwindow. - Debug.Assert(_inflater.AvailableOutput == 0, "We should have copied all stuff out!"); - break; - } - - int bytes = _stream.Read(_buffer, 0, _buffer.Length); - if (bytes <= 0) - { - break; - } - else if (bytes > _buffer.Length) - { - // The stream is either malicious or poorly implemented and returned a number of - // bytes larger than the buffer supplied to it. - throw new InvalidDataException("Deflate64: invalid data"); - } - - _inflater.SetInput(_buffer, 0, bytes); - } - - return count - remainingCount; - } - - private void ValidateParameters(byte[] array, int offset, int count) - { - if (array is null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (array.Length - offset < count) - { - throw new ArgumentException("Deflate64: invalid offset/count combination"); - } - } - - private void EnsureNotDisposed() + public override bool CanRead + { + get { if (_stream is null) { - ThrowStreamClosedException(); + return false; } - } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowStreamClosedException() - { - throw new ObjectDisposedException(null, "Deflate64: stream has been disposed"); + return (_mode == CompressionMode.Decompress && _stream.CanRead); } + } - private void EnsureDecompressionMode() + public override bool CanWrite + { + get { - if (_mode != CompressionMode.Decompress) + if (_stream is null) { - ThrowCannotReadFromDeflateManagedStreamException(); + return false; } - } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotReadFromDeflateManagedStreamException() - { - throw new InvalidOperationException("Deflate64: cannot read from this stream"); + return (_mode == CompressionMode.Compress && _stream.CanWrite); } + } - private void EnsureCompressionMode() + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException("Deflate64: not supported"); + + public override long Position + { + get => throw new NotSupportedException("Deflate64: not supported"); + set => throw new NotSupportedException("Deflate64: not supported"); + } + + public override void Flush() => EnsureNotDisposed(); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Deflate64: not supported"); + + public override void SetLength(long value) => + throw new NotSupportedException("Deflate64: not supported"); + + public override int Read(byte[] array, int offset, int count) + { + EnsureDecompressionMode(); + ValidateParameters(array, offset, count); + EnsureNotDisposed(); + + int bytesRead; + int currentOffset = offset; + int remainingCount = count; + + while (true) { - if (_mode != CompressionMode.Compress) + bytesRead = _inflater.Inflate(array, currentOffset, remainingCount); + currentOffset += bytesRead; + remainingCount -= bytesRead; + + if (remainingCount == 0) { - ThrowCannotWriteToDeflateManagedStreamException(); + break; } + + if (_inflater.Finished()) + { + // if we finished decompressing, we can't have anything left in the outputwindow. + Debug.Assert( + _inflater.AvailableOutput == 0, + "We should have copied all stuff out!" + ); + break; + } + + int bytes = _stream.Read(_buffer, 0, _buffer.Length); + if (bytes <= 0) + { + break; + } + else if (bytes > _buffer.Length) + { + // The stream is either malicious or poorly implemented and returned a number of + // bytes larger than the buffer supplied to it. + throw new InvalidDataException("Deflate64: invalid data"); + } + + _inflater.SetInput(_buffer, 0, bytes); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotWriteToDeflateManagedStreamException() + return count - remainingCount; + } + + private void ValidateParameters(byte[] array, int offset, int count) + { + if (array is null) { - throw new InvalidOperationException("Deflate64: cannot write to this stream"); + throw new ArgumentNullException(nameof(array)); } - public override void Write(byte[] array, int offset, int count) + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (array.Length - offset < count) + { + throw new ArgumentException("Deflate64: invalid offset/count combination"); + } + } + + private void EnsureNotDisposed() + { + if (_stream is null) + { + ThrowStreamClosedException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowStreamClosedException() => + throw new ObjectDisposedException(null, "Deflate64: stream has been disposed"); + + private void EnsureDecompressionMode() + { + if (_mode != CompressionMode.Decompress) + { + ThrowCannotReadFromDeflateManagedStreamException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotReadFromDeflateManagedStreamException() => + throw new InvalidOperationException("Deflate64: cannot read from this stream"); + + private void EnsureCompressionMode() + { + if (_mode != CompressionMode.Compress) { ThrowCannotWriteToDeflateManagedStreamException(); } + } - // This is called by Dispose: - private void PurgeBuffers(bool disposing) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotWriteToDeflateManagedStreamException() => + throw new InvalidOperationException("Deflate64: cannot write to this stream"); + + public override void Write(byte[] array, int offset, int count) => + ThrowCannotWriteToDeflateManagedStreamException(); + + // This is called by Dispose: + private void PurgeBuffers(bool disposing) + { + if (!disposing) { - if (!disposing) - { - return; - } - - if (_stream is null) - { - return; - } - - Flush(); + return; } - protected override void Dispose(bool disposing) + if (_stream is null) { + return; + } + + Flush(); + } + + protected override void Dispose(bool disposing) + { + try + { + PurgeBuffers(disposing); + } + finally + { + // Close the underlying stream even if PurgeBuffers threw. + // Stream.Close() may throw here (may or may not be due to the same error). + // In this case, we still need to clean up internal resources, hence the inner finally blocks. try { - PurgeBuffers(disposing); + if (disposing) + { + _stream?.Dispose(); + } } finally { - // Close the underlying stream even if PurgeBuffers threw. - // Stream.Close() may throw here (may or may not be due to the same error). - // In this case, we still need to clean up internal resources, hence the inner finally blocks. + _stream = null; + try { - if (disposing) - { - _stream?.Dispose(); - } + _inflater?.Dispose(); } finally { - _stream = null; - - try - { - _inflater?.Dispose(); - } - finally - { - _inflater = null; - base.Dispose(disposing); - } + _inflater = null; + base.Dispose(disposing); } } } diff --git a/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs b/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs index 16010e17..6a12df3b 100644 --- a/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs +++ b/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs @@ -4,45 +4,42 @@ using System.Diagnostics; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +internal sealed class DeflateInput { - internal sealed class DeflateInput + public DeflateInput(byte[] buffer) => Buffer = buffer; + + public byte[] Buffer { get; } + public int Count { get; set; } + public int StartIndex { get; set; } + + internal void ConsumeBytes(int n) { - public DeflateInput(byte[] buffer) + Debug.Assert(n <= Count, "Should use more bytes than what we have in the buffer"); + StartIndex += n; + Count -= n; + Debug.Assert(StartIndex + Count <= Buffer.Length, "Input buffer is in invalid state!"); + } + + internal InputState DumpState() => new InputState(Count, StartIndex); + + internal void RestoreState(InputState state) + { + Count = state._count; + StartIndex = state._startIndex; + } + + internal /*readonly */ + readonly struct InputState + { + internal readonly int _count; + internal readonly int _startIndex; + + internal InputState(int count, int startIndex) { - Buffer = buffer; - } - - public byte[] Buffer { get; } - public int Count { get; set; } - public int StartIndex { get; set; } - - internal void ConsumeBytes(int n) - { - Debug.Assert(n <= Count, "Should use more bytes than what we have in the buffer"); - StartIndex += n; - Count -= n; - Debug.Assert(StartIndex + Count <= Buffer.Length, "Input buffer is in invalid state!"); - } - - internal InputState DumpState() => new InputState(Count, StartIndex); - - internal void RestoreState(InputState state) - { - Count = state._count; - StartIndex = state._startIndex; - } - - internal /*readonly */struct InputState - { - internal readonly int _count; - internal readonly int _startIndex; - - internal InputState(int count, int startIndex) - { - _count = count; - _startIndex = startIndex; - } + _count = count; + _startIndex = startIndex; } } } diff --git a/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs b/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs index f5f26276..71e3f680 100644 --- a/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs +++ b/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs @@ -5,246 +5,974 @@ using System; using System.Diagnostics; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +internal static class FastEncoderStatics { - internal static class FastEncoderStatics + // static information for encoding, DO NOT MODIFY + + internal static ReadOnlySpan FAST_ENCODER_TREE_STRUCTURE_DATA => + new byte[] + { + 0xec, + 0xbd, + 0x07, + 0x60, + 0x1c, + 0x49, + 0x96, + 0x25, + 0x26, + 0x2f, + 0x6d, + 0xca, + 0x7b, + 0x7f, + 0x4a, + 0xf5, + 0x4a, + 0xd7, + 0xe0, + 0x74, + 0xa1, + 0x08, + 0x80, + 0x60, + 0x13, + 0x24, + 0xd8, + 0x90, + 0x40, + 0x10, + 0xec, + 0xc1, + 0x88, + 0xcd, + 0xe6, + 0x92, + 0xec, + 0x1d, + 0x69, + 0x47, + 0x23, + 0x29, + 0xab, + 0x2a, + 0x81, + 0xca, + 0x65, + 0x56, + 0x65, + 0x5d, + 0x66, + 0x16, + 0x40, + 0xcc, + 0xed, + 0x9d, + 0xbc, + 0xf7, + 0xde, + 0x7b, + 0xef, + 0xbd, + 0xf7, + 0xde, + 0x7b, + 0xef, + 0xbd, + 0xf7, + 0xba, + 0x3b, + 0x9d, + 0x4e, + 0x27, + 0xf7, + 0xdf, + 0xff, + 0x3f, + 0x5c, + 0x66, + 0x64, + 0x01, + 0x6c, + 0xf6, + 0xce, + 0x4a, + 0xda, + 0xc9, + 0x9e, + 0x21, + 0x80, + 0xaa, + 0xc8, + 0x1f, + 0x3f, + 0x7e, + 0x7c, + 0x1f, + 0x3f + }; + + internal static ReadOnlySpan B_FINAL_FAST_ENCODER_TREE_STRUCTURE_DATA => + new byte[] + { + 0xed, + 0xbd, + 0x07, + 0x60, + 0x1c, + 0x49, + 0x96, + 0x25, + 0x26, + 0x2f, + 0x6d, + 0xca, + 0x7b, + 0x7f, + 0x4a, + 0xf5, + 0x4a, + 0xd7, + 0xe0, + 0x74, + 0xa1, + 0x08, + 0x80, + 0x60, + 0x13, + 0x24, + 0xd8, + 0x90, + 0x40, + 0x10, + 0xec, + 0xc1, + 0x88, + 0xcd, + 0xe6, + 0x92, + 0xec, + 0x1d, + 0x69, + 0x47, + 0x23, + 0x29, + 0xab, + 0x2a, + 0x81, + 0xca, + 0x65, + 0x56, + 0x65, + 0x5d, + 0x66, + 0x16, + 0x40, + 0xcc, + 0xed, + 0x9d, + 0xbc, + 0xf7, + 0xde, + 0x7b, + 0xef, + 0xbd, + 0xf7, + 0xde, + 0x7b, + 0xef, + 0xbd, + 0xf7, + 0xba, + 0x3b, + 0x9d, + 0x4e, + 0x27, + 0xf7, + 0xdf, + 0xff, + 0x3f, + 0x5c, + 0x66, + 0x64, + 0x01, + 0x6c, + 0xf6, + 0xce, + 0x4a, + 0xda, + 0xc9, + 0x9e, + 0x21, + 0x80, + 0xaa, + 0xc8, + 0x1f, + 0x3f, + 0x7e, + 0x7c, + 0x1f, + 0x3f + }; + + // Output a currentMatch with length matchLen (>= MIN_MATCH) and displacement matchPos + // + // Optimisation: unlike the other encoders, here we have an array of codes for each currentMatch + // length (not just each currentMatch length slot), complete with all the extra bits filled in, in + // a single array element. + // + // There are many advantages to doing this: + // + // 1. A single array lookup on g_FastEncoderLiteralCodeInfo, instead of separate array lookups + // on g_LengthLookup (to get the length slot), g_FastEncoderLiteralTreeLength, + // g_FastEncoderLiteralTreeCode, g_ExtraLengthBits, and g_BitMask + // + // 2. The array is an array of ULONGs, so no access penalty, unlike for accessing those USHORT + // code arrays in the other encoders (although they could be made into ULONGs with some + // modifications to the source). + // + // Note, if we could guarantee that codeLen <= 16 always, then we could skip an if statement here. + // + // A completely different optimisation is used for the distance codes since, obviously, a table for + // all 8192 distances combining their extra bits is not feasible. The distance codeinfo table is + // made up of code[], len[] and # extraBits for this code. + // + // The advantages are similar to the above; a ULONG array instead of a USHORT and BYTE array, better + // cache locality, fewer memory operations. + // + + + // Encoding information for literal and Length. + // The least 5 significant bits are the length + // and the rest is the code bits. + + internal static readonly uint[] FAST_ENCODER_LITERAL_CODE_INFO = { - // static information for encoding, DO NOT MODIFY + 0x0000d7ee, + 0x0004d7ee, + 0x0002d7ee, + 0x0006d7ee, + 0x0001d7ee, + 0x0005d7ee, + 0x0003d7ee, + 0x0007d7ee, + 0x000037ee, + 0x0000c7ec, + 0x00000126, + 0x000437ee, + 0x000237ee, + 0x000637ee, + 0x000137ee, + 0x000537ee, + 0x000337ee, + 0x000737ee, + 0x0000b7ee, + 0x0004b7ee, + 0x0002b7ee, + 0x0006b7ee, + 0x0001b7ee, + 0x0005b7ee, + 0x0003b7ee, + 0x0007b7ee, + 0x000077ee, + 0x000477ee, + 0x000277ee, + 0x000677ee, + 0x000017ed, + 0x000177ee, + 0x00000526, + 0x000577ee, + 0x000023ea, + 0x0001c7ec, + 0x000377ee, + 0x000777ee, + 0x000217ed, + 0x000063ea, + 0x00000b68, + 0x00000ee9, + 0x00005beb, + 0x000013ea, + 0x00000467, + 0x00001b68, + 0x00000c67, + 0x00002ee9, + 0x00000768, + 0x00001768, + 0x00000f68, + 0x00001ee9, + 0x00001f68, + 0x00003ee9, + 0x000053ea, + 0x000001e9, + 0x000000e8, + 0x000021e9, + 0x000011e9, + 0x000010e8, + 0x000031e9, + 0x000033ea, + 0x000008e8, + 0x0000f7ee, + 0x0004f7ee, + 0x000018e8, + 0x000009e9, + 0x000004e8, + 0x000029e9, + 0x000014e8, + 0x000019e9, + 0x000073ea, + 0x0000dbeb, + 0x00000ce8, + 0x00003beb, + 0x0002f7ee, + 0x000039e9, + 0x00000bea, + 0x000005e9, + 0x00004bea, + 0x000025e9, + 0x000027ec, + 0x000015e9, + 0x000035e9, + 0x00000de9, + 0x00002bea, + 0x000127ec, + 0x0000bbeb, + 0x0006f7ee, + 0x0001f7ee, + 0x0000a7ec, + 0x00007beb, + 0x0005f7ee, + 0x0000fbeb, + 0x0003f7ee, + 0x0007f7ee, + 0x00000fee, + 0x00000326, + 0x00000267, + 0x00000a67, + 0x00000667, + 0x00000726, + 0x00001ce8, + 0x000002e8, + 0x00000e67, + 0x000000a6, + 0x0001a7ec, + 0x00002de9, + 0x000004a6, + 0x00000167, + 0x00000967, + 0x000002a6, + 0x00000567, + 0x000117ed, + 0x000006a6, + 0x000001a6, + 0x000005a6, + 0x00000d67, + 0x000012e8, + 0x00000ae8, + 0x00001de9, + 0x00001ae8, + 0x000007eb, + 0x000317ed, + 0x000067ec, + 0x000097ed, + 0x000297ed, + 0x00040fee, + 0x00020fee, + 0x00060fee, + 0x00010fee, + 0x00050fee, + 0x00030fee, + 0x00070fee, + 0x00008fee, + 0x00048fee, + 0x00028fee, + 0x00068fee, + 0x00018fee, + 0x00058fee, + 0x00038fee, + 0x00078fee, + 0x00004fee, + 0x00044fee, + 0x00024fee, + 0x00064fee, + 0x00014fee, + 0x00054fee, + 0x00034fee, + 0x00074fee, + 0x0000cfee, + 0x0004cfee, + 0x0002cfee, + 0x0006cfee, + 0x0001cfee, + 0x0005cfee, + 0x0003cfee, + 0x0007cfee, + 0x00002fee, + 0x00042fee, + 0x00022fee, + 0x00062fee, + 0x00012fee, + 0x00052fee, + 0x00032fee, + 0x00072fee, + 0x0000afee, + 0x0004afee, + 0x0002afee, + 0x0006afee, + 0x0001afee, + 0x0005afee, + 0x0003afee, + 0x0007afee, + 0x00006fee, + 0x00046fee, + 0x00026fee, + 0x00066fee, + 0x00016fee, + 0x00056fee, + 0x00036fee, + 0x00076fee, + 0x0000efee, + 0x0004efee, + 0x0002efee, + 0x0006efee, + 0x0001efee, + 0x0005efee, + 0x0003efee, + 0x0007efee, + 0x00001fee, + 0x00041fee, + 0x00021fee, + 0x00061fee, + 0x00011fee, + 0x00051fee, + 0x00031fee, + 0x00071fee, + 0x00009fee, + 0x00049fee, + 0x00029fee, + 0x00069fee, + 0x00019fee, + 0x00059fee, + 0x00039fee, + 0x00079fee, + 0x00005fee, + 0x00045fee, + 0x00025fee, + 0x00065fee, + 0x00015fee, + 0x00055fee, + 0x00035fee, + 0x00075fee, + 0x0000dfee, + 0x0004dfee, + 0x0002dfee, + 0x0006dfee, + 0x0001dfee, + 0x0005dfee, + 0x0003dfee, + 0x0007dfee, + 0x00003fee, + 0x00043fee, + 0x00023fee, + 0x00063fee, + 0x00013fee, + 0x00053fee, + 0x00033fee, + 0x00073fee, + 0x0000bfee, + 0x0004bfee, + 0x0002bfee, + 0x0006bfee, + 0x0001bfee, + 0x0005bfee, + 0x0003bfee, + 0x0007bfee, + 0x00007fee, + 0x00047fee, + 0x00027fee, + 0x00067fee, + 0x00017fee, + 0x000197ed, + 0x000397ed, + 0x000057ed, + 0x00057fee, + 0x000257ed, + 0x00037fee, + 0x000157ed, + 0x00077fee, + 0x000357ed, + 0x0000ffee, + 0x0004ffee, + 0x0002ffee, + 0x0006ffee, + 0x0001ffee, + 0x00000084, + 0x00000003, + 0x00000184, + 0x00000044, + 0x00000144, + 0x000000c5, + 0x000002c5, + 0x000001c5, + 0x000003c6, + 0x000007c6, + 0x00000026, + 0x00000426, + 0x000003a7, + 0x00000ba7, + 0x000007a7, + 0x00000fa7, + 0x00000227, + 0x00000627, + 0x00000a27, + 0x00000e27, + 0x00000068, + 0x00000868, + 0x00001068, + 0x00001868, + 0x00000369, + 0x00001369, + 0x00002369, + 0x00003369, + 0x000006ea, + 0x000026ea, + 0x000046ea, + 0x000066ea, + 0x000016eb, + 0x000036eb, + 0x000056eb, + 0x000076eb, + 0x000096eb, + 0x0000b6eb, + 0x0000d6eb, + 0x0000f6eb, + 0x00003dec, + 0x00007dec, + 0x0000bdec, + 0x0000fdec, + 0x00013dec, + 0x00017dec, + 0x0001bdec, + 0x0001fdec, + 0x00006bed, + 0x0000ebed, + 0x00016bed, + 0x0001ebed, + 0x00026bed, + 0x0002ebed, + 0x00036bed, + 0x0003ebed, + 0x000003ec, + 0x000043ec, + 0x000083ec, + 0x0000c3ec, + 0x000103ec, + 0x000143ec, + 0x000183ec, + 0x0001c3ec, + 0x00001bee, + 0x00009bee, + 0x00011bee, + 0x00019bee, + 0x00021bee, + 0x00029bee, + 0x00031bee, + 0x00039bee, + 0x00041bee, + 0x00049bee, + 0x00051bee, + 0x00059bee, + 0x00061bee, + 0x00069bee, + 0x00071bee, + 0x00079bee, + 0x000167f0, + 0x000367f0, + 0x000567f0, + 0x000767f0, + 0x000967f0, + 0x000b67f0, + 0x000d67f0, + 0x000f67f0, + 0x001167f0, + 0x001367f0, + 0x001567f0, + 0x001767f0, + 0x001967f0, + 0x001b67f0, + 0x001d67f0, + 0x001f67f0, + 0x000087ef, + 0x000187ef, + 0x000287ef, + 0x000387ef, + 0x000487ef, + 0x000587ef, + 0x000687ef, + 0x000787ef, + 0x000887ef, + 0x000987ef, + 0x000a87ef, + 0x000b87ef, + 0x000c87ef, + 0x000d87ef, + 0x000e87ef, + 0x000f87ef, + 0x0000e7f0, + 0x0002e7f0, + 0x0004e7f0, + 0x0006e7f0, + 0x0008e7f0, + 0x000ae7f0, + 0x000ce7f0, + 0x000ee7f0, + 0x0010e7f0, + 0x0012e7f0, + 0x0014e7f0, + 0x0016e7f0, + 0x0018e7f0, + 0x001ae7f0, + 0x001ce7f0, + 0x001ee7f0, + 0x0005fff3, + 0x000dfff3, + 0x0015fff3, + 0x001dfff3, + 0x0025fff3, + 0x002dfff3, + 0x0035fff3, + 0x003dfff3, + 0x0045fff3, + 0x004dfff3, + 0x0055fff3, + 0x005dfff3, + 0x0065fff3, + 0x006dfff3, + 0x0075fff3, + 0x007dfff3, + 0x0085fff3, + 0x008dfff3, + 0x0095fff3, + 0x009dfff3, + 0x00a5fff3, + 0x00adfff3, + 0x00b5fff3, + 0x00bdfff3, + 0x00c5fff3, + 0x00cdfff3, + 0x00d5fff3, + 0x00ddfff3, + 0x00e5fff3, + 0x00edfff3, + 0x00f5fff3, + 0x00fdfff3, + 0x0003fff3, + 0x000bfff3, + 0x0013fff3, + 0x001bfff3, + 0x0023fff3, + 0x002bfff3, + 0x0033fff3, + 0x003bfff3, + 0x0043fff3, + 0x004bfff3, + 0x0053fff3, + 0x005bfff3, + 0x0063fff3, + 0x006bfff3, + 0x0073fff3, + 0x007bfff3, + 0x0083fff3, + 0x008bfff3, + 0x0093fff3, + 0x009bfff3, + 0x00a3fff3, + 0x00abfff3, + 0x00b3fff3, + 0x00bbfff3, + 0x00c3fff3, + 0x00cbfff3, + 0x00d3fff3, + 0x00dbfff3, + 0x00e3fff3, + 0x00ebfff3, + 0x00f3fff3, + 0x00fbfff3, + 0x0007fff3, + 0x000ffff3, + 0x0017fff3, + 0x001ffff3, + 0x0027fff3, + 0x002ffff3, + 0x0037fff3, + 0x003ffff3, + 0x0047fff3, + 0x004ffff3, + 0x0057fff3, + 0x005ffff3, + 0x0067fff3, + 0x006ffff3, + 0x0077fff3, + 0x007ffff3, + 0x0087fff3, + 0x008ffff3, + 0x0097fff3, + 0x009ffff3, + 0x00a7fff3, + 0x00affff3, + 0x00b7fff3, + 0x00bffff3, + 0x00c7fff3, + 0x00cffff3, + 0x00d7fff3, + 0x00dffff3, + 0x00e7fff3, + 0x00effff3, + 0x00f7fff3, + 0x00fffff3, + 0x0001e7f1, + 0x0003e7f1, + 0x0005e7f1, + 0x0007e7f1, + 0x0009e7f1, + 0x000be7f1, + 0x000de7f1, + 0x000fe7f1, + 0x0011e7f1, + 0x0013e7f1, + 0x0015e7f1, + 0x0017e7f1, + 0x0019e7f1, + 0x001be7f1, + 0x001de7f1, + 0x001fe7f1, + 0x0021e7f1, + 0x0023e7f1, + 0x0025e7f1, + 0x0027e7f1, + 0x0029e7f1, + 0x002be7f1, + 0x002de7f1, + 0x002fe7f1, + 0x0031e7f1, + 0x0033e7f1, + 0x0035e7f1, + 0x0037e7f1, + 0x0039e7f1, + 0x003be7f1, + 0x003de7f1, + 0x000047eb + }; - internal static ReadOnlySpan FAST_ENCODER_TREE_STRUCTURE_DATA => new byte[] + internal static readonly uint[] FAST_ENCODER_DISTANCE_CODE_INFO = + { + 0x00000f06, + 0x0001ff0a, + 0x0003ff0b, + 0x0007ff0b, + 0x0000ff19, + 0x00003f18, + 0x0000bf28, + 0x00007f28, + 0x00001f37, + 0x00005f37, + 0x00000d45, + 0x00002f46, + 0x00000054, + 0x00001d55, + 0x00000864, + 0x00000365, + 0x00000474, + 0x00001375, + 0x00000c84, + 0x00000284, + 0x00000a94, + 0x00000694, + 0x00000ea4, + 0x000001a4, + 0x000009b4, + 0x00000bb5, + 0x000005c4, + 0x00001bc5, + 0x000007d5, + 0x000017d5, + 0x00000000, + 0x00000100 + }; + + internal static readonly uint[] BIT_MASK = + { + 0, + 1, + 3, + 7, + 15, + 31, + 63, + 127, + 255, + 511, + 1023, + 2047, + 4095, + 8191, + 16383, + 32767 + }; + internal static readonly byte[] EXTRA_LENGTH_BITS = + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0 + }; + internal static readonly byte[] EXTRA_DISTANCE_BITS = + { + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 0, + 0 + }; + internal const int NUM_CHARS = 256; + internal const int NUM_LENGTH_BASE_CODES = 29; + internal const int NUM_DIST_BASE_CODES = 30; + + internal const uint FAST_ENCODER_POST_TREE_BIT_BUF = 0x0022; + internal const int FAST_ENCODER_POST_TREE_BIT_COUNT = 9; + + internal const uint NO_COMPRESSION_HEADER = 0x0; + internal const int NO_COMPRESSION_HEADER_BIT_COUNT = 3; + internal const uint B_FINAL_NO_COMPRESSION_HEADER = 0x1; + internal const int B_FINAL_NO_COMPRESSION_HEADER_BIT_COUNT = 3; + internal const int MAX_CODE_LEN = 16; + + private static readonly byte[] S_DIST_LOOKUP = CreateDistanceLookup(); + + private static byte[] CreateDistanceLookup() + { + var result = new byte[512]; + + // Generate the global slot tables which allow us to convert a distance + // (0..32K) to a distance slot (0..29) + // + // Distance table + // Extra Extra Extra + // Code Bits Dist Code Bits Dist Code Bits Distance + // ---- ---- ---- ---- ---- ------ ---- ---- -------- + // 0 0 1 10 4 33-48 20 9 1025-1536 + // 1 0 2 11 4 49-64 21 9 1537-2048 + // 2 0 3 12 5 65-96 22 10 2049-3072 + // 3 0 4 13 5 97-128 23 10 3073-4096 + // 4 1 5,6 14 6 129-192 24 11 4097-6144 + // 5 1 7,8 15 6 193-256 25 11 6145-8192 + // 6 2 9-12 16 7 257-384 26 12 8193-12288 + // 7 2 13-16 17 7 385-512 27 12 12289-16384 + // 8 3 17-24 18 8 513-768 28 13 16385-24576 + // 9 3 25-32 19 8 769-1024 29 13 24577-32768 + + // Initialize the mapping length (0..255) -> length code (0..28) + //int length = 0; + //for (code = 0; code < FastEncoderStatics.NumLengthBaseCodes-1; code++) { + // for (int n = 0; n < (1 << FastEncoderStatics.ExtraLengthBits[code]); n++) + // lengthLookup[length++] = (byte) code; + //} + //lengthLookup[length-1] = (byte) code; + + // Initialize the mapping dist (0..32K) -> dist code (0..29) + var dist = 0; + int code; + for (code = 0; code < 16; code++) { - 0xec,0xbd,0x07,0x60,0x1c,0x49,0x96,0x25,0x26,0x2f,0x6d,0xca, - 0x7b,0x7f,0x4a,0xf5,0x4a,0xd7,0xe0,0x74,0xa1,0x08,0x80,0x60, - 0x13,0x24,0xd8,0x90,0x40,0x10,0xec,0xc1,0x88,0xcd,0xe6,0x92, - 0xec,0x1d,0x69,0x47,0x23,0x29,0xab,0x2a,0x81,0xca,0x65,0x56, - 0x65,0x5d,0x66,0x16,0x40,0xcc,0xed,0x9d,0xbc,0xf7,0xde,0x7b, - 0xef,0xbd,0xf7,0xde,0x7b,0xef,0xbd,0xf7,0xba,0x3b,0x9d,0x4e, - 0x27,0xf7,0xdf,0xff,0x3f,0x5c,0x66,0x64,0x01,0x6c,0xf6,0xce, - 0x4a,0xda,0xc9,0x9e,0x21,0x80,0xaa,0xc8,0x1f,0x3f,0x7e,0x7c, - 0x1f,0x3f - }; - - internal static ReadOnlySpan B_FINAL_FAST_ENCODER_TREE_STRUCTURE_DATA => new byte[] - { - 0xed,0xbd,0x07,0x60,0x1c,0x49,0x96,0x25,0x26,0x2f,0x6d,0xca, - 0x7b,0x7f,0x4a,0xf5,0x4a,0xd7,0xe0,0x74,0xa1,0x08,0x80,0x60, - 0x13,0x24,0xd8,0x90,0x40,0x10,0xec,0xc1,0x88,0xcd,0xe6,0x92, - 0xec,0x1d,0x69,0x47,0x23,0x29,0xab,0x2a,0x81,0xca,0x65,0x56, - 0x65,0x5d,0x66,0x16,0x40,0xcc,0xed,0x9d,0xbc,0xf7,0xde,0x7b, - 0xef,0xbd,0xf7,0xde,0x7b,0xef,0xbd,0xf7,0xba,0x3b,0x9d,0x4e, - 0x27,0xf7,0xdf,0xff,0x3f,0x5c,0x66,0x64,0x01,0x6c,0xf6,0xce, - 0x4a,0xda,0xc9,0x9e,0x21,0x80,0xaa,0xc8,0x1f,0x3f,0x7e,0x7c, - 0x1f,0x3f - }; - - // Output a currentMatch with length matchLen (>= MIN_MATCH) and displacement matchPos - // - // Optimisation: unlike the other encoders, here we have an array of codes for each currentMatch - // length (not just each currentMatch length slot), complete with all the extra bits filled in, in - // a single array element. - // - // There are many advantages to doing this: - // - // 1. A single array lookup on g_FastEncoderLiteralCodeInfo, instead of separate array lookups - // on g_LengthLookup (to get the length slot), g_FastEncoderLiteralTreeLength, - // g_FastEncoderLiteralTreeCode, g_ExtraLengthBits, and g_BitMask - // - // 2. The array is an array of ULONGs, so no access penalty, unlike for accessing those USHORT - // code arrays in the other encoders (although they could be made into ULONGs with some - // modifications to the source). - // - // Note, if we could guarantee that codeLen <= 16 always, then we could skip an if statement here. - // - // A completely different optimisation is used for the distance codes since, obviously, a table for - // all 8192 distances combining their extra bits is not feasible. The distance codeinfo table is - // made up of code[], len[] and # extraBits for this code. - // - // The advantages are similar to the above; a ULONG array instead of a USHORT and BYTE array, better - // cache locality, fewer memory operations. - // - - - // Encoding information for literal and Length. - // The least 5 significant bits are the length - // and the rest is the code bits. - - internal static readonly uint[] FAST_ENCODER_LITERAL_CODE_INFO = - { - 0x0000d7ee,0x0004d7ee,0x0002d7ee,0x0006d7ee,0x0001d7ee,0x0005d7ee,0x0003d7ee, - 0x0007d7ee,0x000037ee,0x0000c7ec,0x00000126,0x000437ee,0x000237ee,0x000637ee, - 0x000137ee,0x000537ee,0x000337ee,0x000737ee,0x0000b7ee,0x0004b7ee,0x0002b7ee, - 0x0006b7ee,0x0001b7ee,0x0005b7ee,0x0003b7ee,0x0007b7ee,0x000077ee,0x000477ee, - 0x000277ee,0x000677ee,0x000017ed,0x000177ee,0x00000526,0x000577ee,0x000023ea, - 0x0001c7ec,0x000377ee,0x000777ee,0x000217ed,0x000063ea,0x00000b68,0x00000ee9, - 0x00005beb,0x000013ea,0x00000467,0x00001b68,0x00000c67,0x00002ee9,0x00000768, - 0x00001768,0x00000f68,0x00001ee9,0x00001f68,0x00003ee9,0x000053ea,0x000001e9, - 0x000000e8,0x000021e9,0x000011e9,0x000010e8,0x000031e9,0x000033ea,0x000008e8, - 0x0000f7ee,0x0004f7ee,0x000018e8,0x000009e9,0x000004e8,0x000029e9,0x000014e8, - 0x000019e9,0x000073ea,0x0000dbeb,0x00000ce8,0x00003beb,0x0002f7ee,0x000039e9, - 0x00000bea,0x000005e9,0x00004bea,0x000025e9,0x000027ec,0x000015e9,0x000035e9, - 0x00000de9,0x00002bea,0x000127ec,0x0000bbeb,0x0006f7ee,0x0001f7ee,0x0000a7ec, - 0x00007beb,0x0005f7ee,0x0000fbeb,0x0003f7ee,0x0007f7ee,0x00000fee,0x00000326, - 0x00000267,0x00000a67,0x00000667,0x00000726,0x00001ce8,0x000002e8,0x00000e67, - 0x000000a6,0x0001a7ec,0x00002de9,0x000004a6,0x00000167,0x00000967,0x000002a6, - 0x00000567,0x000117ed,0x000006a6,0x000001a6,0x000005a6,0x00000d67,0x000012e8, - 0x00000ae8,0x00001de9,0x00001ae8,0x000007eb,0x000317ed,0x000067ec,0x000097ed, - 0x000297ed,0x00040fee,0x00020fee,0x00060fee,0x00010fee,0x00050fee,0x00030fee, - 0x00070fee,0x00008fee,0x00048fee,0x00028fee,0x00068fee,0x00018fee,0x00058fee, - 0x00038fee,0x00078fee,0x00004fee,0x00044fee,0x00024fee,0x00064fee,0x00014fee, - 0x00054fee,0x00034fee,0x00074fee,0x0000cfee,0x0004cfee,0x0002cfee,0x0006cfee, - 0x0001cfee,0x0005cfee,0x0003cfee,0x0007cfee,0x00002fee,0x00042fee,0x00022fee, - 0x00062fee,0x00012fee,0x00052fee,0x00032fee,0x00072fee,0x0000afee,0x0004afee, - 0x0002afee,0x0006afee,0x0001afee,0x0005afee,0x0003afee,0x0007afee,0x00006fee, - 0x00046fee,0x00026fee,0x00066fee,0x00016fee,0x00056fee,0x00036fee,0x00076fee, - 0x0000efee,0x0004efee,0x0002efee,0x0006efee,0x0001efee,0x0005efee,0x0003efee, - 0x0007efee,0x00001fee,0x00041fee,0x00021fee,0x00061fee,0x00011fee,0x00051fee, - 0x00031fee,0x00071fee,0x00009fee,0x00049fee,0x00029fee,0x00069fee,0x00019fee, - 0x00059fee,0x00039fee,0x00079fee,0x00005fee,0x00045fee,0x00025fee,0x00065fee, - 0x00015fee,0x00055fee,0x00035fee,0x00075fee,0x0000dfee,0x0004dfee,0x0002dfee, - 0x0006dfee,0x0001dfee,0x0005dfee,0x0003dfee,0x0007dfee,0x00003fee,0x00043fee, - 0x00023fee,0x00063fee,0x00013fee,0x00053fee,0x00033fee,0x00073fee,0x0000bfee, - 0x0004bfee,0x0002bfee,0x0006bfee,0x0001bfee,0x0005bfee,0x0003bfee,0x0007bfee, - 0x00007fee,0x00047fee,0x00027fee,0x00067fee,0x00017fee,0x000197ed,0x000397ed, - 0x000057ed,0x00057fee,0x000257ed,0x00037fee,0x000157ed,0x00077fee,0x000357ed, - 0x0000ffee,0x0004ffee,0x0002ffee,0x0006ffee,0x0001ffee,0x00000084,0x00000003, - 0x00000184,0x00000044,0x00000144,0x000000c5,0x000002c5,0x000001c5,0x000003c6, - 0x000007c6,0x00000026,0x00000426,0x000003a7,0x00000ba7,0x000007a7,0x00000fa7, - 0x00000227,0x00000627,0x00000a27,0x00000e27,0x00000068,0x00000868,0x00001068, - 0x00001868,0x00000369,0x00001369,0x00002369,0x00003369,0x000006ea,0x000026ea, - 0x000046ea,0x000066ea,0x000016eb,0x000036eb,0x000056eb,0x000076eb,0x000096eb, - 0x0000b6eb,0x0000d6eb,0x0000f6eb,0x00003dec,0x00007dec,0x0000bdec,0x0000fdec, - 0x00013dec,0x00017dec,0x0001bdec,0x0001fdec,0x00006bed,0x0000ebed,0x00016bed, - 0x0001ebed,0x00026bed,0x0002ebed,0x00036bed,0x0003ebed,0x000003ec,0x000043ec, - 0x000083ec,0x0000c3ec,0x000103ec,0x000143ec,0x000183ec,0x0001c3ec,0x00001bee, - 0x00009bee,0x00011bee,0x00019bee,0x00021bee,0x00029bee,0x00031bee,0x00039bee, - 0x00041bee,0x00049bee,0x00051bee,0x00059bee,0x00061bee,0x00069bee,0x00071bee, - 0x00079bee,0x000167f0,0x000367f0,0x000567f0,0x000767f0,0x000967f0,0x000b67f0, - 0x000d67f0,0x000f67f0,0x001167f0,0x001367f0,0x001567f0,0x001767f0,0x001967f0, - 0x001b67f0,0x001d67f0,0x001f67f0,0x000087ef,0x000187ef,0x000287ef,0x000387ef, - 0x000487ef,0x000587ef,0x000687ef,0x000787ef,0x000887ef,0x000987ef,0x000a87ef, - 0x000b87ef,0x000c87ef,0x000d87ef,0x000e87ef,0x000f87ef,0x0000e7f0,0x0002e7f0, - 0x0004e7f0,0x0006e7f0,0x0008e7f0,0x000ae7f0,0x000ce7f0,0x000ee7f0,0x0010e7f0, - 0x0012e7f0,0x0014e7f0,0x0016e7f0,0x0018e7f0,0x001ae7f0,0x001ce7f0,0x001ee7f0, - 0x0005fff3,0x000dfff3,0x0015fff3,0x001dfff3,0x0025fff3,0x002dfff3,0x0035fff3, - 0x003dfff3,0x0045fff3,0x004dfff3,0x0055fff3,0x005dfff3,0x0065fff3,0x006dfff3, - 0x0075fff3,0x007dfff3,0x0085fff3,0x008dfff3,0x0095fff3,0x009dfff3,0x00a5fff3, - 0x00adfff3,0x00b5fff3,0x00bdfff3,0x00c5fff3,0x00cdfff3,0x00d5fff3,0x00ddfff3, - 0x00e5fff3,0x00edfff3,0x00f5fff3,0x00fdfff3,0x0003fff3,0x000bfff3,0x0013fff3, - 0x001bfff3,0x0023fff3,0x002bfff3,0x0033fff3,0x003bfff3,0x0043fff3,0x004bfff3, - 0x0053fff3,0x005bfff3,0x0063fff3,0x006bfff3,0x0073fff3,0x007bfff3,0x0083fff3, - 0x008bfff3,0x0093fff3,0x009bfff3,0x00a3fff3,0x00abfff3,0x00b3fff3,0x00bbfff3, - 0x00c3fff3,0x00cbfff3,0x00d3fff3,0x00dbfff3,0x00e3fff3,0x00ebfff3,0x00f3fff3, - 0x00fbfff3,0x0007fff3,0x000ffff3,0x0017fff3,0x001ffff3,0x0027fff3,0x002ffff3, - 0x0037fff3,0x003ffff3,0x0047fff3,0x004ffff3,0x0057fff3,0x005ffff3,0x0067fff3, - 0x006ffff3,0x0077fff3,0x007ffff3,0x0087fff3,0x008ffff3,0x0097fff3,0x009ffff3, - 0x00a7fff3,0x00affff3,0x00b7fff3,0x00bffff3,0x00c7fff3,0x00cffff3,0x00d7fff3, - 0x00dffff3,0x00e7fff3,0x00effff3,0x00f7fff3,0x00fffff3,0x0001e7f1,0x0003e7f1, - 0x0005e7f1,0x0007e7f1,0x0009e7f1,0x000be7f1,0x000de7f1,0x000fe7f1,0x0011e7f1, - 0x0013e7f1,0x0015e7f1,0x0017e7f1,0x0019e7f1,0x001be7f1,0x001de7f1,0x001fe7f1, - 0x0021e7f1,0x0023e7f1,0x0025e7f1,0x0027e7f1,0x0029e7f1,0x002be7f1,0x002de7f1, - 0x002fe7f1,0x0031e7f1,0x0033e7f1,0x0035e7f1,0x0037e7f1,0x0039e7f1,0x003be7f1, - 0x003de7f1,0x000047eb - }; - - internal static readonly uint[] FAST_ENCODER_DISTANCE_CODE_INFO = - { - 0x00000f06,0x0001ff0a,0x0003ff0b,0x0007ff0b,0x0000ff19,0x00003f18,0x0000bf28, - 0x00007f28,0x00001f37,0x00005f37,0x00000d45,0x00002f46,0x00000054,0x00001d55, - 0x00000864,0x00000365,0x00000474,0x00001375,0x00000c84,0x00000284,0x00000a94, - 0x00000694,0x00000ea4,0x000001a4,0x000009b4,0x00000bb5,0x000005c4,0x00001bc5, - 0x000007d5,0x000017d5,0x00000000,0x00000100 - }; - - internal static readonly uint[] BIT_MASK = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 }; - internal static readonly byte[] EXTRA_LENGTH_BITS = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - internal static readonly byte[] EXTRA_DISTANCE_BITS = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 }; - internal const int NUM_CHARS = 256; - internal const int NUM_LENGTH_BASE_CODES = 29; - internal const int NUM_DIST_BASE_CODES = 30; - - internal const uint FAST_ENCODER_POST_TREE_BIT_BUF = 0x0022; - internal const int FAST_ENCODER_POST_TREE_BIT_COUNT = 9; - - internal const uint NO_COMPRESSION_HEADER = 0x0; - internal const int NO_COMPRESSION_HEADER_BIT_COUNT = 3; - internal const uint B_FINAL_NO_COMPRESSION_HEADER = 0x1; - internal const int B_FINAL_NO_COMPRESSION_HEADER_BIT_COUNT = 3; - internal const int MAX_CODE_LEN = 16; - - private static readonly byte[] S_DIST_LOOKUP = CreateDistanceLookup(); - - private static byte[] CreateDistanceLookup() - { - byte[] result = new byte[512]; - - // Generate the global slot tables which allow us to convert a distance - // (0..32K) to a distance slot (0..29) - // - // Distance table - // Extra Extra Extra - // Code Bits Dist Code Bits Dist Code Bits Distance - // ---- ---- ---- ---- ---- ------ ---- ---- -------- - // 0 0 1 10 4 33-48 20 9 1025-1536 - // 1 0 2 11 4 49-64 21 9 1537-2048 - // 2 0 3 12 5 65-96 22 10 2049-3072 - // 3 0 4 13 5 97-128 23 10 3073-4096 - // 4 1 5,6 14 6 129-192 24 11 4097-6144 - // 5 1 7,8 15 6 193-256 25 11 6145-8192 - // 6 2 9-12 16 7 257-384 26 12 8193-12288 - // 7 2 13-16 17 7 385-512 27 12 12289-16384 - // 8 3 17-24 18 8 513-768 28 13 16385-24576 - // 9 3 25-32 19 8 769-1024 29 13 24577-32768 - - // Initialize the mapping length (0..255) -> length code (0..28) - //int length = 0; - //for (code = 0; code < FastEncoderStatics.NumLengthBaseCodes-1; code++) { - // for (int n = 0; n < (1 << FastEncoderStatics.ExtraLengthBits[code]); n++) - // lengthLookup[length++] = (byte) code; - //} - //lengthLookup[length-1] = (byte) code; - - // Initialize the mapping dist (0..32K) -> dist code (0..29) - int dist = 0; - int code; - for (code = 0; code < 16; code++) + for (var n = 0; n < (1 << EXTRA_DISTANCE_BITS[code]); n++) { - for (int n = 0; n < (1 << EXTRA_DISTANCE_BITS[code]); n++) - { - result[dist++] = (byte)code; - } + result[dist++] = (byte)code; } - - dist >>= 7; // from now on, all distances are divided by 128 - - for (; code < NUM_DIST_BASE_CODES; code++) - { - for (int n = 0; n < (1 << (EXTRA_DISTANCE_BITS[code] - 7)); n++) - { - result[256 + dist++] = (byte)code; - } - } - - return result; } - // Return the position slot (0...29) of a match offset (0...32767) - internal static int GetSlot(int pos) => - S_DIST_LOOKUP[((pos) < 256) ? (pos) : (256 + ((pos) >> 7))]; + dist >>= 7; // from now on, all distances are divided by 128 - // Reverse 'length' of the bits in code - public static uint BitReverse(uint code, int length) + for (; code < NUM_DIST_BASE_CODES; code++) { - uint newCode = 0; - - Debug.Assert(length > 0 && length <= 16, "Invalid len"); - do + for (var n = 0; n < (1 << (EXTRA_DISTANCE_BITS[code] - 7)); n++) { - newCode |= (code & 1); - newCode <<= 1; - code >>= 1; - } while (--length > 0); - - return newCode >> 1; + result[256 + dist++] = (byte)code; + } } + + return result; + } + + // Return the position slot (0...29) of a match offset (0...32767) + internal static int GetSlot(int pos) => + S_DIST_LOOKUP[((pos) < 256) ? (pos) : (256 + ((pos) >> 7))]; + + // Reverse 'length' of the bits in code + public static uint BitReverse(uint code, int length) + { + uint newCode = 0; + + Debug.Assert(length > 0 && length <= 16, "Invalid len"); + do + { + newCode |= (code & 1); + newCode <<= 1; + code >>= 1; + } while (--length > 0); + + return newCode >> 1; } } diff --git a/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs b/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs index 017c1cb9..051b613e 100644 --- a/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs +++ b/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs @@ -6,320 +6,325 @@ using System; using System.Diagnostics; using System.IO; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +// Strictly speaking this class is not a HuffmanTree, this class is +// a lookup table combined with a HuffmanTree. The idea is to speed up +// the lookup for short symbols (they should appear more frequently ideally.) +// However we don't want to create a huge table since it might take longer to +// build the table than decoding (Deflate usually generates new tables frequently.) +// +// Jean-loup Gailly and Mark Adler gave a very good explanation about this. +// The full text (algorithm.txt) can be found inside +// ftp://ftp.uu.net/pub/archiving/zip/zlib/zlib.zip. +// +// Following paper explains decoding in details: +// Hirschberg and Lelewer, "Efficient decoding of prefix codes," +// Comm. ACM, 33,4, April 1990, pp. 449-459. +// + +internal sealed class HuffmanTree { - // Strictly speaking this class is not a HuffmanTree, this class is - // a lookup table combined with a HuffmanTree. The idea is to speed up - // the lookup for short symbols (they should appear more frequently ideally.) - // However we don't want to create a huge table since it might take longer to - // build the table than decoding (Deflate usually generates new tables frequently.) - // - // Jean-loup Gailly and Mark Adler gave a very good explanation about this. - // The full text (algorithm.txt) can be found inside - // ftp://ftp.uu.net/pub/archiving/zip/zlib/zlib.zip. - // - // Following paper explains decoding in details: - // Hirschberg and Lelewer, "Efficient decoding of prefix codes," - // Comm. ACM, 33,4, April 1990, pp. 449-459. - // + internal const int MAX_LITERAL_TREE_ELEMENTS = 288; + internal const int MAX_DIST_TREE_ELEMENTS = 32; + internal const int END_OF_BLOCK_CODE = 256; + internal const int NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS = 19; - internal sealed class HuffmanTree + private readonly int _tableBits; + private readonly short[] _table; + private readonly short[] _left; + private readonly short[] _right; + private readonly byte[] _codeLengthArray; +#if DEBUG + private uint[]? _codeArrayDebug; +#endif + + private readonly int _tableMask; + + // huffman tree for static block + public static HuffmanTree StaticLiteralLengthTree { get; } = + new HuffmanTree(GetStaticLiteralTreeLength()); + + public static HuffmanTree StaticDistanceTree { get; } = + new HuffmanTree(GetStaticDistanceTreeLength()); + + public HuffmanTree(byte[] codeLengths) { - internal const int MAX_LITERAL_TREE_ELEMENTS = 288; - internal const int MAX_DIST_TREE_ELEMENTS = 32; - internal const int END_OF_BLOCK_CODE = 256; - internal const int NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS = 19; + Debug.Assert( + codeLengths.Length == MAX_LITERAL_TREE_ELEMENTS + || codeLengths.Length == MAX_DIST_TREE_ELEMENTS + || codeLengths.Length == NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS, + "we only expect three kinds of Length here" + ); + _codeLengthArray = codeLengths; - private readonly int _tableBits; - private readonly short[] _table; - private readonly short[] _left; - private readonly short[] _right; - private readonly byte[] _codeLengthArray; + if (_codeLengthArray.Length == MAX_LITERAL_TREE_ELEMENTS) + { + // bits for Literal/Length tree table + _tableBits = 9; + } + else + { + // bits for distance tree table and code length tree table + _tableBits = 7; + } + _tableMask = (1 << _tableBits) - 1; + + _table = new short[1 << _tableBits]; + + // I need to find proof that left and right array will always be + // enough. I think they are. + _left = new short[2 * _codeLengthArray.Length]; + _right = new short[2 * _codeLengthArray.Length]; + + CreateTable(); + } + + // Generate the array contains huffman codes lengths for static huffman tree. + // The data is in RFC 1951. + private static byte[] GetStaticLiteralTreeLength() + { + var literalTreeLength = new byte[MAX_LITERAL_TREE_ELEMENTS]; + for (var i = 0; i <= 143; i++) + { + literalTreeLength[i] = 8; + } + + for (var i = 144; i <= 255; i++) + { + literalTreeLength[i] = 9; + } + + for (var i = 256; i <= 279; i++) + { + literalTreeLength[i] = 7; + } + + for (var i = 280; i <= 287; i++) + { + literalTreeLength[i] = 8; + } + + return literalTreeLength; + } + + private static byte[] GetStaticDistanceTreeLength() + { + var staticDistanceTreeLength = new byte[MAX_DIST_TREE_ELEMENTS]; + for (var i = 0; i < MAX_DIST_TREE_ELEMENTS; i++) + { + staticDistanceTreeLength[i] = 5; + } + return staticDistanceTreeLength; + } + + // Calculate the huffman code for each character based on the code length for each character. + // This algorithm is described in standard RFC 1951 + private uint[] CalculateHuffmanCode() + { + Span bitLengthCount = stackalloc uint[17]; + foreach (int codeLength in _codeLengthArray) + { + bitLengthCount[codeLength]++; + } + bitLengthCount[0] = 0; // clear count for length 0 + + Span nextCode = stackalloc uint[17]; + uint tempCode = 0; + for (var bits = 1; bits <= 16; bits++) + { + tempCode = (tempCode + bitLengthCount[bits - 1]) << 1; + nextCode[bits] = tempCode; + } + + var code = new uint[MAX_LITERAL_TREE_ELEMENTS]; + for (var i = 0; i < _codeLengthArray.Length; i++) + { + int len = _codeLengthArray[i]; + + if (len > 0) + { + code[i] = FastEncoderStatics.BitReverse(nextCode[len], len); + nextCode[len]++; + } + } + return code; + } + + private void CreateTable() + { + var codeArray = CalculateHuffmanCode(); #if DEBUG - private uint[]? _codeArrayDebug; + _codeArrayDebug = codeArray; #endif - private readonly int _tableMask; + var avail = (short)_codeLengthArray.Length; - // huffman tree for static block - public static HuffmanTree StaticLiteralLengthTree { get; } = new HuffmanTree(GetStaticLiteralTreeLength()); - - public static HuffmanTree StaticDistanceTree { get; } = new HuffmanTree(GetStaticDistanceTreeLength()); - - public HuffmanTree(byte[] codeLengths) + for (var ch = 0; ch < _codeLengthArray.Length; ch++) { - Debug.Assert( - codeLengths.Length == MAX_LITERAL_TREE_ELEMENTS || - codeLengths.Length == MAX_DIST_TREE_ELEMENTS || - codeLengths.Length == NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS, - "we only expect three kinds of Length here"); - _codeLengthArray = codeLengths; - - if (_codeLengthArray.Length == MAX_LITERAL_TREE_ELEMENTS) + // length of this code + int len = _codeLengthArray[ch]; + if (len > 0) { - // bits for Literal/Length tree table - _tableBits = 9; - } - else - { - // bits for distance tree table and code length tree table - _tableBits = 7; - } - _tableMask = (1 << _tableBits) - 1; + // start value (bit reversed) + var start = (int)codeArray[ch]; - _table = new short[1 << _tableBits]; - - // I need to find proof that left and right array will always be - // enough. I think they are. - _left = new short[2 * _codeLengthArray.Length]; - _right = new short[2 * _codeLengthArray.Length]; - - CreateTable(); - } - - // Generate the array contains huffman codes lengths for static huffman tree. - // The data is in RFC 1951. - private static byte[] GetStaticLiteralTreeLength() - { - byte[] literalTreeLength = new byte[MAX_LITERAL_TREE_ELEMENTS]; - for (int i = 0; i <= 143; i++) - { - literalTreeLength[i] = 8; - } - - for (int i = 144; i <= 255; i++) - { - literalTreeLength[i] = 9; - } - - for (int i = 256; i <= 279; i++) - { - literalTreeLength[i] = 7; - } - - for (int i = 280; i <= 287; i++) - { - literalTreeLength[i] = 8; - } - - return literalTreeLength; - } - - private static byte[] GetStaticDistanceTreeLength() - { - byte[] staticDistanceTreeLength = new byte[MAX_DIST_TREE_ELEMENTS]; - for (int i = 0; i < MAX_DIST_TREE_ELEMENTS; i++) - { - staticDistanceTreeLength[i] = 5; - } - return staticDistanceTreeLength; - } - - // Calculate the huffman code for each character based on the code length for each character. - // This algorithm is described in standard RFC 1951 - private uint[] CalculateHuffmanCode() - { - Span bitLengthCount = stackalloc uint[17]; - foreach (int codeLength in _codeLengthArray) - { - bitLengthCount[codeLength]++; - } - bitLengthCount[0] = 0; // clear count for length 0 - - Span nextCode = stackalloc uint[17]; - uint tempCode = 0; - for (int bits = 1; bits <= 16; bits++) - { - tempCode = (tempCode + bitLengthCount[bits - 1]) << 1; - nextCode[bits] = tempCode; - } - - uint[] code = new uint[MAX_LITERAL_TREE_ELEMENTS]; - for (int i = 0; i < _codeLengthArray.Length; i++) - { - int len = _codeLengthArray[i]; - - if (len > 0) + if (len <= _tableBits) { - code[i] = FastEncoderStatics.BitReverse(nextCode[len], len); - nextCode[len]++; - } - } - return code; - } - - private void CreateTable() - { - uint[] codeArray = CalculateHuffmanCode(); -#if DEBUG - _codeArrayDebug = codeArray; -#endif - - short avail = (short)_codeLengthArray.Length; - - for (int ch = 0; ch < _codeLengthArray.Length; ch++) - { - // length of this code - int len = _codeLengthArray[ch]; - if (len > 0) - { - // start value (bit reversed) - int start = (int)codeArray[ch]; - - if (len <= _tableBits) + // If a particular symbol is shorter than nine bits, + // then that symbol's translation is duplicated + // in all those entries that start with that symbol's bits. + // For example, if the symbol is four bits, then it's duplicated + // 32 times in a nine-bit table. If a symbol is nine bits long, + // it appears in the table once. + // + // Make sure that in the loop below, code is always + // less than table_size. + // + // On last iteration we store at array index: + // initial_start_at + (locs-1)*increment + // = initial_start_at + locs*increment - increment + // = initial_start_at + (1 << tableBits) - increment + // = initial_start_at + table_size - increment + // + // Therefore we must ensure: + // initial_start_at + table_size - increment < table_size + // or: initial_start_at < increment + // + var increment = 1 << len; + if (start >= increment) { - // If a particular symbol is shorter than nine bits, - // then that symbol's translation is duplicated - // in all those entries that start with that symbol's bits. - // For example, if the symbol is four bits, then it's duplicated - // 32 times in a nine-bit table. If a symbol is nine bits long, - // it appears in the table once. - // - // Make sure that in the loop below, code is always - // less than table_size. - // - // On last iteration we store at array index: - // initial_start_at + (locs-1)*increment - // = initial_start_at + locs*increment - increment - // = initial_start_at + (1 << tableBits) - increment - // = initial_start_at + table_size - increment - // - // Therefore we must ensure: - // initial_start_at + table_size - increment < table_size - // or: initial_start_at < increment - // - int increment = 1 << len; - if (start >= increment) + throw new InvalidDataException("Deflate64: invalid Huffman data"); + } + + // Note the bits in the table are reverted. + var locs = 1 << (_tableBits - len); + for (var j = 0; j < locs; j++) + { + _table[start] = (short)ch; + start += increment; + } + } + else + { + // For any code which has length longer than num_elements, + // build a binary tree. + + var overflowBits = len - _tableBits; // the nodes we need to respent the data. + var codeBitMask = 1 << _tableBits; // mask to get current bit (the bits can't fit in the table) + + // the left, right table is used to repesent the + // the rest bits. When we got the first part (number bits.) and look at + // tbe table, we will need to follow the tree to find the real character. + // This is in place to avoid bloating the table if there are + // a few ones with long code. + var index = start & ((1 << _tableBits) - 1); + var array = _table; + + do + { + var value = array[index]; + + if (value == 0) { + // set up next pointer if this node is not used before. + array[index] = (short)-avail; // use next available slot. + value = (short)-avail; + avail++; + } + + if (value > 0) + { + // prevent an IndexOutOfRangeException from array[index] throw new InvalidDataException("Deflate64: invalid Huffman data"); } - // Note the bits in the table are reverted. - int locs = 1 << (_tableBits - len); - for (int j = 0; j < locs; j++) + Debug.Assert( + value < 0, + "CreateTable: Only negative numbers are used for tree pointers!" + ); + + if ((start & codeBitMask) == 0) { - _table[start] = (short)ch; - start += increment; + // if current bit is 0, go change the left array + array = _left; } - } - else - { - // For any code which has length longer than num_elements, - // build a binary tree. - - int overflowBits = len - _tableBits; // the nodes we need to respent the data. - int codeBitMask = 1 << _tableBits; // mask to get current bit (the bits can't fit in the table) - - // the left, right table is used to repesent the - // the rest bits. When we got the first part (number bits.) and look at - // tbe table, we will need to follow the tree to find the real character. - // This is in place to avoid bloating the table if there are - // a few ones with long code. - int index = start & ((1 << _tableBits) - 1); - short[] array = _table; - - do + else { - short value = array[index]; + // if current bit is 1, set value in the right array + array = _right; + } + index = -value; // go to next node - if (value == 0) - { - // set up next pointer if this node is not used before. - array[index] = (short)-avail; // use next available slot. - value = (short)-avail; - avail++; - } + codeBitMask <<= 1; + overflowBits--; + } while (overflowBits != 0); - if (value > 0) - { - // prevent an IndexOutOfRangeException from array[index] - throw new InvalidDataException("Deflate64: invalid Huffman data"); - } - - Debug.Assert(value < 0, "CreateTable: Only negative numbers are used for tree pointers!"); - - if ((start & codeBitMask) == 0) - { - // if current bit is 0, go change the left array - array = _left; - } - else - { - // if current bit is 1, set value in the right array - array = _right; - } - index = -value; // go to next node - - codeBitMask <<= 1; - overflowBits--; - } while (overflowBits != 0); - - array[index] = (short)ch; - } + array[index] = (short)ch; } } } + } - // - // This function will try to get enough bits from input and - // try to decode the bits. - // If there are no enought bits in the input, this function will return -1. - // - public int GetNextSymbol(InputBuffer input) - { - // Try to load 16 bits into input buffer if possible and get the bitBuffer value. - // If there aren't 16 bits available we will return all we have in the - // input buffer. - uint bitBuffer = input.TryLoad16Bits(); - if (input.AvailableBits == 0) - { // running out of input. - return -1; - } - - // decode an element - int symbol = _table[bitBuffer & _tableMask]; - if (symbol < 0) - { // this will be the start of the binary tree - // navigate the tree - uint mask = (uint)1 << _tableBits; - do - { - symbol = -symbol; - if ((bitBuffer & mask) == 0) - { - symbol = _left[symbol]; - } - else - { - symbol = _right[symbol]; - } - - mask <<= 1; - } while (symbol < 0); - } - - int codeLength = _codeLengthArray[symbol]; - - // huffman code lengths must be at least 1 bit long - if (codeLength <= 0) - { - throw new InvalidDataException("Deflate64: invalid Huffman data"); - } - - // - // If this code is longer than the # bits we had in the bit buffer (i.e. - // we read only part of the code), we can hit the entry in the table or the tree - // for another symbol. However the length of another symbol will not match the - // available bits count. - if (codeLength > input.AvailableBits) - { - // We already tried to load 16 bits and maximum length is 15, - // so this means we are running out of input. - return -1; - } - - input.SkipBits(codeLength); - return symbol; + // + // This function will try to get enough bits from input and + // try to decode the bits. + // If there are no enought bits in the input, this function will return -1. + // + public int GetNextSymbol(InputBuffer input) + { + // Try to load 16 bits into input buffer if possible and get the bitBuffer value. + // If there aren't 16 bits available we will return all we have in the + // input buffer. + var bitBuffer = input.TryLoad16Bits(); + if (input.AvailableBits == 0) + { // running out of input. + return -1; } + + // decode an element + int symbol = _table[bitBuffer & _tableMask]; + if (symbol < 0) + { // this will be the start of the binary tree + // navigate the tree + var mask = (uint)1 << _tableBits; + do + { + symbol = -symbol; + if ((bitBuffer & mask) == 0) + { + symbol = _left[symbol]; + } + else + { + symbol = _right[symbol]; + } + + mask <<= 1; + } while (symbol < 0); + } + + int codeLength = _codeLengthArray[symbol]; + + // huffman code lengths must be at least 1 bit long + if (codeLength <= 0) + { + throw new InvalidDataException("Deflate64: invalid Huffman data"); + } + + // + // If this code is longer than the # bits we had in the bit buffer (i.e. + // we read only part of the code), we can hit the entry in the table or the tree + // for another symbol. However the length of another symbol will not match the + // available bits count. + if (codeLength > input.AvailableBits) + { + // We already tried to load 16 bits and maximum length is 15, + // so this means we are running out of input. + return -1; + } + + input.SkipBits(codeLength); + return symbol; } } diff --git a/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs b/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs index de9b78d5..6a3c226b 100644 --- a/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs +++ b/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs @@ -32,711 +32,847 @@ using System; using System.Diagnostics; using System.IO; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +internal sealed class InflaterManaged { - internal sealed class InflaterManaged - { - // const tables used in decoding: + // const tables used in decoding: - // Extra bits for length code 257 - 285. - private static ReadOnlySpan S_EXTRA_LENGTH_BITS => new byte[] - { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,16 }; - - // The base length for length code 257 - 285. - // The formula to get the real length for a length code is lengthBase[code - 257] + (value stored in extraBits) - private static readonly int[] S_LENGTH_BASE = - { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,3}; - - // The base distance for distance code 0 - 31 - // The real distance for a distance code is distanceBasePosition[code] + (value stored in extraBits) - private static readonly int[] S_DISTANCE_BASE_POSITION = - { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,32769,49153 }; - - // code lengths for code length alphabet is stored in following order - private static ReadOnlySpan S_CODE_ORDER => new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - - private static ReadOnlySpan S_STATIC_DISTANCE_TREE_TABLE => new byte[] + // Extra bits for length code 257 - 285. + private static ReadOnlySpan S_EXTRA_LENGTH_BITS => + new byte[] { - 0x00,0x10,0x08,0x18,0x04,0x14,0x0c,0x1c,0x02,0x12,0x0a,0x1a, - 0x06,0x16,0x0e,0x1e,0x01,0x11,0x09,0x19,0x05,0x15,0x0d,0x1d, - 0x03,0x13,0x0b,0x1b,0x07,0x17,0x0f,0x1f + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 16 }; - private readonly OutputWindow _output; - private readonly InputBuffer _input; - private HuffmanTree _literalLengthTree; - private HuffmanTree _distanceTree; + // The base length for length code 257 - 285. + // The formula to get the real length for a length code is lengthBase[code - 257] + (value stored in extraBits) + private static readonly int[] S_LENGTH_BASE = + { + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 15, + 17, + 19, + 23, + 27, + 31, + 35, + 43, + 51, + 59, + 67, + 83, + 99, + 115, + 131, + 163, + 195, + 227, + 3 + }; - private InflaterState _state; - //private bool _hasFormatReader; - private int _bfinal; - private BlockType _blockType; + // The base distance for distance code 0 - 31 + // The real distance for a distance code is distanceBasePosition[code] + (value stored in extraBits) + private static readonly int[] S_DISTANCE_BASE_POSITION = + { + 1, + 2, + 3, + 4, + 5, + 7, + 9, + 13, + 17, + 25, + 33, + 49, + 65, + 97, + 129, + 193, + 257, + 385, + 513, + 769, + 1025, + 1537, + 2049, + 3073, + 4097, + 6145, + 8193, + 12289, + 16385, + 24577, + 32769, + 49153 + }; - // uncompressed block - private readonly byte[] _blockLengthBuffer = new byte[4]; - private int _blockLength; + // code lengths for code length alphabet is stored in following order + private static ReadOnlySpan S_CODE_ORDER => + new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - // compressed block - private int _length; - private int _distanceCode; - private int _extraBits; - - private int _loopCounter; - private int _literalLengthCodeCount; - private int _distanceCodeCount; - private int _codeLengthCodeCount; - private int _codeArraySize; - private int _lengthCode; - - private readonly byte[] _codeList; // temporary array to store the code length for literal/Length and distance - private readonly byte[] _codeLengthTreeCodeLength; - private readonly bool _deflate64; - private HuffmanTree _codeLengthTree; - - //private IFileFormatReader _formatReader; // class to decode header and footer (e.g. gzip) - - internal InflaterManaged(/*IFileFormatReader reader, */bool deflate64) + private static ReadOnlySpan S_STATIC_DISTANCE_TREE_TABLE => + new byte[] { - _output = new OutputWindow(); - _input = new InputBuffer(); + 0x00, + 0x10, + 0x08, + 0x18, + 0x04, + 0x14, + 0x0c, + 0x1c, + 0x02, + 0x12, + 0x0a, + 0x1a, + 0x06, + 0x16, + 0x0e, + 0x1e, + 0x01, + 0x11, + 0x09, + 0x19, + 0x05, + 0x15, + 0x0d, + 0x1d, + 0x03, + 0x13, + 0x0b, + 0x1b, + 0x07, + 0x17, + 0x0f, + 0x1f + }; - _codeList = new byte[HuffmanTree.MAX_LITERAL_TREE_ELEMENTS + HuffmanTree.MAX_DIST_TREE_ELEMENTS]; - _codeLengthTreeCodeLength = new byte[HuffmanTree.NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS]; - _deflate64 = deflate64; - //if (reader != null) - //{ - // _formatReader = reader; - // _hasFormatReader = true; - //} - Reset(); - } + private readonly OutputWindow _output; + private readonly InputBuffer _input; + private HuffmanTree _literalLengthTree; + private HuffmanTree _distanceTree; - private void Reset() + private InflaterState _state; + + //private bool _hasFormatReader; + private int _bfinal; + private BlockType _blockType; + + // uncompressed block + private readonly byte[] _blockLengthBuffer = new byte[4]; + private int _blockLength; + + // compressed block + private int _length; + private int _distanceCode; + private int _extraBits; + + private int _loopCounter; + private int _literalLengthCodeCount; + private int _distanceCodeCount; + private int _codeLengthCodeCount; + private int _codeArraySize; + private int _lengthCode; + + private readonly byte[] _codeList; // temporary array to store the code length for literal/Length and distance + private readonly byte[] _codeLengthTreeCodeLength; + private readonly bool _deflate64; + private HuffmanTree _codeLengthTree; + + //private IFileFormatReader _formatReader; // class to decode header and footer (e.g. gzip) + + internal InflaterManaged( /*IFileFormatReader reader, */ + bool deflate64 + ) + { + _output = new OutputWindow(); + _input = new InputBuffer(); + + _codeList = new byte[ + HuffmanTree.MAX_LITERAL_TREE_ELEMENTS + HuffmanTree.MAX_DIST_TREE_ELEMENTS + ]; + _codeLengthTreeCodeLength = new byte[HuffmanTree.NUMBER_OF_CODE_LENGTH_TREE_ELEMENTS]; + _deflate64 = deflate64; + //if (reader != null) + //{ + // _formatReader = reader; + // _hasFormatReader = true; + //} + Reset(); + } + + private void Reset() => + _state = //_hasFormatReader ? + //InflaterState.ReadingHeader : // start by reading Header info + InflaterState.ReadingBFinal; // start by reading BFinal bit + + public void SetInput(byte[] inputBytes, int offset, int length) => + _input.SetInput(inputBytes, offset, length); // append the bytes + + public bool Finished() => + _state == InflaterState.Done || _state == InflaterState.VerifyingFooter; + + public int AvailableOutput => _output.AvailableBytes; + + public int Inflate(byte[] bytes, int offset, int length) + { + // copy bytes from output to outputbytes if we have available bytes + // if buffer is not filled up. keep decoding until no input are available + // if decodeBlock returns false. Throw an exception. + var count = 0; + do { - _state = //_hasFormatReader ? - //InflaterState.ReadingHeader : // start by reading Header info - InflaterState.ReadingBFinal; // start by reading BFinal bit - } - - public void SetInput(byte[] inputBytes, int offset, int length) => - _input.SetInput(inputBytes, offset, length); // append the bytes - - public bool Finished() => _state == InflaterState.Done || _state == InflaterState.VerifyingFooter; - - public int AvailableOutput => _output.AvailableBytes; - - public int Inflate(byte[] bytes, int offset, int length) - { - // copy bytes from output to outputbytes if we have available bytes - // if buffer is not filled up. keep decoding until no input are available - // if decodeBlock returns false. Throw an exception. - int count = 0; - do + var copied = _output.CopyTo(bytes, offset, length); + if (copied > 0) { - int copied = _output.CopyTo(bytes, offset, length); - if (copied > 0) - { - //if (_hasFormatReader) - //{ - // _formatReader.UpdateWithBytesRead(bytes, offset, copied); - //} + //if (_hasFormatReader) + //{ + // _formatReader.UpdateWithBytesRead(bytes, offset, copied); + //} - offset += copied; - count += copied; - length -= copied; - } - - if (length == 0) - { // filled in the bytes array - break; - } - // Decode will return false when more input is needed - } while (!Finished() && Decode()); - - if (_state == InflaterState.VerifyingFooter) - { // finished reading CRC - // In this case finished is true and output window has all the data. - // But some data in output window might not be copied out. - if (_output.AvailableBytes == 0) - { - //_formatReader.Validate(); - } + offset += copied; + count += copied; + length -= copied; } - return count; + if (length == 0) + { // filled in the bytes array + break; + } + // Decode will return false when more input is needed + } while (!Finished() && Decode()); + + if (_state == InflaterState.VerifyingFooter) + { // finished reading CRC + // In this case finished is true and output window has all the data. + // But some data in output window might not be copied out. + if (_output.AvailableBytes == 0) + { + //_formatReader.Validate(); + } } - //Each block of compressed data begins with 3 header bits - // containing the following data: - // first bit BFINAL - // next 2 bits BTYPE - // Note that the header bits do not necessarily begin on a byte - // boundary, since a block does not necessarily occupy an integral - // number of bytes. - // BFINAL is set if and only if this is the last block of the data - // set. - // BTYPE specifies how the data are compressed, as follows: - // 00 - no compression - // 01 - compressed with fixed Huffman codes - // 10 - compressed with dynamic Huffman codes - // 11 - reserved (error) - // The only difference between the two compressed cases is how the - // Huffman codes for the literal/length and distance alphabets are - // defined. - // - // This function returns true for success (end of block or output window is full,) - // false if we are short of input - // - private bool Decode() - { - bool eob = false; - bool result = false; + return count; + } - if (Finished()) + //Each block of compressed data begins with 3 header bits + // containing the following data: + // first bit BFINAL + // next 2 bits BTYPE + // Note that the header bits do not necessarily begin on a byte + // boundary, since a block does not necessarily occupy an integral + // number of bytes. + // BFINAL is set if and only if this is the last block of the data + // set. + // BTYPE specifies how the data are compressed, as follows: + // 00 - no compression + // 01 - compressed with fixed Huffman codes + // 10 - compressed with dynamic Huffman codes + // 11 - reserved (error) + // The only difference between the two compressed cases is how the + // Huffman codes for the literal/length and distance alphabets are + // defined. + // + // This function returns true for success (end of block or output window is full,) + // false if we are short of input + // + private bool Decode() + { + var eob = false; + var result = false; + + if (Finished()) + { + return true; + } + + //if (_hasFormatReader) + //{ + // if (_state == InflaterState.ReadingHeader) + // { + // if (!_formatReader.ReadHeader(_input)) + // { + // return false; + // } + // _state = InflaterState.ReadingBFinal; + // } + // else if (_state == InflaterState.StartReadingFooter || _state == InflaterState.ReadingFooter) + // { + // if (!_formatReader.ReadFooter(_input)) + // return false; + + // _state = InflaterState.VerifyingFooter; + // return true; + // } + //} + + if (_state == InflaterState.ReadingBFinal) + { + // reading bfinal bit + // Need 1 bit + if (!_input.EnsureBitsAvailable(1)) { - return true; + return false; } - //if (_hasFormatReader) - //{ - // if (_state == InflaterState.ReadingHeader) - // { - // if (!_formatReader.ReadHeader(_input)) - // { - // return false; - // } - // _state = InflaterState.ReadingBFinal; - // } - // else if (_state == InflaterState.StartReadingFooter || _state == InflaterState.ReadingFooter) - // { - // if (!_formatReader.ReadFooter(_input)) - // return false; + _bfinal = _input.GetBits(1); + _state = InflaterState.ReadingBType; + } - // _state = InflaterState.VerifyingFooter; - // return true; - // } - //} - - if (_state == InflaterState.ReadingBFinal) + if (_state == InflaterState.ReadingBType) + { + // Need 2 bits + if (!_input.EnsureBitsAvailable(2)) { - // reading bfinal bit - // Need 1 bit - if (!_input.EnsureBitsAvailable(1)) - { - return false; - } - - _bfinal = _input.GetBits(1); _state = InflaterState.ReadingBType; + return false; } - if (_state == InflaterState.ReadingBType) - { - // Need 2 bits - if (!_input.EnsureBitsAvailable(2)) - { - _state = InflaterState.ReadingBType; - return false; - } - - _blockType = (BlockType)_input.GetBits(2); - if (_blockType == BlockType.Dynamic) - { - _state = InflaterState.ReadingNumLitCodes; - } - else if (_blockType == BlockType.Static) - { - _literalLengthTree = HuffmanTree.StaticLiteralLengthTree; - _distanceTree = HuffmanTree.StaticDistanceTree; - _state = InflaterState.DecodeTop; - } - else if (_blockType == BlockType.Uncompressed) - { - _state = InflaterState.UncompressedAligning; - } - else - { - throw new InvalidDataException("Deflate64: unknown block type"); - } - } - + _blockType = (BlockType)_input.GetBits(2); if (_blockType == BlockType.Dynamic) { - if (_state < InflaterState.DecodeTop) - { - // we are reading the header - result = DecodeDynamicBlockHeader(); - } - else - { - result = DecodeBlock(out eob); // this can returns true when output is full - } + _state = InflaterState.ReadingNumLitCodes; } else if (_blockType == BlockType.Static) { - result = DecodeBlock(out eob); + _literalLengthTree = HuffmanTree.StaticLiteralLengthTree; + _distanceTree = HuffmanTree.StaticDistanceTree; + _state = InflaterState.DecodeTop; } else if (_blockType == BlockType.Uncompressed) { - result = DecodeUncompressedBlock(out eob); + _state = InflaterState.UncompressedAligning; } else { throw new InvalidDataException("Deflate64: unknown block type"); } - - // - // If we reached the end of the block and the block we were decoding had - // bfinal=1 (final block) - // - if (eob && (_bfinal != 0)) - { - //if (_hasFormatReader) - // _state = InflaterState.StartReadingFooter; - //else - _state = InflaterState.Done; - } - return result; } - - // Format of Non-compressed blocks (BTYPE=00): - // - // Any bits of input up to the next byte boundary are ignored. - // The rest of the block consists of the following information: - // - // 0 1 2 3 4... - // +---+---+---+---+================================+ - // | LEN | NLEN |... LEN bytes of literal data...| - // +---+---+---+---+================================+ - // - // LEN is the number of data bytes in the block. NLEN is the - // one's complement of LEN. - private bool DecodeUncompressedBlock(out bool endOfBlock) + if (_blockType == BlockType.Dynamic) { - endOfBlock = false; - while (true) + if (_state < InflaterState.DecodeTop) { - switch (_state) - { - case InflaterState.UncompressedAligning: // initial state when calling this function - // we must skip to a byte boundary - _input.SkipToByteBoundary(); - _state = InflaterState.UncompressedByte1; - goto case InflaterState.UncompressedByte1; - - case InflaterState.UncompressedByte1: // decoding block length - case InflaterState.UncompressedByte2: - case InflaterState.UncompressedByte3: - case InflaterState.UncompressedByte4: - int bits = _input.GetBits(8); - if (bits < 0) - { - return false; - } - - _blockLengthBuffer[_state - InflaterState.UncompressedByte1] = (byte)bits; - if (_state == InflaterState.UncompressedByte4) - { - _blockLength = _blockLengthBuffer[0] + ((int)_blockLengthBuffer[1]) * 256; - int blockLengthComplement = _blockLengthBuffer[2] + ((int)_blockLengthBuffer[3]) * 256; - - // make sure complement matches - if ((ushort)_blockLength != (ushort)(~blockLengthComplement)) - { - throw new InvalidDataException("Deflate64: invalid block length"); - } - } - - _state += 1; - break; - - case InflaterState.DecodingUncompressed: // copying block data - - // Directly copy bytes from input to output. - int bytesCopied = _output.CopyFrom(_input, _blockLength); - _blockLength -= bytesCopied; - - if (_blockLength == 0) - { - // Done with this block, need to re-init bit buffer for next block - _state = InflaterState.ReadingBFinal; - endOfBlock = true; - return true; - } - - // We can fail to copy all bytes for two reasons: - // Running out of Input - // running out of free space in output window - if (_output.FreeBytes == 0) - { - return true; - } - - return false; - - default: - Debug./*Fail*/Assert(false, "check why we are here!"); - throw new InvalidDataException("Deflate64: unknown state"); - } + // we are reading the header + result = DecodeDynamicBlockHeader(); + } + else + { + result = DecodeBlock(out eob); // this can returns true when output is full } } - - private bool DecodeBlock(out bool endOfBlockCodeSeen) + else if (_blockType == BlockType.Static) { - endOfBlockCodeSeen = false; - - int freeBytes = _output.FreeBytes; // it is a little bit faster than frequently accessing the property - while (freeBytes > 65536) - { - // With Deflate64 we can have up to a 64kb length, so we ensure at least that much space is available - // in the OutputWindow to avoid overwriting previous unflushed output data. - - int symbol; - switch (_state) - { - case InflaterState.DecodeTop: - // decode an element from the literal tree - - // TODO: optimize this!!! - symbol = _literalLengthTree.GetNextSymbol(_input); - if (symbol < 0) - { - // running out of input - return false; - } - - if (symbol < 256) - { - // literal - _output.Write((byte)symbol); - --freeBytes; - } - else if (symbol == 256) - { - // end of block - endOfBlockCodeSeen = true; - // Reset state - _state = InflaterState.ReadingBFinal; - return true; - } - else - { - // length/distance pair - symbol -= 257; // length code started at 257 - if (symbol < 8) - { - symbol += 3; // match length = 3,4,5,6,7,8,9,10 - _extraBits = 0; - } - else if (!_deflate64 && symbol == 28) - { - // extra bits for code 285 is 0 - symbol = 258; // code 285 means length 258 - _extraBits = 0; - } - else - { - if (symbol < 0 || symbol >= S_EXTRA_LENGTH_BITS.Length) - { - throw new InvalidDataException("Deflate64: invalid data"); - } - _extraBits = S_EXTRA_LENGTH_BITS[symbol]; - Debug.Assert(_extraBits != 0, "We handle other cases separately!"); - } - _length = symbol; - goto case InflaterState.HaveInitialLength; - } - break; - - case InflaterState.HaveInitialLength: - if (_extraBits > 0) - { - _state = InflaterState.HaveInitialLength; - int bits = _input.GetBits(_extraBits); - if (bits < 0) - { - return false; - } - - if (_length < 0 || _length >= S_LENGTH_BASE.Length) - { - throw new InvalidDataException("Deflate64: invalid data"); - } - _length = S_LENGTH_BASE[_length] + bits; - } - _state = InflaterState.HaveFullLength; - goto case InflaterState.HaveFullLength; - - case InflaterState.HaveFullLength: - if (_blockType == BlockType.Dynamic) - { - _distanceCode = _distanceTree.GetNextSymbol(_input); - } - else - { - // get distance code directly for static block - _distanceCode = _input.GetBits(5); - if (_distanceCode >= 0) - { - _distanceCode = S_STATIC_DISTANCE_TREE_TABLE[_distanceCode]; - } - } - - if (_distanceCode < 0) - { - // running out input - return false; - } - - _state = InflaterState.HaveDistCode; - goto case InflaterState.HaveDistCode; - - case InflaterState.HaveDistCode: - // To avoid a table lookup we note that for distanceCode > 3, - // extra_bits = (distanceCode-2) >> 1 - int offset; - if (_distanceCode > 3) - { - _extraBits = (_distanceCode - 2) >> 1; - int bits = _input.GetBits(_extraBits); - if (bits < 0) - { - return false; - } - offset = S_DISTANCE_BASE_POSITION[_distanceCode] + bits; - } - else - { - offset = _distanceCode + 1; - } - - _output.WriteLengthDistance(_length, offset); - freeBytes -= _length; - _state = InflaterState.DecodeTop; - break; - - default: - Debug./*Fail*/Assert(false, "check why we are here!"); - throw new InvalidDataException("Deflate64: unknown state"); - } - } - - return true; + result = DecodeBlock(out eob); + } + else if (_blockType == BlockType.Uncompressed) + { + result = DecodeUncompressedBlock(out eob); + } + else + { + throw new InvalidDataException("Deflate64: unknown block type"); } + // + // If we reached the end of the block and the block we were decoding had + // bfinal=1 (final block) + // + if (eob && (_bfinal != 0)) + { + //if (_hasFormatReader) + // _state = InflaterState.StartReadingFooter; + //else + _state = InflaterState.Done; + } + return result; + } - // Format of the dynamic block header: - // 5 Bits: HLIT, # of Literal/Length codes - 257 (257 - 286) - // 5 Bits: HDIST, # of Distance codes - 1 (1 - 32) - // 4 Bits: HCLEN, # of Code Length codes - 4 (4 - 19) - // - // (HCLEN + 4) x 3 bits: code lengths for the code length - // alphabet given just above, in the order: 16, 17, 18, - // 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - // - // These code lengths are interpreted as 3-bit integers - // (0-7); as above, a code length of 0 means the - // corresponding symbol (literal/length or distance code - // length) is not used. - // - // HLIT + 257 code lengths for the literal/length alphabet, - // encoded using the code length Huffman code - // - // HDIST + 1 code lengths for the distance alphabet, - // encoded using the code length Huffman code - // - // The code length repeat codes can cross from HLIT + 257 to the - // HDIST + 1 code lengths. In other words, all code lengths form - // a single sequence of HLIT + HDIST + 258 values. - private bool DecodeDynamicBlockHeader() + // Format of Non-compressed blocks (BTYPE=00): + // + // Any bits of input up to the next byte boundary are ignored. + // The rest of the block consists of the following information: + // + // 0 1 2 3 4... + // +---+---+---+---+================================+ + // | LEN | NLEN |... LEN bytes of literal data...| + // +---+---+---+---+================================+ + // + // LEN is the number of data bytes in the block. NLEN is the + // one's complement of LEN. + private bool DecodeUncompressedBlock(out bool endOfBlock) + { + endOfBlock = false; + while (true) { switch (_state) { - case InflaterState.ReadingNumLitCodes: - _literalLengthCodeCount = _input.GetBits(5); - if (_literalLengthCodeCount < 0) + case InflaterState.UncompressedAligning: // initial state when calling this function + // we must skip to a byte boundary + _input.SkipToByteBoundary(); + _state = InflaterState.UncompressedByte1; + goto case InflaterState.UncompressedByte1; + + case InflaterState.UncompressedByte1: // decoding block length + case InflaterState.UncompressedByte2: + case InflaterState.UncompressedByte3: + case InflaterState.UncompressedByte4: + var bits = _input.GetBits(8); + if (bits < 0) { return false; } - _literalLengthCodeCount += 257; - _state = InflaterState.ReadingNumDistCodes; - goto case InflaterState.ReadingNumDistCodes; - case InflaterState.ReadingNumDistCodes: - _distanceCodeCount = _input.GetBits(5); - if (_distanceCodeCount < 0) + _blockLengthBuffer[_state - InflaterState.UncompressedByte1] = (byte)bits; + if (_state == InflaterState.UncompressedByte4) { + _blockLength = _blockLengthBuffer[0] + (_blockLengthBuffer[1] * 256); + var blockLengthComplement = + _blockLengthBuffer[2] + (_blockLengthBuffer[3] * 256); + + // make sure complement matches + if ((ushort)_blockLength != (ushort)(~blockLengthComplement)) + { + throw new InvalidDataException("Deflate64: invalid block length"); + } + } + + _state += 1; + break; + + case InflaterState.DecodingUncompressed: // copying block data + + // Directly copy bytes from input to output. + var bytesCopied = _output.CopyFrom(_input, _blockLength); + _blockLength -= bytesCopied; + + if (_blockLength == 0) + { + // Done with this block, need to re-init bit buffer for next block + _state = InflaterState.ReadingBFinal; + endOfBlock = true; + return true; + } + + // We can fail to copy all bytes for two reasons: + // Running out of Input + // running out of free space in output window + if (_output.FreeBytes == 0) + { + return true; + } + + return false; + + default: + Debug. /*Fail*/ + Assert(false, "check why we are here!"); + throw new InvalidDataException("Deflate64: unknown state"); + } + } + } + + private bool DecodeBlock(out bool endOfBlockCodeSeen) + { + endOfBlockCodeSeen = false; + + var freeBytes = _output.FreeBytes; // it is a little bit faster than frequently accessing the property + while (freeBytes > 65536) + { + // With Deflate64 we can have up to a 64kb length, so we ensure at least that much space is available + // in the OutputWindow to avoid overwriting previous unflushed output data. + + int symbol; + switch (_state) + { + case InflaterState.DecodeTop: + // decode an element from the literal tree + + // TODO: optimize this!!! + symbol = _literalLengthTree.GetNextSymbol(_input); + if (symbol < 0) + { + // running out of input return false; } - _distanceCodeCount += 1; - _state = InflaterState.ReadingNumCodeLengthCodes; - goto case InflaterState.ReadingNumCodeLengthCodes; - case InflaterState.ReadingNumCodeLengthCodes: - _codeLengthCodeCount = _input.GetBits(4); - if (_codeLengthCodeCount < 0) + if (symbol < 256) { - return false; + // literal + _output.Write((byte)symbol); + --freeBytes; } - _codeLengthCodeCount += 4; - _loopCounter = 0; - _state = InflaterState.ReadingCodeLengthCodes; - goto case InflaterState.ReadingCodeLengthCodes; - - case InflaterState.ReadingCodeLengthCodes: - while (_loopCounter < _codeLengthCodeCount) + else if (symbol == 256) { - int bits = _input.GetBits(3); + // end of block + endOfBlockCodeSeen = true; + // Reset state + _state = InflaterState.ReadingBFinal; + return true; + } + else + { + // length/distance pair + symbol -= 257; // length code started at 257 + if (symbol < 8) + { + symbol += 3; // match length = 3,4,5,6,7,8,9,10 + _extraBits = 0; + } + else if (!_deflate64 && symbol == 28) + { + // extra bits for code 285 is 0 + symbol = 258; // code 285 means length 258 + _extraBits = 0; + } + else + { + if (symbol < 0 || symbol >= S_EXTRA_LENGTH_BITS.Length) + { + throw new InvalidDataException("Deflate64: invalid data"); + } + _extraBits = S_EXTRA_LENGTH_BITS[symbol]; + Debug.Assert(_extraBits != 0, "We handle other cases separately!"); + } + _length = symbol; + goto case InflaterState.HaveInitialLength; + } + break; + + case InflaterState.HaveInitialLength: + if (_extraBits > 0) + { + _state = InflaterState.HaveInitialLength; + var bits = _input.GetBits(_extraBits); if (bits < 0) { return false; } - _codeLengthTreeCodeLength[S_CODE_ORDER[_loopCounter]] = (byte)bits; - ++_loopCounter; - } - for (int i = _codeLengthCodeCount; i < S_CODE_ORDER.Length; i++) + if (_length < 0 || _length >= S_LENGTH_BASE.Length) + { + throw new InvalidDataException("Deflate64: invalid data"); + } + _length = S_LENGTH_BASE[_length] + bits; + } + _state = InflaterState.HaveFullLength; + goto case InflaterState.HaveFullLength; + + case InflaterState.HaveFullLength: + if (_blockType == BlockType.Dynamic) { - _codeLengthTreeCodeLength[S_CODE_ORDER[i]] = 0; + _distanceCode = _distanceTree.GetNextSymbol(_input); } - - // create huffman tree for code length - _codeLengthTree = new HuffmanTree(_codeLengthTreeCodeLength); - _codeArraySize = _literalLengthCodeCount + _distanceCodeCount; - _loopCounter = 0; // reset loop count - - _state = InflaterState.ReadingTreeCodesBefore; - goto case InflaterState.ReadingTreeCodesBefore; - - case InflaterState.ReadingTreeCodesBefore: - case InflaterState.ReadingTreeCodesAfter: - while (_loopCounter < _codeArraySize) + else { - if (_state == InflaterState.ReadingTreeCodesBefore) + // get distance code directly for static block + _distanceCode = _input.GetBits(5); + if (_distanceCode >= 0) { - if ((_lengthCode = _codeLengthTree.GetNextSymbol(_input)) < 0) - { - return false; - } + _distanceCode = S_STATIC_DISTANCE_TREE_TABLE[_distanceCode]; } - - // The alphabet for code lengths is as follows: - // 0 - 15: Represent code lengths of 0 - 15 - // 16: Copy the previous code length 3 - 6 times. - // The next 2 bits indicate repeat length - // (0 = 3, ... , 3 = 6) - // Example: Codes 8, 16 (+2 bits 11), - // 16 (+2 bits 10) will expand to - // 12 code lengths of 8 (1 + 6 + 5) - // 17: Repeat a code length of 0 for 3 - 10 times. - // (3 bits of length) - // 18: Repeat a code length of 0 for 11 - 138 times - // (7 bits of length) - if (_lengthCode <= 15) - { - _codeList[_loopCounter++] = (byte)_lengthCode; - } - else - { - int repeatCount; - if (_lengthCode == 16) - { - if (!_input.EnsureBitsAvailable(2)) - { - _state = InflaterState.ReadingTreeCodesAfter; - return false; - } - - if (_loopCounter == 0) - { - // can't have "prev code" on first code - throw new InvalidDataException(); - } - - byte previousCode = _codeList[_loopCounter - 1]; - repeatCount = _input.GetBits(2) + 3; - - if (_loopCounter + repeatCount > _codeArraySize) - { - throw new InvalidDataException(); - } - - for (int j = 0; j < repeatCount; j++) - { - _codeList[_loopCounter++] = previousCode; - } - } - else if (_lengthCode == 17) - { - if (!_input.EnsureBitsAvailable(3)) - { - _state = InflaterState.ReadingTreeCodesAfter; - return false; - } - - repeatCount = _input.GetBits(3) + 3; - - if (_loopCounter + repeatCount > _codeArraySize) - { - throw new InvalidDataException(); - } - - for (int j = 0; j < repeatCount; j++) - { - _codeList[_loopCounter++] = 0; - } - } - else - { - // code == 18 - if (!_input.EnsureBitsAvailable(7)) - { - _state = InflaterState.ReadingTreeCodesAfter; - return false; - } - - repeatCount = _input.GetBits(7) + 11; - - if (_loopCounter + repeatCount > _codeArraySize) - { - throw new InvalidDataException(); - } - - for (int j = 0; j < repeatCount; j++) - { - _codeList[_loopCounter++] = 0; - } - } - } - _state = InflaterState.ReadingTreeCodesBefore; // we want to read the next code. } + + if (_distanceCode < 0) + { + // running out input + return false; + } + + _state = InflaterState.HaveDistCode; + goto case InflaterState.HaveDistCode; + + case InflaterState.HaveDistCode: + // To avoid a table lookup we note that for distanceCode > 3, + // extra_bits = (distanceCode-2) >> 1 + int offset; + if (_distanceCode > 3) + { + _extraBits = (_distanceCode - 2) >> 1; + var bits = _input.GetBits(_extraBits); + if (bits < 0) + { + return false; + } + offset = S_DISTANCE_BASE_POSITION[_distanceCode] + bits; + } + else + { + offset = _distanceCode + 1; + } + + _output.WriteLengthDistance(_length, offset); + freeBytes -= _length; + _state = InflaterState.DecodeTop; break; default: - Debug./*Fail*/Assert(false, "check why we are here!"); + Debug. /*Fail*/ + Assert(false, "check why we are here!"); throw new InvalidDataException("Deflate64: unknown state"); } - - byte[] literalTreeCodeLength = new byte[HuffmanTree.MAX_LITERAL_TREE_ELEMENTS]; - byte[] distanceTreeCodeLength = new byte[HuffmanTree.MAX_DIST_TREE_ELEMENTS]; - - // Create literal and distance tables - Array.Copy(_codeList, literalTreeCodeLength, _literalLengthCodeCount); - Array.Copy(_codeList, _literalLengthCodeCount, distanceTreeCodeLength, 0, _distanceCodeCount); - - // Make sure there is an end-of-block code, otherwise how could we ever end? - if (literalTreeCodeLength[HuffmanTree.END_OF_BLOCK_CODE] == 0) - { - throw new InvalidDataException(); - } - - _literalLengthTree = new HuffmanTree(literalTreeCodeLength); - _distanceTree = new HuffmanTree(distanceTreeCodeLength); - _state = InflaterState.DecodeTop; - return true; } - public void Dispose() { } + return true; } + + // Format of the dynamic block header: + // 5 Bits: HLIT, # of Literal/Length codes - 257 (257 - 286) + // 5 Bits: HDIST, # of Distance codes - 1 (1 - 32) + // 4 Bits: HCLEN, # of Code Length codes - 4 (4 - 19) + // + // (HCLEN + 4) x 3 bits: code lengths for the code length + // alphabet given just above, in the order: 16, 17, 18, + // 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + // + // These code lengths are interpreted as 3-bit integers + // (0-7); as above, a code length of 0 means the + // corresponding symbol (literal/length or distance code + // length) is not used. + // + // HLIT + 257 code lengths for the literal/length alphabet, + // encoded using the code length Huffman code + // + // HDIST + 1 code lengths for the distance alphabet, + // encoded using the code length Huffman code + // + // The code length repeat codes can cross from HLIT + 257 to the + // HDIST + 1 code lengths. In other words, all code lengths form + // a single sequence of HLIT + HDIST + 258 values. + private bool DecodeDynamicBlockHeader() + { + switch (_state) + { + case InflaterState.ReadingNumLitCodes: + _literalLengthCodeCount = _input.GetBits(5); + if (_literalLengthCodeCount < 0) + { + return false; + } + _literalLengthCodeCount += 257; + _state = InflaterState.ReadingNumDistCodes; + goto case InflaterState.ReadingNumDistCodes; + + case InflaterState.ReadingNumDistCodes: + _distanceCodeCount = _input.GetBits(5); + if (_distanceCodeCount < 0) + { + return false; + } + _distanceCodeCount += 1; + _state = InflaterState.ReadingNumCodeLengthCodes; + goto case InflaterState.ReadingNumCodeLengthCodes; + + case InflaterState.ReadingNumCodeLengthCodes: + _codeLengthCodeCount = _input.GetBits(4); + if (_codeLengthCodeCount < 0) + { + return false; + } + _codeLengthCodeCount += 4; + _loopCounter = 0; + _state = InflaterState.ReadingCodeLengthCodes; + goto case InflaterState.ReadingCodeLengthCodes; + + case InflaterState.ReadingCodeLengthCodes: + while (_loopCounter < _codeLengthCodeCount) + { + var bits = _input.GetBits(3); + if (bits < 0) + { + return false; + } + _codeLengthTreeCodeLength[S_CODE_ORDER[_loopCounter]] = (byte)bits; + ++_loopCounter; + } + + for (var i = _codeLengthCodeCount; i < S_CODE_ORDER.Length; i++) + { + _codeLengthTreeCodeLength[S_CODE_ORDER[i]] = 0; + } + + // create huffman tree for code length + _codeLengthTree = new HuffmanTree(_codeLengthTreeCodeLength); + _codeArraySize = _literalLengthCodeCount + _distanceCodeCount; + _loopCounter = 0; // reset loop count + + _state = InflaterState.ReadingTreeCodesBefore; + goto case InflaterState.ReadingTreeCodesBefore; + + case InflaterState.ReadingTreeCodesBefore: + case InflaterState.ReadingTreeCodesAfter: + while (_loopCounter < _codeArraySize) + { + if (_state == InflaterState.ReadingTreeCodesBefore) + { + if ((_lengthCode = _codeLengthTree.GetNextSymbol(_input)) < 0) + { + return false; + } + } + + // The alphabet for code lengths is as follows: + // 0 - 15: Represent code lengths of 0 - 15 + // 16: Copy the previous code length 3 - 6 times. + // The next 2 bits indicate repeat length + // (0 = 3, ... , 3 = 6) + // Example: Codes 8, 16 (+2 bits 11), + // 16 (+2 bits 10) will expand to + // 12 code lengths of 8 (1 + 6 + 5) + // 17: Repeat a code length of 0 for 3 - 10 times. + // (3 bits of length) + // 18: Repeat a code length of 0 for 11 - 138 times + // (7 bits of length) + if (_lengthCode <= 15) + { + _codeList[_loopCounter++] = (byte)_lengthCode; + } + else + { + int repeatCount; + if (_lengthCode == 16) + { + if (!_input.EnsureBitsAvailable(2)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + if (_loopCounter == 0) + { + // can't have "prev code" on first code + throw new InvalidDataException(); + } + + var previousCode = _codeList[_loopCounter - 1]; + repeatCount = _input.GetBits(2) + 3; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (var j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = previousCode; + } + } + else if (_lengthCode == 17) + { + if (!_input.EnsureBitsAvailable(3)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + repeatCount = _input.GetBits(3) + 3; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (var j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = 0; + } + } + else + { + // code == 18 + if (!_input.EnsureBitsAvailable(7)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + repeatCount = _input.GetBits(7) + 11; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (var j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = 0; + } + } + } + _state = InflaterState.ReadingTreeCodesBefore; // we want to read the next code. + } + break; + + default: + Debug. /*Fail*/ + Assert(false, "check why we are here!"); + throw new InvalidDataException("Deflate64: unknown state"); + } + + var literalTreeCodeLength = new byte[HuffmanTree.MAX_LITERAL_TREE_ELEMENTS]; + var distanceTreeCodeLength = new byte[HuffmanTree.MAX_DIST_TREE_ELEMENTS]; + + // Create literal and distance tables + Array.Copy(_codeList, literalTreeCodeLength, _literalLengthCodeCount); + Array.Copy( + _codeList, + _literalLengthCodeCount, + distanceTreeCodeLength, + 0, + _distanceCodeCount + ); + + // Make sure there is an end-of-block code, otherwise how could we ever end? + if (literalTreeCodeLength[HuffmanTree.END_OF_BLOCK_CODE] == 0) + { + throw new InvalidDataException(); + } + + _literalLengthTree = new HuffmanTree(literalTreeCodeLength); + _distanceTree = new HuffmanTree(distanceTreeCodeLength); + _state = InflaterState.DecodeTop; + return true; + } + + public void Dispose() { } } diff --git a/src/SharpCompress/Compressors/Deflate64/InflaterState.cs b/src/SharpCompress/Compressors/Deflate64/InflaterState.cs index 356ea88f..711c3245 100644 --- a/src/SharpCompress/Compressors/Deflate64/InflaterState.cs +++ b/src/SharpCompress/Compressors/Deflate64/InflaterState.cs @@ -2,41 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +// Do not rearrange the enum values. +internal enum InflaterState { - // Do not rearrange the enum values. - internal enum InflaterState - { - ReadingHeader = 0, // Only applies to GZIP + ReadingHeader = 0, // Only applies to GZIP - ReadingBFinal = 2, // About to read bfinal bit - ReadingBType = 3, // About to read blockType bits + ReadingBFinal = 2, // About to read bfinal bit + ReadingBType = 3, // About to read blockType bits - ReadingNumLitCodes = 4, // About to read # literal codes - ReadingNumDistCodes = 5, // About to read # dist codes - ReadingNumCodeLengthCodes = 6, // About to read # code length codes - ReadingCodeLengthCodes = 7, // In the middle of reading the code length codes - ReadingTreeCodesBefore = 8, // In the middle of reading tree codes (loop top) - ReadingTreeCodesAfter = 9, // In the middle of reading tree codes (extension; code > 15) + ReadingNumLitCodes = 4, // About to read # literal codes + ReadingNumDistCodes = 5, // About to read # dist codes + ReadingNumCodeLengthCodes = 6, // About to read # code length codes + ReadingCodeLengthCodes = 7, // In the middle of reading the code length codes + ReadingTreeCodesBefore = 8, // In the middle of reading tree codes (loop top) + ReadingTreeCodesAfter = 9, // In the middle of reading tree codes (extension; code > 15) - DecodeTop = 10, // About to decode a literal (char/match) in a compressed block - HaveInitialLength = 11, // Decoding a match, have the literal code (base length) - HaveFullLength = 12, // Ditto, now have the full match length (incl. extra length bits) - HaveDistCode = 13, // Ditto, now have the distance code also, need extra dist bits + DecodeTop = 10, // About to decode a literal (char/match) in a compressed block + HaveInitialLength = 11, // Decoding a match, have the literal code (base length) + HaveFullLength = 12, // Ditto, now have the full match length (incl. extra length bits) + HaveDistCode = 13, // Ditto, now have the distance code also, need extra dist bits - /* uncompressed blocks */ - UncompressedAligning = 15, - UncompressedByte1 = 16, - UncompressedByte2 = 17, - UncompressedByte3 = 18, - UncompressedByte4 = 19, - DecodingUncompressed = 20, + /* uncompressed blocks */ + UncompressedAligning = 15, + UncompressedByte1 = 16, + UncompressedByte2 = 17, + UncompressedByte3 = 18, + UncompressedByte4 = 19, + DecodingUncompressed = 20, - // These three apply only to GZIP - StartReadingFooter = 21, // (Initialisation for reading footer) - ReadingFooter = 22, - VerifyingFooter = 23, + // These three apply only to GZIP + StartReadingFooter = 21, // (Initialisation for reading footer) + ReadingFooter = 22, + VerifyingFooter = 23, - Done = 24 // Finished - } + Done = 24 // Finished } diff --git a/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs b/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs index 931a22fa..28d61e42 100644 --- a/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs +++ b/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs @@ -7,40 +7,50 @@ using System; using System.Diagnostics; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +// This class can be used to read bits from an byte array quickly. +// Normally we get bits from 'bitBuffer' field and bitsInBuffer stores +// the number of bits available in 'BitBuffer'. +// When we used up the bits in bitBuffer, we will try to get byte from +// the byte array and copy the byte to appropiate position in bitBuffer. +// +// The byte array is not reused. We will go from 'start' to 'end'. +// When we reach the end, most read operations will return -1, +// which means we are running out of input. + +internal sealed class InputBuffer { - // This class can be used to read bits from an byte array quickly. - // Normally we get bits from 'bitBuffer' field and bitsInBuffer stores - // the number of bits available in 'BitBuffer'. - // When we used up the bits in bitBuffer, we will try to get byte from - // the byte array and copy the byte to appropiate position in bitBuffer. - // - // The byte array is not reused. We will go from 'start' to 'end'. - // When we reach the end, most read operations will return -1, - // which means we are running out of input. + private byte[] _buffer; // byte array to store input + private int _start; // start poisition of the buffer + private int _end; // end position of the buffer + private uint _bitBuffer = 0; // store the bits here, we can quickly shift in this buffer + private int _bitsInBuffer = 0; // number of bits available in bitBuffer - internal sealed class InputBuffer + /// Total bits available in the input buffer. + public int AvailableBits => _bitsInBuffer; + + /// Total bytes available in the input buffer. + public int AvailableBytes => (_end - _start) + (_bitsInBuffer / 8); + + /// Ensure that count bits are in the bit buffer. + /// Can be up to 16. + /// Returns false if input is not sufficient to make this true. + public bool EnsureBitsAvailable(int count) { - private byte[] _buffer; // byte array to store input - private int _start; // start poisition of the buffer - private int _end; // end position of the buffer - private uint _bitBuffer = 0; // store the bits here, we can quickly shift in this buffer - private int _bitsInBuffer = 0; // number of bits available in bitBuffer + Debug.Assert(0 < count && count <= 16, "count is invalid."); - /// Total bits available in the input buffer. - public int AvailableBits => _bitsInBuffer; - - /// Total bytes available in the input buffer. - public int AvailableBytes => (_end - _start) + (_bitsInBuffer / 8); - - /// Ensure that count bits are in the bit buffer. - /// Can be up to 16. - /// Returns false if input is not sufficient to make this true. - public bool EnsureBitsAvailable(int count) + // manual inlining to improve perf + if (_bitsInBuffer < count) { - Debug.Assert(0 < count && count <= 16, "count is invalid."); + if (NeedsInput()) + { + return false; + } + // insert a byte to bitbuffer + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; - // manual inlining to improve perf if (_bitsInBuffer < count) { if (NeedsInput()) @@ -50,155 +60,147 @@ namespace SharpCompress.Compressors.Deflate64 // insert a byte to bitbuffer _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; _bitsInBuffer += 8; - - if (_bitsInBuffer < count) - { - if (NeedsInput()) - { - return false; - } - // insert a byte to bitbuffer - _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; - _bitsInBuffer += 8; - } } - - return true; } - /// - /// This function will try to load 16 or more bits into bitBuffer. - /// It returns whatever is contained in bitBuffer after loading. - /// The main difference between this and GetBits is that this will - /// never return -1. So the caller needs to check AvailableBits to - /// see how many bits are available. - /// - public uint TryLoad16Bits() + return true; + } + + /// + /// This function will try to load 16 or more bits into bitBuffer. + /// It returns whatever is contained in bitBuffer after loading. + /// The main difference between this and GetBits is that this will + /// never return -1. So the caller needs to check AvailableBits to + /// see how many bits are available. + /// + public uint TryLoad16Bits() + { + if (_bitsInBuffer < 8) { - if (_bitsInBuffer < 8) + if (_start < _end) { - if (_start < _end) - { - _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; - _bitsInBuffer += 8; - } - - if (_start < _end) - { - _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; - _bitsInBuffer += 8; - } - } - else if (_bitsInBuffer < 16) - { - if (_start < _end) - { - _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; - _bitsInBuffer += 8; - } + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; } - return _bitBuffer; + if (_start < _end) + { + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + } } - - private uint GetBitMask(int count) => ((uint)1 << count) - 1; - - /// Gets count bits from the input buffer. Returns -1 if not enough bits available. - public int GetBits(int count) + else if (_bitsInBuffer < 16) { - Debug.Assert(0 < count && count <= 16, "count is invalid."); - - if (!EnsureBitsAvailable(count)) + if (_start < _end) { - return -1; + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; } - - int result = (int)(_bitBuffer & GetBitMask(count)); - _bitBuffer >>= count; - _bitsInBuffer -= count; - return result; } - /// - /// Copies length bytes from input buffer to output buffer starting at output[offset]. - /// You have to make sure, that the buffer is byte aligned. If not enough bytes are - /// available, copies fewer bytes. - /// - /// Returns the number of bytes copied, 0 if no byte is available. - public int CopyTo(byte[] output, int offset, int length) + return _bitBuffer; + } + + private uint GetBitMask(int count) => ((uint)1 << count) - 1; + + /// Gets count bits from the input buffer. Returns -1 if not enough bits available. + public int GetBits(int count) + { + Debug.Assert(0 < count && count <= 16, "count is invalid."); + + if (!EnsureBitsAvailable(count)) { - Debug.Assert(output != null); - Debug.Assert(offset >= 0); - Debug.Assert(length >= 0); - Debug.Assert(offset <= output.Length - length); - Debug.Assert((_bitsInBuffer % 8) == 0); - - // Copy the bytes in bitBuffer first. - int bytesFromBitBuffer = 0; - while (_bitsInBuffer > 0 && length > 0) - { - output[offset++] = (byte)_bitBuffer; - _bitBuffer >>= 8; - _bitsInBuffer -= 8; - length--; - bytesFromBitBuffer++; - } - - if (length == 0) - { - return bytesFromBitBuffer; - } - - int avail = _end - _start; - if (length > avail) - { - length = avail; - } - - Array.Copy(_buffer, _start, output, offset, length); - _start += length; - return bytesFromBitBuffer + length; + return -1; } - /// - /// Return true is all input bytes are used. - /// This means the caller can call SetInput to add more input. - /// - public bool NeedsInput() => _start == _end; + var result = (int)(_bitBuffer & GetBitMask(count)); + _bitBuffer >>= count; + _bitsInBuffer -= count; + return result; + } - /// - /// Set the byte array to be processed. - /// All the bits remained in bitBuffer will be processed before the new bytes. - /// We don't clone the byte array here since it is expensive. - /// The caller should make sure after a buffer is passed in. - /// It will not be changed before calling this function again. - /// - public void SetInput(byte[] buffer, int offset, int length) + /// + /// Copies length bytes from input buffer to output buffer starting at output[offset]. + /// You have to make sure, that the buffer is byte aligned. If not enough bytes are + /// available, copies fewer bytes. + /// + /// Returns the number of bytes copied, 0 if no byte is available. + public int CopyTo(byte[] output, int offset, int length) + { + Debug.Assert(output != null); + Debug.Assert(offset >= 0); + Debug.Assert(length >= 0); + Debug.Assert(offset <= output.Length - length); + Debug.Assert((_bitsInBuffer % 8) == 0); + + // Copy the bytes in bitBuffer first. + var bytesFromBitBuffer = 0; + while (_bitsInBuffer > 0 && length > 0) { - Debug.Assert(buffer != null); - Debug.Assert(offset >= 0); - Debug.Assert(length >= 0); - Debug.Assert(offset <= buffer.Length - length); - Debug.Assert(_start == _end); - - _buffer = buffer; - _start = offset; - _end = offset + length; + output[offset++] = (byte)_bitBuffer; + _bitBuffer >>= 8; + _bitsInBuffer -= 8; + length--; + bytesFromBitBuffer++; } - /// Skip n bits in the buffer. - public void SkipBits(int n) + if (length == 0) { - Debug.Assert(_bitsInBuffer >= n, "No enough bits in the buffer, Did you call EnsureBitsAvailable?"); - _bitBuffer >>= n; - _bitsInBuffer -= n; + return bytesFromBitBuffer; } - /// Skips to the next byte boundary. - public void SkipToByteBoundary() + var avail = _end - _start; + if (length > avail) { - _bitBuffer >>= (_bitsInBuffer % 8); - _bitsInBuffer = _bitsInBuffer - (_bitsInBuffer % 8); + length = avail; } + + Array.Copy(_buffer, _start, output, offset, length); + _start += length; + return bytesFromBitBuffer + length; + } + + /// + /// Return true is all input bytes are used. + /// This means the caller can call SetInput to add more input. + /// + public bool NeedsInput() => _start == _end; + + /// + /// Set the byte array to be processed. + /// All the bits remained in bitBuffer will be processed before the new bytes. + /// We don't clone the byte array here since it is expensive. + /// The caller should make sure after a buffer is passed in. + /// It will not be changed before calling this function again. + /// + public void SetInput(byte[] buffer, int offset, int length) + { + Debug.Assert(buffer != null); + Debug.Assert(offset >= 0); + Debug.Assert(length >= 0); + Debug.Assert(offset <= buffer.Length - length); + Debug.Assert(_start == _end); + + _buffer = buffer; + _start = offset; + _end = offset + length; + } + + /// Skip n bits in the buffer. + public void SkipBits(int n) + { + Debug.Assert( + _bitsInBuffer >= n, + "No enough bits in the buffer, Did you call EnsureBitsAvailable?" + ); + _bitBuffer >>= n; + _bitsInBuffer -= n; + } + + /// Skips to the next byte boundary. + public void SkipToByteBoundary() + { + _bitBuffer >>= (_bitsInBuffer % 8); + _bitsInBuffer -= (_bitsInBuffer % 8); } } diff --git a/src/SharpCompress/Compressors/Deflate64/Match.cs b/src/SharpCompress/Compressors/Deflate64/Match.cs index 4d5ce54b..c75cbe7a 100644 --- a/src/SharpCompress/Compressors/Deflate64/Match.cs +++ b/src/SharpCompress/Compressors/Deflate64/Match.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +/// +/// This class represents a match in the history window. +/// +internal sealed class Match { - /// - /// This class represents a match in the history window. - /// - internal sealed class Match - { - internal MatchState State { get; set; } - internal int Position { get; set; } - internal int Length { get; set; } - internal byte Symbol { get; set; } - } + internal MatchState State { get; set; } + internal int Position { get; set; } + internal int Length { get; set; } + internal byte Symbol { get; set; } } diff --git a/src/SharpCompress/Compressors/Deflate64/MatchState.cs b/src/SharpCompress/Compressors/Deflate64/MatchState.cs index f88913bc..858e91e8 100644 --- a/src/SharpCompress/Compressors/Deflate64/MatchState.cs +++ b/src/SharpCompress/Compressors/Deflate64/MatchState.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +internal enum MatchState { - internal enum MatchState - { - HasSymbol = 1, - HasMatch = 2, - HasSymbolAndMatch = 3 - } + HasSymbol = 1, + HasMatch = 2, + HasSymbolAndMatch = 3 } diff --git a/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs b/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs index 13bf90c2..90b65cd3 100644 --- a/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs +++ b/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs @@ -5,147 +5,148 @@ using System; using System.Diagnostics; -namespace SharpCompress.Compressors.Deflate64 +namespace SharpCompress.Compressors.Deflate64; + +/// +/// This class maintains a window for decompressed output. +/// We need to keep this because the decompressed information can be +/// a literal or a length/distance pair. For length/distance pair, +/// we need to look back in the output window and copy bytes from there. +/// We use a byte array of WindowSize circularly. +/// +internal sealed class OutputWindow { - /// - /// This class maintains a window for decompressed output. - /// We need to keep this because the decompressed information can be - /// a literal or a length/distance pair. For length/distance pair, - /// we need to look back in the output window and copy bytes from there. - /// We use a byte array of WindowSize circularly. - /// - internal sealed class OutputWindow + // With Deflate64 we can have up to a 65536 length as well as up to a 65538 distance. This means we need a Window that is at + // least 131074 bytes long so we have space to retrieve up to a full 64kb in lookback and place it in our buffer without + // overwriting existing data. OutputWindow requires that the WindowSize be an exponent of 2, so we round up to 2^18. + private const int WINDOW_SIZE = 262144; + private const int WINDOW_MASK = 262143; + + private readonly byte[] _window = new byte[WINDOW_SIZE]; // The window is 2^18 bytes + private int _end; // this is the position to where we should write next byte + private int _bytesUsed; // The number of bytes in the output window which is not consumed. + + /// Add a byte to output window. + public void Write(byte b) { - // With Deflate64 we can have up to a 65536 length as well as up to a 65538 distance. This means we need a Window that is at - // least 131074 bytes long so we have space to retrieve up to a full 64kb in lookback and place it in our buffer without - // overwriting existing data. OutputWindow requires that the WindowSize be an exponent of 2, so we round up to 2^18. - private const int WINDOW_SIZE = 262144; - private const int WINDOW_MASK = 262143; + Debug.Assert(_bytesUsed < WINDOW_SIZE, "Can't add byte when window is full!"); + _window[_end++] = b; + _end &= WINDOW_MASK; + ++_bytesUsed; + } - private readonly byte[] _window = new byte[WINDOW_SIZE]; // The window is 2^18 bytes - private int _end; // this is the position to where we should write next byte - private int _bytesUsed; // The number of bytes in the output window which is not consumed. + public void WriteLengthDistance(int length, int distance) + { + Debug.Assert((_bytesUsed + length) <= WINDOW_SIZE, "No Enough space"); - /// Add a byte to output window. - public void Write(byte b) + // move backwards distance bytes in the output stream, + // and copy length bytes from this position to the output stream. + _bytesUsed += length; + var copyStart = (_end - distance) & WINDOW_MASK; // start position for coping. + + var border = WINDOW_SIZE - length; + if (copyStart <= border && _end < border) { - Debug.Assert(_bytesUsed < WINDOW_SIZE, "Can't add byte when window is full!"); - _window[_end++] = b; - _end &= WINDOW_MASK; - ++_bytesUsed; - } - - public void WriteLengthDistance(int length, int distance) - { - Debug.Assert((_bytesUsed + length) <= WINDOW_SIZE, "No Enough space"); - - // move backwards distance bytes in the output stream, - // and copy length bytes from this position to the output stream. - _bytesUsed += length; - int copyStart = (_end - distance) & WINDOW_MASK; // start position for coping. - - int border = WINDOW_SIZE - length; - if (copyStart <= border && _end < border) + if (length <= distance) { - if (length <= distance) - { - Array.Copy(_window, copyStart, _window, _end, length); - _end += length; - } - else - { - // The referenced string may overlap the current - // position; for example, if the last 2 bytes decoded have values - // X and Y, a string reference with - // adds X,Y,X,Y,X to the output stream. - while (length-- > 0) - { - _window[_end++] = _window[copyStart++]; - } - } + Array.Copy(_window, copyStart, _window, _end, length); + _end += length; } else { - // copy byte by byte + // The referenced string may overlap the current + // position; for example, if the last 2 bytes decoded have values + // X and Y, a string reference with + // adds X,Y,X,Y,X to the output stream. while (length-- > 0) { _window[_end++] = _window[copyStart++]; - _end &= WINDOW_MASK; - copyStart &= WINDOW_MASK; } } } - - /// - /// Copy up to length of bytes from input directly. - /// This is used for uncompressed block. - /// - public int CopyFrom(InputBuffer input, int length) + else { - length = Math.Min(Math.Min(length, WINDOW_SIZE - _bytesUsed), input.AvailableBytes); - int copied; - - // We might need wrap around to copy all bytes. - int tailLen = WINDOW_SIZE - _end; - if (length > tailLen) + // copy byte by byte + while (length-- > 0) { - // copy the first part - copied = input.CopyTo(_window, _end, tailLen); - if (copied == tailLen) - { - // only try to copy the second part if we have enough bytes in input - copied += input.CopyTo(_window, 0, length - tailLen); - } + _window[_end++] = _window[copyStart++]; + _end &= WINDOW_MASK; + copyStart &= WINDOW_MASK; } - else - { - // only one copy is needed if there is no wrap around. - copied = input.CopyTo(_window, _end, length); - } - - _end = (_end + copied) & WINDOW_MASK; - _bytesUsed += copied; - return copied; - } - - /// Free space in output window. - public int FreeBytes => WINDOW_SIZE - _bytesUsed; - - /// Bytes not consumed in output window. - public int AvailableBytes => _bytesUsed; - - /// Copy the decompressed bytes to output array. - public int CopyTo(byte[] output, int offset, int length) - { - int copyEnd; - - if (length > _bytesUsed) - { - // we can copy all the decompressed bytes out - copyEnd = _end; - length = _bytesUsed; - } - else - { - copyEnd = (_end - _bytesUsed + length) & WINDOW_MASK; // copy length of bytes - } - - int copied = length; - - int tailLen = length - copyEnd; - if (tailLen > 0) - { - // this means we need to copy two parts separately - // copy tailLen bytes from the end of output window - Array.Copy(_window, WINDOW_SIZE - tailLen, - output, offset, tailLen); - offset += tailLen; - length = copyEnd; - } - Array.Copy(_window, copyEnd - length, output, offset, length); - _bytesUsed -= copied; - Debug.Assert(_bytesUsed >= 0, "check this function and find why we copied more bytes than we have"); - return copied; } } + + /// + /// Copy up to length of bytes from input directly. + /// This is used for uncompressed block. + /// + public int CopyFrom(InputBuffer input, int length) + { + length = Math.Min(Math.Min(length, WINDOW_SIZE - _bytesUsed), input.AvailableBytes); + int copied; + + // We might need wrap around to copy all bytes. + var tailLen = WINDOW_SIZE - _end; + if (length > tailLen) + { + // copy the first part + copied = input.CopyTo(_window, _end, tailLen); + if (copied == tailLen) + { + // only try to copy the second part if we have enough bytes in input + copied += input.CopyTo(_window, 0, length - tailLen); + } + } + else + { + // only one copy is needed if there is no wrap around. + copied = input.CopyTo(_window, _end, length); + } + + _end = (_end + copied) & WINDOW_MASK; + _bytesUsed += copied; + return copied; + } + + /// Free space in output window. + public int FreeBytes => WINDOW_SIZE - _bytesUsed; + + /// Bytes not consumed in output window. + public int AvailableBytes => _bytesUsed; + + /// Copy the decompressed bytes to output array. + public int CopyTo(byte[] output, int offset, int length) + { + int copyEnd; + + if (length > _bytesUsed) + { + // we can copy all the decompressed bytes out + copyEnd = _end; + length = _bytesUsed; + } + else + { + copyEnd = (_end - _bytesUsed + length) & WINDOW_MASK; // copy length of bytes + } + + var copied = length; + + var tailLen = length - copyEnd; + if (tailLen > 0) + { + // this means we need to copy two parts separately + // copy tailLen bytes from the end of output window + Array.Copy(_window, WINDOW_SIZE - tailLen, output, offset, tailLen); + offset += tailLen; + length = copyEnd; + } + Array.Copy(_window, copyEnd - length, output, offset, length); + _bytesUsed -= copied; + Debug.Assert( + _bytesUsed >= 0, + "check this function and find why we copied more bytes than we have" + ); + return copied; + } } diff --git a/src/SharpCompress/Compressors/Filters/BCJ2Filter.cs b/src/SharpCompress/Compressors/Filters/BCJ2Filter.cs index d767cd6c..f658b385 100644 --- a/src/SharpCompress/Compressors/Filters/BCJ2Filter.cs +++ b/src/SharpCompress/Compressors/Filters/BCJ2Filter.cs @@ -1,221 +1,212 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Filters +namespace SharpCompress.Compressors.Filters; + +internal class BCJ2Filter : Stream { - internal class BCJ2Filter : Stream + private readonly Stream _baseStream; + private readonly byte[] _input = new byte[4096]; + private int _inputOffset; + private int _inputCount; + private bool _endReached; + + private long _position; + private readonly byte[] _output = new byte[4]; + private int _outputOffset; + private int _outputCount; + + private readonly byte[] _control; + private readonly byte[] _data1; + private readonly byte[] _data2; + + private int _controlPos; + private int _data1Pos; + private int _data2Pos; + + private readonly ushort[] _p = new ushort[256 + 2]; + private uint _range, + _code; + private byte _prevByte; + private bool _isDisposed; + + private const int K_NUM_TOP_BITS = 24; + private const int K_TOP_VALUE = 1 << K_NUM_TOP_BITS; + + private const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; + private const int K_BIT_MODEL_TOTAL = 1 << K_NUM_BIT_MODEL_TOTAL_BITS; + private const int K_NUM_MOVE_BITS = 5; + + private static bool IsJ(byte b0, byte b1) => (b1 & 0xFE) == 0xE8 || IsJcc(b0, b1); + + private static bool IsJcc(byte b0, byte b1) => b0 == 0x0F && (b1 & 0xF0) == 0x80; + + public BCJ2Filter(byte[] control, byte[] data1, byte[] data2, Stream baseStream) { - private readonly Stream _baseStream; - private readonly byte[] _input = new byte[4096]; - private int _inputOffset; - private int _inputCount; - private bool _endReached; + _control = control; + _data1 = data1; + _data2 = data2; + _baseStream = baseStream; - private long _position; - private readonly byte[] _output = new byte[4]; - private int _outputOffset; - private int _outputCount; - - private readonly byte[] _control; - private readonly byte[] _data1; - private readonly byte[] _data2; - - private int _controlPos; - private int _data1Pos; - private int _data2Pos; - - private readonly ushort[] _p = new ushort[256 + 2]; - private uint _range, _code; - private byte _prevByte; - private bool _isDisposed; - - private const int K_NUM_TOP_BITS = 24; - private const int K_TOP_VALUE = 1 << K_NUM_TOP_BITS; - - private const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; - private const int K_BIT_MODEL_TOTAL = 1 << K_NUM_BIT_MODEL_TOTAL_BITS; - private const int K_NUM_MOVE_BITS = 5; - - private static bool IsJ(byte b0, byte b1) + int i; + for (i = 0; i < _p.Length; i++) { - return (b1 & 0xFE) == 0xE8 || IsJcc(b0, b1); + _p[i] = K_BIT_MODEL_TOTAL >> 1; } - private static bool IsJcc(byte b0, byte b1) + _code = 0; + _range = 0xFFFFFFFF; + for (i = 0; i < 5; i++) { - return b0 == 0x0F && (b1 & 0xF0) == 0x80; + _code = (_code << 8) | control[_controlPos++]; } + } - public BCJ2Filter(byte[] control, byte[] data1, byte[] data2, Stream baseStream) + protected override void Dispose(bool disposing) + { + if (_isDisposed) { - _control = control; - _data1 = data1; - _data2 = data2; - _baseStream = baseStream; + return; + } + _isDisposed = true; + base.Dispose(disposing); + _baseStream.Dispose(); + } - int i; - for (i = 0; i < _p.Length; i++) + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => _baseStream.Length + _data1.Length + _data2.Length; + + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + var size = 0; + byte b = 0; + + while (!_endReached && size < count) + { + while (_outputOffset < _outputCount) { - _p[i] = K_BIT_MODEL_TOTAL >> 1; - } - - _code = 0; - _range = 0xFFFFFFFF; - for (i = 0; i < 5; i++) - { - _code = (_code << 8) | control[_controlPos++]; - } - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - _baseStream.Dispose(); - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => _baseStream.Length + _data1.Length + _data2.Length; - - public override long Position { get => _position; set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - int size = 0; - byte b = 0; - - while (!_endReached && size < count) - { - while (_outputOffset < _outputCount) - { - b = _output[_outputOffset++]; - buffer[offset++] = b; - size++; - _position++; - - _prevByte = b; - if (size == count) - { - return size; - } - } - - if (_inputOffset == _inputCount) - { - _inputOffset = 0; - _inputCount = _baseStream.Read(_input, 0, _input.Length); - if (_inputCount == 0) - { - _endReached = true; - break; - } - } - - b = _input[_inputOffset++]; + b = _output[_outputOffset++]; buffer[offset++] = b; size++; _position++; - if (!IsJ(_prevByte, b)) + _prevByte = b; + if (size == count) { + return size; + } + } + + if (_inputOffset == _inputCount) + { + _inputOffset = 0; + _inputCount = _baseStream.Read(_input, 0, _input.Length); + if (_inputCount == 0) + { + _endReached = true; + break; + } + } + + b = _input[_inputOffset++]; + buffer[offset++] = b; + size++; + _position++; + + if (!IsJ(_prevByte, b)) + { + _prevByte = b; + } + else + { + int prob; + if (b == 0xE8) + { + prob = _prevByte; + } + else if (b == 0xE9) + { + prob = 256; + } + else + { + prob = 257; + } + + var bound = (_range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _p[prob]; + if (_code < bound) + { + _range = bound; + _p[prob] += (ushort)((K_BIT_MODEL_TOTAL - _p[prob]) >> K_NUM_MOVE_BITS); + if (_range < K_TOP_VALUE) + { + _range <<= 8; + _code = (_code << 8) | _control[_controlPos++]; + } _prevByte = b; } else { - int prob; + _range -= bound; + _code -= bound; + _p[prob] -= (ushort)(_p[prob] >> K_NUM_MOVE_BITS); + if (_range < K_TOP_VALUE) + { + _range <<= 8; + _code = (_code << 8) | _control[_controlPos++]; + } + + uint dest; if (b == 0xE8) { - prob = _prevByte; - } - else if (b == 0xE9) - { - prob = 256; + dest = (uint)( + (_data1[_data1Pos++] << 24) + | (_data1[_data1Pos++] << 16) + | (_data1[_data1Pos++] << 8) + | _data1[_data1Pos++] + ); } else { - prob = 257; + dest = (uint)( + (_data2[_data2Pos++] << 24) + | (_data2[_data2Pos++] << 16) + | (_data2[_data2Pos++] << 8) + | _data2[_data2Pos++] + ); } + dest -= (uint)(_position + 4); - uint bound = (_range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _p[prob]; - if (_code < bound) - { - _range = bound; - _p[prob] += (ushort)((K_BIT_MODEL_TOTAL - _p[prob]) >> K_NUM_MOVE_BITS); - if (_range < K_TOP_VALUE) - { - _range <<= 8; - _code = (_code << 8) | _control[_controlPos++]; - } - _prevByte = b; - } - else - { - _range -= bound; - _code -= bound; - _p[prob] -= (ushort)(_p[prob] >> K_NUM_MOVE_BITS); - if (_range < K_TOP_VALUE) - { - _range <<= 8; - _code = (_code << 8) | _control[_controlPos++]; - } - - uint dest; - if (b == 0xE8) - { - dest = - (uint) - ((_data1[_data1Pos++] << 24) | (_data1[_data1Pos++] << 16) | (_data1[_data1Pos++] << 8) | - _data1[_data1Pos++]); - } - else - { - dest = - (uint) - ((_data2[_data2Pos++] << 24) | (_data2[_data2Pos++] << 16) | (_data2[_data2Pos++] << 8) | - _data2[_data2Pos++]); - } - dest -= (uint)(_position + 4); - - _output[0] = (byte)dest; - _output[1] = (byte)(dest >> 8); - _output[2] = (byte)(dest >> 16); - _output[3] = (byte)(dest >> 24); - _outputOffset = 0; - _outputCount = 4; - } + _output[0] = (byte)dest; + _output[1] = (byte)(dest >> 8); + _output[2] = (byte)(dest >> 16); + _output[3] = (byte)(dest >> 24); + _outputOffset = 0; + _outputCount = 4; } } - - return size; } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + return size; } -} \ No newline at end of file + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Compressors/Filters/BCJFilter.cs b/src/SharpCompress/Compressors/Filters/BCJFilter.cs index 4bc6bb2e..ca8e8c7e 100644 --- a/src/SharpCompress/Compressors/Filters/BCJFilter.cs +++ b/src/SharpCompress/Compressors/Filters/BCJFilter.cs @@ -1,113 +1,119 @@ using System.IO; -namespace SharpCompress.Compressors.Filters +namespace SharpCompress.Compressors.Filters; + +internal class BCJFilter : Filter { - internal class BCJFilter : Filter + private static readonly bool[] MASK_TO_ALLOWED_STATUS = { - private static readonly bool[] MASK_TO_ALLOWED_STATUS = { true, true, true, false, true, false, false, false }; + true, + true, + true, + false, + true, + false, + false, + false + }; - private static readonly int[] MASK_TO_BIT_NUMBER = { 0, 1, 2, 2, 3, 3, 3, 3 }; + private static readonly int[] MASK_TO_BIT_NUMBER = { 0, 1, 2, 2, 3, 3, 3, 3 }; - private int _pos; - private int _prevMask; + private int _pos; + private int _prevMask; - public BCJFilter(bool isEncoder, Stream baseStream) - : base(isEncoder, baseStream, 5) + public BCJFilter(bool isEncoder, Stream baseStream) : base(isEncoder, baseStream, 5) => + _pos = 5; + + private static bool Test86MsByte(byte b) => b == 0x00 || b == 0xFF; + + protected override int Transform(byte[] buffer, int offset, int count) + { + var prevPos = offset - 1; + var end = offset + count - 5; + int i; + + for (i = offset; i <= end; ++i) { - _pos = 5; - } - - private static bool Test86MsByte(byte b) - { - return b == 0x00 || b == 0xFF; - } - - protected override int Transform(byte[] buffer, int offset, int count) - { - int prevPos = offset - 1; - int end = offset + count - 5; - int i; - - for (i = offset; i <= end; ++i) + if ((buffer[i] & 0xFE) != 0xE8) { - if ((buffer[i] & 0xFE) != 0xE8) - { - continue; - } - - prevPos = i - prevPos; - if ((prevPos & ~3) != 0) - { - // (unsigned)prevPos > 3 - _prevMask = 0; - } - else - { - _prevMask = (_prevMask << (prevPos - 1)) & 7; - if (_prevMask != 0) - { - if (!MASK_TO_ALLOWED_STATUS[_prevMask] || Test86MsByte( - buffer[i + 4 - MASK_TO_BIT_NUMBER[_prevMask]])) - { - prevPos = i; - _prevMask = (_prevMask << 1) | 1; - continue; - } - } - } - - prevPos = i; - - if (Test86MsByte(buffer[i + 4])) - { - int src = buffer[i + 1] - | (buffer[i + 2] << 8) - | (buffer[i + 3] << 16) - | (buffer[i + 4] << 24); - int dest; - while (true) - { - if (_isEncoder) - { - dest = src + (_pos + i - offset); - } - else - { - dest = src - (_pos + i - offset); - } - - if (_prevMask == 0) - { - break; - } - - int index = MASK_TO_BIT_NUMBER[_prevMask] * 8; - if (!Test86MsByte((byte)(dest >> (24 - index)))) - { - break; - } - - src = dest ^ ((1 << (32 - index)) - 1); - } - - buffer[i + 1] = (byte)dest; - buffer[i + 2] = (byte)(dest >> 8); - buffer[i + 3] = (byte)(dest >> 16); - buffer[i + 4] = (byte)(~(((dest >> 24) & 1) - 1)); - i += 4; - } - else - { - _prevMask = (_prevMask << 1) | 1; - } + continue; } prevPos = i - prevPos; - _prevMask = ((prevPos & ~3) != 0) ? 0 : _prevMask << (prevPos - 1); + if ((prevPos & ~3) != 0) + { + // (unsigned)prevPos > 3 + _prevMask = 0; + } + else + { + _prevMask = (_prevMask << (prevPos - 1)) & 7; + if (_prevMask != 0) + { + if ( + !MASK_TO_ALLOWED_STATUS[_prevMask] + || Test86MsByte(buffer[i + 4 - MASK_TO_BIT_NUMBER[_prevMask]]) + ) + { + prevPos = i; + _prevMask = (_prevMask << 1) | 1; + continue; + } + } + } - i -= offset; - _pos += i; - return i; + prevPos = i; + + if (Test86MsByte(buffer[i + 4])) + { + var src = + buffer[i + 1] + | (buffer[i + 2] << 8) + | (buffer[i + 3] << 16) + | (buffer[i + 4] << 24); + int dest; + while (true) + { + if (_isEncoder) + { + dest = src + (_pos + i - offset); + } + else + { + dest = src - (_pos + i - offset); + } + + if (_prevMask == 0) + { + break; + } + + var index = MASK_TO_BIT_NUMBER[_prevMask] * 8; + if (!Test86MsByte((byte)(dest >> (24 - index)))) + { + break; + } + + src = dest ^ ((1 << (32 - index)) - 1); + } + + buffer[i + 1] = (byte)dest; + buffer[i + 2] = (byte)(dest >> 8); + buffer[i + 3] = (byte)(dest >> 16); + buffer[i + 4] = (byte)(~(((dest >> 24) & 1) - 1)); + i += 4; + } + else + { + _prevMask = (_prevMask << 1) | 1; + } } + + prevPos = i - prevPos; + _prevMask = ((prevPos & ~3) != 0) ? 0 : _prevMask << (prevPos - 1); + + i -= offset; + _pos += i; + return i; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/Filters/BranchExecFilter.cs b/src/SharpCompress/Compressors/Filters/BranchExecFilter.cs index 02f92ac5..83260180 100644 --- a/src/SharpCompress/Compressors/Filters/BranchExecFilter.cs +++ b/src/SharpCompress/Compressors/Filters/BranchExecFilter.cs @@ -5,293 +5,320 @@ */ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace SharpCompress.Compressors.Filters +namespace SharpCompress.Compressors.Filters; + +[CLSCompliant(false)] +public sealed class BranchExecFilter { - [CLSCompliant(false)] - public sealed class BranchExecFilter + public enum Alignment : int { - public enum Alignment : int + ARCH_x86_ALIGNMENT = 1, + ARCH_PowerPC_ALIGNMENT = 4, + ARCH_IA64_ALIGNMENT = 16, + ARCH_ARM_ALIGNMENT = 4, + ARCH_ARMTHUMB_ALIGNMENT = 2, + ARCH_SPARC_ALIGNMENT = 4, + } + + public static void X86Converter(byte[] data, uint ip, ref uint state) + { + long i = 0; + long size = data.Length; + uint pos = 0; + var mask = state & 7; + if (size < 5) { - ARCH_x86_ALIGNMENT = 1, - ARCH_PowerPC_ALIGNMENT = 4, - ARCH_IA64_ALIGNMENT = 16, - ARCH_ARM_ALIGNMENT = 4, - ARCH_ARMTHUMB_ALIGNMENT = 2, - ARCH_SPARC_ALIGNMENT = 4, - } - - public static void X86Converter(byte[] data, UInt32 ip, ref UInt32 state) { - - long i = 0; - long size = data.Length; - UInt32 pos = 0; - UInt32 mask = state & 7; - if (size < 5) - return; - size -= 4; - ip += 5; - - for (;;) - { - i = pos; - - for (; i < size; i++) - { - if ((data[i] & 0xFE) == 0xE8) - { - break; - } - } - - UInt32 d = (UInt32)(i) - pos; - pos = (UInt32)i; - if (i >= size) - { - state = (d > 2 ? 0 : mask >> (int)d); - return; - } - if (d > 2) - { - mask = 0; - } - else - { - mask >>= (int)d; - if (mask != 0 && (mask > 4 || mask == 3 || (((((data[(UInt32)(mask >> 1) + 1])) + 1) & 0xFE) == 0) )) - { - mask = (mask >> 1) | 4; - pos++; - continue; - } - } - - if ((((data[i + 4]) + 1) & 0xFE) == 0) - { - UInt32 inst = ((UInt32)data[i + 4] << 24) | ((UInt32)data[i + 3] << 16) | ((UInt32)data[i + 2] << 8) | ((UInt32)data[i + 1]); - UInt32 cur = ip + (UInt32)pos; - pos += 5; - - inst -= cur; - if (mask != 0) - { - UInt32 sh = (mask & 6) << 2; - if (((((((Byte)(inst >> (int)sh))) + 1) & 0xFE) == 0)) - { - inst ^= (((UInt32)0x100 << (int)sh) - 1); - inst -= cur; - } - mask = 0; - } - data[i + 1] = (Byte)inst; - data[i + 2] = (Byte)(inst >> 8); - data[i + 3] = (Byte)(inst >> 16); - data[i + 4] = (Byte)(0 - ((inst >> 24) & 1)); - } - else - { - mask = (mask >> 1) | 4; - pos++; - } - } - } - - public static void PowerPCConverter(byte[] data, UInt32 ip) - { - long i = 0; - long size = data.Length; - size &= ~(UInt32)3; - ip -= 4; - - for (;; ) // infinite loop - { - for (;; ) // infinite loop - { - if (i >= size) - return; - i += 4; - - if ((data[i - 4] & 0xFC) == 0x48 && (data[i - 1] & 3) == 1) - break; - } - { - UInt32 inst = BitConverter.ToUInt32(data, (int)i - 4); - - if (BitConverter.IsLittleEndian) - { - inst = Utility.SwapUINT32(inst); - } - - inst -= (UInt32)(ip + i); - inst &= 0x03FFFFFF; - inst |= 0x48000000; - - Utility.SetBigUInt32(ref data, inst, (i - 4)); - } - } - } - - public static void ARMConverter(byte[] data, UInt32 ip) - { - long i = 0; - long size = data.Length; - size &= ~(UInt32)3; - ip += 4; - - for (;;) // infinite loop - { - for (;;) // infinite loop - { - if (i >= size) - { - return; - } - - i += 4; - if (data[i - 1] == 0xEB) - break; - } - - UInt32 inst = BitConverter.ToUInt32(data, (int)i - 4); - inst <<= 2; - inst -= (UInt32)(ip + i); - inst >>= 2; - inst &= 0x00FFFFFF; - inst |= 0xEB000000; - - Utility.SetLittleUInt32(ref data, inst, i - 4); - } - } - - public static void ARMTConverter(byte[] data, UInt32 ip) - { - long i = 0; - long size = data.Length; - size &= ~(UInt32)1; - long lim = size - 4; - - for (;;) - { - UInt32 b1; - for (;;) - { - UInt32 b3; - if (i > lim) - return; - b1 = data[i + 1]; - b3 = data[i + 3]; - i += 2; - b1 ^= 8; - if ((b3 & b1) >= 0xF8) - break; - } - - UInt32 inst = ((UInt32)b1 << 19) - + (((UInt32)data[i + 1] & 0x7) << 8) - + (((UInt32)data[i - 2] << 11)) - + (data[i]); - - i += 2; - - UInt32 cur = ((UInt32)(ip + i)) >> 1; - inst -= cur; - - - data[i - 4] = (Byte)(inst >> 11); - data[i - 3] = (Byte)(0xF0 | ((inst >> 19) & 0x7)); - data[i - 2] = (Byte)inst; - data[i - 1] = (Byte)(0xF8 | (inst >> 8)); - } - } - - public static void IA64Converter(byte[] data, UInt32 ip) - { - UInt32 i = 0; - long size = data.Length; - if (size < 16) - throw new InvalidDataException("Unexpected data size"); - size -= 16; - - do - { - UInt32 m = ((UInt32)0x334B0000 >> (data[i] & 0x1E)) & 3; - if (m != 0) - { - m++; - do - { - UInt32 iterator = (UInt32)( (i + (m * 5) - 8)); - if (((data[iterator + 3] >> (int)m) & 15) == 5 - && (((data[iterator - 1] | ((UInt32)data[iterator] << 8)) >> (int)m) & 0x70) == 0) - { - UInt32 raw = BitConverter.ToUInt32(data, (int)iterator); - UInt32 inst = raw >> (int)m; - inst = (inst & 0xFFFFF) | ((inst & (1 << 23)) >> 3); - - inst <<= 4; - inst -= (ip + (UInt32)i); - inst >>= 4; - - inst &= 0x1FFFFF; - inst += 0x700000; - inst &= 0x8FFFFF; - raw &= ~((UInt32)0x8FFFFF << (int)m); - raw |= (inst << (int)m); - - Utility.SetLittleUInt32(ref data, raw, iterator); - } - } - while (++m <= 4); - } - i += 16; - } - while (i <= size); return; } - public static void SPARCConverter(byte[] data, UInt32 ip) + size -= 4; + ip += 5; + + for (; ; ) { - long i = 0; - long size = data.Length; - size &= ~(UInt32)3; - ip -= 4; + i = pos; - for (;;) // infinite loop + for (; i < size; i++) { - for (;;) // infinite loop + if ((data[i] & 0xFE) == 0xE8) { - if (i >= size) - return; - - i += 4; - if ((data[i - 4] == 0x40 && (data[i - 3] & 0xC0) == 0) || - (data[i - 4] == 0x7F && (data[i - 3] >= 0xC0))) - break; + break; } - - UInt32 inst = BitConverter.ToUInt32(data, (int)i - 4); + } + + var d = (uint)(i) - pos; + pos = (uint)i; + if (i >= size) + { + state = (d > 2 ? 0 : mask >> (int)d); + return; + } + if (d > 2) + { + mask = 0; + } + else + { + mask >>= (int)d; + if ( + mask != 0 + && (mask > 4 || mask == 3 || (((((data[(mask >> 1) + 1])) + 1) & 0xFE) == 0)) + ) + { + mask = (mask >> 1) | 4; + pos++; + continue; + } + } + + if ((((data[i + 4]) + 1) & 0xFE) == 0) + { + var inst = + ((uint)data[i + 4] << 24) + | ((uint)data[i + 3] << 16) + | ((uint)data[i + 2] << 8) + | data[i + 1]; + var cur = ip + pos; + pos += 5; + + inst -= cur; + if (mask != 0) + { + var sh = (mask & 6) << 2; + if (((((((byte)(inst >> (int)sh))) + 1) & 0xFE) == 0)) + { + inst ^= (((uint)0x100 << (int)sh) - 1); + inst -= cur; + } + mask = 0; + } + data[i + 1] = (byte)inst; + data[i + 2] = (byte)(inst >> 8); + data[i + 3] = (byte)(inst >> 16); + data[i + 4] = (byte)(0 - ((inst >> 24) & 1)); + } + else + { + mask = (mask >> 1) | 4; + pos++; + } + } + } + + public static void PowerPCConverter(byte[] data, uint ip) + { + long i = 0; + long size = data.Length; + size &= ~(uint)3; + ip -= 4; + + for (; ; ) // infinite loop + { + for (; ; ) // infinite loop + { + if (i >= size) + { + return; + } + + i += 4; + + if ((data[i - 4] & 0xFC) == 0x48 && (data[i - 1] & 3) == 1) + { + break; + } + } + { + var inst = BitConverter.ToUInt32(data, (int)i - 4); if (BitConverter.IsLittleEndian) { inst = Utility.SwapUINT32(inst); } - inst <<= 2; - inst -= (UInt32)(ip + i); - - inst &= 0x01FFFFFF; - inst -= (UInt32)1 << 24; - inst ^= 0xFF000000; - inst >>= 2; - inst |= 0x40000000; + inst -= (uint)(ip + i); + inst &= 0x03FFFFFF; + inst |= 0x48000000; Utility.SetBigUInt32(ref data, inst, (i - 4)); } } } + + public static void ARMConverter(byte[] data, uint ip) + { + long i = 0; + long size = data.Length; + size &= ~(uint)3; + ip += 4; + + for (; ; ) // infinite loop + { + for (; ; ) // infinite loop + { + if (i >= size) + { + return; + } + + i += 4; + if (data[i - 1] == 0xEB) + { + break; + } + } + + var inst = BitConverter.ToUInt32(data, (int)i - 4); + inst <<= 2; + inst -= (uint)(ip + i); + inst >>= 2; + inst &= 0x00FFFFFF; + inst |= 0xEB000000; + + Utility.SetLittleUInt32(ref data, inst, i - 4); + } + } + + public static void ARMTConverter(byte[] data, uint ip) + { + long i = 0; + long size = data.Length; + size &= ~(uint)1; + var lim = size - 4; + + for (; ; ) + { + uint b1; + for (; ; ) + { + uint b3; + if (i > lim) + { + return; + } + + b1 = data[i + 1]; + b3 = data[i + 3]; + i += 2; + b1 ^= 8; + if ((b3 & b1) >= 0xF8) + { + break; + } + } + + var inst = + (b1 << 19) + + (((uint)data[i + 1] & 0x7) << 8) + + (((uint)data[i - 2] << 11)) + + (data[i]); + + i += 2; + + var cur = ((uint)(ip + i)) >> 1; + inst -= cur; + + data[i - 4] = (byte)(inst >> 11); + data[i - 3] = (byte)(0xF0 | ((inst >> 19) & 0x7)); + data[i - 2] = (byte)inst; + data[i - 1] = (byte)(0xF8 | (inst >> 8)); + } + } + + public static void IA64Converter(byte[] data, uint ip) + { + uint i = 0; + long size = data.Length; + if (size < 16) + { + throw new InvalidDataException("Unexpected data size"); + } + + size -= 16; + + do + { + var m = ((uint)0x334B0000 >> (data[i] & 0x1E)) & 3; + if (m != 0) + { + m++; + do + { + var iterator = (i + (m * 5) - 8); + if ( + ((data[iterator + 3] >> (int)m) & 15) == 5 + && (((data[iterator - 1] | ((uint)data[iterator] << 8)) >> (int)m) & 0x70) + == 0 + ) + { + var raw = BitConverter.ToUInt32(data, (int)iterator); + var inst = raw >> (int)m; + inst = (inst & 0xFFFFF) | ((inst & (1 << 23)) >> 3); + + inst <<= 4; + inst -= (ip + i); + inst >>= 4; + + inst &= 0x1FFFFF; + inst += 0x700000; + inst &= 0x8FFFFF; + raw &= ~((uint)0x8FFFFF << (int)m); + raw |= (inst << (int)m); + + Utility.SetLittleUInt32(ref data, raw, iterator); + } + } while (++m <= 4); + } + i += 16; + } while (i <= size); + return; + } + + public static void SPARCConverter(byte[] data, uint ip) + { + long i = 0; + long size = data.Length; + size &= ~(uint)3; + ip -= 4; + + for (; ; ) // infinite loop + { + for (; ; ) // infinite loop + { + if (i >= size) + { + return; + } + + i += 4; + if ( + (data[i - 4] == 0x40 && (data[i - 3] & 0xC0) == 0) + || (data[i - 4] == 0x7F && (data[i - 3] >= 0xC0)) + ) + { + break; + } + } + + var inst = BitConverter.ToUInt32(data, (int)i - 4); + + if (BitConverter.IsLittleEndian) + { + inst = Utility.SwapUINT32(inst); + } + + inst <<= 2; + inst -= (uint)(ip + i); + + inst &= 0x01FFFFFF; + inst -= (uint)1 << 24; + inst ^= 0xFF000000; + inst >>= 2; + inst |= 0x40000000; + + Utility.SetBigUInt32(ref data, inst, (i - 4)); + } + } } diff --git a/src/SharpCompress/Compressors/Filters/Filter.cs b/src/SharpCompress/Compressors/Filters/Filter.cs index b7fd8159..93d95195 100644 --- a/src/SharpCompress/Compressors/Filters/Filter.cs +++ b/src/SharpCompress/Compressors/Filters/Filter.cs @@ -1,154 +1,148 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Filters +namespace SharpCompress.Compressors.Filters; + +internal abstract class Filter : Stream { - internal abstract class Filter : Stream + protected bool _isEncoder; + protected Stream _baseStream; + + private readonly byte[] _tail; + private readonly byte[] _window; + private int _transformed; + private int _read; + private bool _endReached; + private bool _isDisposed; + + protected Filter(bool isEncoder, Stream baseStream, int lookahead) { - protected bool _isEncoder; - protected Stream _baseStream; + _isEncoder = isEncoder; + _baseStream = baseStream; + _tail = new byte[lookahead - 1]; + _window = new byte[_tail.Length * 2]; + } - private readonly byte[] _tail; - private readonly byte[] _window; - private int _transformed; - private int _read; - private bool _endReached; - private bool _isDisposed; - - protected Filter(bool isEncoder, Stream baseStream, int lookahead) + protected override void Dispose(bool disposing) + { + if (_isDisposed) { - _isEncoder = isEncoder; - _baseStream = baseStream; - _tail = new byte[lookahead - 1]; - _window = new byte[_tail.Length * 2]; + return; } + _isDisposed = true; + base.Dispose(disposing); + _baseStream.Dispose(); + } - protected override void Dispose(bool disposing) + public override bool CanRead => !_isEncoder; + + public override bool CanSeek => false; + + public override bool CanWrite => _isEncoder; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => _baseStream.Length; + + public override long Position + { + get => _baseStream.Position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + var size = 0; + + if (_transformed > 0) { - if (_isDisposed) + var copySize = _transformed; + if (copySize > count) { - return; + copySize = count; } - _isDisposed = true; - base.Dispose(disposing); - _baseStream.Dispose(); + Buffer.BlockCopy(_tail, 0, buffer, offset, copySize); + _transformed -= copySize; + _read -= copySize; + offset += copySize; + count -= copySize; + size += copySize; + Buffer.BlockCopy(_tail, copySize, _tail, 0, _read); } - - public override bool CanRead => !_isEncoder; - - public override bool CanSeek => false; - - public override bool CanWrite => _isEncoder; - - public override void Flush() + if (count == 0) { - throw new NotSupportedException(); - } - - public override long Length => _baseStream.Length; - - public override long Position { get => _baseStream.Position; set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - int size = 0; - - if (_transformed > 0) - { - int copySize = _transformed; - if (copySize > count) - { - copySize = count; - } - Buffer.BlockCopy(_tail, 0, buffer, offset, copySize); - _transformed -= copySize; - _read -= copySize; - offset += copySize; - count -= copySize; - size += copySize; - Buffer.BlockCopy(_tail, copySize, _tail, 0, _read); - } - if (count == 0) - { - return size; - } - - int inSize = _read; - if (inSize > count) - { - inSize = count; - } - Buffer.BlockCopy(_tail, 0, buffer, offset, inSize); - _read -= inSize; - Buffer.BlockCopy(_tail, inSize, _tail, 0, _read); - while (!_endReached && inSize < count) - { - int baseRead = _baseStream.Read(buffer, offset + inSize, count - inSize); - inSize += baseRead; - if (baseRead == 0) - { - _endReached = true; - } - } - while (!_endReached && _read < _tail.Length) - { - int baseRead = _baseStream.Read(_tail, _read, _tail.Length - _read); - _read += baseRead; - if (baseRead == 0) - { - _endReached = true; - } - } - - if (inSize > _tail.Length) - { - _transformed = Transform(buffer, offset, inSize); - offset += _transformed; - count -= _transformed; - size += _transformed; - inSize -= _transformed; - _transformed = 0; - } - - if (count == 0) - { - return size; - } - - Buffer.BlockCopy(buffer, offset, _window, 0, inSize); - Buffer.BlockCopy(_tail, 0, _window, inSize, _read); - if (inSize + _read > _tail.Length) - { - _transformed = Transform(_window, 0, inSize + _read); - } - else - { - _transformed = inSize + _read; - } - Buffer.BlockCopy(_window, 0, buffer, offset, inSize); - Buffer.BlockCopy(_window, inSize, _tail, 0, _read); - size += inSize; - _transformed -= inSize; - return size; } - public override long Seek(long offset, SeekOrigin origin) + var inSize = _read; + if (inSize > count) { - throw new NotSupportedException(); + inSize = count; + } + Buffer.BlockCopy(_tail, 0, buffer, offset, inSize); + _read -= inSize; + Buffer.BlockCopy(_tail, inSize, _tail, 0, _read); + while (!_endReached && inSize < count) + { + var baseRead = _baseStream.Read(buffer, offset + inSize, count - inSize); + inSize += baseRead; + if (baseRead == 0) + { + _endReached = true; + } + } + while (!_endReached && _read < _tail.Length) + { + var baseRead = _baseStream.Read(_tail, _read, _tail.Length - _read); + _read += baseRead; + if (baseRead == 0) + { + _endReached = true; + } } - public override void SetLength(long value) + if (inSize > _tail.Length) { - throw new NotSupportedException(); + _transformed = Transform(buffer, offset, inSize); + offset += _transformed; + count -= _transformed; + size += _transformed; + inSize -= _transformed; + _transformed = 0; } - public override void Write(byte[] buffer, int offset, int count) + if (count == 0) { - Transform(buffer, offset, count); - _baseStream.Write(buffer, offset, count); + return size; } - protected abstract int Transform(byte[] buffer, int offset, int count); + Buffer.BlockCopy(buffer, offset, _window, 0, inSize); + Buffer.BlockCopy(_tail, 0, _window, inSize, _read); + if (inSize + _read > _tail.Length) + { + _transformed = Transform(_window, 0, inSize + _read); + } + else + { + _transformed = inSize + _read; + } + Buffer.BlockCopy(_window, 0, buffer, offset, inSize); + Buffer.BlockCopy(_window, inSize, _tail, 0, _read); + size += inSize; + _transformed -= inSize; + + return size; } -} \ No newline at end of file + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + Transform(buffer, offset, count); + _baseStream.Write(buffer, offset, count); + } + + protected abstract int Transform(byte[] buffer, int offset, int count); +} diff --git a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs index 3e44ff89..24823c73 100644 --- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs @@ -4,279 +4,278 @@ using System.Security.Cryptography; using System.Text; using SharpCompress.Compressors.LZMA.Utilites; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal sealed class AesDecoderStream : DecoderStream2 { - internal sealed class AesDecoderStream : DecoderStream2 + private readonly Stream mStream; + private readonly ICryptoTransform mDecoder; + private readonly byte[] mBuffer; + private long mWritten; + private readonly long mLimit; + private int mOffset; + private int mEnding; + private int mUnderflow; + private bool isDisposed; + + public AesDecoderStream(Stream input, byte[] info, IPasswordProvider pass, long limit) { - private readonly Stream mStream; - private readonly ICryptoTransform mDecoder; - private readonly byte[] mBuffer; - private long mWritten; - private readonly long mLimit; - private int mOffset; - private int mEnding; - private int mUnderflow; - private bool isDisposed; - - public AesDecoderStream(Stream input, byte[] info, IPasswordProvider pass, long limit) + if (pass.CryptoGetTextPassword() == null) { - if (pass.CryptoGetTextPassword() == null) - { - throw new SharpCompress.Common.CryptographicException("Encrypted 7Zip archive has no password specified."); - } - - mStream = input; - mLimit = limit; - - if (((uint)input.Length & 15) != 0) - { - throw new NotSupportedException("AES decoder does not support padding."); - } - - Init(info, out int numCyclesPower, out byte[] salt, out byte[] seed); - - byte[] password = Encoding.Unicode.GetBytes(pass.CryptoGetTextPassword()); - byte[]? key = InitKey(numCyclesPower, salt, password); - if (key == null) - { - throw new InvalidOperationException("Initialized with null key"); - } - - using (var aes = Aes.Create()) - { - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.None; - mDecoder = aes.CreateDecryptor(key, seed); - } - - mBuffer = new byte[4 << 10]; + throw new SharpCompress.Common.CryptographicException( + "Encrypted 7Zip archive has no password specified." + ); } - protected override void Dispose(bool disposing) + mStream = input; + mLimit = limit; + + if (((uint)input.Length & 15) != 0) { - try - { - if (isDisposed) - { - return; - } - isDisposed = true; - if (disposing) - { - mStream.Dispose(); - mDecoder.Dispose(); - } - } - finally - { - base.Dispose(disposing); - } + throw new NotSupportedException("AES decoder does not support padding."); } - public override long Position => mWritten; + Init(info, out int numCyclesPower, out byte[] salt, out byte[] seed); - public override long Length => mLimit; - - public override int Read(byte[] buffer, int offset, int count) + byte[] password = Encoding.Unicode.GetBytes(pass.CryptoGetTextPassword()); + byte[]? key = InitKey(numCyclesPower, salt, password); + if (key == null) { - if (count == 0 - || mWritten == mLimit) - { - return 0; - } - - if (mUnderflow > 0) - { - return HandleUnderflow(buffer, offset, count); - } - - // Need at least 16 bytes to proceed. - if (mEnding - mOffset < 16) - { - Buffer.BlockCopy(mBuffer, mOffset, mBuffer, 0, mEnding - mOffset); - mEnding -= mOffset; - mOffset = 0; - - do - { - int read = mStream.Read(mBuffer, mEnding, mBuffer.Length - mEnding); - if (read == 0) - { - // We are not done decoding and have less than 16 bytes. - throw new EndOfStreamException(); - } - - mEnding += read; - } - while (mEnding - mOffset < 16); - } - - // We shouldn't return more data than we are limited to. - // Currently this is handled by forcing an underflow if - // the stream length is not a multiple of the block size. - if (count > mLimit - mWritten) - { - count = (int)(mLimit - mWritten); - } - - // We cannot transform less than 16 bytes into the target buffer, - // but we also cannot return zero, so we need to handle this. - // We transform the data locally and use our own buffer as cache. - if (count < 16) - { - return HandleUnderflow(buffer, offset, count); - } - - if (count > mEnding - mOffset) - { - count = mEnding - mOffset; - } - - // Otherwise we transform directly into the target buffer. - int processed = mDecoder.TransformBlock(mBuffer, mOffset, count & ~15, buffer, offset); - mOffset += processed; - mWritten += processed; - return processed; + throw new InvalidOperationException("Initialized with null key"); } - #region Private Methods - - private void Init(byte[] info, out int numCyclesPower, out byte[] salt, out byte[] iv) + using (var aes = Aes.Create()) { - byte bt = info[0]; - numCyclesPower = bt & 0x3F; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + mDecoder = aes.CreateDecryptor(key, seed); + } - if ((bt & 0xC0) == 0) + mBuffer = new byte[4 << 10]; + } + + protected override void Dispose(bool disposing) + { + try + { + if (isDisposed) { - salt = Array.Empty(); - iv = Array.Empty(); return; } - - int saltSize = (bt >> 7) & 1; - int ivSize = (bt >> 6) & 1; - if (info.Length == 1) + isDisposed = true; + if (disposing) { - throw new InvalidOperationException(); - } - - byte bt2 = info[1]; - saltSize += (bt2 >> 4); - ivSize += (bt2 & 15); - if (info.Length < 2 + saltSize + ivSize) - { - throw new InvalidOperationException(); - } - - salt = new byte[saltSize]; - for (int i = 0; i < saltSize; i++) - { - salt[i] = info[i + 2]; - } - - iv = new byte[16]; - for (int i = 0; i < ivSize; i++) - { - iv[i] = info[i + saltSize + 2]; - } - - if (numCyclesPower > 24) - { - throw new NotSupportedException(); + mStream.Dispose(); + mDecoder.Dispose(); } } - - private byte[]? InitKey(int mNumCyclesPower, byte[] salt, byte[] pass) + finally { - if (mNumCyclesPower == 0x3F) - { - var key = new byte[32]; - - int pos; - for (pos = 0; pos < salt.Length; pos++) - { - key[pos] = salt[pos]; - } - - for (int i = 0; i < pass.Length && pos < 32; i++) - { - key[pos++] = pass[i]; - } - - return key; - } - else - { -#if NETSTANDARD2_0 - using IncrementalHash sha = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); - byte[] counter = new byte[8]; - long numRounds = 1L << mNumCyclesPower; - for (long round = 0; round < numRounds; round++) - { - sha.AppendData(salt, 0, salt.Length); - sha.AppendData(pass, 0, pass.Length); - sha.AppendData(counter, 0, 8); - - // This mirrors the counter so we don't have to convert long to byte[] each round. - // (It also ensures the counter is little endian, which BitConverter does not.) - for (int i = 0; i < 8; i++) - { - if (++counter[i] != 0) - { - break; - } - } - } - return sha.GetHashAndReset(); -#else - using var sha = SHA256.Create(); - byte[] counter = new byte[8]; - long numRounds = 1L << mNumCyclesPower; - for (long round = 0; round < numRounds; round++) - { - sha.TransformBlock(salt, 0, salt.Length, null, 0); - sha.TransformBlock(pass, 0, pass.Length, null, 0); - sha.TransformBlock(counter, 0, 8, null, 0); - - // This mirrors the counter so we don't have to convert long to byte[] each round. - // (It also ensures the counter is little endian, which BitConverter does not.) - for (int i = 0; i < 8; i++) - { - if (++counter[i] != 0) - { - break; - } - } - } - - sha.TransformFinalBlock(counter, 0, 0); - return sha.Hash; -#endif - } + base.Dispose(disposing); } - - private int HandleUnderflow(byte[] buffer, int offset, int count) - { - // If this is zero we were called to create a new underflow buffer. - // Just transform as much as possible so we can feed from it as long as possible. - if (mUnderflow == 0) - { - int blockSize = (mEnding - mOffset) & ~15; - mUnderflow = mDecoder.TransformBlock(mBuffer, mOffset, blockSize, mBuffer, mOffset); - } - - if (count > mUnderflow) - { - count = mUnderflow; - } - - Buffer.BlockCopy(mBuffer, mOffset, buffer, offset, count); - mWritten += count; - mOffset += count; - mUnderflow -= count; - return count; - } - - #endregion } + + public override long Position => mWritten; + + public override long Length => mLimit; + + public override int Read(byte[] buffer, int offset, int count) + { + if (count == 0 || mWritten == mLimit) + { + return 0; + } + + if (mUnderflow > 0) + { + return HandleUnderflow(buffer, offset, count); + } + + // Need at least 16 bytes to proceed. + if (mEnding - mOffset < 16) + { + Buffer.BlockCopy(mBuffer, mOffset, mBuffer, 0, mEnding - mOffset); + mEnding -= mOffset; + mOffset = 0; + + do + { + int read = mStream.Read(mBuffer, mEnding, mBuffer.Length - mEnding); + if (read == 0) + { + // We are not done decoding and have less than 16 bytes. + throw new EndOfStreamException(); + } + + mEnding += read; + } while (mEnding - mOffset < 16); + } + + // We shouldn't return more data than we are limited to. + // Currently this is handled by forcing an underflow if + // the stream length is not a multiple of the block size. + if (count > mLimit - mWritten) + { + count = (int)(mLimit - mWritten); + } + + // We cannot transform less than 16 bytes into the target buffer, + // but we also cannot return zero, so we need to handle this. + // We transform the data locally and use our own buffer as cache. + if (count < 16) + { + return HandleUnderflow(buffer, offset, count); + } + + if (count > mEnding - mOffset) + { + count = mEnding - mOffset; + } + + // Otherwise we transform directly into the target buffer. + int processed = mDecoder.TransformBlock(mBuffer, mOffset, count & ~15, buffer, offset); + mOffset += processed; + mWritten += processed; + return processed; + } + + #region Private Methods + + private void Init(byte[] info, out int numCyclesPower, out byte[] salt, out byte[] iv) + { + byte bt = info[0]; + numCyclesPower = bt & 0x3F; + + if ((bt & 0xC0) == 0) + { + salt = Array.Empty(); + iv = Array.Empty(); + return; + } + + int saltSize = (bt >> 7) & 1; + int ivSize = (bt >> 6) & 1; + if (info.Length == 1) + { + throw new InvalidOperationException(); + } + + byte bt2 = info[1]; + saltSize += (bt2 >> 4); + ivSize += (bt2 & 15); + if (info.Length < 2 + saltSize + ivSize) + { + throw new InvalidOperationException(); + } + + salt = new byte[saltSize]; + for (int i = 0; i < saltSize; i++) + { + salt[i] = info[i + 2]; + } + + iv = new byte[16]; + for (int i = 0; i < ivSize; i++) + { + iv[i] = info[i + saltSize + 2]; + } + + if (numCyclesPower > 24) + { + throw new NotSupportedException(); + } + } + + private byte[]? InitKey(int mNumCyclesPower, byte[] salt, byte[] pass) + { + if (mNumCyclesPower == 0x3F) + { + var key = new byte[32]; + + int pos; + for (pos = 0; pos < salt.Length; pos++) + { + key[pos] = salt[pos]; + } + + for (int i = 0; i < pass.Length && pos < 32; i++) + { + key[pos++] = pass[i]; + } + + return key; + } + else + { +#if NETSTANDARD2_0 + using IncrementalHash sha = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + byte[] counter = new byte[8]; + long numRounds = 1L << mNumCyclesPower; + for (long round = 0; round < numRounds; round++) + { + sha.AppendData(salt, 0, salt.Length); + sha.AppendData(pass, 0, pass.Length); + sha.AppendData(counter, 0, 8); + + // This mirrors the counter so we don't have to convert long to byte[] each round. + // (It also ensures the counter is little endian, which BitConverter does not.) + for (int i = 0; i < 8; i++) + { + if (++counter[i] != 0) + { + break; + } + } + } + return sha.GetHashAndReset(); +#else + using var sha = SHA256.Create(); + byte[] counter = new byte[8]; + long numRounds = 1L << mNumCyclesPower; + for (long round = 0; round < numRounds; round++) + { + sha.TransformBlock(salt, 0, salt.Length, null, 0); + sha.TransformBlock(pass, 0, pass.Length, null, 0); + sha.TransformBlock(counter, 0, 8, null, 0); + + // This mirrors the counter so we don't have to convert long to byte[] each round. + // (It also ensures the counter is little endian, which BitConverter does not.) + for (int i = 0; i < 8; i++) + { + if (++counter[i] != 0) + { + break; + } + } + } + + sha.TransformFinalBlock(counter, 0, 0); + return sha.Hash; +#endif + } + } + + private int HandleUnderflow(byte[] buffer, int offset, int count) + { + // If this is zero we were called to create a new underflow buffer. + // Just transform as much as possible so we can feed from it as long as possible. + if (mUnderflow == 0) + { + int blockSize = (mEnding - mOffset) & ~15; + mUnderflow = mDecoder.TransformBlock(mBuffer, mOffset, blockSize, mBuffer, mOffset); + } + + if (count > mUnderflow) + { + count = mUnderflow; + } + + Buffer.BlockCopy(mBuffer, mOffset, buffer, offset, count); + mWritten += count; + mOffset += count; + mUnderflow -= count; + return count; + } + + #endregion } diff --git a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs index 62a7ad65..fd18b16c 100644 --- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs @@ -2,282 +2,251 @@ using System.Collections.Generic; using System.IO; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal class Bcj2DecoderStream : DecoderStream2 { - internal class Bcj2DecoderStream : DecoderStream2 + private const int K_NUM_TOP_BITS = 24; + private const uint K_TOP_VALUE = (1 << K_NUM_TOP_BITS); + + private class RangeDecoder { - private const int K_NUM_TOP_BITS = 24; - private const uint K_TOP_VALUE = (1 << K_NUM_TOP_BITS); + internal readonly Stream _mStream; + internal uint _range; + internal uint _code; - private class RangeDecoder + public RangeDecoder(Stream stream) { - internal readonly Stream _mStream; - internal uint _range; - internal uint _code; - - public RangeDecoder(Stream stream) + _mStream = stream; + _range = 0xFFFFFFFF; + for (var i = 0; i < 5; i++) { - _mStream = stream; - _range = 0xFFFFFFFF; - for (int i = 0; i < 5; i++) - { - _code = (_code << 8) | ReadByte(); - } - } - - public byte ReadByte() - { - int bt = _mStream.ReadByte(); - if (bt < 0) - { - throw new EndOfStreamException(); - } - - return (byte)bt; - } - - public void Dispose() - { - _mStream.Dispose(); + _code = (_code << 8) | ReadByte(); } } - private class StatusDecoder + public byte ReadByte() { - private const int NUM_MOVE_BITS = 5; - - private const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; - private const uint K_BIT_MODEL_TOTAL = 1u << K_NUM_BIT_MODEL_TOTAL_BITS; - - private uint _prob; - - public StatusDecoder() + var bt = _mStream.ReadByte(); + if (bt < 0) { - _prob = K_BIT_MODEL_TOTAL / 2; + throw new EndOfStreamException(); } - private void UpdateModel(uint symbol) - { - /* - Prob -= (Prob + ((symbol - 1) & ((1 << numMoveBits) - 1))) >> numMoveBits; - Prob += (1 - symbol) << (kNumBitModelTotalBits - numMoveBits); - */ - if (symbol == 0) - { - _prob += (K_BIT_MODEL_TOTAL - _prob) >> NUM_MOVE_BITS; - } - else - { - _prob -= (_prob) >> NUM_MOVE_BITS; - } - } + return (byte)bt; + } - public uint Decode(RangeDecoder decoder) + public void Dispose() => _mStream.Dispose(); + } + + private class StatusDecoder + { + private const int NUM_MOVE_BITS = 5; + + private const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; + private const uint K_BIT_MODEL_TOTAL = 1u << K_NUM_BIT_MODEL_TOTAL_BITS; + + private uint _prob; + + public StatusDecoder() => _prob = K_BIT_MODEL_TOTAL / 2; + + public uint Decode(RangeDecoder decoder) + { + var newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; + if (decoder._code < newBound) { - uint newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; - if (decoder._code < newBound) - { - decoder._range = newBound; - _prob += (K_BIT_MODEL_TOTAL - _prob) >> NUM_MOVE_BITS; - if (decoder._range < K_TOP_VALUE) - { - decoder._code = (decoder._code << 8) | decoder.ReadByte(); - decoder._range <<= 8; - } - return 0; - } - decoder._range -= newBound; - decoder._code -= newBound; - _prob -= _prob >> NUM_MOVE_BITS; + decoder._range = newBound; + _prob += (K_BIT_MODEL_TOTAL - _prob) >> NUM_MOVE_BITS; if (decoder._range < K_TOP_VALUE) { decoder._code = (decoder._code << 8) | decoder.ReadByte(); decoder._range <<= 8; } - return 1; - } - } - - private readonly Stream _mMainStream; - private readonly Stream _mCallStream; - private readonly Stream _mJumpStream; - private readonly RangeDecoder _mRangeDecoder; - private readonly StatusDecoder[] _mStatusDecoder; - private long _mWritten; - private readonly IEnumerator _mIter; - private bool _mFinished; - private bool _isDisposed; - - public Bcj2DecoderStream(Stream[] streams, byte[] info, long limit) - { - if (info != null && info.Length > 0) - { - throw new NotSupportedException(); - } - - if (streams.Length != 4) - { - throw new NotSupportedException(); - } - - _mMainStream = streams[0]; - _mCallStream = streams[1]; - _mJumpStream = streams[2]; - _mRangeDecoder = new RangeDecoder(streams[3]); - - _mStatusDecoder = new StatusDecoder[256 + 2]; - for (int i = 0; i < _mStatusDecoder.Length; i++) - { - _mStatusDecoder[i] = new StatusDecoder(); - } - - _mIter = Run().GetEnumerator(); - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - base.Dispose(disposing); - _mMainStream.Dispose(); - _mCallStream.Dispose(); - _mJumpStream.Dispose(); - } - - private static bool IsJcc(byte b0, byte b1) - { - return b0 == 0x0F - && (b1 & 0xF0) == 0x80; - } - - private static bool IsJ(byte b0, byte b1) - { - return (b1 & 0xFE) == 0xE8 - || IsJcc(b0, b1); - } - - private static int GetIndex(byte b0, byte b1) - { - if (b1 == 0xE8) - { - return b0; - } - if (b1 == 0xE9) - { - return 256; - } - return 257; - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count == 0 || _mFinished) - { return 0; } - - for (int i = 0; i < count; i++) + decoder._range -= newBound; + decoder._code -= newBound; + _prob -= _prob >> NUM_MOVE_BITS; + if (decoder._range < K_TOP_VALUE) { - if (!_mIter.MoveNext()) - { - _mFinished = true; - return i; - } - - buffer[offset + i] = _mIter.Current; + decoder._code = (decoder._code << 8) | decoder.ReadByte(); + decoder._range <<= 8; } + return 1; + } + } - return count; + private readonly Stream _mMainStream; + private readonly Stream _mCallStream; + private readonly Stream _mJumpStream; + private readonly RangeDecoder _mRangeDecoder; + private readonly StatusDecoder[] _mStatusDecoder; + private long _mWritten; + private readonly IEnumerator _mIter; + private bool _mFinished; + private bool _isDisposed; + + public Bcj2DecoderStream(Stream[] streams, byte[] info, long limit) + { + if (info != null && info.Length > 0) + { + throw new NotSupportedException(); } - public override int ReadByte() + if (streams.Length != 4) { - if (_mFinished) - { - return -1; - } + throw new NotSupportedException(); + } + _mMainStream = streams[0]; + _mCallStream = streams[1]; + _mJumpStream = streams[2]; + _mRangeDecoder = new RangeDecoder(streams[3]); + + _mStatusDecoder = new StatusDecoder[256 + 2]; + for (var i = 0; i < _mStatusDecoder.Length; i++) + { + _mStatusDecoder[i] = new StatusDecoder(); + } + + _mIter = Run().GetEnumerator(); + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + base.Dispose(disposing); + _mMainStream.Dispose(); + _mCallStream.Dispose(); + _mJumpStream.Dispose(); + } + + private static bool IsJcc(byte b0, byte b1) => b0 == 0x0F && (b1 & 0xF0) == 0x80; + + private static bool IsJ(byte b0, byte b1) => (b1 & 0xFE) == 0xE8 || IsJcc(b0, b1); + + private static int GetIndex(byte b0, byte b1) + { + if (b1 == 0xE8) + { + return b0; + } + if (b1 == 0xE9) + { + return 256; + } + return 257; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count == 0 || _mFinished) + { + return 0; + } + + for (var i = 0; i < count; i++) + { if (!_mIter.MoveNext()) { _mFinished = true; - return -1; + return i; } - return _mIter.Current; + buffer[offset + i] = _mIter.Current; } - public IEnumerable Run() + return count; + } + + public override int ReadByte() + { + if (_mFinished) { - const uint kBurstSize = (1u << 18); + return -1; + } - byte prevByte = 0; - uint processedBytes = 0; - for (; ; ) + if (!_mIter.MoveNext()) + { + _mFinished = true; + return -1; + } + + return _mIter.Current; + } + + public IEnumerable Run() + { + const uint kBurstSize = (1u << 18); + + byte prevByte = 0; + uint processedBytes = 0; + for (; ; ) + { + byte b = 0; + uint i; + for (i = 0; i < kBurstSize; i++) { - byte b = 0; - uint i; - for (i = 0; i < kBurstSize; i++) + var tmp = _mMainStream.ReadByte(); + if (tmp < 0) { - int tmp = _mMainStream.ReadByte(); - if (tmp < 0) + yield break; + } + + b = (byte)tmp; + _mWritten++; + yield return b; + if (IsJ(prevByte, b)) + { + break; + } + + prevByte = b; + } + + processedBytes += i; + if (i == kBurstSize) + { + continue; + } + + if (_mStatusDecoder[GetIndex(prevByte, b)].Decode(_mRangeDecoder) == 1) + { + var s = (b == 0xE8) ? _mCallStream : _mJumpStream; + + uint src = 0; + for (i = 0; i < 4; i++) + { + var b0 = s.ReadByte(); + if (b0 < 0) { - yield break; + throw new EndOfStreamException(); } - b = (byte)tmp; - _mWritten++; - yield return b; - if (IsJ(prevByte, b)) - { - break; - } - - prevByte = b; + src <<= 8; + src |= (uint)b0; } - processedBytes += i; - if (i == kBurstSize) - { - continue; - } - - if (_mStatusDecoder[GetIndex(prevByte, b)].Decode(_mRangeDecoder) == 1) - { - Stream s = (b == 0xE8) ? _mCallStream : _mJumpStream; - - uint src = 0; - for (i = 0; i < 4; i++) - { - int b0 = s.ReadByte(); - if (b0 < 0) - { - throw new EndOfStreamException(); - } - - src <<= 8; - src |= (uint)b0; - } - - uint dest = src - (uint)(_mWritten + 4); - _mWritten++; - yield return (byte)dest; - _mWritten++; - yield return (byte)(dest >> 8); - _mWritten++; - yield return (byte)(dest >> 16); - _mWritten++; - yield return (byte)(dest >> 24); - prevByte = (byte)(dest >> 24); - processedBytes += 4; - } - else - { - prevByte = b; - } + var dest = src - (uint)(_mWritten + 4); + _mWritten++; + yield return (byte)dest; + _mWritten++; + yield return (byte)(dest >> 8); + _mWritten++; + yield return (byte)(dest >> 16); + _mWritten++; + yield return (byte)(dest >> 24); + prevByte = (byte)(dest >> 24); + processedBytes += 4; + } + else + { + prevByte = b; } } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/LZMA/BitVector.cs b/src/SharpCompress/Compressors/LZMA/BitVector.cs index 9f267f72..5b40536d 100644 --- a/src/SharpCompress/Compressors/LZMA/BitVector.cs +++ b/src/SharpCompress/Compressors/LZMA/BitVector.cs @@ -1,101 +1,99 @@ -using System; +using System; using System.Collections.Generic; using System.Text; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal class BitVector { - internal class BitVector + private readonly uint[] _mBits; + + public BitVector(int length) { - private readonly uint[] _mBits; + Length = length; + _mBits = new uint[(length + 31) >> 5]; + } - public BitVector(int length) + public BitVector(int length, bool initValue) + { + Length = length; + _mBits = new uint[(length + 31) >> 5]; + + if (initValue) { - Length = length; - _mBits = new uint[(length + 31) >> 5]; - } - - public BitVector(int length, bool initValue) - { - Length = length; - _mBits = new uint[(length + 31) >> 5]; - - if (initValue) + for (var i = 0; i < _mBits.Length; i++) { - for (int i = 0; i < _mBits.Length; i++) - { - _mBits[i] = ~0u; - } + _mBits[i] = ~0u; } } - - public BitVector(List bits) - : this(bits.Count) - { - for (int i = 0; i < bits.Count; i++) - { - if (bits[i]) - { - SetBit(i); - } - } - } - - public bool[] ToArray() - { - bool[] bits = new bool[Length]; - for (int i = 0; i < bits.Length; i++) - { - bits[i] = this[i]; - } - return bits; - } - - public int Length { get; } - - public bool this[int index] - { - get - { - if (index < 0 || index >= Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return (_mBits[index >> 5] & (1u << (index & 31))) != 0; - } - } - - public void SetBit(int index) - { - if (index < 0 || index >= Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - _mBits[index >> 5] |= 1u << (index & 31); - } - - internal bool GetAndSet(int index) - { - if (index < 0 || index >= Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - uint bits = _mBits[index >> 5]; - uint mask = 1u << (index & 31); - _mBits[index >> 5] |= mask; - return (bits & mask) != 0; - } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(Length); - for (int i = 0; i < Length; i++) - { - sb.Append(this[i] ? 'x' : '.'); - } - return sb.ToString(); - } } -} \ No newline at end of file + + public BitVector(List bits) : this(bits.Count) + { + for (var i = 0; i < bits.Count; i++) + { + if (bits[i]) + { + SetBit(i); + } + } + } + + public bool[] ToArray() + { + var bits = new bool[Length]; + for (var i = 0; i < bits.Length; i++) + { + bits[i] = this[i]; + } + return bits; + } + + public int Length { get; } + + public bool this[int index] + { + get + { + if (index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return (_mBits[index >> 5] & (1u << (index & 31))) != 0; + } + } + + public void SetBit(int index) + { + if (index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + _mBits[index >> 5] |= 1u << (index & 31); + } + + internal bool GetAndSet(int index) + { + if (index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var bits = _mBits[index >> 5]; + var mask = 1u << (index & 31); + _mBits[index >> 5] |= mask; + return (bits & mask) != 0; + } + + public override string ToString() + { + var sb = new StringBuilder(Length); + for (var i = 0; i < Length; i++) + { + sb.Append(this[i] ? 'x' : '.'); + } + return sb.ToString(); + } +} diff --git a/src/SharpCompress/Compressors/LZMA/CRC.cs b/src/SharpCompress/Compressors/LZMA/CRC.cs index 2ee0600a..ea2969bd 100644 --- a/src/SharpCompress/Compressors/LZMA/CRC.cs +++ b/src/SharpCompress/Compressors/LZMA/CRC.cs @@ -1,89 +1,77 @@ using System; using System.IO; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal static class Crc { - internal static class Crc + internal const uint INIT_CRC = 0xFFFFFFFF; + internal static readonly uint[] TABLE = new uint[4 * 256]; + + static Crc() { - internal const uint INIT_CRC = 0xFFFFFFFF; - internal static readonly uint[] TABLE = new uint[4 * 256]; + const uint kCrcPoly = 0xEDB88320; - static Crc() + for (uint i = 0; i < 256; i++) { - const uint kCrcPoly = 0xEDB88320; - - for (uint i = 0; i < 256; i++) + var r = i; + for (var j = 0; j < 8; j++) { - uint r = i; - for (int j = 0; j < 8; j++) - { - r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); - } - - TABLE[i] = r; + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); } - for (uint i = 256; i < TABLE.Length; i++) - { - uint r = TABLE[i - 256]; - TABLE[i] = TABLE[r & 0xFF] ^ (r >> 8); - } + TABLE[i] = r; } - public static uint From(Stream stream, long length) + for (uint i = 256; i < TABLE.Length; i++) { - uint crc = INIT_CRC; - byte[] buffer = new byte[Math.Min(length, 4 << 10)]; - while (length > 0) - { - int delta = stream.Read(buffer, 0, (int)Math.Min(length, buffer.Length)); - if (delta == 0) - { - throw new EndOfStreamException(); - } - crc = Update(crc, buffer, 0, delta); - length -= delta; - } - return Finish(crc); - } - - public static uint Finish(uint crc) - { - return ~crc; - } - - public static uint Update(uint crc, byte bt) - { - return TABLE[(crc & 0xFF) ^ bt] ^ (crc >> 8); - } - - public static uint Update(uint crc, uint value) - { - crc ^= value; - return TABLE[0x300 + (crc & 0xFF)] - ^ TABLE[0x200 + ((crc >> 8) & 0xFF)] - ^ TABLE[0x100 + ((crc >> 16) & 0xFF)] - ^ TABLE[0x000 + (crc >> 24)]; - } - - public static uint Update(uint crc, ulong value) - { - return Update(Update(crc, (uint)value), (uint)(value >> 32)); - } - - public static uint Update(uint crc, long value) - { - return Update(crc, (ulong)value); - } - - public static uint Update(uint crc, byte[] buffer, int offset, int length) - { - for (int i = 0; i < length; i++) - { - crc = Update(crc, buffer[offset + i]); - } - - return crc; + var r = TABLE[i - 256]; + TABLE[i] = TABLE[r & 0xFF] ^ (r >> 8); } } -} \ No newline at end of file + + public static uint From(Stream stream, long length) + { + var crc = INIT_CRC; + var buffer = new byte[Math.Min(length, 4 << 10)]; + while (length > 0) + { + var delta = stream.Read(buffer, 0, (int)Math.Min(length, buffer.Length)); + if (delta == 0) + { + throw new EndOfStreamException(); + } + crc = Update(crc, buffer, 0, delta); + length -= delta; + } + return Finish(crc); + } + + public static uint Finish(uint crc) => ~crc; + + public static uint Update(uint crc, byte bt) => TABLE[(crc & 0xFF) ^ bt] ^ (crc >> 8); + + public static uint Update(uint crc, uint value) + { + crc ^= value; + return TABLE[0x300 + (crc & 0xFF)] + ^ TABLE[0x200 + ((crc >> 8) & 0xFF)] + ^ TABLE[0x100 + ((crc >> 16) & 0xFF)] + ^ TABLE[0x000 + (crc >> 24)]; + } + + public static uint Update(uint crc, ulong value) => + Update(Update(crc, (uint)value), (uint)(value >> 32)); + + public static uint Update(uint crc, long value) => Update(crc, (ulong)value); + + public static uint Update(uint crc, byte[] buffer, int offset, int length) + { + for (var i = 0; i < length; i++) + { + crc = Update(crc, buffer[offset + i]); + } + + return crc; + } +} diff --git a/src/SharpCompress/Compressors/LZMA/DecoderStream.cs b/src/SharpCompress/Compressors/LZMA/DecoderStream.cs index 1921ebab..16865351 100644 --- a/src/SharpCompress/Compressors/LZMA/DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/DecoderStream.cs @@ -4,179 +4,215 @@ using SharpCompress.Common.SevenZip; using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.IO; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal abstract class DecoderStream2 : Stream { - internal abstract class DecoderStream2 : Stream + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position { - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } - internal static class DecoderStreamHelper + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} + +internal static class DecoderStreamHelper +{ + private static int FindCoderIndexForOutStreamIndex(CFolder folderInfo, int outStreamIndex) { - private static int FindCoderIndexForOutStreamIndex(CFolder folderInfo, int outStreamIndex) - { - for (int coderIndex = 0; coderIndex < folderInfo._coders.Count; coderIndex++) - { - var coderInfo = folderInfo._coders[coderIndex]; - outStreamIndex -= coderInfo._numOutStreams; - if (outStreamIndex < 0) - { - return coderIndex; - } - } - - throw new InvalidOperationException("Could not link output stream to coder."); - } - - private static void FindPrimaryOutStreamIndex(CFolder folderInfo, out int primaryCoderIndex, - out int primaryOutStreamIndex) - { - bool foundPrimaryOutStream = false; - primaryCoderIndex = -1; - primaryOutStreamIndex = -1; - - for (int outStreamIndex = 0, coderIndex = 0; - coderIndex < folderInfo._coders.Count; - coderIndex++) - { - for (int coderOutStreamIndex = 0; - coderOutStreamIndex < folderInfo._coders[coderIndex]._numOutStreams; - coderOutStreamIndex++, outStreamIndex++) - { - if (folderInfo.FindBindPairForOutStream(outStreamIndex) < 0) - { - if (foundPrimaryOutStream) - { - throw new NotSupportedException("Multiple output streams."); - } - - foundPrimaryOutStream = true; - primaryCoderIndex = coderIndex; - primaryOutStreamIndex = outStreamIndex; - } - } - } - - if (!foundPrimaryOutStream) - { - throw new NotSupportedException("No output stream."); - } - } - - private static Stream CreateDecoderStream(Stream[] packStreams, long[] packSizes, Stream[] outStreams, - CFolder folderInfo, int coderIndex, IPasswordProvider pass) + for (var coderIndex = 0; coderIndex < folderInfo._coders.Count; coderIndex++) { var coderInfo = folderInfo._coders[coderIndex]; - if (coderInfo._numOutStreams != 1) + outStreamIndex -= coderInfo._numOutStreams; + if (outStreamIndex < 0) { - throw new NotSupportedException("Multiple output streams are not supported."); + return coderIndex; } - - int inStreamId = 0; - for (int i = 0; i < coderIndex; i++) - { - inStreamId += folderInfo._coders[i]._numInStreams; - } - - int outStreamId = 0; - for (int i = 0; i < coderIndex; i++) - { - outStreamId += folderInfo._coders[i]._numOutStreams; - } - - Stream[] inStreams = new Stream[coderInfo._numInStreams]; - - for (int i = 0; i < inStreams.Length; i++, inStreamId++) - { - int bindPairIndex = folderInfo.FindBindPairForInStream(inStreamId); - if (bindPairIndex >= 0) - { - int pairedOutIndex = folderInfo._bindPairs[bindPairIndex]._outIndex; - - if (outStreams[pairedOutIndex] != null) - { - throw new NotSupportedException("Overlapping stream bindings are not supported."); - } - - int otherCoderIndex = FindCoderIndexForOutStreamIndex(folderInfo, pairedOutIndex); - inStreams[i] = CreateDecoderStream(packStreams, packSizes, outStreams, folderInfo, otherCoderIndex, - pass); - - //inStreamSizes[i] = folderInfo.UnpackSizes[pairedOutIndex]; - - if (outStreams[pairedOutIndex] != null) - { - throw new NotSupportedException("Overlapping stream bindings are not supported."); - } - - outStreams[pairedOutIndex] = inStreams[i]; - } - else - { - int index = folderInfo.FindPackStreamArrayIndex(inStreamId); - if (index < 0) - { - throw new NotSupportedException("Could not find input stream binding."); - } - - inStreams[i] = packStreams[index]; - - //inStreamSizes[i] = packSizes[index]; - } - } - - long unpackSize = folderInfo._unpackSizes[outStreamId]; - return DecoderRegistry.CreateDecoderStream(coderInfo._methodId, inStreams, coderInfo._props, pass, unpackSize); } - internal static Stream CreateDecoderStream(Stream inStream, long startPos, long[] packSizes, CFolder folderInfo, - IPasswordProvider pass) + throw new InvalidOperationException("Could not link output stream to coder."); + } + + private static void FindPrimaryOutStreamIndex( + CFolder folderInfo, + out int primaryCoderIndex, + out int primaryOutStreamIndex + ) + { + var foundPrimaryOutStream = false; + primaryCoderIndex = -1; + primaryOutStreamIndex = -1; + + for ( + int outStreamIndex = 0, coderIndex = 0; + coderIndex < folderInfo._coders.Count; + coderIndex++ + ) { - if (!folderInfo.CheckStructure()) + for ( + var coderOutStreamIndex = 0; + coderOutStreamIndex < folderInfo._coders[coderIndex]._numOutStreams; + coderOutStreamIndex++, outStreamIndex++ + ) { - throw new NotSupportedException("Unsupported stream binding structure."); + if (folderInfo.FindBindPairForOutStream(outStreamIndex) < 0) + { + if (foundPrimaryOutStream) + { + throw new NotSupportedException("Multiple output streams."); + } + + foundPrimaryOutStream = true; + primaryCoderIndex = coderIndex; + primaryOutStreamIndex = outStreamIndex; + } } + } - Stream[] inStreams = new Stream[folderInfo._packStreams.Count]; - for (int j = 0; j < folderInfo._packStreams.Count; j++) - { - inStreams[j] = new BufferedSubStream(inStream, startPos, packSizes[j]); - startPos += packSizes[j]; - } - - Stream[] outStreams = new Stream[folderInfo._unpackSizes.Count]; - - int primaryCoderIndex, primaryOutStreamIndex; - FindPrimaryOutStreamIndex(folderInfo, out primaryCoderIndex, out primaryOutStreamIndex); - return CreateDecoderStream(inStreams, packSizes, outStreams, folderInfo, primaryCoderIndex, pass); + if (!foundPrimaryOutStream) + { + throw new NotSupportedException("No output stream."); } } -} \ No newline at end of file + + private static Stream CreateDecoderStream( + Stream[] packStreams, + long[] packSizes, + Stream[] outStreams, + CFolder folderInfo, + int coderIndex, + IPasswordProvider pass + ) + { + var coderInfo = folderInfo._coders[coderIndex]; + if (coderInfo._numOutStreams != 1) + { + throw new NotSupportedException("Multiple output streams are not supported."); + } + + var inStreamId = 0; + for (var i = 0; i < coderIndex; i++) + { + inStreamId += folderInfo._coders[i]._numInStreams; + } + + var outStreamId = 0; + for (var i = 0; i < coderIndex; i++) + { + outStreamId += folderInfo._coders[i]._numOutStreams; + } + + var inStreams = new Stream[coderInfo._numInStreams]; + + for (var i = 0; i < inStreams.Length; i++, inStreamId++) + { + var bindPairIndex = folderInfo.FindBindPairForInStream(inStreamId); + if (bindPairIndex >= 0) + { + var pairedOutIndex = folderInfo._bindPairs[bindPairIndex]._outIndex; + + if (outStreams[pairedOutIndex] != null) + { + throw new NotSupportedException( + "Overlapping stream bindings are not supported." + ); + } + + var otherCoderIndex = FindCoderIndexForOutStreamIndex(folderInfo, pairedOutIndex); + inStreams[i] = CreateDecoderStream( + packStreams, + packSizes, + outStreams, + folderInfo, + otherCoderIndex, + pass + ); + + //inStreamSizes[i] = folderInfo.UnpackSizes[pairedOutIndex]; + + if (outStreams[pairedOutIndex] != null) + { + throw new NotSupportedException( + "Overlapping stream bindings are not supported." + ); + } + + outStreams[pairedOutIndex] = inStreams[i]; + } + else + { + var index = folderInfo.FindPackStreamArrayIndex(inStreamId); + if (index < 0) + { + throw new NotSupportedException("Could not find input stream binding."); + } + + inStreams[i] = packStreams[index]; + + //inStreamSizes[i] = packSizes[index]; + } + } + + var unpackSize = folderInfo._unpackSizes[outStreamId]; + return DecoderRegistry.CreateDecoderStream( + coderInfo._methodId, + inStreams, + coderInfo._props, + pass, + unpackSize + ); + } + + internal static Stream CreateDecoderStream( + Stream inStream, + long startPos, + long[] packSizes, + CFolder folderInfo, + IPasswordProvider pass + ) + { + if (!folderInfo.CheckStructure()) + { + throw new NotSupportedException("Unsupported stream binding structure."); + } + + var inStreams = new Stream[folderInfo._packStreams.Count]; + for (var j = 0; j < folderInfo._packStreams.Count; j++) + { + inStreams[j] = new BufferedSubStream(inStream, startPos, packSizes[j]); + startPos += packSizes[j]; + } + + var outStreams = new Stream[folderInfo._unpackSizes.Count]; + + FindPrimaryOutStreamIndex( + folderInfo, + out var primaryCoderIndex, + out var primaryOutStreamIndex + ); + return CreateDecoderStream( + inStreams, + packSizes, + outStreams, + folderInfo, + primaryCoderIndex, + pass + ); + } +} diff --git a/src/SharpCompress/Compressors/LZMA/ICoder.cs b/src/SharpCompress/Compressors/LZMA/ICoder.cs index 3ae84f48..569568f5 100644 --- a/src/SharpCompress/Compressors/LZMA/ICoder.cs +++ b/src/SharpCompress/Compressors/LZMA/ICoder.cs @@ -1,172 +1,164 @@ using System; using System.IO; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +/// +/// The exception that is thrown when an error in input stream occurs during decoding. +/// +internal class DataErrorException : Exception +{ + public DataErrorException() : base("Data Error") { } +} + +/// +/// The exception that is thrown when the value of an argument is outside the allowable range. +/// +internal class InvalidParamException : Exception +{ + public InvalidParamException() : base("Invalid Parameter") { } +} + +public interface ICodeProgress { /// - /// The exception that is thrown when an error in input stream occurs during decoding. + /// Callback progress. /// - internal class DataErrorException : Exception - { - public DataErrorException() - : base("Data Error") - { - } - } - - /// - /// The exception that is thrown when the value of an argument is outside the allowable range. - /// - internal class InvalidParamException : Exception - { - public InvalidParamException() - : base("Invalid Parameter") - { - } - } - - public interface ICodeProgress - { - /// - /// Callback progress. - /// - /// - /// input size. -1 if unknown. - /// - /// - /// output size. -1 if unknown. - /// - void SetProgress(Int64 inSize, Int64 outSize); - } - - internal interface ICoder - { - /// - /// Codes streams. - /// - /// - /// input Stream. - /// - /// - /// output Stream. - /// - /// - /// input Size. -1 if unknown. - /// - /// - /// output Size. -1 if unknown. - /// - /// - /// callback progress reference. - /// - void Code(Stream inStream, Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress); - } - - /* - public interface ICoder2 - { - void Code(ISequentialInStream []inStreams, - const UInt64 []inSizes, - ISequentialOutStream []outStreams, - UInt64 []outSizes, - ICodeProgress progress); - }; - */ - - /// - /// Provides the fields that represent properties idenitifiers for compressing. - /// - internal enum CoderPropId - { - /// - /// Specifies default property. - /// - DefaultProp = 0, - - /// - /// Specifies size of dictionary. - /// - DictionarySize, - - /// - /// Specifies size of memory for PPM*. - /// - UsedMemorySize, - - /// - /// Specifies order for PPM methods. - /// - Order, - - /// - /// Specifies Block Size. - /// - BlockSize, - - /// - /// Specifies number of postion state bits for LZMA (0 - x - 4). - /// - PosStateBits, - - /// - /// Specifies number of literal context bits for LZMA (0 - x - 8). - /// - LitContextBits, - - /// - /// Specifies number of literal position bits for LZMA (0 - x - 4). - /// - LitPosBits, - - /// - /// Specifies number of fast bytes for LZ*. - /// - NumFastBytes, - - /// - /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". - /// - MatchFinder, - - /// - /// Specifies the number of match finder cyckes. - /// - MatchFinderCycles, - - /// - /// Specifies number of passes. - /// - NumPasses, - - /// - /// Specifies number of algorithm. - /// - Algorithm, - - /// - /// Specifies the number of threads. - /// - NumThreads, - - /// - /// Specifies mode with end marker. - /// - EndMarker - } - - internal interface ISetCoderProperties - { - void SetCoderProperties(CoderPropId[] propIDs, object[] properties); - } - - internal interface IWriteCoderProperties - { - void WriteCoderProperties(Stream outStream); - } - - internal interface ISetDecoderProperties - { - void SetDecoderProperties(byte[] properties); - } + /// + /// input size. -1 if unknown. + /// + /// + /// output size. -1 if unknown. + /// + void SetProgress(long inSize, long outSize); +} + +internal interface ICoder +{ + /// + /// Codes streams. + /// + /// + /// input Stream. + /// + /// + /// output Stream. + /// + /// + /// input Size. -1 if unknown. + /// + /// + /// output Size. -1 if unknown. + /// + /// + /// callback progress reference. + /// + void Code(Stream inStream, Stream outStream, long inSize, long outSize, ICodeProgress progress); +} + +/* +public interface ICoder2 +{ + void Code(ISequentialInStream []inStreams, + const UInt64 []inSizes, + ISequentialOutStream []outStreams, + UInt64 []outSizes, + ICodeProgress progress); +}; +*/ + +/// +/// Provides the fields that represent properties idenitifiers for compressing. +/// +internal enum CoderPropId +{ + /// + /// Specifies default property. + /// + DefaultProp = 0, + + /// + /// Specifies size of dictionary. + /// + DictionarySize, + + /// + /// Specifies size of memory for PPM*. + /// + UsedMemorySize, + + /// + /// Specifies order for PPM methods. + /// + Order, + + /// + /// Specifies Block Size. + /// + BlockSize, + + /// + /// Specifies number of postion state bits for LZMA (0 - x - 4). + /// + PosStateBits, + + /// + /// Specifies number of literal context bits for LZMA (0 - x - 8). + /// + LitContextBits, + + /// + /// Specifies number of literal position bits for LZMA (0 - x - 4). + /// + LitPosBits, + + /// + /// Specifies number of fast bytes for LZ*. + /// + NumFastBytes, + + /// + /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B". + /// + MatchFinder, + + /// + /// Specifies the number of match finder cyckes. + /// + MatchFinderCycles, + + /// + /// Specifies number of passes. + /// + NumPasses, + + /// + /// Specifies number of algorithm. + /// + Algorithm, + + /// + /// Specifies the number of threads. + /// + NumThreads, + + /// + /// Specifies mode with end marker. + /// + EndMarker +} + +internal interface ISetCoderProperties +{ + void SetCoderProperties(CoderPropId[] propIDs, object[] properties); +} + +internal interface IWriteCoderProperties +{ + void WriteCoderProperties(Stream outStream); +} + +internal interface ISetDecoderProperties +{ + void SetDecoderProperties(byte[] properties); } diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzBinTree.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzBinTree.cs index 37c70720..b123e5d5 100644 --- a/src/SharpCompress/Compressors/LZMA/LZ/LzBinTree.cs +++ b/src/SharpCompress/Compressors/LZMA/LZ/LzBinTree.cs @@ -3,151 +3,303 @@ using System; using System.IO; -namespace SharpCompress.Compressors.LZMA.LZ +namespace SharpCompress.Compressors.LZMA.LZ; + +internal sealed class BinTree : InWindow { - internal sealed class BinTree : InWindow + private uint _cyclicBufferPos; + private uint _cyclicBufferSize; + private uint _matchMaxLen; + + private uint[] _son; + private uint[] _hash; + + private uint _cutValue = 0xFF; + private uint _hashMask; + private uint _hashSizeSum; + + private bool _hashArray = true; + + private const uint K_HASH2_SIZE = 1 << 10; + private const uint K_HASH3_SIZE = 1 << 16; + private const uint K_BT2_HASH_SIZE = 1 << 16; + private const uint K_START_MAX_LEN = 1; + private const uint K_HASH3_OFFSET = K_HASH2_SIZE; + private const uint K_EMPTY_HASH_VALUE = 0; + private const uint K_MAX_VAL_FOR_NORMALIZE = ((uint)1 << 31) - 1; + + private uint _kNumHashDirectBytes; + private uint _kMinMatchCheck = 4; + private uint _kFixHashSize = K_HASH2_SIZE + K_HASH3_SIZE; + + public void SetType(int numHashBytes) { - private UInt32 _cyclicBufferPos; - private UInt32 _cyclicBufferSize; - private UInt32 _matchMaxLen; - - private UInt32[] _son; - private UInt32[] _hash; - - private UInt32 _cutValue = 0xFF; - private UInt32 _hashMask; - private UInt32 _hashSizeSum; - - private bool _hashArray = true; - - private const UInt32 K_HASH2_SIZE = 1 << 10; - private const UInt32 K_HASH3_SIZE = 1 << 16; - private const UInt32 K_BT2_HASH_SIZE = 1 << 16; - private const UInt32 K_START_MAX_LEN = 1; - private const UInt32 K_HASH3_OFFSET = K_HASH2_SIZE; - private const UInt32 K_EMPTY_HASH_VALUE = 0; - private const UInt32 K_MAX_VAL_FOR_NORMALIZE = ((UInt32)1 << 31) - 1; - - private UInt32 _kNumHashDirectBytes; - private UInt32 _kMinMatchCheck = 4; - private UInt32 _kFixHashSize = K_HASH2_SIZE + K_HASH3_SIZE; - - public void SetType(int numHashBytes) + _hashArray = (numHashBytes > 2); + if (_hashArray) { - _hashArray = (numHashBytes > 2); - if (_hashArray) + _kNumHashDirectBytes = 0; + _kMinMatchCheck = 4; + _kFixHashSize = K_HASH2_SIZE + K_HASH3_SIZE; + } + else + { + _kNumHashDirectBytes = 2; + _kMinMatchCheck = 2 + 1; + _kFixHashSize = 0; + } + } + + public new void SetStream(Stream stream) => base.SetStream(stream); + + public new void ReleaseStream() => base.ReleaseStream(); + + public new void Init() + { + base.Init(); + for (uint i = 0; i < _hashSizeSum; i++) + { + _hash[i] = K_EMPTY_HASH_VALUE; + } + _cyclicBufferPos = 0; + ReduceOffsets(-1); + } + + public new void MovePos() + { + if (++_cyclicBufferPos >= _cyclicBufferSize) + { + _cyclicBufferPos = 0; + } + base.MovePos(); + if (_pos == K_MAX_VAL_FOR_NORMALIZE) + { + Normalize(); + } + } + + public new byte GetIndexByte(int index) => base.GetIndexByte(index); + + public new uint GetMatchLen(int index, uint distance, uint limit) => + base.GetMatchLen(index, distance, limit); + + public new uint GetNumAvailableBytes() => base.GetNumAvailableBytes(); + + public void Create( + uint historySize, + uint keepAddBufferBefore, + uint matchMaxLen, + uint keepAddBufferAfter + ) + { + if (historySize > K_MAX_VAL_FOR_NORMALIZE - 256) + { + throw new ArgumentOutOfRangeException(nameof(historySize)); + } + _cutValue = 16 + (matchMaxLen >> 1); + + var windowReservSize = + ((historySize + keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2) + 256; + + Create( + historySize + keepAddBufferBefore, + matchMaxLen + keepAddBufferAfter, + windowReservSize + ); + + _matchMaxLen = matchMaxLen; + + var cyclicBufferSize = historySize + 1; + if (_cyclicBufferSize != cyclicBufferSize) + { + _son = new uint[(_cyclicBufferSize = cyclicBufferSize) * 2]; + } + + var hs = K_BT2_HASH_SIZE; + + if (_hashArray) + { + hs = historySize - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; + if (hs > (1 << 24)) { - _kNumHashDirectBytes = 0; - _kMinMatchCheck = 4; - _kFixHashSize = K_HASH2_SIZE + K_HASH3_SIZE; + hs >>= 1; + } + _hashMask = hs; + hs++; + hs += _kFixHashSize; + } + if (hs != _hashSizeSum) + { + _hash = new uint[_hashSizeSum = hs]; + } + } + + public uint GetMatches(uint[] distances) + { + uint lenLimit; + if (_pos + _matchMaxLen <= _streamPos) + { + lenLimit = _matchMaxLen; + } + else + { + lenLimit = _streamPos - _pos; + if (lenLimit < _kMinMatchCheck) + { + MovePos(); + return 0; + } + } + + uint offset = 0; + var matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + var cur = _bufferOffset + _pos; + var maxLen = K_START_MAX_LEN; // to avoid items for len < hashSize; + uint hashValue, + hash2Value = 0, + hash3Value = 0; + + if (_hashArray) + { + var temp = Crc.TABLE[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + hash2Value = temp & (K_HASH2_SIZE - 1); + temp ^= ((uint)(_bufferBase[cur + 2]) << 8); + hash3Value = temp & (K_HASH3_SIZE - 1); + hashValue = (temp ^ (Crc.TABLE[_bufferBase[cur + 3]] << 5)) & _hashMask; + } + else + { + hashValue = _bufferBase[cur] ^ ((uint)(_bufferBase[cur + 1]) << 8); + } + + var curMatch = _hash[_kFixHashSize + hashValue]; + if (_hashArray) + { + var curMatch2 = _hash[hash2Value]; + var curMatch3 = _hash[K_HASH3_OFFSET + hash3Value]; + _hash[hash2Value] = _pos; + _hash[K_HASH3_OFFSET + hash3Value] = _pos; + if (curMatch2 > matchMinPos) + { + if (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur]) + { + distances[offset++] = maxLen = 2; + distances[offset++] = _pos - curMatch2 - 1; + } + } + if (curMatch3 > matchMinPos) + { + if (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur]) + { + if (curMatch3 == curMatch2) + { + offset -= 2; + } + distances[offset++] = maxLen = 3; + distances[offset++] = _pos - curMatch3 - 1; + curMatch2 = curMatch3; + } + } + if (offset != 0 && curMatch2 == curMatch) + { + offset -= 2; + maxLen = K_START_MAX_LEN; + } + } + + _hash[_kFixHashSize + hashValue] = _pos; + + var ptr0 = (_cyclicBufferPos << 1) + 1; + var ptr1 = (_cyclicBufferPos << 1); + + uint len0, + len1; + len0 = len1 = _kNumHashDirectBytes; + + if (_kNumHashDirectBytes != 0) + { + if (curMatch > matchMinPos) + { + if ( + _bufferBase[_bufferOffset + curMatch + _kNumHashDirectBytes] + != _bufferBase[cur + _kNumHashDirectBytes] + ) + { + distances[offset++] = maxLen = _kNumHashDirectBytes; + distances[offset++] = _pos - curMatch - 1; + } + } + } + + var count = _cutValue; + + while (true) + { + if (curMatch <= matchMinPos || count-- == 0) + { + _son[ptr0] = _son[ptr1] = K_EMPTY_HASH_VALUE; + break; + } + var delta = _pos - curMatch; + var cyclicPos = + ( + (delta <= _cyclicBufferPos) + ? (_cyclicBufferPos - delta) + : (_cyclicBufferPos - delta + _cyclicBufferSize) + ) << 1; + + var pby1 = _bufferOffset + curMatch; + var len = Math.Min(len0, len1); + if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) + { + while (++len != lenLimit) + { + if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) + { + break; + } + } + if (maxLen < len) + { + distances[offset++] = maxLen = len; + distances[offset++] = delta - 1; + if (len == lenLimit) + { + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; + } + } + } + if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) + { + _son[ptr1] = curMatch; + ptr1 = cyclicPos + 1; + curMatch = _son[ptr1]; + len1 = len; } else { - _kNumHashDirectBytes = 2; - _kMinMatchCheck = 2 + 1; - _kFixHashSize = 0; + _son[ptr0] = curMatch; + ptr0 = cyclicPos; + curMatch = _son[ptr0]; + len0 = len; } } + MovePos(); + return offset; + } - public new void SetStream(Stream stream) + public void Skip(uint num) + { + do { - base.SetStream(stream); - } - - public new void ReleaseStream() - { - base.ReleaseStream(); - } - - public new void Init() - { - base.Init(); - for (UInt32 i = 0; i < _hashSizeSum; i++) - { - _hash[i] = K_EMPTY_HASH_VALUE; - } - _cyclicBufferPos = 0; - ReduceOffsets(-1); - } - - public new void MovePos() - { - if (++_cyclicBufferPos >= _cyclicBufferSize) - { - _cyclicBufferPos = 0; - } - base.MovePos(); - if (_pos == K_MAX_VAL_FOR_NORMALIZE) - { - Normalize(); - } - } - - public new Byte GetIndexByte(Int32 index) - { - return base.GetIndexByte(index); - } - - public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) - { - return base.GetMatchLen(index, distance, limit); - } - - public new UInt32 GetNumAvailableBytes() - { - return base.GetNumAvailableBytes(); - } - - public void Create(UInt32 historySize, UInt32 keepAddBufferBefore, - UInt32 matchMaxLen, UInt32 keepAddBufferAfter) - { - if (historySize > K_MAX_VAL_FOR_NORMALIZE - 256) - { - throw new ArgumentOutOfRangeException(nameof(historySize)); - } - _cutValue = 16 + (matchMaxLen >> 1); - - UInt32 windowReservSize = (historySize + keepAddBufferBefore + - matchMaxLen + keepAddBufferAfter) / 2 + 256; - - base.Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize); - - _matchMaxLen = matchMaxLen; - - UInt32 cyclicBufferSize = historySize + 1; - if (_cyclicBufferSize != cyclicBufferSize) - { - _son = new UInt32[(_cyclicBufferSize = cyclicBufferSize) * 2]; - } - - UInt32 hs = K_BT2_HASH_SIZE; - - if (_hashArray) - { - hs = historySize - 1; - hs |= (hs >> 1); - hs |= (hs >> 2); - hs |= (hs >> 4); - hs |= (hs >> 8); - hs >>= 1; - hs |= 0xFFFF; - if (hs > (1 << 24)) - { - hs >>= 1; - } - _hashMask = hs; - hs++; - hs += _kFixHashSize; - } - if (hs != _hashSizeSum) - { - _hash = new UInt32[_hashSizeSum = hs]; - } - } - - public UInt32 GetMatches(UInt32[] distances) - { - UInt32 lenLimit; + uint lenLimit; if (_pos + _matchMaxLen <= _streamPos) { lenLimit = _matchMaxLen; @@ -158,87 +310,41 @@ namespace SharpCompress.Compressors.LZMA.LZ if (lenLimit < _kMinMatchCheck) { MovePos(); - return 0; + continue; } } - UInt32 offset = 0; - UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; - UInt32 cur = _bufferOffset + _pos; - UInt32 maxLen = K_START_MAX_LEN; // to avoid items for len < hashSize; - UInt32 hashValue, hash2Value = 0, hash3Value = 0; + var matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; + var cur = _bufferOffset + _pos; + + uint hashValue; if (_hashArray) { - UInt32 temp = Crc.TABLE[_bufferBase[cur]] ^ _bufferBase[cur + 1]; - hash2Value = temp & (K_HASH2_SIZE - 1); - temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8); - hash3Value = temp & (K_HASH3_SIZE - 1); + var temp = Crc.TABLE[_bufferBase[cur]] ^ _bufferBase[cur + 1]; + var hash2Value = temp & (K_HASH2_SIZE - 1); + _hash[hash2Value] = _pos; + temp ^= ((uint)(_bufferBase[cur + 2]) << 8); + var hash3Value = temp & (K_HASH3_SIZE - 1); + _hash[K_HASH3_OFFSET + hash3Value] = _pos; hashValue = (temp ^ (Crc.TABLE[_bufferBase[cur + 3]] << 5)) & _hashMask; } else { - hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8); - } - - UInt32 curMatch = _hash[_kFixHashSize + hashValue]; - if (_hashArray) - { - UInt32 curMatch2 = _hash[hash2Value]; - UInt32 curMatch3 = _hash[K_HASH3_OFFSET + hash3Value]; - _hash[hash2Value] = _pos; - _hash[K_HASH3_OFFSET + hash3Value] = _pos; - if (curMatch2 > matchMinPos) - { - if (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur]) - { - distances[offset++] = maxLen = 2; - distances[offset++] = _pos - curMatch2 - 1; - } - } - if (curMatch3 > matchMinPos) - { - if (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur]) - { - if (curMatch3 == curMatch2) - { - offset -= 2; - } - distances[offset++] = maxLen = 3; - distances[offset++] = _pos - curMatch3 - 1; - curMatch2 = curMatch3; - } - } - if (offset != 0 && curMatch2 == curMatch) - { - offset -= 2; - maxLen = K_START_MAX_LEN; - } + hashValue = _bufferBase[cur] ^ ((uint)(_bufferBase[cur + 1]) << 8); } + var curMatch = _hash[_kFixHashSize + hashValue]; _hash[_kFixHashSize + hashValue] = _pos; - UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; - UInt32 ptr1 = (_cyclicBufferPos << 1); + var ptr0 = (_cyclicBufferPos << 1) + 1; + var ptr1 = (_cyclicBufferPos << 1); - UInt32 len0, len1; + uint len0, + len1; len0 = len1 = _kNumHashDirectBytes; - if (_kNumHashDirectBytes != 0) - { - if (curMatch > matchMinPos) - { - if (_bufferBase[_bufferOffset + curMatch + _kNumHashDirectBytes] != - _bufferBase[cur + _kNumHashDirectBytes]) - { - distances[offset++] = maxLen = _kNumHashDirectBytes; - distances[offset++] = _pos - curMatch - 1; - } - } - } - - UInt32 count = _cutValue; - + var count = _cutValue; while (true) { if (curMatch <= matchMinPos || count-- == 0) @@ -246,13 +352,17 @@ namespace SharpCompress.Compressors.LZMA.LZ _son[ptr0] = _son[ptr1] = K_EMPTY_HASH_VALUE; break; } - UInt32 delta = _pos - curMatch; - UInt32 cyclicPos = ((delta <= _cyclicBufferPos) - ? (_cyclicBufferPos - delta) - : (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; - UInt32 pby1 = _bufferOffset + curMatch; - UInt32 len = Math.Min(len0, len1); + var delta = _pos - curMatch; + var cyclicPos = + ( + (delta <= _cyclicBufferPos) + ? (_cyclicBufferPos - delta) + : (_cyclicBufferPos - delta + _cyclicBufferSize) + ) << 1; + + var pby1 = _bufferOffset + curMatch; + var len = Math.Min(len0, len1); if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) { while (++len != lenLimit) @@ -262,16 +372,11 @@ namespace SharpCompress.Compressors.LZMA.LZ break; } } - if (maxLen < len) + if (len == lenLimit) { - distances[offset++] = maxLen = len; - distances[offset++] = delta - 1; - if (len == lenLimit) - { - _son[ptr1] = _son[cyclicPos]; - _son[ptr0] = _son[cyclicPos + 1]; - break; - } + _son[ptr1] = _son[cyclicPos]; + _son[ptr0] = _son[cyclicPos + 1]; + break; } } if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) @@ -290,137 +395,33 @@ namespace SharpCompress.Compressors.LZMA.LZ } } MovePos(); - return offset; - } + } while (--num != 0); + } - public void Skip(UInt32 num) + private void NormalizeLinks(uint[] items, uint numItems, uint subValue) + { + for (uint i = 0; i < numItems; i++) { - do + var value = items[i]; + if (value <= subValue) { - UInt32 lenLimit; - if (_pos + _matchMaxLen <= _streamPos) - { - lenLimit = _matchMaxLen; - } - else - { - lenLimit = _streamPos - _pos; - if (lenLimit < _kMinMatchCheck) - { - MovePos(); - continue; - } - } - - UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0; - UInt32 cur = _bufferOffset + _pos; - - UInt32 hashValue; - - if (_hashArray) - { - UInt32 temp = Crc.TABLE[_bufferBase[cur]] ^ _bufferBase[cur + 1]; - UInt32 hash2Value = temp & (K_HASH2_SIZE - 1); - _hash[hash2Value] = _pos; - temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8); - UInt32 hash3Value = temp & (K_HASH3_SIZE - 1); - _hash[K_HASH3_OFFSET + hash3Value] = _pos; - hashValue = (temp ^ (Crc.TABLE[_bufferBase[cur + 3]] << 5)) & _hashMask; - } - else - { - hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8); - } - - UInt32 curMatch = _hash[_kFixHashSize + hashValue]; - _hash[_kFixHashSize + hashValue] = _pos; - - UInt32 ptr0 = (_cyclicBufferPos << 1) + 1; - UInt32 ptr1 = (_cyclicBufferPos << 1); - - UInt32 len0, len1; - len0 = len1 = _kNumHashDirectBytes; - - UInt32 count = _cutValue; - while (true) - { - if (curMatch <= matchMinPos || count-- == 0) - { - _son[ptr0] = _son[ptr1] = K_EMPTY_HASH_VALUE; - break; - } - - UInt32 delta = _pos - curMatch; - UInt32 cyclicPos = ((delta <= _cyclicBufferPos) - ? (_cyclicBufferPos - delta) - : (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1; - - UInt32 pby1 = _bufferOffset + curMatch; - UInt32 len = Math.Min(len0, len1); - if (_bufferBase[pby1 + len] == _bufferBase[cur + len]) - { - while (++len != lenLimit) - { - if (_bufferBase[pby1 + len] != _bufferBase[cur + len]) - { - break; - } - } - if (len == lenLimit) - { - _son[ptr1] = _son[cyclicPos]; - _son[ptr0] = _son[cyclicPos + 1]; - break; - } - } - if (_bufferBase[pby1 + len] < _bufferBase[cur + len]) - { - _son[ptr1] = curMatch; - ptr1 = cyclicPos + 1; - curMatch = _son[ptr1]; - len1 = len; - } - else - { - _son[ptr0] = curMatch; - ptr0 = cyclicPos; - curMatch = _son[ptr0]; - len0 = len; - } - } - MovePos(); + value = K_EMPTY_HASH_VALUE; } - while (--num != 0); - } - - private void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue) - { - for (UInt32 i = 0; i < numItems; i++) + else { - UInt32 value = items[i]; - if (value <= subValue) - { - value = K_EMPTY_HASH_VALUE; - } - else - { - value -= subValue; - } - items[i] = value; + value -= subValue; } - } - - private void Normalize() - { - UInt32 subValue = _pos - _cyclicBufferSize; - NormalizeLinks(_son, _cyclicBufferSize * 2, subValue); - NormalizeLinks(_hash, _hashSizeSum, subValue); - ReduceOffsets((Int32)subValue); - } - - public void SetCutValue(UInt32 cutValue) - { - _cutValue = cutValue; + items[i] = value; } } + + private void Normalize() + { + var subValue = _pos - _cyclicBufferSize; + NormalizeLinks(_son, _cyclicBufferSize * 2, subValue); + NormalizeLinks(_hash, _hashSizeSum, subValue); + ReduceOffsets((int)subValue); + } + + public void SetCutValue(uint cutValue) => _cutValue = cutValue; } diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzInWindow.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzInWindow.cs index 9ce2121a..9df3c27d 100644 --- a/src/SharpCompress/Compressors/LZMA/LZ/LzInWindow.cs +++ b/src/SharpCompress/Compressors/LZMA/LZ/LzInWindow.cs @@ -1,185 +1,172 @@ #nullable disable -using System; using System.IO; -namespace SharpCompress.Compressors.LZMA.LZ +namespace SharpCompress.Compressors.LZMA.LZ; + +internal class InWindow { - internal class InWindow + public byte[] _bufferBase; // pointer to buffer with data + private Stream _stream; + private uint _posLimit; // offset (from _buffer) of first byte when new block reading must be done + private bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream + + private uint _pointerToLastSafePosition; + + public uint _bufferOffset; + + public uint _blockSize; // Size of Allocated memory block + public uint _pos; // offset (from _buffer) of curent byte + private uint _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos + private uint _keepSizeAfter; // how many BYTEs must be kept buffer after _pos + public uint _streamPos; // offset (from _buffer) of first not read byte from Stream + + public void MoveBlock() { - public Byte[] _bufferBase; // pointer to buffer with data - private Stream _stream; - private UInt32 _posLimit; // offset (from _buffer) of first byte when new block reading must be done - private bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream + var offset = _bufferOffset + _pos - _keepSizeBefore; - private UInt32 _pointerToLastSafePosition; - - public UInt32 _bufferOffset; - - public UInt32 _blockSize; // Size of Allocated memory block - public UInt32 _pos; // offset (from _buffer) of curent byte - private UInt32 _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos - private UInt32 _keepSizeAfter; // how many BYTEs must be kept buffer after _pos - public UInt32 _streamPos; // offset (from _buffer) of first not read byte from Stream - - public void MoveBlock() + // we need one additional byte, since MovePos moves on 1 byte. + if (offset > 0) { - UInt32 offset = _bufferOffset + _pos - _keepSizeBefore; - - // we need one additional byte, since MovePos moves on 1 byte. - if (offset > 0) - { - offset--; - } - - UInt32 numBytes = _bufferOffset + _streamPos - offset; - - // check negative offset ???? - for (UInt32 i = 0; i < numBytes; i++) - { - _bufferBase[i] = _bufferBase[offset + i]; - } - _bufferOffset -= offset; + offset--; } - public virtual void ReadBlock() + var numBytes = _bufferOffset + _streamPos - offset; + + // check negative offset ???? + for (uint i = 0; i < numBytes; i++) { - if (_streamEndWasReached) + _bufferBase[i] = _bufferBase[offset + i]; + } + _bufferOffset -= offset; + } + + public virtual void ReadBlock() + { + if (_streamEndWasReached) + { + return; + } + while (true) + { + var size = (int)((0 - _bufferOffset) + _blockSize - _streamPos); + if (size == 0) { return; } - while (true) + var numReadBytes = + _stream != null + ? _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size) + : 0; + if (numReadBytes == 0) { - int size = (int)((0 - _bufferOffset) + _blockSize - _streamPos); - if (size == 0) - { - return; - } - int numReadBytes = _stream != null - ? _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size) - : 0; - if (numReadBytes == 0) - { - _posLimit = _streamPos; - UInt32 pointerToPostion = _bufferOffset + _posLimit; - if (pointerToPostion > _pointerToLastSafePosition) - { - _posLimit = _pointerToLastSafePosition - _bufferOffset; - } - - _streamEndWasReached = true; - return; - } - _streamPos += (UInt32)numReadBytes; - if (_streamPos >= _pos + _keepSizeAfter) - { - _posLimit = _streamPos - _keepSizeAfter; - } - } - } - - private void Free() - { - _bufferBase = null; - } - - public void Create(UInt32 keepSizeBefore, UInt32 keepSizeAfter, UInt32 keepSizeReserv) - { - _keepSizeBefore = keepSizeBefore; - _keepSizeAfter = keepSizeAfter; - UInt32 blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv; - if (_bufferBase is null || _blockSize != blockSize) - { - Free(); - _blockSize = blockSize; - _bufferBase = new Byte[_blockSize]; - } - _pointerToLastSafePosition = _blockSize - keepSizeAfter; - _streamEndWasReached = false; - } - - public void SetStream(Stream stream) - { - _stream = stream; - if (_streamEndWasReached) - { - _streamEndWasReached = false; - if (IsDataStarved) - { - ReadBlock(); - } - } - } - - public void ReleaseStream() - { - _stream = null; - } - - public void Init() - { - _bufferOffset = 0; - _pos = 0; - _streamPos = 0; - _streamEndWasReached = false; - ReadBlock(); - } - - public void MovePos() - { - _pos++; - if (_pos > _posLimit) - { - UInt32 pointerToPostion = _bufferOffset + _pos; + _posLimit = _streamPos; + var pointerToPostion = _bufferOffset + _posLimit; if (pointerToPostion > _pointerToLastSafePosition) { - MoveBlock(); + _posLimit = _pointerToLastSafePosition - _bufferOffset; } + + _streamEndWasReached = true; + return; + } + _streamPos += (uint)numReadBytes; + if (_streamPos >= _pos + _keepSizeAfter) + { + _posLimit = _streamPos - _keepSizeAfter; + } + } + } + + private void Free() => _bufferBase = null; + + public void Create(uint keepSizeBefore, uint keepSizeAfter, uint keepSizeReserv) + { + _keepSizeBefore = keepSizeBefore; + _keepSizeAfter = keepSizeAfter; + var blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv; + if (_bufferBase is null || _blockSize != blockSize) + { + Free(); + _blockSize = blockSize; + _bufferBase = new byte[_blockSize]; + } + _pointerToLastSafePosition = _blockSize - keepSizeAfter; + _streamEndWasReached = false; + } + + public void SetStream(Stream stream) + { + _stream = stream; + if (_streamEndWasReached) + { + _streamEndWasReached = false; + if (IsDataStarved) + { ReadBlock(); } } - - public Byte GetIndexByte(Int32 index) - { - return _bufferBase[_bufferOffset + _pos + index]; - } - - // index + limit have not to exceed _keepSizeAfter; - public UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit) - { - if (_streamEndWasReached) - { - if ((_pos + index) + limit > _streamPos) - { - limit = _streamPos - (UInt32)(_pos + index); - } - } - distance++; - - // Byte *pby = _buffer + (size_t)_pos + index; - UInt32 pby = _bufferOffset + _pos + (UInt32)index; - - UInt32 i; - for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++) - { - ; - } - return i; - } - - public UInt32 GetNumAvailableBytes() - { - return _streamPos - _pos; - } - - public void ReduceOffsets(Int32 subValue) - { - _bufferOffset += (UInt32)subValue; - _posLimit -= (UInt32)subValue; - _pos -= (UInt32)subValue; - _streamPos -= (UInt32)subValue; - } - - public bool IsDataStarved => _streamPos - _pos < _keepSizeAfter; } -} \ No newline at end of file + + public void ReleaseStream() => _stream = null; + + public void Init() + { + _bufferOffset = 0; + _pos = 0; + _streamPos = 0; + _streamEndWasReached = false; + ReadBlock(); + } + + public void MovePos() + { + _pos++; + if (_pos > _posLimit) + { + var pointerToPostion = _bufferOffset + _pos; + if (pointerToPostion > _pointerToLastSafePosition) + { + MoveBlock(); + } + ReadBlock(); + } + } + + public byte GetIndexByte(int index) => _bufferBase[_bufferOffset + _pos + index]; + + // index + limit have not to exceed _keepSizeAfter; + public uint GetMatchLen(int index, uint distance, uint limit) + { + if (_streamEndWasReached) + { + if ((_pos + index) + limit > _streamPos) + { + limit = _streamPos - (uint)(_pos + index); + } + } + distance++; + + // Byte *pby = _buffer + (size_t)_pos + index; + var pby = _bufferOffset + _pos + (uint)index; + + uint i; + for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++) + { + ; + } + return i; + } + + public uint GetNumAvailableBytes() => _streamPos - _pos; + + public void ReduceOffsets(int subValue) + { + _bufferOffset += (uint)subValue; + _posLimit -= (uint)subValue; + _pos -= (uint)subValue; + _streamPos -= (uint)subValue; + } + + public bool IsDataStarved => _streamPos - _pos < _keepSizeAfter; +} diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs index 483b3a98..6efabba1 100644 --- a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs +++ b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs @@ -3,205 +3,198 @@ using System; using System.IO; -namespace SharpCompress.Compressors.LZMA.LZ +namespace SharpCompress.Compressors.LZMA.LZ; + +internal class OutWindow { - internal class OutWindow + private byte[] _buffer; + private int _windowSize; + private int _pos; + private int _streamPos; + private int _pendingLen; + private int _pendingDist; + private Stream _stream; + + public long _total; + public long _limit; + + public void Create(int windowSize) { - private byte[] _buffer; - private int _windowSize; - private int _pos; - private int _streamPos; - private int _pendingLen; - private int _pendingDist; - private Stream _stream; - - public long _total; - public long _limit; - - public void Create(int windowSize) + if (_windowSize != windowSize) + { + _buffer = new byte[windowSize]; + } + else + { + _buffer[windowSize - 1] = 0; + } + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + _pendingLen = 0; + _total = 0; + _limit = 0; + } + + public void Reset() => Create(_windowSize); + + public void Init(Stream stream) + { + ReleaseStream(); + _stream = stream; + } + + public void Train(Stream stream) + { + var len = stream.Length; + var size = (len < _windowSize) ? (int)len : _windowSize; + stream.Position = len - size; + _total = 0; + _limit = size; + _pos = _windowSize - size; + CopyStream(stream, size); + if (_pos == _windowSize) { - if (_windowSize != windowSize) - { - _buffer = new byte[windowSize]; - } - else - { - _buffer[windowSize - 1] = 0; - } - _windowSize = windowSize; _pos = 0; - _streamPos = 0; - _pendingLen = 0; - _total = 0; - _limit = 0; } + _streamPos = _pos; + } - public void Reset() + public void ReleaseStream() + { + Flush(); + _stream = null; + } + + public void Flush() + { + if (_stream is null) { - Create(_windowSize); + return; } - - public void Init(Stream stream) + var size = _pos - _streamPos; + if (size == 0) { - ReleaseStream(); - _stream = stream; + return; } - - public void Train(Stream stream) + _stream.Write(_buffer, _streamPos, size); + if (_pos >= _windowSize) { - long len = stream.Length; - int size = (len < _windowSize) ? (int)len : _windowSize; - stream.Position = len - size; - _total = 0; - _limit = size; - _pos = _windowSize - size; - CopyStream(stream, size); - if (_pos == _windowSize) + _pos = 0; + } + _streamPos = _pos; + } + + public void CopyBlock(int distance, int len) + { + var size = len; + var pos = _pos - distance - 1; + if (pos < 0) + { + pos += _windowSize; + } + for (; size > 0 && _pos < _windowSize && _total < _limit; size--) + { + if (pos >= _windowSize) { - _pos = 0; + pos = 0; } - _streamPos = _pos; - } - - public void ReleaseStream() - { - Flush(); - _stream = null; - } - - public void Flush() - { - if (_stream is null) - { - return; - } - int size = _pos - _streamPos; - if (size == 0) - { - return; - } - _stream.Write(_buffer, _streamPos, size); - if (_pos >= _windowSize) - { - _pos = 0; - } - _streamPos = _pos; - } - - public void CopyBlock(int distance, int len) - { - int size = len; - int pos = _pos - distance - 1; - if (pos < 0) - { - pos += _windowSize; - } - for (; size > 0 && _pos < _windowSize && _total < _limit; size--) - { - if (pos >= _windowSize) - { - pos = 0; - } - _buffer[_pos++] = _buffer[pos++]; - _total++; - if (_pos >= _windowSize) - { - Flush(); - } - } - _pendingLen = size; - _pendingDist = distance; - } - - public void PutByte(byte b) - { - _buffer[_pos++] = b; + _buffer[_pos++] = _buffer[pos++]; _total++; if (_pos >= _windowSize) { Flush(); } } - - public byte GetByte(int distance) - { - int pos = _pos - distance - 1; - if (pos < 0) - { - pos += _windowSize; - } - return _buffer[pos]; - } - - public int CopyStream(Stream stream, int len) - { - int size = len; - while (size > 0 && _pos < _windowSize && _total < _limit) - { - int curSize = _windowSize - _pos; - if (curSize > _limit - _total) - { - curSize = (int)(_limit - _total); - } - if (curSize > size) - { - curSize = size; - } - int numReadBytes = stream.Read(_buffer, _pos, curSize); - if (numReadBytes == 0) - { - throw new DataErrorException(); - } - size -= numReadBytes; - _pos += numReadBytes; - _total += numReadBytes; - if (_pos >= _windowSize) - { - Flush(); - } - } - return len - size; - } - - public void SetLimit(long size) - { - _limit = _total + size; - } - - public bool HasSpace => _pos < _windowSize && _total < _limit; - - public bool HasPending => _pendingLen > 0; - - public int Read(byte[] buffer, int offset, int count) - { - if (_streamPos >= _pos) - { - return 0; - } - - int size = _pos - _streamPos; - if (size > count) - { - size = count; - } - Buffer.BlockCopy(_buffer, _streamPos, buffer, offset, size); - _streamPos += size; - if (_streamPos >= _windowSize) - { - _pos = 0; - _streamPos = 0; - } - return size; - } - - public void CopyPending() - { - if (_pendingLen > 0) - { - CopyBlock(_pendingDist, _pendingLen); - } - } - - public int AvailableBytes => _pos - _streamPos; + _pendingLen = size; + _pendingDist = distance; } -} \ No newline at end of file + + public void PutByte(byte b) + { + _buffer[_pos++] = b; + _total++; + if (_pos >= _windowSize) + { + Flush(); + } + } + + public byte GetByte(int distance) + { + var pos = _pos - distance - 1; + if (pos < 0) + { + pos += _windowSize; + } + return _buffer[pos]; + } + + public int CopyStream(Stream stream, int len) + { + var size = len; + while (size > 0 && _pos < _windowSize && _total < _limit) + { + var curSize = _windowSize - _pos; + if (curSize > _limit - _total) + { + curSize = (int)(_limit - _total); + } + if (curSize > size) + { + curSize = size; + } + var numReadBytes = stream.Read(_buffer, _pos, curSize); + if (numReadBytes == 0) + { + throw new DataErrorException(); + } + size -= numReadBytes; + _pos += numReadBytes; + _total += numReadBytes; + if (_pos >= _windowSize) + { + Flush(); + } + } + return len - size; + } + + public void SetLimit(long size) => _limit = _total + size; + + public bool HasSpace => _pos < _windowSize && _total < _limit; + + public bool HasPending => _pendingLen > 0; + + public int Read(byte[] buffer, int offset, int count) + { + if (_streamPos >= _pos) + { + return 0; + } + + var size = _pos - _streamPos; + if (size > count) + { + size = count; + } + Buffer.BlockCopy(_buffer, _streamPos, buffer, offset, size); + _streamPos += size; + if (_streamPos >= _windowSize) + { + _pos = 0; + _streamPos = 0; + } + return size; + } + + public void CopyPending() + { + if (_pendingLen > 0) + { + CopyBlock(_pendingDist, _pendingLen); + } + } + + public int AvailableBytes => _pos - _streamPos; +} diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs index a736bca1..4b63a621 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -4,221 +4,237 @@ using System.IO; using SharpCompress.Crypto; using SharpCompress.IO; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +// TODO: +// - Write as well as read +// - Multi-volume support +// - Use of the data size / member size values at the end of the stream + +/// +/// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html +/// +public sealed class LZipStream : Stream { - // TODO: - // - Write as well as read - // - Multi-volume support - // - Use of the data size / member size values at the end of the stream + private readonly Stream _stream; + private readonly CountingWritableSubStream? _countingWritableSubStream; + private bool _disposed; + private bool _finished; - /// - /// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html - /// - public sealed class LZipStream : Stream + private long _writeCount; + + public LZipStream(Stream stream, CompressionMode mode) { - private readonly Stream _stream; - private readonly CountingWritableSubStream? _countingWritableSubStream; - private bool _disposed; - private bool _finished; + Mode = mode; - private long _writeCount; - - public LZipStream(Stream stream, CompressionMode mode) + if (mode == CompressionMode.Decompress) { - Mode = mode; - - if (mode == CompressionMode.Decompress) + var dSize = ValidateAndReadSize(stream); + if (dSize == 0) { - int dSize = ValidateAndReadSize(stream); - if (dSize == 0) - { - throw new IOException("Not an LZip stream"); - } - byte[] properties = GetProperties(dSize); - _stream = new LzmaStream(properties, stream); - } - else - { - //default - int dSize = 104 * 1024; - WriteHeaderSize(stream); - - _countingWritableSubStream = new CountingWritableSubStream(stream); - _stream = new Crc32Stream(new LzmaStream(new LzmaEncoderProperties(true, dSize), false, _countingWritableSubStream)); + throw new IOException("Not an LZip stream"); } + var properties = GetProperties(dSize); + _stream = new LzmaStream(properties, stream); } - - public void Finish() + else { - if (!_finished) - { - if (Mode == CompressionMode.Compress) - { - var crc32Stream = (Crc32Stream)_stream; - crc32Stream.WrappedStream.Dispose(); - crc32Stream.Dispose(); - var compressedCount = _countingWritableSubStream!.Count; + //default + var dSize = 104 * 1024; + WriteHeaderSize(stream); - Span intBuf = stackalloc byte[8]; - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc); - _countingWritableSubStream.Write(intBuf.Slice(0, 4)); - - BinaryPrimitives.WriteInt64LittleEndian(intBuf, _writeCount); - _countingWritableSubStream.Write(intBuf); - - //total with headers - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20); - _countingWritableSubStream.Write(intBuf); - } - _finished = true; - } + _countingWritableSubStream = new CountingWritableSubStream(stream); + _stream = new Crc32Stream( + new LzmaStream( + new LzmaEncoderProperties(true, dSize), + false, + _countingWritableSubStream + ) + ); } + } - #region Stream methods - - protected override void Dispose(bool disposing) + public void Finish() + { + if (!_finished) { - if (_disposed) + if (Mode == CompressionMode.Compress) { - return; - } - _disposed = true; - if (disposing) - { - Finish(); - _stream.Dispose(); + var crc32Stream = (Crc32Stream)_stream; + crc32Stream.WrappedStream.Dispose(); + crc32Stream.Dispose(); + var compressedCount = _countingWritableSubStream!.Count; + + Span intBuf = stackalloc byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc); + _countingWritableSubStream.Write(intBuf.Slice(0, 4)); + + BinaryPrimitives.WriteInt64LittleEndian(intBuf, _writeCount); + _countingWritableSubStream.Write(intBuf); + + //total with headers + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20); + _countingWritableSubStream.Write(intBuf); } + _finished = true; } + } - public CompressionMode Mode { get; } + #region Stream methods - public override bool CanRead => Mode == CompressionMode.Decompress; - - public override bool CanSeek => false; - - public override bool CanWrite => Mode == CompressionMode.Compress; - - public override void Flush() + protected override void Dispose(bool disposing) + { + if (_disposed) { - _stream.Flush(); + return; } + _disposed = true; + if (disposing) + { + Finish(); + _stream.Dispose(); + } + } - // TODO: Both Length and Position are sometimes feasible, but would require - // reading the output length when we initialize. - public override long Length => throw new NotImplementedException(); + public CompressionMode Mode { get; } - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override bool CanRead => Mode == CompressionMode.Decompress; - public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); + public override bool CanSeek => false; - public override int ReadByte() => _stream.ReadByte(); + public override bool CanWrite => Mode == CompressionMode.Compress; - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void Flush() => _stream.Flush(); - public override void SetLength(long value) => throw new NotImplementedException(); + // TODO: Both Length and Position are sometimes feasible, but would require + // reading the output length when we initialize. + public override long Length => throw new NotImplementedException(); + public override long Position + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) => + _stream.Read(buffer, offset, count); + + public override int ReadByte() => _stream.ReadByte(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotImplementedException(); #if !NETFRAMEWORK && !NETSTANDARD2_0 - public override int Read(Span buffer) - { - return _stream.Read(buffer); - } + public override int Read(Span buffer) => _stream.Read(buffer); - public override void Write(ReadOnlySpan buffer) - { - _stream.Write(buffer); - - _writeCount += buffer.Length; - } + public override void Write(ReadOnlySpan buffer) + { + _stream.Write(buffer); + _writeCount += buffer.Length; + } #endif - public override void Write(byte[] buffer, int offset, int count) - { - _stream.Write(buffer, offset, count); - _writeCount += count; - } - - public override void WriteByte(byte value) - { - _stream.WriteByte(value); - ++_writeCount; - } - - #endregion - - /// - /// Determines if the given stream is positioned at the start of a v1 LZip - /// file, as indicated by the ASCII characters "LZIP" and a version byte - /// of 1, followed by at least one byte. - /// - /// The stream to read from. Must not be null. - /// true if the given stream is an LZip file, false otherwise. - public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0; - - /// - /// Reads the 6-byte header of the stream, and returns 0 if either the header - /// couldn't be read or it isn't a validate LZIP header, or the dictionary - /// size if it *is* a valid LZIP file. - /// - public static int ValidateAndReadSize(Stream stream) - { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - // Read the header - Span header = stackalloc byte[6]; - int n = stream.Read(header); - - // TODO: Handle reading only part of the header? - - if (n != 6) - { - return 0; - } - - if (header[0] != 'L' || header[1] != 'Z' || header[2] != 'I' || header[3] != 'P' || header[4] != 1 /* version 1 */) - { - return 0; - } - int basePower = header[5] & 0x1F; - int subtractionNumerator = (header[5] & 0xE0) >> 5; - return (1 << basePower) - subtractionNumerator * (1 << (basePower - 4)); - } - - private static readonly byte[] headerBytes = new byte[6] { (byte)'L', (byte)'Z', (byte)'I', (byte)'P', 1, 113 }; - - public static void WriteHeaderSize(Stream stream) - { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - // hard coding the dictionary size encoding - stream.Write(headerBytes, 0, 6); - } - - /// - /// Creates a byte array to communicate the parameters and dictionary size to LzmaStream. - /// - private static byte[] GetProperties(int dictionarySize) => - new byte[] - { - // Parameters as per http://www.nongnu.org/lzip/manual/lzip_manual.html#Stream-format - // but encoded as a single byte in the format LzmaStream expects. - // literal_context_bits = 3 - // literal_pos_state_bits = 0 - // pos_state_bits = 2 - 93, - // Dictionary size as 4-byte little-endian value - (byte)(dictionarySize & 0xff), - (byte)((dictionarySize >> 8) & 0xff), - (byte)((dictionarySize >> 16) & 0xff), - (byte)((dictionarySize >> 24) & 0xff) - }; + public override void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + _writeCount += count; } + + public override void WriteByte(byte value) + { + _stream.WriteByte(value); + ++_writeCount; + } + + #endregion + + /// + /// Determines if the given stream is positioned at the start of a v1 LZip + /// file, as indicated by the ASCII characters "LZIP" and a version byte + /// of 1, followed by at least one byte. + /// + /// The stream to read from. Must not be null. + /// true if the given stream is an LZip file, false otherwise. + public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0; + + /// + /// Reads the 6-byte header of the stream, and returns 0 if either the header + /// couldn't be read or it isn't a validate LZIP header, or the dictionary + /// size if it *is* a valid LZIP file. + /// + public static int ValidateAndReadSize(Stream stream) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + // Read the header + Span header = stackalloc byte[6]; + var n = stream.Read(header); + + // TODO: Handle reading only part of the header? + + if (n != 6) + { + return 0; + } + + if ( + header[0] != 'L' + || header[1] != 'Z' + || header[2] != 'I' + || header[3] != 'P' + || header[4] != 1 /* version 1 */ + ) + { + return 0; + } + var basePower = header[5] & 0x1F; + var subtractionNumerator = (header[5] & 0xE0) >> 5; + return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4))); + } + + private static readonly byte[] headerBytes = new byte[6] + { + (byte)'L', + (byte)'Z', + (byte)'I', + (byte)'P', + 1, + 113 + }; + + public static void WriteHeaderSize(Stream stream) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + // hard coding the dictionary size encoding + stream.Write(headerBytes, 0, 6); + } + + /// + /// Creates a byte array to communicate the parameters and dictionary size to LzmaStream. + /// + private static byte[] GetProperties(int dictionarySize) => + new byte[] + { + // Parameters as per http://www.nongnu.org/lzip/manual/lzip_manual.html#Stream-format + // but encoded as a single byte in the format LzmaStream expects. + // literal_context_bits = 3 + // literal_pos_state_bits = 0 + // pos_state_bits = 2 + 93, + // Dictionary size as 4-byte little-endian value + (byte)(dictionarySize & 0xff), + (byte)((dictionarySize >> 8) & 0xff), + (byte)((dictionarySize >> 16) & 0xff), + (byte)((dictionarySize >> 24) & 0xff) + }; } diff --git a/src/SharpCompress/Compressors/LZMA/Log.cs b/src/SharpCompress/Compressors/LZMA/Log.cs index f4cbed5e..e954e259 100644 --- a/src/SharpCompress/Compressors/LZMA/Log.cs +++ b/src/SharpCompress/Compressors/LZMA/Log.cs @@ -2,85 +2,78 @@ using System.Collections.Generic; using System.Diagnostics; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal static class Log { - internal static class Log + private static readonly Stack INDENT = new Stack(); + private static bool NEEDS_INDENT = true; + + static Log() => INDENT.Push(""); + + public static void PushIndent(string indent = " ") => INDENT.Push(INDENT.Peek() + indent); + + public static void PopIndent() { - private static readonly Stack INDENT = new Stack(); - private static bool NEEDS_INDENT = true; - - static Log() + if (INDENT.Count == 1) { - INDENT.Push(""); + throw new InvalidOperationException(); } - public static void PushIndent(string indent = " ") - { - INDENT.Push(INDENT.Peek() + indent); - } + INDENT.Pop(); + } - public static void PopIndent() + private static void EnsureIndent() + { + if (NEEDS_INDENT) { - if (INDENT.Count == 1) - { - throw new InvalidOperationException(); - } - - INDENT.Pop(); - } - - private static void EnsureIndent() - { - if (NEEDS_INDENT) - { - NEEDS_INDENT = false; - Debug.Write(INDENT.Peek()); - } - } - - public static void Write(object value) - { - EnsureIndent(); - Debug.Write(value); - } - - public static void Write(string text) - { - EnsureIndent(); - Debug.Write(text); - } - - public static void Write(string format, params object[] args) - { - EnsureIndent(); - Debug.Write(string.Format(format, args)); - } - - public static void WriteLine() - { - Debug.WriteLine(""); - NEEDS_INDENT = true; - } - - public static void WriteLine(object value) - { - EnsureIndent(); - Debug.WriteLine(value); - NEEDS_INDENT = true; - } - - public static void WriteLine(string text) - { - EnsureIndent(); - Debug.WriteLine(text); - NEEDS_INDENT = true; - } - - public static void WriteLine(string format, params object[] args) - { - EnsureIndent(); - Debug.WriteLine(string.Format(format, args)); - NEEDS_INDENT = true; + NEEDS_INDENT = false; + Debug.Write(INDENT.Peek()); } } -} \ No newline at end of file + + public static void Write(object value) + { + EnsureIndent(); + Debug.Write(value); + } + + public static void Write(string text) + { + EnsureIndent(); + Debug.Write(text); + } + + public static void Write(string format, params object[] args) + { + EnsureIndent(); + Debug.Write(string.Format(format, args)); + } + + public static void WriteLine() + { + Debug.WriteLine(""); + NEEDS_INDENT = true; + } + + public static void WriteLine(object value) + { + EnsureIndent(); + Debug.WriteLine(value); + NEEDS_INDENT = true; + } + + public static void WriteLine(string text) + { + EnsureIndent(); + Debug.WriteLine(text); + NEEDS_INDENT = true; + } + + public static void WriteLine(string format, params object[] args) + { + EnsureIndent(); + Debug.WriteLine(string.Format(format, args)); + NEEDS_INDENT = true; + } +} diff --git a/src/SharpCompress/Compressors/LZMA/LzmaBase.cs b/src/SharpCompress/Compressors/LZMA/LzmaBase.cs index 6e2b8a5d..d8bfa062 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaBase.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaBase.cs @@ -1,109 +1,93 @@ -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal abstract class Base { - internal abstract class Base + public const uint K_NUM_REP_DISTANCES = 4; + public const uint K_NUM_STATES = 12; + + // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; + // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; + // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; + // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + + public struct State { - public const uint K_NUM_REP_DISTANCES = 4; - public const uint K_NUM_STATES = 12; + public uint _index; - // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; - // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; - // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; - // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + public void Init() => _index = 0; - public struct State + public void UpdateChar() { - public uint _index; - - public void Init() + if (_index < 4) { _index = 0; } - - public void UpdateChar() + else if (_index < 10) { - if (_index < 4) - { - _index = 0; - } - else if (_index < 10) - { - _index -= 3; - } - else - { - _index -= 6; - } + _index -= 3; } - - public void UpdateMatch() + else { - _index = (uint)(_index < 7 ? 7 : 10); - } - - public void UpdateRep() - { - _index = (uint)(_index < 7 ? 8 : 11); - } - - public void UpdateShortRep() - { - _index = (uint)(_index < 7 ? 9 : 11); - } - - public bool IsCharState() - { - return _index < 7; + _index -= 6; } } - public const int K_NUM_POS_SLOT_BITS = 6; - public const int K_DIC_LOG_SIZE_MIN = 0; + public void UpdateMatch() => _index = (uint)(_index < 7 ? 7 : 10); - // public const int kDicLogSizeMax = 30; - // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; + public void UpdateRep() => _index = (uint)(_index < 7 ? 8 : 11); - public const int K_NUM_LEN_TO_POS_STATES_BITS = 2; // it's for speed optimization - public const uint K_NUM_LEN_TO_POS_STATES = 1 << K_NUM_LEN_TO_POS_STATES_BITS; + public void UpdateShortRep() => _index = (uint)(_index < 7 ? 9 : 11); - public const uint K_MATCH_MIN_LEN = 2; - - public static uint GetLenToPosState(uint len) - { - len -= K_MATCH_MIN_LEN; - if (len < K_NUM_LEN_TO_POS_STATES) - { - return len; - } - return K_NUM_LEN_TO_POS_STATES - 1; - } - - public const int K_NUM_ALIGN_BITS = 4; - public const uint K_ALIGN_TABLE_SIZE = 1 << K_NUM_ALIGN_BITS; - public const uint K_ALIGN_MASK = (K_ALIGN_TABLE_SIZE - 1); - - public const uint K_START_POS_MODEL_INDEX = 4; - public const uint K_END_POS_MODEL_INDEX = 14; - public const uint K_NUM_POS_MODELS = K_END_POS_MODEL_INDEX - K_START_POS_MODEL_INDEX; - - public const uint K_NUM_FULL_DISTANCES = 1 << ((int)K_END_POS_MODEL_INDEX / 2); - - public const uint K_NUM_LIT_POS_STATES_BITS_ENCODING_MAX = 4; - public const uint K_NUM_LIT_CONTEXT_BITS_MAX = 8; - - public const int K_NUM_POS_STATES_BITS_MAX = 4; - public const uint K_NUM_POS_STATES_MAX = (1 << K_NUM_POS_STATES_BITS_MAX); - public const int K_NUM_POS_STATES_BITS_ENCODING_MAX = 4; - public const uint K_NUM_POS_STATES_ENCODING_MAX = (1 << K_NUM_POS_STATES_BITS_ENCODING_MAX); - - public const int K_NUM_LOW_LEN_BITS = 3; - public const int K_NUM_MID_LEN_BITS = 3; - public const int K_NUM_HIGH_LEN_BITS = 8; - public const uint K_NUM_LOW_LEN_SYMBOLS = 1 << K_NUM_LOW_LEN_BITS; - public const uint K_NUM_MID_LEN_SYMBOLS = 1 << K_NUM_MID_LEN_BITS; - - public const uint K_NUM_LEN_SYMBOLS = K_NUM_LOW_LEN_SYMBOLS + K_NUM_MID_LEN_SYMBOLS + - (1 << K_NUM_HIGH_LEN_BITS); - - public const uint K_MATCH_MAX_LEN = K_MATCH_MIN_LEN + K_NUM_LEN_SYMBOLS - 1; + public bool IsCharState() => _index < 7; } -} \ No newline at end of file + + public const int K_NUM_POS_SLOT_BITS = 6; + public const int K_DIC_LOG_SIZE_MIN = 0; + + // public const int kDicLogSizeMax = 30; + // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; + + public const int K_NUM_LEN_TO_POS_STATES_BITS = 2; // it's for speed optimization + public const uint K_NUM_LEN_TO_POS_STATES = 1 << K_NUM_LEN_TO_POS_STATES_BITS; + + public const uint K_MATCH_MIN_LEN = 2; + + public static uint GetLenToPosState(uint len) + { + len -= K_MATCH_MIN_LEN; + if (len < K_NUM_LEN_TO_POS_STATES) + { + return len; + } + return K_NUM_LEN_TO_POS_STATES - 1; + } + + public const int K_NUM_ALIGN_BITS = 4; + public const uint K_ALIGN_TABLE_SIZE = 1 << K_NUM_ALIGN_BITS; + public const uint K_ALIGN_MASK = (K_ALIGN_TABLE_SIZE - 1); + + public const uint K_START_POS_MODEL_INDEX = 4; + public const uint K_END_POS_MODEL_INDEX = 14; + public const uint K_NUM_POS_MODELS = K_END_POS_MODEL_INDEX - K_START_POS_MODEL_INDEX; + + public const uint K_NUM_FULL_DISTANCES = 1 << ((int)K_END_POS_MODEL_INDEX / 2); + + public const uint K_NUM_LIT_POS_STATES_BITS_ENCODING_MAX = 4; + public const uint K_NUM_LIT_CONTEXT_BITS_MAX = 8; + + public const int K_NUM_POS_STATES_BITS_MAX = 4; + public const uint K_NUM_POS_STATES_MAX = (1 << K_NUM_POS_STATES_BITS_MAX); + public const int K_NUM_POS_STATES_BITS_ENCODING_MAX = 4; + public const uint K_NUM_POS_STATES_ENCODING_MAX = (1 << K_NUM_POS_STATES_BITS_ENCODING_MAX); + + public const int K_NUM_LOW_LEN_BITS = 3; + public const int K_NUM_MID_LEN_BITS = 3; + public const int K_NUM_HIGH_LEN_BITS = 8; + public const uint K_NUM_LOW_LEN_SYMBOLS = 1 << K_NUM_LOW_LEN_BITS; + public const uint K_NUM_MID_LEN_SYMBOLS = 1 << K_NUM_MID_LEN_BITS; + + public const uint K_NUM_LEN_SYMBOLS = + K_NUM_LOW_LEN_SYMBOLS + K_NUM_MID_LEN_SYMBOLS + (1 << K_NUM_HIGH_LEN_BITS); + + public const uint K_MATCH_MAX_LEN = K_MATCH_MIN_LEN + K_NUM_LEN_SYMBOLS - 1; +} diff --git a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs index f54c2bf7..31039761 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs @@ -5,471 +5,497 @@ using System.IO; using SharpCompress.Compressors.LZMA.LZ; using SharpCompress.Compressors.LZMA.RangeCoder; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream { - public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream + private class LenDecoder { - private class LenDecoder + private BitDecoder _choice = new BitDecoder(); + private BitDecoder _choice2 = new BitDecoder(); + private readonly BitTreeDecoder[] _lowCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX]; + private readonly BitTreeDecoder[] _midCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX]; + private BitTreeDecoder _highCoder = new BitTreeDecoder(Base.K_NUM_HIGH_LEN_BITS); + private uint _numPosStates; + + public void Create(uint numPosStates) { - private BitDecoder _choice = new BitDecoder(); - private BitDecoder _choice2 = new BitDecoder(); - private readonly BitTreeDecoder[] _lowCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX]; - private readonly BitTreeDecoder[] _midCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX]; - private BitTreeDecoder _highCoder = new BitTreeDecoder(Base.K_NUM_HIGH_LEN_BITS); - private uint _numPosStates; - - public void Create(uint numPosStates) + for (var posState = _numPosStates; posState < numPosStates; posState++) { - for (uint posState = _numPosStates; posState < numPosStates; posState++) - { - _lowCoder[posState] = new BitTreeDecoder(Base.K_NUM_LOW_LEN_BITS); - _midCoder[posState] = new BitTreeDecoder(Base.K_NUM_MID_LEN_BITS); - } - _numPosStates = numPosStates; - } - - public void Init() - { - _choice.Init(); - for (uint posState = 0; posState < _numPosStates; posState++) - { - _lowCoder[posState].Init(); - _midCoder[posState].Init(); - } - _choice2.Init(); - _highCoder.Init(); - } - - public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) - { - if (_choice.Decode(rangeDecoder) == 0) - { - return _lowCoder[posState].Decode(rangeDecoder); - } - uint symbol = Base.K_NUM_LOW_LEN_SYMBOLS; - if (_choice2.Decode(rangeDecoder) == 0) - { - symbol += _midCoder[posState].Decode(rangeDecoder); - } - else - { - symbol += Base.K_NUM_MID_LEN_SYMBOLS; - symbol += _highCoder.Decode(rangeDecoder); - } - return symbol; + _lowCoder[posState] = new BitTreeDecoder(Base.K_NUM_LOW_LEN_BITS); + _midCoder[posState] = new BitTreeDecoder(Base.K_NUM_MID_LEN_BITS); } + _numPosStates = numPosStates; } - private class LiteralDecoder + public void Init() { - private struct Decoder2 + _choice.Init(); + for (uint posState = 0; posState < _numPosStates; posState++) { - private BitDecoder[] _decoders; - - public void Create() - { - _decoders = new BitDecoder[0x300]; - } - - public void Init() - { - for (int i = 0; i < 0x300; i++) - { - _decoders[i].Init(); - } - } - - public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) - { - uint symbol = 1; - do - { - symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder); - } - while (symbol < 0x100); - return (byte)symbol; - } - - public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte) - { - uint symbol = 1; - do - { - uint matchBit = (uint)(matchByte >> 7) & 1; - matchByte <<= 1; - uint bit = _decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); - symbol = (symbol << 1) | bit; - if (matchBit != bit) - { - while (symbol < 0x100) - { - symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder); - } - break; - } - } - while (symbol < 0x100); - return (byte)symbol; - } - } - - private Decoder2[] _coders; - private int _numPrevBits; - private int _numPosBits; - private uint _posMask; - - public void Create(int numPosBits, int numPrevBits) - { - if (_coders != null && _numPrevBits == numPrevBits && - _numPosBits == numPosBits) - { - return; - } - _numPosBits = numPosBits; - _posMask = ((uint)1 << numPosBits) - 1; - _numPrevBits = numPrevBits; - uint numStates = (uint)1 << (_numPrevBits + _numPosBits); - _coders = new Decoder2[numStates]; - for (uint i = 0; i < numStates; i++) - { - _coders[i].Create(); - } - } - - public void Init() - { - uint numStates = (uint)1 << (_numPrevBits + _numPosBits); - for (uint i = 0; i < numStates; i++) - { - _coders[i].Init(); - } - } - - private uint GetState(uint pos, byte prevByte) - { - return ((pos & _posMask) << _numPrevBits) + (uint)(prevByte >> (8 - _numPrevBits)); - } - - public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) - { - return _coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); - } - - public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) - { - return _coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); + _lowCoder[posState].Init(); + _midCoder[posState].Init(); } + _choice2.Init(); + _highCoder.Init(); } - private OutWindow _outWindow; - - private readonly BitDecoder[] _isMatchDecoders = new BitDecoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX]; - private readonly BitDecoder[] _isRepDecoders = new BitDecoder[Base.K_NUM_STATES]; - private readonly BitDecoder[] _isRepG0Decoders = new BitDecoder[Base.K_NUM_STATES]; - private readonly BitDecoder[] _isRepG1Decoders = new BitDecoder[Base.K_NUM_STATES]; - private readonly BitDecoder[] _isRepG2Decoders = new BitDecoder[Base.K_NUM_STATES]; - private readonly BitDecoder[] _isRep0LongDecoders = new BitDecoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX]; - - private readonly BitTreeDecoder[] _posSlotDecoder = new BitTreeDecoder[Base.K_NUM_LEN_TO_POS_STATES]; - private readonly BitDecoder[] _posDecoders = new BitDecoder[Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX]; - - private BitTreeDecoder _posAlignDecoder = new BitTreeDecoder(Base.K_NUM_ALIGN_BITS); - - private readonly LenDecoder _lenDecoder = new LenDecoder(); - private readonly LenDecoder _repLenDecoder = new LenDecoder(); - - private readonly LiteralDecoder _literalDecoder = new LiteralDecoder(); - - private int _dictionarySize; - - private uint _posStateMask; - - private Base.State _state = new Base.State(); - private uint _rep0, _rep1, _rep2, _rep3; - - public Decoder() + public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) { - _dictionarySize = -1; - for (int i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) + if (_choice.Decode(rangeDecoder) == 0) { - _posSlotDecoder[i] = new BitTreeDecoder(Base.K_NUM_POS_SLOT_BITS); + return _lowCoder[posState].Decode(rangeDecoder); } - } - - private void CreateDictionary() - { - if (_dictionarySize < 0) + var symbol = Base.K_NUM_LOW_LEN_SYMBOLS; + if (_choice2.Decode(rangeDecoder) == 0) { - throw new InvalidParamException(); - } - _outWindow = new OutWindow(); - int blockSize = Math.Max(_dictionarySize, (1 << 12)); - _outWindow.Create(blockSize); - } - - private void SetLiteralProperties(int lp, int lc) - { - if (lp > 8) - { - throw new InvalidParamException(); - } - if (lc > 8) - { - throw new InvalidParamException(); - } - _literalDecoder.Create(lp, lc); - } - - private void SetPosBitsProperties(int pb) - { - if (pb > Base.K_NUM_POS_STATES_BITS_MAX) - { - throw new InvalidParamException(); - } - uint numPosStates = (uint)1 << pb; - _lenDecoder.Create(numPosStates); - _repLenDecoder.Create(numPosStates); - _posStateMask = numPosStates - 1; - } - - private void Init() - { - uint i; - for (i = 0; i < Base.K_NUM_STATES; i++) - { - for (uint j = 0; j <= _posStateMask; j++) - { - uint index = (i << Base.K_NUM_POS_STATES_BITS_MAX) + j; - _isMatchDecoders[index].Init(); - _isRep0LongDecoders[index].Init(); - } - _isRepDecoders[i].Init(); - _isRepG0Decoders[i].Init(); - _isRepG1Decoders[i].Init(); - _isRepG2Decoders[i].Init(); - } - - _literalDecoder.Init(); - for (i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) - { - _posSlotDecoder[i].Init(); - } - - // _PosSpecDecoder.Init(); - for (i = 0; i < Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX; i++) - { - _posDecoders[i].Init(); - } - - _lenDecoder.Init(); - _repLenDecoder.Init(); - _posAlignDecoder.Init(); - - _state.Init(); - _rep0 = 0; - _rep1 = 0; - _rep2 = 0; - _rep3 = 0; - } - - public void Code(Stream inStream, Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress) - { - if (_outWindow is null) - { - CreateDictionary(); - } - _outWindow.Init(outStream); - if (outSize > 0) - { - _outWindow.SetLimit(outSize); + symbol += _midCoder[posState].Decode(rangeDecoder); } else { - _outWindow.SetLimit(Int64.MaxValue - _outWindow._total); + symbol += Base.K_NUM_MID_LEN_SYMBOLS; + symbol += _highCoder.Decode(rangeDecoder); + } + return symbol; + } + } + + private class LiteralDecoder + { + private struct Decoder2 + { + private BitDecoder[] _decoders; + + public void Create() => _decoders = new BitDecoder[0x300]; + + public void Init() + { + for (var i = 0; i < 0x300; i++) + { + _decoders[i].Init(); + } } - RangeCoder.Decoder rangeDecoder = new RangeCoder.Decoder(); - rangeDecoder.Init(inStream); + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) + { + uint symbol = 1; + do + { + symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder); + } while (symbol < 0x100); + return (byte)symbol; + } - Code(_dictionarySize, _outWindow, rangeDecoder); - - _outWindow.ReleaseStream(); - rangeDecoder.ReleaseStream(); - - _outWindow = null; + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte) + { + uint symbol = 1; + do + { + var matchBit = (uint)(matchByte >> 7) & 1; + matchByte <<= 1; + var bit = _decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + { + symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder); + } + break; + } + } while (symbol < 0x100); + return (byte)symbol; + } } - internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder) + private Decoder2[] _coders; + private int _numPrevBits; + private int _numPosBits; + private uint _posMask; + + public void Create(int numPosBits, int numPrevBits) { - int dictionarySizeCheck = Math.Max(dictionarySize, 1); - - outWindow.CopyPending(); - - while (outWindow.HasSpace) + if (_coders != null && _numPrevBits == numPrevBits && _numPosBits == numPosBits) { - uint posState = (uint)outWindow._total & _posStateMask; - if (_isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Decode(rangeDecoder) == 0) + return; + } + _numPosBits = numPosBits; + _posMask = ((uint)1 << numPosBits) - 1; + _numPrevBits = numPrevBits; + var numStates = (uint)1 << (_numPrevBits + _numPosBits); + _coders = new Decoder2[numStates]; + for (uint i = 0; i < numStates; i++) + { + _coders[i].Create(); + } + } + + public void Init() + { + var numStates = (uint)1 << (_numPrevBits + _numPosBits); + for (uint i = 0; i < numStates; i++) + { + _coders[i].Init(); + } + } + + private uint GetState(uint pos, byte prevByte) => + ((pos & _posMask) << _numPrevBits) + (uint)(prevByte >> (8 - _numPrevBits)); + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) => + _coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); + + public byte DecodeWithMatchByte( + RangeCoder.Decoder rangeDecoder, + uint pos, + byte prevByte, + byte matchByte + ) => _coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); + } + + private OutWindow _outWindow; + + private readonly BitDecoder[] _isMatchDecoders = new BitDecoder[ + Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX + ]; + private readonly BitDecoder[] _isRepDecoders = new BitDecoder[Base.K_NUM_STATES]; + private readonly BitDecoder[] _isRepG0Decoders = new BitDecoder[Base.K_NUM_STATES]; + private readonly BitDecoder[] _isRepG1Decoders = new BitDecoder[Base.K_NUM_STATES]; + private readonly BitDecoder[] _isRepG2Decoders = new BitDecoder[Base.K_NUM_STATES]; + private readonly BitDecoder[] _isRep0LongDecoders = new BitDecoder[ + Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX + ]; + + private readonly BitTreeDecoder[] _posSlotDecoder = new BitTreeDecoder[ + Base.K_NUM_LEN_TO_POS_STATES + ]; + private readonly BitDecoder[] _posDecoders = new BitDecoder[ + Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX + ]; + + private BitTreeDecoder _posAlignDecoder = new BitTreeDecoder(Base.K_NUM_ALIGN_BITS); + + private readonly LenDecoder _lenDecoder = new LenDecoder(); + private readonly LenDecoder _repLenDecoder = new LenDecoder(); + + private readonly LiteralDecoder _literalDecoder = new LiteralDecoder(); + + private int _dictionarySize; + + private uint _posStateMask; + + private Base.State _state = new Base.State(); + private uint _rep0, + _rep1, + _rep2, + _rep3; + + public Decoder() + { + _dictionarySize = -1; + for (var i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) + { + _posSlotDecoder[i] = new BitTreeDecoder(Base.K_NUM_POS_SLOT_BITS); + } + } + + private void CreateDictionary() + { + if (_dictionarySize < 0) + { + throw new InvalidParamException(); + } + _outWindow = new OutWindow(); + var blockSize = Math.Max(_dictionarySize, (1 << 12)); + _outWindow.Create(blockSize); + } + + private void SetLiteralProperties(int lp, int lc) + { + if (lp > 8) + { + throw new InvalidParamException(); + } + if (lc > 8) + { + throw new InvalidParamException(); + } + _literalDecoder.Create(lp, lc); + } + + private void SetPosBitsProperties(int pb) + { + if (pb > Base.K_NUM_POS_STATES_BITS_MAX) + { + throw new InvalidParamException(); + } + var numPosStates = (uint)1 << pb; + _lenDecoder.Create(numPosStates); + _repLenDecoder.Create(numPosStates); + _posStateMask = numPosStates - 1; + } + + private void Init() + { + uint i; + for (i = 0; i < Base.K_NUM_STATES; i++) + { + for (uint j = 0; j <= _posStateMask; j++) + { + var index = (i << Base.K_NUM_POS_STATES_BITS_MAX) + j; + _isMatchDecoders[index].Init(); + _isRep0LongDecoders[index].Init(); + } + _isRepDecoders[i].Init(); + _isRepG0Decoders[i].Init(); + _isRepG1Decoders[i].Init(); + _isRepG2Decoders[i].Init(); + } + + _literalDecoder.Init(); + for (i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) + { + _posSlotDecoder[i].Init(); + } + + // _PosSpecDecoder.Init(); + for (i = 0; i < Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX; i++) + { + _posDecoders[i].Init(); + } + + _lenDecoder.Init(); + _repLenDecoder.Init(); + _posAlignDecoder.Init(); + + _state.Init(); + _rep0 = 0; + _rep1 = 0; + _rep2 = 0; + _rep3 = 0; + } + + public void Code( + Stream inStream, + Stream outStream, + long inSize, + long outSize, + ICodeProgress progress + ) + { + if (_outWindow is null) + { + CreateDictionary(); + } + _outWindow.Init(outStream); + if (outSize > 0) + { + _outWindow.SetLimit(outSize); + } + else + { + _outWindow.SetLimit(long.MaxValue - _outWindow._total); + } + + var rangeDecoder = new RangeCoder.Decoder(); + rangeDecoder.Init(inStream); + + Code(_dictionarySize, _outWindow, rangeDecoder); + + _outWindow.ReleaseStream(); + rangeDecoder.ReleaseStream(); + + _outWindow = null; + } + + internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder) + { + var dictionarySizeCheck = Math.Max(dictionarySize, 1); + + outWindow.CopyPending(); + + while (outWindow.HasSpace) + { + var posState = (uint)outWindow._total & _posStateMask; + if ( + _isMatchDecoders[ + (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState + ].Decode(rangeDecoder) == 0 + ) + { + byte b; + var prevByte = outWindow.GetByte(0); + if (!_state.IsCharState()) { - byte b; - byte prevByte = outWindow.GetByte(0); - if (!_state.IsCharState()) - { - b = _literalDecoder.DecodeWithMatchByte(rangeDecoder, - (uint)outWindow._total, prevByte, - outWindow.GetByte((int)_rep0)); - } - else - { - b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow._total, prevByte); - } - outWindow.PutByte(b); - _state.UpdateChar(); + b = _literalDecoder.DecodeWithMatchByte( + rangeDecoder, + (uint)outWindow._total, + prevByte, + outWindow.GetByte((int)_rep0) + ); } else { - uint len; - if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1) + b = _literalDecoder.DecodeNormal( + rangeDecoder, + (uint)outWindow._total, + prevByte + ); + } + outWindow.PutByte(b); + _state.UpdateChar(); + } + else + { + uint len; + if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1) + { + if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0) { - if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0) + if ( + _isRep0LongDecoders[ + (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState + ].Decode(rangeDecoder) == 0 + ) { - if ( - _isRep0LongDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Decode( - rangeDecoder) == 0) - { - _state.UpdateShortRep(); - outWindow.PutByte(outWindow.GetByte((int)_rep0)); - continue; - } + _state.UpdateShortRep(); + outWindow.PutByte(outWindow.GetByte((int)_rep0)); + continue; } - else - { - UInt32 distance; - if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0) - { - distance = _rep1; - } - else - { - if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0) - { - distance = _rep2; - } - else - { - distance = _rep3; - _rep3 = _rep2; - } - _rep2 = _rep1; - } - _rep1 = _rep0; - _rep0 = distance; - } - len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN; - _state.UpdateRep(); } else { - _rep3 = _rep2; - _rep2 = _rep1; - _rep1 = _rep0; - len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState); - _state.UpdateMatch(); - uint posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder); - if (posSlot >= Base.K_START_POS_MODEL_INDEX) + uint distance; + if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0) { - int numDirectBits = (int)((posSlot >> 1) - 1); - _rep0 = ((2 | (posSlot & 1)) << numDirectBits); - if (posSlot < Base.K_END_POS_MODEL_INDEX) - { - _rep0 += BitTreeDecoder.ReverseDecode(_posDecoders, - _rep0 - posSlot - 1, rangeDecoder, numDirectBits); - } - else - { - _rep0 += (rangeDecoder.DecodeDirectBits( - numDirectBits - Base.K_NUM_ALIGN_BITS) << Base.K_NUM_ALIGN_BITS); - _rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder); - } + distance = _rep1; } else { - _rep0 = posSlot; + if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0) + { + distance = _rep2; + } + else + { + distance = _rep3; + _rep3 = _rep2; + } + _rep2 = _rep1; } + _rep1 = _rep0; + _rep0 = distance; } - if (_rep0 >= outWindow._total || _rep0 >= dictionarySizeCheck) - { - if (_rep0 == 0xFFFFFFFF) - { - return true; - } - throw new DataErrorException(); - } - outWindow.CopyBlock((int)_rep0, (int)len); + len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN; + _state.UpdateRep(); } - } - return false; - } - - public void SetDecoderProperties(byte[] properties) - { - if (properties.Length < 1) - { - throw new InvalidParamException(); - } - int lc = properties[0] % 9; - int remainder = properties[0] / 9; - int lp = remainder % 5; - int pb = remainder / 5; - if (pb > Base.K_NUM_POS_STATES_BITS_MAX) - { - throw new InvalidParamException(); - } - SetLiteralProperties(lp, lc); - SetPosBitsProperties(pb); - Init(); - if (properties.Length >= 5) - { - _dictionarySize = 0; - for (int i = 0; i < 4; i++) + else { - _dictionarySize += properties[1 + i] << (i * 8); + _rep3 = _rep2; + _rep2 = _rep1; + _rep1 = _rep0; + len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState); + _state.UpdateMatch(); + var posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder); + if (posSlot >= Base.K_START_POS_MODEL_INDEX) + { + var numDirectBits = (int)((posSlot >> 1) - 1); + _rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < Base.K_END_POS_MODEL_INDEX) + { + _rep0 += BitTreeDecoder.ReverseDecode( + _posDecoders, + _rep0 - posSlot - 1, + rangeDecoder, + numDirectBits + ); + } + else + { + _rep0 += ( + rangeDecoder.DecodeDirectBits(numDirectBits - Base.K_NUM_ALIGN_BITS) + << Base.K_NUM_ALIGN_BITS + ); + _rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder); + } + } + else + { + _rep0 = posSlot; + } } + if (_rep0 >= outWindow._total || _rep0 >= dictionarySizeCheck) + { + if (_rep0 == 0xFFFFFFFF) + { + return true; + } + throw new DataErrorException(); + } + outWindow.CopyBlock((int)_rep0, (int)len); } } - - public void Train(Stream stream) - { - if (_outWindow is null) - { - CreateDictionary(); - } - _outWindow.Train(stream); - } - - /* - public override bool CanRead { get { return true; }} - public override bool CanWrite { get { return true; }} - public override bool CanSeek { get { return true; }} - public override long Length { get { return 0; }} - public override long Position - { - get { return 0; } - set { } - } - public override void Flush() { } - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - public override void Write(byte[] buffer, int offset, int count) - { - } - public override long Seek(long offset, System.IO.SeekOrigin origin) - { - return 0; - } - public override void SetLength(long value) {} - */ + return false; } + + public void SetDecoderProperties(byte[] properties) + { + if (properties.Length < 1) + { + throw new InvalidParamException(); + } + var lc = properties[0] % 9; + var remainder = properties[0] / 9; + var lp = remainder % 5; + var pb = remainder / 5; + if (pb > Base.K_NUM_POS_STATES_BITS_MAX) + { + throw new InvalidParamException(); + } + SetLiteralProperties(lp, lc); + SetPosBitsProperties(pb); + Init(); + if (properties.Length >= 5) + { + _dictionarySize = 0; + for (var i = 0; i < 4; i++) + { + _dictionarySize += properties[1 + i] << (i * 8); + } + } + } + + public void Train(Stream stream) + { + if (_outWindow is null) + { + CreateDictionary(); + } + _outWindow.Train(stream); + } + + /* + public override bool CanRead { get { return true; }} + public override bool CanWrite { get { return true; }} + public override bool CanSeek { get { return true; }} + public override long Length { get { return 0; }} + public override long Position + { + get { return 0; } + set { } + } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + public override void Write(byte[] buffer, int offset, int count) + { + } + public override long Seek(long offset, System.IO.SeekOrigin origin) + { + return 0; + } + public override void SetLength(long value) {} + */ } diff --git a/src/SharpCompress/Compressors/LZMA/LzmaEncoder.cs b/src/SharpCompress/Compressors/LZMA/LzmaEncoder.cs index 5dab5002..a802bc86 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaEncoder.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaEncoder.cs @@ -5,1069 +5,1208 @@ using System.IO; using SharpCompress.Compressors.LZMA.LZ; using SharpCompress.Compressors.LZMA.RangeCoder; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +internal class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties { - internal class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties + private enum EMatchFinderType { - private enum EMatchFinderType + Bt2, + Bt4 + } + + private const uint K_IFINITY_PRICE = 0xFFFFFFF; + + private static readonly byte[] G_FAST_POS = new byte[1 << 11]; + + static Encoder() + { + const byte kFastSlots = 22; + var c = 2; + G_FAST_POS[0] = 0; + G_FAST_POS[1] = 1; + for (byte slotFast = 2; slotFast < kFastSlots; slotFast++) { - Bt2, - Bt4 - } - - private const UInt32 K_IFINITY_PRICE = 0xFFFFFFF; - - private static readonly Byte[] G_FAST_POS = new Byte[1 << 11]; - - static Encoder() - { - const Byte kFastSlots = 22; - int c = 2; - G_FAST_POS[0] = 0; - G_FAST_POS[1] = 1; - for (Byte slotFast = 2; slotFast < kFastSlots; slotFast++) + var k = ((uint)1 << ((slotFast >> 1) - 1)); + for (uint j = 0; j < k; j++, c++) { - UInt32 k = ((UInt32)1 << ((slotFast >> 1) - 1)); - for (UInt32 j = 0; j < k; j++, c++) - { - G_FAST_POS[c] = slotFast; - } + G_FAST_POS[c] = slotFast; } } + } - private static UInt32 GetPosSlot(UInt32 pos) + private static uint GetPosSlot(uint pos) + { + if (pos < (1 << 11)) { - if (pos < (1 << 11)) - { - return G_FAST_POS[pos]; - } - if (pos < (1 << 21)) - { - return (UInt32)(G_FAST_POS[pos >> 10] + 20); - } - return (UInt32)(G_FAST_POS[pos >> 20] + 40); + return G_FAST_POS[pos]; } - - private static UInt32 GetPosSlot2(UInt32 pos) + if (pos < (1 << 21)) { - if (pos < (1 << 17)) - { - return (UInt32)(G_FAST_POS[pos >> 6] + 12); - } - if (pos < (1 << 27)) - { - return (UInt32)(G_FAST_POS[pos >> 16] + 32); - } - return (UInt32)(G_FAST_POS[pos >> 26] + 52); + return (uint)(G_FAST_POS[pos >> 10] + 20); } + return (uint)(G_FAST_POS[pos >> 20] + 40); + } - private Base.State _state = new Base.State(); - private Byte _previousByte; - private readonly UInt32[] _repDistances = new UInt32[Base.K_NUM_REP_DISTANCES]; - - private void BaseInit() + private static uint GetPosSlot2(uint pos) + { + if (pos < (1 << 17)) { - _state.Init(); - _previousByte = 0; - for (UInt32 i = 0; i < Base.K_NUM_REP_DISTANCES; i++) - { - _repDistances[i] = 0; - } + return (uint)(G_FAST_POS[pos >> 6] + 12); } - - private const int K_DEFAULT_DICTIONARY_LOG_SIZE = 22; - private const UInt32 K_NUM_FAST_BYTES_DEFAULT = 0x20; - - private class LiteralEncoder + if (pos < (1 << 27)) { - public struct Encoder2 - { - private BitEncoder[] _encoders; + return (uint)(G_FAST_POS[pos >> 16] + 32); + } + return (uint)(G_FAST_POS[pos >> 26] + 52); + } - public void Create() - { - _encoders = new BitEncoder[0x300]; - } + private Base.State _state = new Base.State(); + private byte _previousByte; + private readonly uint[] _repDistances = new uint[Base.K_NUM_REP_DISTANCES]; - public void Init() - { - for (int i = 0; i < 0x300; i++) - { - _encoders[i].Init(); - } - } + private void BaseInit() + { + _state.Init(); + _previousByte = 0; + for (uint i = 0; i < Base.K_NUM_REP_DISTANCES; i++) + { + _repDistances[i] = 0; + } + } - public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol) - { - uint context = 1; - for (int i = 7; i >= 0; i--) - { - uint bit = (uint)((symbol >> i) & 1); - _encoders[context].Encode(rangeEncoder, bit); - context = (context << 1) | bit; - } - } + private const int K_DEFAULT_DICTIONARY_LOG_SIZE = 22; + private const uint K_NUM_FAST_BYTES_DEFAULT = 0x20; - public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) - { - uint context = 1; - bool same = true; - for (int i = 7; i >= 0; i--) - { - uint bit = (uint)((symbol >> i) & 1); - uint state = context; - if (same) - { - uint matchBit = (uint)((matchByte >> i) & 1); - state += ((1 + matchBit) << 8); - same = (matchBit == bit); - } - _encoders[state].Encode(rangeEncoder, bit); - context = (context << 1) | bit; - } - } + private class LiteralEncoder + { + public struct Encoder2 + { + private BitEncoder[] _encoders; - public uint GetPrice(bool matchMode, byte matchByte, byte symbol) - { - uint price = 0; - uint context = 1; - int i = 7; - if (matchMode) - { - for (; i >= 0; i--) - { - uint matchBit = (uint)(matchByte >> i) & 1; - uint bit = (uint)(symbol >> i) & 1; - price += _encoders[((1 + matchBit) << 8) + context].GetPrice(bit); - context = (context << 1) | bit; - if (matchBit != bit) - { - i--; - break; - } - } - } - for (; i >= 0; i--) - { - uint bit = (uint)(symbol >> i) & 1; - price += _encoders[context].GetPrice(bit); - context = (context << 1) | bit; - } - return price; - } - } - - private Encoder2[] _coders; - private int _numPrevBits; - private int _numPosBits; - private uint _posMask; - - public void Create(int numPosBits, int numPrevBits) - { - if (_coders != null && _numPrevBits == numPrevBits && _numPosBits == numPosBits) - { - return; - } - _numPosBits = numPosBits; - _posMask = ((uint)1 << numPosBits) - 1; - _numPrevBits = numPrevBits; - uint numStates = (uint)1 << (_numPrevBits + _numPosBits); - _coders = new Encoder2[numStates]; - for (uint i = 0; i < numStates; i++) - { - _coders[i].Create(); - } - } + public void Create() => _encoders = new BitEncoder[0x300]; public void Init() { - uint numStates = (uint)1 << (_numPrevBits + _numPosBits); - for (uint i = 0; i < numStates; i++) + for (var i = 0; i < 0x300; i++) { - _coders[i].Init(); + _encoders[i].Init(); } } - public Encoder2 GetSubCoder(UInt32 pos, Byte prevByte) + public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol) { - return _coders[((pos & _posMask) << _numPrevBits) + (uint)(prevByte >> (8 - _numPrevBits))]; - } - } - - private class LenEncoder - { - private BitEncoder _choice = new BitEncoder(); - private BitEncoder _choice2 = new BitEncoder(); - private readonly BitTreeEncoder[] _lowCoder = new BitTreeEncoder[Base.K_NUM_POS_STATES_ENCODING_MAX]; - private readonly BitTreeEncoder[] _midCoder = new BitTreeEncoder[Base.K_NUM_POS_STATES_ENCODING_MAX]; - private BitTreeEncoder _highCoder = new BitTreeEncoder(Base.K_NUM_HIGH_LEN_BITS); - - public LenEncoder() - { - for (UInt32 posState = 0; posState < Base.K_NUM_POS_STATES_ENCODING_MAX; posState++) + uint context = 1; + for (var i = 7; i >= 0; i--) { - _lowCoder[posState] = new BitTreeEncoder(Base.K_NUM_LOW_LEN_BITS); - _midCoder[posState] = new BitTreeEncoder(Base.K_NUM_MID_LEN_BITS); + var bit = (uint)((symbol >> i) & 1); + _encoders[context].Encode(rangeEncoder, bit); + context = (context << 1) | bit; } } - public void Init(UInt32 numPosStates) + public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol) { - _choice.Init(); - _choice2.Init(); - for (UInt32 posState = 0; posState < numPosStates; posState++) + uint context = 1; + var same = true; + for (var i = 7; i >= 0; i--) { - _lowCoder[posState].Init(); - _midCoder[posState].Init(); - } - _highCoder.Init(); - } - - public void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) - { - if (symbol < Base.K_NUM_LOW_LEN_SYMBOLS) - { - _choice.Encode(rangeEncoder, 0); - _lowCoder[posState].Encode(rangeEncoder, symbol); - } - else - { - symbol -= Base.K_NUM_LOW_LEN_SYMBOLS; - _choice.Encode(rangeEncoder, 1); - if (symbol < Base.K_NUM_MID_LEN_SYMBOLS) + var bit = (uint)((symbol >> i) & 1); + var state = context; + if (same) { - _choice2.Encode(rangeEncoder, 0); - _midCoder[posState].Encode(rangeEncoder, symbol); + var matchBit = (uint)((matchByte >> i) & 1); + state += ((1 + matchBit) << 8); + same = (matchBit == bit); } - else + _encoders[state].Encode(rangeEncoder, bit); + context = (context << 1) | bit; + } + } + + public uint GetPrice(bool matchMode, byte matchByte, byte symbol) + { + uint price = 0; + uint context = 1; + var i = 7; + if (matchMode) + { + for (; i >= 0; i--) { - _choice2.Encode(rangeEncoder, 1); - _highCoder.Encode(rangeEncoder, symbol - Base.K_NUM_MID_LEN_SYMBOLS); - } - } - } - - public void SetPrices(UInt32 posState, UInt32 numSymbols, UInt32[] prices, UInt32 st) - { - UInt32 a0 = _choice.GetPrice0(); - UInt32 a1 = _choice.GetPrice1(); - UInt32 b0 = a1 + _choice2.GetPrice0(); - UInt32 b1 = a1 + _choice2.GetPrice1(); - UInt32 i = 0; - for (i = 0; i < Base.K_NUM_LOW_LEN_SYMBOLS; i++) - { - if (i >= numSymbols) - { - return; - } - prices[st + i] = a0 + _lowCoder[posState].GetPrice(i); - } - for (; i < Base.K_NUM_LOW_LEN_SYMBOLS + Base.K_NUM_MID_LEN_SYMBOLS; i++) - { - if (i >= numSymbols) - { - return; - } - prices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.K_NUM_LOW_LEN_SYMBOLS); - } - for (; i < numSymbols; i++) - { - prices[st + i] = b1 + _highCoder.GetPrice(i - Base.K_NUM_LOW_LEN_SYMBOLS - Base.K_NUM_MID_LEN_SYMBOLS); - } - } - } - - private const UInt32 K_NUM_LEN_SPEC_SYMBOLS = Base.K_NUM_LOW_LEN_SYMBOLS + Base.K_NUM_MID_LEN_SYMBOLS; - - private class LenPriceTableEncoder : LenEncoder - { - private readonly UInt32[] _prices = new UInt32[Base.K_NUM_LEN_SYMBOLS << Base.K_NUM_POS_STATES_BITS_ENCODING_MAX]; - private UInt32 _tableSize; - private readonly UInt32[] _counters = new UInt32[Base.K_NUM_POS_STATES_ENCODING_MAX]; - - public void SetTableSize(UInt32 tableSize) - { - _tableSize = tableSize; - } - - public UInt32 GetPrice(UInt32 symbol, UInt32 posState) - { - return _prices[posState * Base.K_NUM_LEN_SYMBOLS + symbol]; - } - - private void UpdateTable(UInt32 posState) - { - SetPrices(posState, _tableSize, _prices, posState * Base.K_NUM_LEN_SYMBOLS); - _counters[posState] = _tableSize; - } - - public void UpdateTables(UInt32 numPosStates) - { - for (UInt32 posState = 0; posState < numPosStates; posState++) - { - UpdateTable(posState); - } - } - - public new void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState) - { - base.Encode(rangeEncoder, symbol, posState); - if (--_counters[posState] == 0) - { - UpdateTable(posState); - } - } - } - - private const UInt32 K_NUM_OPTS = 1 << 12; - - private class Optimal - { - public Base.State _state; - - public bool _prev1IsChar; - public bool _prev2; - - public UInt32 _posPrev2; - public UInt32 _backPrev2; - - public UInt32 _price; - public UInt32 _posPrev; - public UInt32 _backPrev; - - public UInt32 _backs0; - public UInt32 _backs1; - public UInt32 _backs2; - public UInt32 _backs3; - - public void MakeAsChar() - { - _backPrev = 0xFFFFFFFF; - _prev1IsChar = false; - } - - public void MakeAsShortRep() - { - _backPrev = 0; - ; - _prev1IsChar = false; - } - - public bool IsShortRep() - { - return (_backPrev == 0); - } - } - - private readonly Optimal[] _optimum = new Optimal[K_NUM_OPTS]; - private BinTree _matchFinder; - private readonly RangeCoder.Encoder _rangeEncoder = new RangeCoder.Encoder(); - - private readonly BitEncoder[] _isMatch = - new BitEncoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX]; - - private readonly BitEncoder[] _isRep = new BitEncoder[Base.K_NUM_STATES]; - private readonly BitEncoder[] _isRepG0 = new BitEncoder[Base.K_NUM_STATES]; - private readonly BitEncoder[] _isRepG1 = new BitEncoder[Base.K_NUM_STATES]; - private readonly BitEncoder[] _isRepG2 = new BitEncoder[Base.K_NUM_STATES]; - - private readonly BitEncoder[] _isRep0Long = - new BitEncoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX]; - - private readonly BitTreeEncoder[] _posSlotEncoder = new BitTreeEncoder[Base.K_NUM_LEN_TO_POS_STATES]; - - private readonly BitEncoder[] _posEncoders = - new BitEncoder[Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX]; - - private BitTreeEncoder _posAlignEncoder = new BitTreeEncoder(Base.K_NUM_ALIGN_BITS); - - private readonly LenPriceTableEncoder _lenEncoder = new LenPriceTableEncoder(); - private readonly LenPriceTableEncoder _repMatchLenEncoder = new LenPriceTableEncoder(); - - private readonly LiteralEncoder _literalEncoder = new LiteralEncoder(); - - private readonly UInt32[] _matchDistances = new UInt32[Base.K_MATCH_MAX_LEN * 2 + 2]; - - private UInt32 _numFastBytes = K_NUM_FAST_BYTES_DEFAULT; - private UInt32 _longestMatchLength; - private UInt32 _numDistancePairs; - - private UInt32 _additionalOffset; - - private UInt32 _optimumEndIndex; - private UInt32 _optimumCurrentIndex; - - private bool _longestMatchWasFound; - - private readonly UInt32[] _posSlotPrices = new UInt32[1 << (Base.K_NUM_POS_SLOT_BITS + Base.K_NUM_LEN_TO_POS_STATES_BITS)]; - private readonly UInt32[] _distancesPrices = new UInt32[Base.K_NUM_FULL_DISTANCES << Base.K_NUM_LEN_TO_POS_STATES_BITS]; - private readonly UInt32[] _alignPrices = new UInt32[Base.K_ALIGN_TABLE_SIZE]; - private UInt32 _alignPriceCount; - - private UInt32 _distTableSize = (K_DEFAULT_DICTIONARY_LOG_SIZE * 2); - - private int _posStateBits = 2; - private UInt32 _posStateMask = (4 - 1); - private int _numLiteralPosStateBits; - private int _numLiteralContextBits = 3; - - private UInt32 _dictionarySize = (1 << K_DEFAULT_DICTIONARY_LOG_SIZE); - private UInt32 _dictionarySizePrev = 0xFFFFFFFF; - private UInt32 _numFastBytesPrev = 0xFFFFFFFF; - - private Int64 _nowPos64; - private bool _finished; - private Stream _inStream; - - private EMatchFinderType _matchFinderType = EMatchFinderType.Bt4; - private bool _writeEndMark; - - private bool _needReleaseMfStream; - private bool _processingMode; - - private void Create() - { - if (_matchFinder is null) - { - var bt = new BinTree(); - int numHashBytes = 4; - if (_matchFinderType == EMatchFinderType.Bt2) - { - numHashBytes = 2; - } - bt.SetType(numHashBytes); - _matchFinder = bt; - } - _literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits); - - if (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes) - { - return; - } - _matchFinder.Create(_dictionarySize, K_NUM_OPTS, _numFastBytes, Base.K_MATCH_MAX_LEN + 1 + K_NUM_OPTS); - _dictionarySizePrev = _dictionarySize; - _numFastBytesPrev = _numFastBytes; - } - - public Encoder() - { - for (int i = 0; i < K_NUM_OPTS; i++) - { - _optimum[i] = new Optimal(); - } - for (int i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) - { - _posSlotEncoder[i] = new BitTreeEncoder(Base.K_NUM_POS_SLOT_BITS); - } - } - - private void SetWriteEndMarkerMode(bool writeEndMarker) - { - _writeEndMark = writeEndMarker; - } - - private void Init() - { - BaseInit(); - _rangeEncoder.Init(); - - uint i; - for (i = 0; i < Base.K_NUM_STATES; i++) - { - for (uint j = 0; j <= _posStateMask; j++) - { - uint complexState = (i << Base.K_NUM_POS_STATES_BITS_MAX) + j; - _isMatch[complexState].Init(); - _isRep0Long[complexState].Init(); - } - _isRep[i].Init(); - _isRepG0[i].Init(); - _isRepG1[i].Init(); - _isRepG2[i].Init(); - } - _literalEncoder.Init(); - for (i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) - { - _posSlotEncoder[i].Init(); - } - for (i = 0; i < Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX; i++) - { - _posEncoders[i].Init(); - } - - _lenEncoder.Init((UInt32)1 << _posStateBits); - _repMatchLenEncoder.Init((UInt32)1 << _posStateBits); - - _posAlignEncoder.Init(); - - _longestMatchWasFound = false; - _optimumEndIndex = 0; - _optimumCurrentIndex = 0; - _additionalOffset = 0; - } - - private void ReadMatchDistances(out UInt32 lenRes, out UInt32 numDistancePairs) - { - lenRes = 0; - numDistancePairs = _matchFinder.GetMatches(_matchDistances); - if (numDistancePairs > 0) - { - lenRes = _matchDistances[numDistancePairs - 2]; - if (lenRes == _numFastBytes) - { - lenRes += _matchFinder.GetMatchLen((int)lenRes - 1, _matchDistances[numDistancePairs - 1], - Base.K_MATCH_MAX_LEN - lenRes); - } - } - _additionalOffset++; - } - - private void MovePos(UInt32 num) - { - if (num > 0) - { - _matchFinder.Skip(num); - _additionalOffset += num; - } - } - - private UInt32 GetRepLen1Price(Base.State state, UInt32 posState) - { - return _isRepG0[state._index].GetPrice0() + - _isRep0Long[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0(); - } - - private UInt32 GetPureRepPrice(UInt32 repIndex, Base.State state, UInt32 posState) - { - UInt32 price; - if (repIndex == 0) - { - price = _isRepG0[state._index].GetPrice0(); - price += _isRep0Long[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice1(); - } - else - { - price = _isRepG0[state._index].GetPrice1(); - if (repIndex == 1) - { - price += _isRepG1[state._index].GetPrice0(); - } - else - { - price += _isRepG1[state._index].GetPrice1(); - price += _isRepG2[state._index].GetPrice(repIndex - 2); - } - } - return price; - } - - private UInt32 GetRepPrice(UInt32 repIndex, UInt32 len, Base.State state, UInt32 posState) - { - UInt32 price = _repMatchLenEncoder.GetPrice(len - Base.K_MATCH_MIN_LEN, posState); - return price + GetPureRepPrice(repIndex, state, posState); - } - - private UInt32 GetPosLenPrice(UInt32 pos, UInt32 len, UInt32 posState) - { - UInt32 price; - UInt32 lenToPosState = Base.GetLenToPosState(len); - if (pos < Base.K_NUM_FULL_DISTANCES) - { - price = _distancesPrices[(lenToPosState * Base.K_NUM_FULL_DISTANCES) + pos]; - } - else - { - price = _posSlotPrices[(lenToPosState << Base.K_NUM_POS_SLOT_BITS) + GetPosSlot2(pos)] + - _alignPrices[pos & Base.K_ALIGN_MASK]; - } - return price + _lenEncoder.GetPrice(len - Base.K_MATCH_MIN_LEN, posState); - } - - private UInt32 Backward(out UInt32 backRes, UInt32 cur) - { - _optimumEndIndex = cur; - UInt32 posMem = _optimum[cur]._posPrev; - UInt32 backMem = _optimum[cur]._backPrev; - do - { - if (_optimum[cur]._prev1IsChar) - { - _optimum[posMem].MakeAsChar(); - _optimum[posMem]._posPrev = posMem - 1; - if (_optimum[cur]._prev2) - { - _optimum[posMem - 1]._prev1IsChar = false; - _optimum[posMem - 1]._posPrev = _optimum[cur]._posPrev2; - _optimum[posMem - 1]._backPrev = _optimum[cur]._backPrev2; - } - } - UInt32 posPrev = posMem; - UInt32 backCur = backMem; - - backMem = _optimum[posPrev]._backPrev; - posMem = _optimum[posPrev]._posPrev; - - _optimum[posPrev]._backPrev = backCur; - _optimum[posPrev]._posPrev = cur; - cur = posPrev; - } - while (cur > 0); - backRes = _optimum[0]._backPrev; - _optimumCurrentIndex = _optimum[0]._posPrev; - return _optimumCurrentIndex; - } - - private readonly UInt32[] _reps = new UInt32[Base.K_NUM_REP_DISTANCES]; - private readonly UInt32[] _repLens = new UInt32[Base.K_NUM_REP_DISTANCES]; - - private UInt32 GetOptimum(UInt32 position, out UInt32 backRes) - { - if (_optimumEndIndex != _optimumCurrentIndex) - { - UInt32 lenRes = _optimum[_optimumCurrentIndex]._posPrev - _optimumCurrentIndex; - backRes = _optimum[_optimumCurrentIndex]._backPrev; - _optimumCurrentIndex = _optimum[_optimumCurrentIndex]._posPrev; - return lenRes; - } - _optimumCurrentIndex = _optimumEndIndex = 0; - - UInt32 lenMain, numDistancePairs; - if (!_longestMatchWasFound) - { - ReadMatchDistances(out lenMain, out numDistancePairs); - } - else - { - lenMain = _longestMatchLength; - numDistancePairs = _numDistancePairs; - _longestMatchWasFound = false; - } - - UInt32 numAvailableBytes = _matchFinder.GetNumAvailableBytes() + 1; - if (numAvailableBytes < 2) - { - backRes = 0xFFFFFFFF; - return 1; - } - if (numAvailableBytes > Base.K_MATCH_MAX_LEN) - { - numAvailableBytes = Base.K_MATCH_MAX_LEN; - } - - UInt32 repMaxIndex = 0; - UInt32 i; - for (i = 0; i < Base.K_NUM_REP_DISTANCES; i++) - { - _reps[i] = _repDistances[i]; - _repLens[i] = _matchFinder.GetMatchLen(0 - 1, _reps[i], Base.K_MATCH_MAX_LEN); - if (_repLens[i] > _repLens[repMaxIndex]) - { - repMaxIndex = i; - } - } - if (_repLens[repMaxIndex] >= _numFastBytes) - { - backRes = repMaxIndex; - UInt32 lenRes = _repLens[repMaxIndex]; - MovePos(lenRes - 1); - return lenRes; - } - - if (lenMain >= _numFastBytes) - { - backRes = _matchDistances[numDistancePairs - 1] + Base.K_NUM_REP_DISTANCES; - MovePos(lenMain - 1); - return lenMain; - } - - Byte currentByte = _matchFinder.GetIndexByte(0 - 1); - Byte matchByte = _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - 1)); - - if (lenMain < 2 && currentByte != matchByte && _repLens[repMaxIndex] < 2) - { - backRes = 0xFFFFFFFF; - return 1; - } - - _optimum[0]._state = _state; - - UInt32 posState = (position & _posStateMask); - - _optimum[1]._price = _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0() + - _literalEncoder.GetSubCoder(position, _previousByte) - .GetPrice(!_state.IsCharState(), matchByte, currentByte); - _optimum[1].MakeAsChar(); - - UInt32 matchPrice = _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice1(); - UInt32 repMatchPrice = matchPrice + _isRep[_state._index].GetPrice1(); - - if (matchByte == currentByte) - { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState); - if (shortRepPrice < _optimum[1]._price) - { - _optimum[1]._price = shortRepPrice; - _optimum[1].MakeAsShortRep(); - } - } - - UInt32 lenEnd = ((lenMain >= _repLens[repMaxIndex]) ? lenMain : _repLens[repMaxIndex]); - - if (lenEnd < 2) - { - backRes = _optimum[1]._backPrev; - return 1; - } - - _optimum[1]._posPrev = 0; - - _optimum[0]._backs0 = _reps[0]; - _optimum[0]._backs1 = _reps[1]; - _optimum[0]._backs2 = _reps[2]; - _optimum[0]._backs3 = _reps[3]; - - UInt32 len = lenEnd; - do - { - _optimum[len--]._price = K_IFINITY_PRICE; - } - while (len >= 2); - - for (i = 0; i < Base.K_NUM_REP_DISTANCES; i++) - { - UInt32 repLen = _repLens[i]; - if (repLen < 2) - { - continue; - } - UInt32 price = repMatchPrice + GetPureRepPrice(i, _state, posState); - do - { - UInt32 curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState); - Optimal optimum = _optimum[repLen]; - if (curAndLenPrice < optimum._price) - { - optimum._price = curAndLenPrice; - optimum._posPrev = 0; - optimum._backPrev = i; - optimum._prev1IsChar = false; - } - } - while (--repLen >= 2); - } - - UInt32 normalMatchPrice = matchPrice + _isRep[_state._index].GetPrice0(); - - len = ((_repLens[0] >= 2) ? _repLens[0] + 1 : 2); - if (len <= lenMain) - { - UInt32 offs = 0; - while (len > _matchDistances[offs]) - { - offs += 2; - } - for (; ; len++) - { - UInt32 distance = _matchDistances[offs + 1]; - UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState); - Optimal optimum = _optimum[len]; - if (curAndLenPrice < optimum._price) - { - optimum._price = curAndLenPrice; - optimum._posPrev = 0; - optimum._backPrev = distance + Base.K_NUM_REP_DISTANCES; - optimum._prev1IsChar = false; - } - if (len == _matchDistances[offs]) - { - offs += 2; - if (offs == numDistancePairs) + var matchBit = (uint)(matchByte >> i) & 1; + var bit = (uint)(symbol >> i) & 1; + price += _encoders[((1 + matchBit) << 8) + context].GetPrice(bit); + context = (context << 1) | bit; + if (matchBit != bit) { + i--; break; } } } + for (; i >= 0; i--) + { + var bit = (uint)(symbol >> i) & 1; + price += _encoders[context].GetPrice(bit); + context = (context << 1) | bit; + } + return price; } + } - UInt32 cur = 0; + private Encoder2[] _coders; + private int _numPrevBits; + private int _numPosBits; + private uint _posMask; - while (true) + public void Create(int numPosBits, int numPrevBits) + { + if (_coders != null && _numPrevBits == numPrevBits && _numPosBits == numPosBits) { - cur++; - if (cur == lenEnd) + return; + } + _numPosBits = numPosBits; + _posMask = ((uint)1 << numPosBits) - 1; + _numPrevBits = numPrevBits; + var numStates = (uint)1 << (_numPrevBits + _numPosBits); + _coders = new Encoder2[numStates]; + for (uint i = 0; i < numStates; i++) + { + _coders[i].Create(); + } + } + + public void Init() + { + var numStates = (uint)1 << (_numPrevBits + _numPosBits); + for (uint i = 0; i < numStates; i++) + { + _coders[i].Init(); + } + } + + public Encoder2 GetSubCoder(uint pos, byte prevByte) => + _coders[((pos & _posMask) << _numPrevBits) + (uint)(prevByte >> (8 - _numPrevBits))]; + } + + private class LenEncoder + { + private BitEncoder _choice = new BitEncoder(); + private BitEncoder _choice2 = new BitEncoder(); + private readonly BitTreeEncoder[] _lowCoder = new BitTreeEncoder[ + Base.K_NUM_POS_STATES_ENCODING_MAX + ]; + private readonly BitTreeEncoder[] _midCoder = new BitTreeEncoder[ + Base.K_NUM_POS_STATES_ENCODING_MAX + ]; + private BitTreeEncoder _highCoder = new BitTreeEncoder(Base.K_NUM_HIGH_LEN_BITS); + + public LenEncoder() + { + for (uint posState = 0; posState < Base.K_NUM_POS_STATES_ENCODING_MAX; posState++) + { + _lowCoder[posState] = new BitTreeEncoder(Base.K_NUM_LOW_LEN_BITS); + _midCoder[posState] = new BitTreeEncoder(Base.K_NUM_MID_LEN_BITS); + } + } + + public void Init(uint numPosStates) + { + _choice.Init(); + _choice2.Init(); + for (uint posState = 0; posState < numPosStates; posState++) + { + _lowCoder[posState].Init(); + _midCoder[posState].Init(); + } + _highCoder.Init(); + } + + public void Encode(RangeCoder.Encoder rangeEncoder, uint symbol, uint posState) + { + if (symbol < Base.K_NUM_LOW_LEN_SYMBOLS) + { + _choice.Encode(rangeEncoder, 0); + _lowCoder[posState].Encode(rangeEncoder, symbol); + } + else + { + symbol -= Base.K_NUM_LOW_LEN_SYMBOLS; + _choice.Encode(rangeEncoder, 1); + if (symbol < Base.K_NUM_MID_LEN_SYMBOLS) { - return Backward(out backRes, cur); + _choice2.Encode(rangeEncoder, 0); + _midCoder[posState].Encode(rangeEncoder, symbol); } - UInt32 newLen; - ReadMatchDistances(out newLen, out numDistancePairs); - if (newLen >= _numFastBytes) + else { - _numDistancePairs = numDistancePairs; - _longestMatchLength = newLen; - _longestMatchWasFound = true; - return Backward(out backRes, cur); + _choice2.Encode(rangeEncoder, 1); + _highCoder.Encode(rangeEncoder, symbol - Base.K_NUM_MID_LEN_SYMBOLS); } - position++; - UInt32 posPrev = _optimum[cur]._posPrev; - Base.State state; - if (_optimum[cur]._prev1IsChar) + } + } + + public void SetPrices(uint posState, uint numSymbols, uint[] prices, uint st) + { + var a0 = _choice.GetPrice0(); + var a1 = _choice.GetPrice1(); + var b0 = a1 + _choice2.GetPrice0(); + var b1 = a1 + _choice2.GetPrice1(); + uint i = 0; + for (i = 0; i < Base.K_NUM_LOW_LEN_SYMBOLS; i++) + { + if (i >= numSymbols) { - posPrev--; - if (_optimum[cur]._prev2) + return; + } + prices[st + i] = a0 + _lowCoder[posState].GetPrice(i); + } + for (; i < Base.K_NUM_LOW_LEN_SYMBOLS + Base.K_NUM_MID_LEN_SYMBOLS; i++) + { + if (i >= numSymbols) + { + return; + } + prices[st + i] = b0 + _midCoder[posState].GetPrice(i - Base.K_NUM_LOW_LEN_SYMBOLS); + } + for (; i < numSymbols; i++) + { + prices[st + i] = + b1 + + _highCoder.GetPrice( + i - Base.K_NUM_LOW_LEN_SYMBOLS - Base.K_NUM_MID_LEN_SYMBOLS + ); + } + } + } + + private const uint K_NUM_LEN_SPEC_SYMBOLS = + Base.K_NUM_LOW_LEN_SYMBOLS + Base.K_NUM_MID_LEN_SYMBOLS; + + private class LenPriceTableEncoder : LenEncoder + { + private readonly uint[] _prices = new uint[ + Base.K_NUM_LEN_SYMBOLS << Base.K_NUM_POS_STATES_BITS_ENCODING_MAX + ]; + private uint _tableSize; + private readonly uint[] _counters = new uint[Base.K_NUM_POS_STATES_ENCODING_MAX]; + + public void SetTableSize(uint tableSize) => _tableSize = tableSize; + + public uint GetPrice(uint symbol, uint posState) => + _prices[(posState * Base.K_NUM_LEN_SYMBOLS) + symbol]; + + private void UpdateTable(uint posState) + { + SetPrices(posState, _tableSize, _prices, posState * Base.K_NUM_LEN_SYMBOLS); + _counters[posState] = _tableSize; + } + + public void UpdateTables(uint numPosStates) + { + for (uint posState = 0; posState < numPosStates; posState++) + { + UpdateTable(posState); + } + } + + public new void Encode(RangeCoder.Encoder rangeEncoder, uint symbol, uint posState) + { + base.Encode(rangeEncoder, symbol, posState); + if (--_counters[posState] == 0) + { + UpdateTable(posState); + } + } + } + + private const uint K_NUM_OPTS = 1 << 12; + + private class Optimal + { + public Base.State _state; + + public bool _prev1IsChar; + public bool _prev2; + + public uint _posPrev2; + public uint _backPrev2; + + public uint _price; + public uint _posPrev; + public uint _backPrev; + + public uint _backs0; + public uint _backs1; + public uint _backs2; + public uint _backs3; + + public void MakeAsChar() + { + _backPrev = 0xFFFFFFFF; + _prev1IsChar = false; + } + + public void MakeAsShortRep() + { + _backPrev = 0; + ; + _prev1IsChar = false; + } + + public bool IsShortRep() => (_backPrev == 0); + } + + private readonly Optimal[] _optimum = new Optimal[K_NUM_OPTS]; + private BinTree _matchFinder; + private readonly RangeCoder.Encoder _rangeEncoder = new RangeCoder.Encoder(); + + private readonly BitEncoder[] _isMatch = new BitEncoder[ + Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX + ]; + + private readonly BitEncoder[] _isRep = new BitEncoder[Base.K_NUM_STATES]; + private readonly BitEncoder[] _isRepG0 = new BitEncoder[Base.K_NUM_STATES]; + private readonly BitEncoder[] _isRepG1 = new BitEncoder[Base.K_NUM_STATES]; + private readonly BitEncoder[] _isRepG2 = new BitEncoder[Base.K_NUM_STATES]; + + private readonly BitEncoder[] _isRep0Long = new BitEncoder[ + Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX + ]; + + private readonly BitTreeEncoder[] _posSlotEncoder = new BitTreeEncoder[ + Base.K_NUM_LEN_TO_POS_STATES + ]; + + private readonly BitEncoder[] _posEncoders = new BitEncoder[ + Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX + ]; + + private BitTreeEncoder _posAlignEncoder = new BitTreeEncoder(Base.K_NUM_ALIGN_BITS); + + private readonly LenPriceTableEncoder _lenEncoder = new LenPriceTableEncoder(); + private readonly LenPriceTableEncoder _repMatchLenEncoder = new LenPriceTableEncoder(); + + private readonly LiteralEncoder _literalEncoder = new LiteralEncoder(); + + private readonly uint[] _matchDistances = new uint[(Base.K_MATCH_MAX_LEN * 2) + 2]; + + private uint _numFastBytes = K_NUM_FAST_BYTES_DEFAULT; + private uint _longestMatchLength; + private uint _numDistancePairs; + + private uint _additionalOffset; + + private uint _optimumEndIndex; + private uint _optimumCurrentIndex; + + private bool _longestMatchWasFound; + + private readonly uint[] _posSlotPrices = new uint[ + 1 << (Base.K_NUM_POS_SLOT_BITS + Base.K_NUM_LEN_TO_POS_STATES_BITS) + ]; + private readonly uint[] _distancesPrices = new uint[ + Base.K_NUM_FULL_DISTANCES << Base.K_NUM_LEN_TO_POS_STATES_BITS + ]; + private readonly uint[] _alignPrices = new uint[Base.K_ALIGN_TABLE_SIZE]; + private uint _alignPriceCount; + + private uint _distTableSize = (K_DEFAULT_DICTIONARY_LOG_SIZE * 2); + + private int _posStateBits = 2; + private uint _posStateMask = (4 - 1); + private int _numLiteralPosStateBits; + private int _numLiteralContextBits = 3; + + private uint _dictionarySize = (1 << K_DEFAULT_DICTIONARY_LOG_SIZE); + private uint _dictionarySizePrev = 0xFFFFFFFF; + private uint _numFastBytesPrev = 0xFFFFFFFF; + + private long _nowPos64; + private bool _finished; + private Stream _inStream; + + private EMatchFinderType _matchFinderType = EMatchFinderType.Bt4; + private bool _writeEndMark; + + private bool _needReleaseMfStream; + private bool _processingMode; + + private void Create() + { + if (_matchFinder is null) + { + var bt = new BinTree(); + var numHashBytes = 4; + if (_matchFinderType == EMatchFinderType.Bt2) + { + numHashBytes = 2; + } + bt.SetType(numHashBytes); + _matchFinder = bt; + } + _literalEncoder.Create(_numLiteralPosStateBits, _numLiteralContextBits); + + if (_dictionarySize == _dictionarySizePrev && _numFastBytesPrev == _numFastBytes) + { + return; + } + _matchFinder.Create( + _dictionarySize, + K_NUM_OPTS, + _numFastBytes, + Base.K_MATCH_MAX_LEN + 1 + K_NUM_OPTS + ); + _dictionarySizePrev = _dictionarySize; + _numFastBytesPrev = _numFastBytes; + } + + public Encoder() + { + for (var i = 0; i < K_NUM_OPTS; i++) + { + _optimum[i] = new Optimal(); + } + for (var i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) + { + _posSlotEncoder[i] = new BitTreeEncoder(Base.K_NUM_POS_SLOT_BITS); + } + } + + private void SetWriteEndMarkerMode(bool writeEndMarker) => _writeEndMark = writeEndMarker; + + private void Init() + { + BaseInit(); + _rangeEncoder.Init(); + + uint i; + for (i = 0; i < Base.K_NUM_STATES; i++) + { + for (uint j = 0; j <= _posStateMask; j++) + { + var complexState = (i << Base.K_NUM_POS_STATES_BITS_MAX) + j; + _isMatch[complexState].Init(); + _isRep0Long[complexState].Init(); + } + _isRep[i].Init(); + _isRepG0[i].Init(); + _isRepG1[i].Init(); + _isRepG2[i].Init(); + } + _literalEncoder.Init(); + for (i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++) + { + _posSlotEncoder[i].Init(); + } + for (i = 0; i < Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX; i++) + { + _posEncoders[i].Init(); + } + + _lenEncoder.Init((uint)1 << _posStateBits); + _repMatchLenEncoder.Init((uint)1 << _posStateBits); + + _posAlignEncoder.Init(); + + _longestMatchWasFound = false; + _optimumEndIndex = 0; + _optimumCurrentIndex = 0; + _additionalOffset = 0; + } + + private void ReadMatchDistances(out uint lenRes, out uint numDistancePairs) + { + lenRes = 0; + numDistancePairs = _matchFinder.GetMatches(_matchDistances); + if (numDistancePairs > 0) + { + lenRes = _matchDistances[numDistancePairs - 2]; + if (lenRes == _numFastBytes) + { + lenRes += _matchFinder.GetMatchLen( + (int)lenRes - 1, + _matchDistances[numDistancePairs - 1], + Base.K_MATCH_MAX_LEN - lenRes + ); + } + } + _additionalOffset++; + } + + private void MovePos(uint num) + { + if (num > 0) + { + _matchFinder.Skip(num); + _additionalOffset += num; + } + } + + private uint GetRepLen1Price(Base.State state, uint posState) => + _isRepG0[state._index].GetPrice0() + + _isRep0Long[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0(); + + private uint GetPureRepPrice(uint repIndex, Base.State state, uint posState) + { + uint price; + if (repIndex == 0) + { + price = _isRepG0[state._index].GetPrice0(); + price += _isRep0Long[ + (state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState + ].GetPrice1(); + } + else + { + price = _isRepG0[state._index].GetPrice1(); + if (repIndex == 1) + { + price += _isRepG1[state._index].GetPrice0(); + } + else + { + price += _isRepG1[state._index].GetPrice1(); + price += _isRepG2[state._index].GetPrice(repIndex - 2); + } + } + return price; + } + + private uint GetRepPrice(uint repIndex, uint len, Base.State state, uint posState) + { + var price = _repMatchLenEncoder.GetPrice(len - Base.K_MATCH_MIN_LEN, posState); + return price + GetPureRepPrice(repIndex, state, posState); + } + + private uint GetPosLenPrice(uint pos, uint len, uint posState) + { + uint price; + var lenToPosState = Base.GetLenToPosState(len); + if (pos < Base.K_NUM_FULL_DISTANCES) + { + price = _distancesPrices[(lenToPosState * Base.K_NUM_FULL_DISTANCES) + pos]; + } + else + { + price = + _posSlotPrices[(lenToPosState << Base.K_NUM_POS_SLOT_BITS) + GetPosSlot2(pos)] + + _alignPrices[pos & Base.K_ALIGN_MASK]; + } + return price + _lenEncoder.GetPrice(len - Base.K_MATCH_MIN_LEN, posState); + } + + private uint Backward(out uint backRes, uint cur) + { + _optimumEndIndex = cur; + var posMem = _optimum[cur]._posPrev; + var backMem = _optimum[cur]._backPrev; + do + { + if (_optimum[cur]._prev1IsChar) + { + _optimum[posMem].MakeAsChar(); + _optimum[posMem]._posPrev = posMem - 1; + if (_optimum[cur]._prev2) + { + _optimum[posMem - 1]._prev1IsChar = false; + _optimum[posMem - 1]._posPrev = _optimum[cur]._posPrev2; + _optimum[posMem - 1]._backPrev = _optimum[cur]._backPrev2; + } + } + var posPrev = posMem; + var backCur = backMem; + + backMem = _optimum[posPrev]._backPrev; + posMem = _optimum[posPrev]._posPrev; + + _optimum[posPrev]._backPrev = backCur; + _optimum[posPrev]._posPrev = cur; + cur = posPrev; + } while (cur > 0); + backRes = _optimum[0]._backPrev; + _optimumCurrentIndex = _optimum[0]._posPrev; + return _optimumCurrentIndex; + } + + private readonly uint[] _reps = new uint[Base.K_NUM_REP_DISTANCES]; + private readonly uint[] _repLens = new uint[Base.K_NUM_REP_DISTANCES]; + + private uint GetOptimum(uint position, out uint backRes) + { + if (_optimumEndIndex != _optimumCurrentIndex) + { + var lenRes = _optimum[_optimumCurrentIndex]._posPrev - _optimumCurrentIndex; + backRes = _optimum[_optimumCurrentIndex]._backPrev; + _optimumCurrentIndex = _optimum[_optimumCurrentIndex]._posPrev; + return lenRes; + } + _optimumCurrentIndex = _optimumEndIndex = 0; + + uint lenMain, + numDistancePairs; + if (!_longestMatchWasFound) + { + ReadMatchDistances(out lenMain, out numDistancePairs); + } + else + { + lenMain = _longestMatchLength; + numDistancePairs = _numDistancePairs; + _longestMatchWasFound = false; + } + + var numAvailableBytes = _matchFinder.GetNumAvailableBytes() + 1; + if (numAvailableBytes < 2) + { + backRes = 0xFFFFFFFF; + return 1; + } + if (numAvailableBytes > Base.K_MATCH_MAX_LEN) + { + numAvailableBytes = Base.K_MATCH_MAX_LEN; + } + + uint repMaxIndex = 0; + uint i; + for (i = 0; i < Base.K_NUM_REP_DISTANCES; i++) + { + _reps[i] = _repDistances[i]; + _repLens[i] = _matchFinder.GetMatchLen(0 - 1, _reps[i], Base.K_MATCH_MAX_LEN); + if (_repLens[i] > _repLens[repMaxIndex]) + { + repMaxIndex = i; + } + } + if (_repLens[repMaxIndex] >= _numFastBytes) + { + backRes = repMaxIndex; + var lenRes = _repLens[repMaxIndex]; + MovePos(lenRes - 1); + return lenRes; + } + + if (lenMain >= _numFastBytes) + { + backRes = _matchDistances[numDistancePairs - 1] + Base.K_NUM_REP_DISTANCES; + MovePos(lenMain - 1); + return lenMain; + } + + var currentByte = _matchFinder.GetIndexByte(0 - 1); + var matchByte = _matchFinder.GetIndexByte((int)(0 - _repDistances[0] - 1 - 1)); + + if (lenMain < 2 && currentByte != matchByte && _repLens[repMaxIndex] < 2) + { + backRes = 0xFFFFFFFF; + return 1; + } + + _optimum[0]._state = _state; + + var posState = (position & _posStateMask); + + _optimum[1]._price = + _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0() + + _literalEncoder + .GetSubCoder(position, _previousByte) + .GetPrice(!_state.IsCharState(), matchByte, currentByte); + _optimum[1].MakeAsChar(); + + var matchPrice = _isMatch[ + (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState + ].GetPrice1(); + var repMatchPrice = matchPrice + _isRep[_state._index].GetPrice1(); + + if (matchByte == currentByte) + { + var shortRepPrice = repMatchPrice + GetRepLen1Price(_state, posState); + if (shortRepPrice < _optimum[1]._price) + { + _optimum[1]._price = shortRepPrice; + _optimum[1].MakeAsShortRep(); + } + } + + var lenEnd = ((lenMain >= _repLens[repMaxIndex]) ? lenMain : _repLens[repMaxIndex]); + + if (lenEnd < 2) + { + backRes = _optimum[1]._backPrev; + return 1; + } + + _optimum[1]._posPrev = 0; + + _optimum[0]._backs0 = _reps[0]; + _optimum[0]._backs1 = _reps[1]; + _optimum[0]._backs2 = _reps[2]; + _optimum[0]._backs3 = _reps[3]; + + var len = lenEnd; + do + { + _optimum[len--]._price = K_IFINITY_PRICE; + } while (len >= 2); + + for (i = 0; i < Base.K_NUM_REP_DISTANCES; i++) + { + var repLen = _repLens[i]; + if (repLen < 2) + { + continue; + } + var price = repMatchPrice + GetPureRepPrice(i, _state, posState); + do + { + var curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState); + var optimum = _optimum[repLen]; + if (curAndLenPrice < optimum._price) + { + optimum._price = curAndLenPrice; + optimum._posPrev = 0; + optimum._backPrev = i; + optimum._prev1IsChar = false; + } + } while (--repLen >= 2); + } + + var normalMatchPrice = matchPrice + _isRep[_state._index].GetPrice0(); + + len = ((_repLens[0] >= 2) ? _repLens[0] + 1 : 2); + if (len <= lenMain) + { + uint offs = 0; + while (len > _matchDistances[offs]) + { + offs += 2; + } + for (; ; len++) + { + var distance = _matchDistances[offs + 1]; + var curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState); + var optimum = _optimum[len]; + if (curAndLenPrice < optimum._price) + { + optimum._price = curAndLenPrice; + optimum._posPrev = 0; + optimum._backPrev = distance + Base.K_NUM_REP_DISTANCES; + optimum._prev1IsChar = false; + } + if (len == _matchDistances[offs]) + { + offs += 2; + if (offs == numDistancePairs) { - state = _optimum[_optimum[cur]._posPrev2]._state; - if (_optimum[cur]._backPrev2 < Base.K_NUM_REP_DISTANCES) - { - state.UpdateRep(); - } - else - { - state.UpdateMatch(); - } + break; + } + } + } + } + + uint cur = 0; + + while (true) + { + cur++; + if (cur == lenEnd) + { + return Backward(out backRes, cur); + } + ReadMatchDistances(out var newLen, out numDistancePairs); + if (newLen >= _numFastBytes) + { + _numDistancePairs = numDistancePairs; + _longestMatchLength = newLen; + _longestMatchWasFound = true; + return Backward(out backRes, cur); + } + position++; + var posPrev = _optimum[cur]._posPrev; + Base.State state; + if (_optimum[cur]._prev1IsChar) + { + posPrev--; + if (_optimum[cur]._prev2) + { + state = _optimum[_optimum[cur]._posPrev2]._state; + if (_optimum[cur]._backPrev2 < Base.K_NUM_REP_DISTANCES) + { + state.UpdateRep(); } else { - state = _optimum[posPrev]._state; + state.UpdateMatch(); } - state.UpdateChar(); } else { state = _optimum[posPrev]._state; } - if (posPrev == cur - 1) + state.UpdateChar(); + } + else + { + state = _optimum[posPrev]._state; + } + if (posPrev == cur - 1) + { + if (_optimum[cur].IsShortRep()) { - if (_optimum[cur].IsShortRep()) - { - state.UpdateShortRep(); - } - else - { - state.UpdateChar(); - } + state.UpdateShortRep(); } else { - UInt32 pos; - if (_optimum[cur]._prev1IsChar && _optimum[cur]._prev2) + state.UpdateChar(); + } + } + else + { + uint pos; + if (_optimum[cur]._prev1IsChar && _optimum[cur]._prev2) + { + posPrev = _optimum[cur]._posPrev2; + pos = _optimum[cur]._backPrev2; + state.UpdateRep(); + } + else + { + pos = _optimum[cur]._backPrev; + if (pos < Base.K_NUM_REP_DISTANCES) { - posPrev = _optimum[cur]._posPrev2; - pos = _optimum[cur]._backPrev2; state.UpdateRep(); } else { - pos = _optimum[cur]._backPrev; - if (pos < Base.K_NUM_REP_DISTANCES) - { - state.UpdateRep(); - } - else - { - state.UpdateMatch(); - } + state.UpdateMatch(); } - Optimal opt = _optimum[posPrev]; - if (pos < Base.K_NUM_REP_DISTANCES) + } + var opt = _optimum[posPrev]; + if (pos < Base.K_NUM_REP_DISTANCES) + { + if (pos == 0) { - if (pos == 0) - { - _reps[0] = opt._backs0; - _reps[1] = opt._backs1; - _reps[2] = opt._backs2; - _reps[3] = opt._backs3; - } - else if (pos == 1) - { - _reps[0] = opt._backs1; - _reps[1] = opt._backs0; - _reps[2] = opt._backs2; - _reps[3] = opt._backs3; - } - else if (pos == 2) - { - _reps[0] = opt._backs2; - _reps[1] = opt._backs0; - _reps[2] = opt._backs1; - _reps[3] = opt._backs3; - } - else - { - _reps[0] = opt._backs3; - _reps[1] = opt._backs0; - _reps[2] = opt._backs1; - _reps[3] = opt._backs2; - } + _reps[0] = opt._backs0; + _reps[1] = opt._backs1; + _reps[2] = opt._backs2; + _reps[3] = opt._backs3; + } + else if (pos == 1) + { + _reps[0] = opt._backs1; + _reps[1] = opt._backs0; + _reps[2] = opt._backs2; + _reps[3] = opt._backs3; + } + else if (pos == 2) + { + _reps[0] = opt._backs2; + _reps[1] = opt._backs0; + _reps[2] = opt._backs1; + _reps[3] = opt._backs3; } else { - _reps[0] = (pos - Base.K_NUM_REP_DISTANCES); + _reps[0] = opt._backs3; _reps[1] = opt._backs0; _reps[2] = opt._backs1; _reps[3] = opt._backs2; } } - _optimum[cur]._state = state; - _optimum[cur]._backs0 = _reps[0]; - _optimum[cur]._backs1 = _reps[1]; - _optimum[cur]._backs2 = _reps[2]; - _optimum[cur]._backs3 = _reps[3]; - UInt32 curPrice = _optimum[cur]._price; - - currentByte = _matchFinder.GetIndexByte(0 - 1); - matchByte = _matchFinder.GetIndexByte((Int32)(0 - _reps[0] - 1 - 1)); - - posState = (position & _posStateMask); - - UInt32 curAnd1Price = curPrice + - _isMatch[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0() + - _literalEncoder.GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2)). - GetPrice(!state.IsCharState(), matchByte, currentByte); - - Optimal nextOptimum = _optimum[cur + 1]; - - bool nextIsChar = false; - if (curAnd1Price < nextOptimum._price) + else { - nextOptimum._price = curAnd1Price; + _reps[0] = (pos - Base.K_NUM_REP_DISTANCES); + _reps[1] = opt._backs0; + _reps[2] = opt._backs1; + _reps[3] = opt._backs2; + } + } + _optimum[cur]._state = state; + _optimum[cur]._backs0 = _reps[0]; + _optimum[cur]._backs1 = _reps[1]; + _optimum[cur]._backs2 = _reps[2]; + _optimum[cur]._backs3 = _reps[3]; + var curPrice = _optimum[cur]._price; + + currentByte = _matchFinder.GetIndexByte(0 - 1); + matchByte = _matchFinder.GetIndexByte((int)(0 - _reps[0] - 1 - 1)); + + posState = (position & _posStateMask); + + var curAnd1Price = + curPrice + + _isMatch[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice0() + + _literalEncoder + .GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2)) + .GetPrice(!state.IsCharState(), matchByte, currentByte); + + var nextOptimum = _optimum[cur + 1]; + + var nextIsChar = false; + if (curAnd1Price < nextOptimum._price) + { + nextOptimum._price = curAnd1Price; + nextOptimum._posPrev = cur; + nextOptimum.MakeAsChar(); + nextIsChar = true; + } + + matchPrice = + curPrice + + _isMatch[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice1(); + repMatchPrice = matchPrice + _isRep[state._index].GetPrice1(); + + if ( + matchByte == currentByte + && !(nextOptimum._posPrev < cur && nextOptimum._backPrev == 0) + ) + { + var shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState); + if (shortRepPrice <= nextOptimum._price) + { + nextOptimum._price = shortRepPrice; nextOptimum._posPrev = cur; - nextOptimum.MakeAsChar(); + nextOptimum.MakeAsShortRep(); nextIsChar = true; } + } - matchPrice = curPrice + _isMatch[(state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].GetPrice1(); - repMatchPrice = matchPrice + _isRep[state._index].GetPrice1(); + var numAvailableBytesFull = _matchFinder.GetNumAvailableBytes() + 1; + numAvailableBytesFull = Math.Min(K_NUM_OPTS - 1 - cur, numAvailableBytesFull); + numAvailableBytes = numAvailableBytesFull; - if (matchByte == currentByte && - !(nextOptimum._posPrev < cur && nextOptimum._backPrev == 0)) + if (numAvailableBytes < 2) + { + continue; + } + if (numAvailableBytes > _numFastBytes) + { + numAvailableBytes = _numFastBytes; + } + if (!nextIsChar && matchByte != currentByte) + { + // try Literal + rep0 + var t = Math.Min(numAvailableBytesFull - 1, _numFastBytes); + var lenTest2 = _matchFinder.GetMatchLen(0, _reps[0], t); + if (lenTest2 >= 2) { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(state, posState); - if (shortRepPrice <= nextOptimum._price) + var state2 = state; + state2.UpdateChar(); + var posStateNext = (position + 1) & _posStateMask; + var nextRepMatchPrice = + curAnd1Price + + _isMatch[ + (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext + ].GetPrice1() + + _isRep[state2._index].GetPrice1(); { - nextOptimum._price = shortRepPrice; - nextOptimum._posPrev = cur; - nextOptimum.MakeAsShortRep(); - nextIsChar = true; - } - } - - UInt32 numAvailableBytesFull = _matchFinder.GetNumAvailableBytes() + 1; - numAvailableBytesFull = Math.Min(K_NUM_OPTS - 1 - cur, numAvailableBytesFull); - numAvailableBytes = numAvailableBytesFull; - - if (numAvailableBytes < 2) - { - continue; - } - if (numAvailableBytes > _numFastBytes) - { - numAvailableBytes = _numFastBytes; - } - if (!nextIsChar && matchByte != currentByte) - { - // try Literal + rep0 - UInt32 t = Math.Min(numAvailableBytesFull - 1, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen(0, _reps[0], t); - if (lenTest2 >= 2) - { - Base.State state2 = state; - state2.UpdateChar(); - UInt32 posStateNext = (position + 1) & _posStateMask; - UInt32 nextRepMatchPrice = curAnd1Price + - _isMatch[(state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext] - .GetPrice1() + - _isRep[state2._index].GetPrice1(); - { - UInt32 offset = cur + 1 + lenTest2; - while (lenEnd < offset) - { - _optimum[++lenEnd]._price = K_IFINITY_PRICE; - } - UInt32 curAndLenPrice = nextRepMatchPrice + GetRepPrice( - 0, lenTest2, state2, posStateNext); - Optimal optimum = _optimum[offset]; - if (curAndLenPrice < optimum._price) - { - optimum._price = curAndLenPrice; - optimum._posPrev = cur + 1; - optimum._backPrev = 0; - optimum._prev1IsChar = true; - optimum._prev2 = false; - } - } - } - } - - UInt32 startLen = 2; // speed optimization - - for (UInt32 repIndex = 0; repIndex < Base.K_NUM_REP_DISTANCES; repIndex++) - { - UInt32 lenTest = _matchFinder.GetMatchLen(0 - 1, _reps[repIndex], numAvailableBytes); - if (lenTest < 2) - { - continue; - } - UInt32 lenTestTemp = lenTest; - do - { - while (lenEnd < cur + lenTest) + var offset = cur + 1 + lenTest2; + while (lenEnd < offset) { _optimum[++lenEnd]._price = K_IFINITY_PRICE; } - UInt32 curAndLenPrice = repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState); - Optimal optimum = _optimum[cur + lenTest]; + var curAndLenPrice = + nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + var optimum = _optimum[offset]; if (curAndLenPrice < optimum._price) { optimum._price = curAndLenPrice; - optimum._posPrev = cur; - optimum._backPrev = repIndex; - optimum._prev1IsChar = false; + optimum._posPrev = cur + 1; + optimum._backPrev = 0; + optimum._prev1IsChar = true; + optimum._prev2 = false; } } - while (--lenTest >= 2); - lenTest = lenTestTemp; + } + } - if (repIndex == 0) + uint startLen = 2; // speed optimization + + for (uint repIndex = 0; repIndex < Base.K_NUM_REP_DISTANCES; repIndex++) + { + var lenTest = _matchFinder.GetMatchLen(0 - 1, _reps[repIndex], numAvailableBytes); + if (lenTest < 2) + { + continue; + } + var lenTestTemp = lenTest; + do + { + while (lenEnd < cur + lenTest) { - startLen = lenTest + 1; + _optimum[++lenEnd]._price = K_IFINITY_PRICE; + } + var curAndLenPrice = + repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState); + var optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum._price) + { + optimum._price = curAndLenPrice; + optimum._posPrev = cur; + optimum._backPrev = repIndex; + optimum._prev1IsChar = false; + } + } while (--lenTest >= 2); + lenTest = lenTestTemp; + + if (repIndex == 0) + { + startLen = lenTest + 1; + } + + // if (_maxMode) + if (lenTest < numAvailableBytesFull) + { + var t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + var lenTest2 = _matchFinder.GetMatchLen((int)lenTest, _reps[repIndex], t); + if (lenTest2 >= 2) + { + var state2 = state; + state2.UpdateRep(); + var posStateNext = (position + lenTest) & _posStateMask; + var curAndLenCharPrice = + repMatchPrice + + GetRepPrice(repIndex, lenTest, state, posState) + + _isMatch[ + (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext + ].GetPrice0() + + _literalEncoder + .GetSubCoder( + position + lenTest, + _matchFinder.GetIndexByte((int)lenTest - 1 - 1) + ) + .GetPrice( + true, + _matchFinder.GetIndexByte( + (int)lenTest - 1 - (int)(_reps[repIndex] + 1) + ), + _matchFinder.GetIndexByte((int)lenTest - 1) + ); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + var nextMatchPrice = + curAndLenCharPrice + + _isMatch[ + (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext + ].GetPrice1(); + var nextRepMatchPrice = nextMatchPrice + _isRep[state2._index].GetPrice1(); + + // for(; lenTest2 >= 2; lenTest2--) + { + var offset = lenTest + 1 + lenTest2; + while (lenEnd < cur + offset) + { + _optimum[++lenEnd]._price = K_IFINITY_PRICE; + } + var curAndLenPrice = + nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); + var optimum = _optimum[cur + offset]; + if (curAndLenPrice < optimum._price) + { + optimum._price = curAndLenPrice; + optimum._posPrev = cur + lenTest + 1; + optimum._backPrev = 0; + optimum._prev1IsChar = true; + optimum._prev2 = true; + optimum._posPrev2 = cur; + optimum._backPrev2 = repIndex; + } + } + } + } + } + + if (newLen > numAvailableBytes) + { + newLen = numAvailableBytes; + for ( + numDistancePairs = 0; + newLen > _matchDistances[numDistancePairs]; + numDistancePairs += 2 + ) + { + ; + } + _matchDistances[numDistancePairs] = newLen; + numDistancePairs += 2; + } + if (newLen >= startLen) + { + normalMatchPrice = matchPrice + _isRep[state._index].GetPrice0(); + while (lenEnd < cur + newLen) + { + _optimum[++lenEnd]._price = K_IFINITY_PRICE; + } + + uint offs = 0; + while (startLen > _matchDistances[offs]) + { + offs += 2; + } + + for (var lenTest = startLen; ; lenTest++) + { + var curBack = _matchDistances[offs + 1]; + var curAndLenPrice = + normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState); + var optimum = _optimum[cur + lenTest]; + if (curAndLenPrice < optimum._price) + { + optimum._price = curAndLenPrice; + optimum._posPrev = cur; + optimum._backPrev = curBack + Base.K_NUM_REP_DISTANCES; + optimum._prev1IsChar = false; } - // if (_maxMode) - if (lenTest < numAvailableBytesFull) + if (lenTest == _matchDistances[offs]) { - UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, _reps[repIndex], t); - if (lenTest2 >= 2) + if (lenTest < numAvailableBytesFull) { - Base.State state2 = state; - state2.UpdateRep(); - UInt32 posStateNext = (position + lenTest) & _posStateMask; - UInt32 curAndLenCharPrice = - repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState) + - _isMatch[(state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext].GetPrice0() + - _literalEncoder.GetSubCoder(position + lenTest, - _matchFinder.GetIndexByte((Int32)lenTest - 1 - 1)) - .GetPrice(true, - _matchFinder.GetIndexByte( - (Int32)lenTest - 1 - (Int32)(_reps[repIndex] + 1)), - _matchFinder.GetIndexByte((Int32)lenTest - 1)); - state2.UpdateChar(); - posStateNext = (position + lenTest + 1) & _posStateMask; - UInt32 nextMatchPrice = curAndLenCharPrice + - _isMatch[(state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + posStateNext] - .GetPrice1(); - UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2._index].GetPrice1(); - - // for(; lenTest2 >= 2; lenTest2--) + var t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); + var lenTest2 = _matchFinder.GetMatchLen((int)lenTest, curBack, t); + if (lenTest2 >= 2) { - UInt32 offset = lenTest + 1 + lenTest2; + var state2 = state; + state2.UpdateMatch(); + var posStateNext = (position + lenTest) & _posStateMask; + var curAndLenCharPrice = + curAndLenPrice + + _isMatch[ + (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + + posStateNext + ].GetPrice0() + + _literalEncoder + .GetSubCoder( + position + lenTest, + _matchFinder.GetIndexByte((int)lenTest - 1 - 1) + ) + .GetPrice( + true, + _matchFinder.GetIndexByte( + (int)lenTest - (int)(curBack + 1) - 1 + ), + _matchFinder.GetIndexByte((int)lenTest - 1) + ); + state2.UpdateChar(); + posStateNext = (position + lenTest + 1) & _posStateMask; + var nextMatchPrice = + curAndLenCharPrice + + _isMatch[ + (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + + posStateNext + ].GetPrice1(); + var nextRepMatchPrice = + nextMatchPrice + _isRep[state2._index].GetPrice1(); + + var offset = lenTest + 1 + lenTest2; while (lenEnd < cur + offset) { _optimum[++lenEnd]._price = K_IFINITY_PRICE; } - UInt32 curAndLenPrice = nextRepMatchPrice + - GetRepPrice(0, lenTest2, state2, posStateNext); - Optimal optimum = _optimum[cur + offset]; + curAndLenPrice = + nextRepMatchPrice + + GetRepPrice(0, lenTest2, state2, posStateNext); + optimum = _optimum[cur + offset]; if (curAndLenPrice < optimum._price) { optimum._price = curAndLenPrice; @@ -1076,201 +1215,98 @@ namespace SharpCompress.Compressors.LZMA optimum._prev1IsChar = true; optimum._prev2 = true; optimum._posPrev2 = cur; - optimum._backPrev2 = repIndex; + optimum._backPrev2 = curBack + Base.K_NUM_REP_DISTANCES; } } } - } - } - - if (newLen > numAvailableBytes) - { - newLen = numAvailableBytes; - for (numDistancePairs = 0; newLen > _matchDistances[numDistancePairs]; numDistancePairs += 2) - { - ; - } - _matchDistances[numDistancePairs] = newLen; - numDistancePairs += 2; - } - if (newLen >= startLen) - { - normalMatchPrice = matchPrice + _isRep[state._index].GetPrice0(); - while (lenEnd < cur + newLen) - { - _optimum[++lenEnd]._price = K_IFINITY_PRICE; - } - - UInt32 offs = 0; - while (startLen > _matchDistances[offs]) - { offs += 2; - } - - for (UInt32 lenTest = startLen; ; lenTest++) - { - UInt32 curBack = _matchDistances[offs + 1]; - UInt32 curAndLenPrice = normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState); - Optimal optimum = _optimum[cur + lenTest]; - if (curAndLenPrice < optimum._price) + if (offs == numDistancePairs) { - optimum._price = curAndLenPrice; - optimum._posPrev = cur; - optimum._backPrev = curBack + Base.K_NUM_REP_DISTANCES; - optimum._prev1IsChar = false; - } - - if (lenTest == _matchDistances[offs]) - { - if (lenTest < numAvailableBytesFull) - { - UInt32 t = Math.Min(numAvailableBytesFull - 1 - lenTest, _numFastBytes); - UInt32 lenTest2 = _matchFinder.GetMatchLen((Int32)lenTest, curBack, t); - if (lenTest2 >= 2) - { - Base.State state2 = state; - state2.UpdateMatch(); - UInt32 posStateNext = (position + lenTest) & _posStateMask; - UInt32 curAndLenCharPrice = curAndLenPrice + - _isMatch[ - (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + - posStateNext].GetPrice0() + - _literalEncoder.GetSubCoder(position + lenTest, - _matchFinder.GetIndexByte( - (Int32)lenTest - 1 - 1)) - . - GetPrice(true, - _matchFinder.GetIndexByte( - (Int32)lenTest - - (Int32)(curBack + 1) - 1), - _matchFinder.GetIndexByte( - (Int32)lenTest - 1)); - state2.UpdateChar(); - posStateNext = (position + lenTest + 1) & _posStateMask; - UInt32 nextMatchPrice = curAndLenCharPrice + - _isMatch[ - (state2._index << Base.K_NUM_POS_STATES_BITS_MAX) + - posStateNext].GetPrice1(); - UInt32 nextRepMatchPrice = nextMatchPrice + _isRep[state2._index].GetPrice1(); - - UInt32 offset = lenTest + 1 + lenTest2; - while (lenEnd < cur + offset) - { - _optimum[++lenEnd]._price = K_IFINITY_PRICE; - } - curAndLenPrice = nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext); - optimum = _optimum[cur + offset]; - if (curAndLenPrice < optimum._price) - { - optimum._price = curAndLenPrice; - optimum._posPrev = cur + lenTest + 1; - optimum._backPrev = 0; - optimum._prev1IsChar = true; - optimum._prev2 = true; - optimum._posPrev2 = cur; - optimum._backPrev2 = curBack + Base.K_NUM_REP_DISTANCES; - } - } - } - offs += 2; - if (offs == numDistancePairs) - { - break; - } + break; } } } } } + } - private bool ChangePair(UInt32 smallDist, UInt32 bigDist) + private bool ChangePair(uint smallDist, uint bigDist) + { + const int kDif = 7; + return (smallDist < ((uint)(1) << (32 - kDif)) && bigDist >= (smallDist << kDif)); + } + + private void WriteEndMarker(uint posState) + { + if (!_writeEndMark) { - const int kDif = 7; - return (smallDist < ((UInt32)(1) << (32 - kDif)) && bigDist >= (smallDist << kDif)); + return; } - private void WriteEndMarker(UInt32 posState) - { - if (!_writeEndMark) - { - return; - } + _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode( + _rangeEncoder, + 1 + ); + _isRep[_state._index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + var len = Base.K_MATCH_MIN_LEN; + _lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState); + uint posSlot = (1 << Base.K_NUM_POS_SLOT_BITS) - 1; + var lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + var footerBits = 30; + var posReduced = (((uint)1) << footerBits) - 1; + _rangeEncoder.EncodeDirectBits( + posReduced >> Base.K_NUM_ALIGN_BITS, + footerBits - Base.K_NUM_ALIGN_BITS + ); + _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.K_ALIGN_MASK); + } - _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode(_rangeEncoder, 1); - _isRep[_state._index].Encode(_rangeEncoder, 0); - _state.UpdateMatch(); - UInt32 len = Base.K_MATCH_MIN_LEN; - _lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState); - UInt32 posSlot = (1 << Base.K_NUM_POS_SLOT_BITS) - 1; - UInt32 lenToPosState = Base.GetLenToPosState(len); - _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); - int footerBits = 30; - UInt32 posReduced = (((UInt32)1) << footerBits) - 1; - _rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS, footerBits - Base.K_NUM_ALIGN_BITS); - _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.K_ALIGN_MASK); + private void Flush(uint nowPos) + { + ReleaseMfStream(); + WriteEndMarker(nowPos & _posStateMask); + _rangeEncoder.FlushData(); + _rangeEncoder.FlushStream(); + } + + public void CodeOneBlock(out long inSize, out long outSize, out bool finished) + { + inSize = 0; + outSize = 0; + finished = true; + + if (_inStream != null) + { + _matchFinder.SetStream(_inStream); + _needReleaseMfStream = true; + _inStream = null; } - private void Flush(UInt32 nowPos) + if (_finished) { - ReleaseMfStream(); - WriteEndMarker(nowPos & _posStateMask); - _rangeEncoder.FlushData(); - _rangeEncoder.FlushStream(); + return; } + _finished = true; - public void CodeOneBlock(out Int64 inSize, out Int64 outSize, out bool finished) + var progressPosValuePrev = _nowPos64; + if (_nowPos64 == 0) { - inSize = 0; - outSize = 0; - finished = true; - - if (_inStream != null) + if (_trainSize > 0) { - _matchFinder.SetStream(_inStream); - _needReleaseMfStream = true; - _inStream = null; - } - - if (_finished) - { - return; - } - _finished = true; - - Int64 progressPosValuePrev = _nowPos64; - if (_nowPos64 == 0) - { - if (_trainSize > 0) + for ( + ; + _trainSize > 0 && (!_processingMode || !_matchFinder.IsDataStarved); + _trainSize-- + ) { - for (; _trainSize > 0 && (!_processingMode || !_matchFinder.IsDataStarved); _trainSize--) - { - _matchFinder.Skip(1); - } - if (_trainSize == 0) - { - _previousByte = _matchFinder.GetIndexByte(-1); - } + _matchFinder.Skip(1); } - if (_processingMode && _matchFinder.IsDataStarved) + if (_trainSize == 0) { - _finished = false; - return; + _previousByte = _matchFinder.GetIndexByte(-1); } - if (_matchFinder.GetNumAvailableBytes() == 0) - { - Flush((UInt32)_nowPos64); - return; - } - UInt32 len, numDistancePairs; // it's not used - ReadMatchDistances(out len, out numDistancePairs); - UInt32 posState = (UInt32)(_nowPos64) & _posStateMask; - _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode(_rangeEncoder, 0); - _state.UpdateChar(); - Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); - _literalEncoder.GetSubCoder((UInt32)(_nowPos64), _previousByte).Encode(_rangeEncoder, curByte); - _previousByte = curByte; - _additionalOffset--; - _nowPos64++; } if (_processingMode && _matchFinder.IsDataStarved) { @@ -1279,526 +1315,556 @@ namespace SharpCompress.Compressors.LZMA } if (_matchFinder.GetNumAvailableBytes() == 0) { - Flush((UInt32)_nowPos64); + Flush((uint)_nowPos64); return; } - while (true) + // it's not used + ReadMatchDistances(out var len, out var numDistancePairs); + var posState = (uint)(_nowPos64) & _posStateMask; + _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode( + _rangeEncoder, + 0 + ); + _state.UpdateChar(); + var curByte = _matchFinder.GetIndexByte((int)(0 - _additionalOffset)); + _literalEncoder + .GetSubCoder((uint)(_nowPos64), _previousByte) + .Encode(_rangeEncoder, curByte); + _previousByte = curByte; + _additionalOffset--; + _nowPos64++; + } + if (_processingMode && _matchFinder.IsDataStarved) + { + _finished = false; + return; + } + if (_matchFinder.GetNumAvailableBytes() == 0) + { + Flush((uint)_nowPos64); + return; + } + while (true) + { + if (_processingMode && _matchFinder.IsDataStarved) { + _finished = false; + return; + } + + var len = GetOptimum((uint)_nowPos64, out var pos); + + var posState = ((uint)_nowPos64) & _posStateMask; + var complexState = (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState; + if (len == 1 && pos == 0xFFFFFFFF) + { + _isMatch[complexState].Encode(_rangeEncoder, 0); + var curByte = _matchFinder.GetIndexByte((int)(0 - _additionalOffset)); + var subCoder = _literalEncoder.GetSubCoder((uint)_nowPos64, _previousByte); + if (!_state.IsCharState()) + { + var matchByte = _matchFinder.GetIndexByte( + (int)(0 - _repDistances[0] - 1 - _additionalOffset) + ); + subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte); + } + else + { + subCoder.Encode(_rangeEncoder, curByte); + } + _previousByte = curByte; + _state.UpdateChar(); + } + else + { + _isMatch[complexState].Encode(_rangeEncoder, 1); + if (pos < Base.K_NUM_REP_DISTANCES) + { + _isRep[_state._index].Encode(_rangeEncoder, 1); + if (pos == 0) + { + _isRepG0[_state._index].Encode(_rangeEncoder, 0); + if (len == 1) + { + _isRep0Long[complexState].Encode(_rangeEncoder, 0); + } + else + { + _isRep0Long[complexState].Encode(_rangeEncoder, 1); + } + } + else + { + _isRepG0[_state._index].Encode(_rangeEncoder, 1); + if (pos == 1) + { + _isRepG1[_state._index].Encode(_rangeEncoder, 0); + } + else + { + _isRepG1[_state._index].Encode(_rangeEncoder, 1); + _isRepG2[_state._index].Encode(_rangeEncoder, pos - 2); + } + } + if (len == 1) + { + _state.UpdateShortRep(); + } + else + { + _repMatchLenEncoder.Encode( + _rangeEncoder, + len - Base.K_MATCH_MIN_LEN, + posState + ); + _state.UpdateRep(); + } + var distance = _repDistances[pos]; + if (pos != 0) + { + for (var i = pos; i >= 1; i--) + { + _repDistances[i] = _repDistances[i - 1]; + } + _repDistances[0] = distance; + } + } + else + { + _isRep[_state._index].Encode(_rangeEncoder, 0); + _state.UpdateMatch(); + _lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState); + pos -= Base.K_NUM_REP_DISTANCES; + var posSlot = GetPosSlot(pos); + var lenToPosState = Base.GetLenToPosState(len); + _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); + + if (posSlot >= Base.K_START_POS_MODEL_INDEX) + { + var footerBits = (int)((posSlot >> 1) - 1); + var baseVal = ((2 | (posSlot & 1)) << footerBits); + var posReduced = pos - baseVal; + + if (posSlot < Base.K_END_POS_MODEL_INDEX) + { + BitTreeEncoder.ReverseEncode( + _posEncoders, + baseVal - posSlot - 1, + _rangeEncoder, + footerBits, + posReduced + ); + } + else + { + _rangeEncoder.EncodeDirectBits( + posReduced >> Base.K_NUM_ALIGN_BITS, + footerBits - Base.K_NUM_ALIGN_BITS + ); + _posAlignEncoder.ReverseEncode( + _rangeEncoder, + posReduced & Base.K_ALIGN_MASK + ); + _alignPriceCount++; + } + } + var distance = pos; + for (var i = Base.K_NUM_REP_DISTANCES - 1; i >= 1; i--) + { + _repDistances[i] = _repDistances[i - 1]; + } + _repDistances[0] = distance; + _matchPriceCount++; + } + _previousByte = _matchFinder.GetIndexByte((int)(len - 1 - _additionalOffset)); + } + _additionalOffset -= len; + _nowPos64 += len; + if (_additionalOffset == 0) + { + // if (!_fastMode) + if (_matchPriceCount >= (1 << 7)) + { + FillDistancesPrices(); + } + if (_alignPriceCount >= Base.K_ALIGN_TABLE_SIZE) + { + FillAlignPrices(); + } + inSize = _nowPos64; + outSize = _rangeEncoder.GetProcessedSizeAdd(); if (_processingMode && _matchFinder.IsDataStarved) { _finished = false; return; } - - UInt32 pos; - UInt32 len = GetOptimum((UInt32)_nowPos64, out pos); - - UInt32 posState = ((UInt32)_nowPos64) & _posStateMask; - UInt32 complexState = (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState; - if (len == 1 && pos == 0xFFFFFFFF) + if (_matchFinder.GetNumAvailableBytes() == 0) { - _isMatch[complexState].Encode(_rangeEncoder, 0); - Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset)); - LiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((UInt32)_nowPos64, _previousByte); - if (!_state.IsCharState()) - { - Byte matchByte = - _matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - _additionalOffset)); - subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte); - } - else - { - subCoder.Encode(_rangeEncoder, curByte); - } - _previousByte = curByte; - _state.UpdateChar(); + Flush((uint)_nowPos64); + return; } - else + + if (_nowPos64 - progressPosValuePrev >= (1 << 12)) { - _isMatch[complexState].Encode(_rangeEncoder, 1); - if (pos < Base.K_NUM_REP_DISTANCES) - { - _isRep[_state._index].Encode(_rangeEncoder, 1); - if (pos == 0) - { - _isRepG0[_state._index].Encode(_rangeEncoder, 0); - if (len == 1) - { - _isRep0Long[complexState].Encode(_rangeEncoder, 0); - } - else - { - _isRep0Long[complexState].Encode(_rangeEncoder, 1); - } - } - else - { - _isRepG0[_state._index].Encode(_rangeEncoder, 1); - if (pos == 1) - { - _isRepG1[_state._index].Encode(_rangeEncoder, 0); - } - else - { - _isRepG1[_state._index].Encode(_rangeEncoder, 1); - _isRepG2[_state._index].Encode(_rangeEncoder, pos - 2); - } - } - if (len == 1) - { - _state.UpdateShortRep(); - } - else - { - _repMatchLenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState); - _state.UpdateRep(); - } - UInt32 distance = _repDistances[pos]; - if (pos != 0) - { - for (UInt32 i = pos; i >= 1; i--) - { - _repDistances[i] = _repDistances[i - 1]; - } - _repDistances[0] = distance; - } - } - else - { - _isRep[_state._index].Encode(_rangeEncoder, 0); - _state.UpdateMatch(); - _lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState); - pos -= Base.K_NUM_REP_DISTANCES; - UInt32 posSlot = GetPosSlot(pos); - UInt32 lenToPosState = Base.GetLenToPosState(len); - _posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot); - - if (posSlot >= Base.K_START_POS_MODEL_INDEX) - { - int footerBits = (int)((posSlot >> 1) - 1); - UInt32 baseVal = ((2 | (posSlot & 1)) << footerBits); - UInt32 posReduced = pos - baseVal; - - if (posSlot < Base.K_END_POS_MODEL_INDEX) - { - BitTreeEncoder.ReverseEncode(_posEncoders, - baseVal - posSlot - 1, _rangeEncoder, footerBits, - posReduced); - } - else - { - _rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS, - footerBits - Base.K_NUM_ALIGN_BITS); - _posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.K_ALIGN_MASK); - _alignPriceCount++; - } - } - UInt32 distance = pos; - for (UInt32 i = Base.K_NUM_REP_DISTANCES - 1; i >= 1; i--) - { - _repDistances[i] = _repDistances[i - 1]; - } - _repDistances[0] = distance; - _matchPriceCount++; - } - _previousByte = _matchFinder.GetIndexByte((Int32)(len - 1 - _additionalOffset)); - } - _additionalOffset -= len; - _nowPos64 += len; - if (_additionalOffset == 0) - { - // if (!_fastMode) - if (_matchPriceCount >= (1 << 7)) - { - FillDistancesPrices(); - } - if (_alignPriceCount >= Base.K_ALIGN_TABLE_SIZE) - { - FillAlignPrices(); - } - inSize = _nowPos64; - outSize = _rangeEncoder.GetProcessedSizeAdd(); - if (_processingMode && _matchFinder.IsDataStarved) - { - _finished = false; - return; - } - if (_matchFinder.GetNumAvailableBytes() == 0) - { - Flush((UInt32)_nowPos64); - return; - } - - if (_nowPos64 - progressPosValuePrev >= (1 << 12)) - { - _finished = false; - finished = false; - return; - } + _finished = false; + finished = false; + return; } } } + } - private void ReleaseMfStream() - { - if (_matchFinder != null && _needReleaseMfStream) - { - _matchFinder.ReleaseStream(); - _needReleaseMfStream = false; - } - } - - private void SetOutStream(Stream outStream) - { - _rangeEncoder.SetStream(outStream); - } - - private void ReleaseOutStream() - { - _rangeEncoder.ReleaseStream(); - } - - private void ReleaseStreams() - { - ReleaseMfStream(); - ReleaseOutStream(); - } - - public void SetStreams(Stream inStream, Stream outStream, - Int64 inSize, Int64 outSize) - { - _inStream = inStream; - _finished = false; - Create(); - SetOutStream(outStream); - Init(); - _matchFinder.Init(); - - // if (!_fastMode) - { - FillDistancesPrices(); - FillAlignPrices(); - } - - _lenEncoder.SetTableSize(_numFastBytes + 1 - Base.K_MATCH_MIN_LEN); - _lenEncoder.UpdateTables((UInt32)1 << _posStateBits); - _repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.K_MATCH_MIN_LEN); - _repMatchLenEncoder.UpdateTables((UInt32)1 << _posStateBits); - - _nowPos64 = 0; - } - - public void Code(Stream inStream, Stream outStream, - Int64 inSize, Int64 outSize, ICodeProgress progress) + private void ReleaseMfStream() + { + if (_matchFinder != null && _needReleaseMfStream) { + _matchFinder.ReleaseStream(); _needReleaseMfStream = false; - _processingMode = false; - try + } + } + + private void SetOutStream(Stream outStream) => _rangeEncoder.SetStream(outStream); + + private void ReleaseOutStream() => _rangeEncoder.ReleaseStream(); + + private void ReleaseStreams() + { + ReleaseMfStream(); + ReleaseOutStream(); + } + + public void SetStreams(Stream inStream, Stream outStream, long inSize, long outSize) + { + _inStream = inStream; + _finished = false; + Create(); + SetOutStream(outStream); + Init(); + _matchFinder.Init(); + + // if (!_fastMode) + { + FillDistancesPrices(); + FillAlignPrices(); + } + + _lenEncoder.SetTableSize(_numFastBytes + 1 - Base.K_MATCH_MIN_LEN); + _lenEncoder.UpdateTables((uint)1 << _posStateBits); + _repMatchLenEncoder.SetTableSize(_numFastBytes + 1 - Base.K_MATCH_MIN_LEN); + _repMatchLenEncoder.UpdateTables((uint)1 << _posStateBits); + + _nowPos64 = 0; + } + + public void Code( + Stream inStream, + Stream outStream, + long inSize, + long outSize, + ICodeProgress progress + ) + { + _needReleaseMfStream = false; + _processingMode = false; + try + { + SetStreams(inStream, outStream, inSize, outSize); + while (true) { - SetStreams(inStream, outStream, inSize, outSize); - while (true) + CodeOneBlock(out var processedInSize, out var processedOutSize, out var finished); + if (finished) { - Int64 processedInSize; - Int64 processedOutSize; - bool finished; - CodeOneBlock(out processedInSize, out processedOutSize, out finished); - if (finished) - { - return; - } - if (progress != null) - { - progress.SetProgress(processedInSize, processedOutSize); - } + return; + } + progress?.SetProgress(processedInSize, processedOutSize); + } + } + finally + { + ReleaseStreams(); + } + } + + public long Code(Stream inStream, bool final) + { + _matchFinder.SetStream(inStream); + _processingMode = !final; + try + { + while (true) + { + CodeOneBlock(out var processedInSize, out var processedOutSize, out var finished); + if (finished) + { + return processedInSize; } } - finally + } + finally + { + _matchFinder.ReleaseStream(); + if (final) { ReleaseStreams(); } } + } - public long Code(Stream inStream, bool final) + public void Train(Stream trainStream) + { + if (_nowPos64 > 0) { - _matchFinder.SetStream(inStream); - _processingMode = !final; - try - { - while (true) - { - Int64 processedInSize; - Int64 processedOutSize; - bool finished; - CodeOneBlock(out processedInSize, out processedOutSize, out finished); - if (finished) - { - return processedInSize; - } - } - } - finally - { - _matchFinder.ReleaseStream(); - if (final) - { - ReleaseStreams(); - } - } + throw new InvalidOperationException(); } - - public void Train(Stream trainStream) + _trainSize = (uint)trainStream.Length; + if (_trainSize > 0) { - if (_nowPos64 > 0) + _matchFinder.SetStream(trainStream); + for (; _trainSize > 0 && !_matchFinder.IsDataStarved; _trainSize--) { - throw new InvalidOperationException(); + _matchFinder.Skip(1); } - _trainSize = (uint)trainStream.Length; - if (_trainSize > 0) + if (_trainSize == 0) { - _matchFinder.SetStream(trainStream); - for (; _trainSize > 0 && !_matchFinder.IsDataStarved; _trainSize--) - { - _matchFinder.Skip(1); - } - if (_trainSize == 0) - { - _previousByte = _matchFinder.GetIndexByte(-1); - } - _matchFinder.ReleaseStream(); + _previousByte = _matchFinder.GetIndexByte(-1); } - } - - private const int K_PROP_SIZE = 5; - private readonly Byte[] _properties = new Byte[K_PROP_SIZE]; - - public void WriteCoderProperties(Stream outStream) - { - WriteCoderProperties(_properties); - outStream.Write(_properties, 0, K_PROP_SIZE); - } - - public void WriteCoderProperties(Span span) - { - span[0] = (byte)((_posStateBits * 5 + _numLiteralPosStateBits) * 9 + _numLiteralContextBits); - for (int i = 0; i < 4; i++) - { - span[1 + i] = (byte)((_dictionarySize >> (8 * i)) & 0xFF); - } - } - - private readonly UInt32[] _tempPrices = new UInt32[Base.K_NUM_FULL_DISTANCES]; - private UInt32 _matchPriceCount; - - private void FillDistancesPrices() - { - for (UInt32 i = Base.K_START_POS_MODEL_INDEX; i < Base.K_NUM_FULL_DISTANCES; i++) - { - UInt32 posSlot = GetPosSlot(i); - int footerBits = (int)((posSlot >> 1) - 1); - UInt32 baseVal = ((2 | (posSlot & 1)) << footerBits); - _tempPrices[i] = BitTreeEncoder.ReverseGetPrice(_posEncoders, - baseVal - posSlot - 1, footerBits, i - baseVal); - } - - for (UInt32 lenToPosState = 0; lenToPosState < Base.K_NUM_LEN_TO_POS_STATES; lenToPosState++) - { - UInt32 posSlot; - BitTreeEncoder encoder = _posSlotEncoder[lenToPosState]; - - UInt32 st = (lenToPosState << Base.K_NUM_POS_SLOT_BITS); - for (posSlot = 0; posSlot < _distTableSize; posSlot++) - { - _posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot); - } - for (posSlot = Base.K_END_POS_MODEL_INDEX; posSlot < _distTableSize; posSlot++) - { - _posSlotPrices[st + posSlot] += ((((posSlot >> 1) - 1) - Base.K_NUM_ALIGN_BITS) << - BitEncoder.K_NUM_BIT_PRICE_SHIFT_BITS); - } - - UInt32 st2 = lenToPosState * Base.K_NUM_FULL_DISTANCES; - UInt32 i; - for (i = 0; i < Base.K_START_POS_MODEL_INDEX; i++) - { - _distancesPrices[st2 + i] = _posSlotPrices[st + i]; - } - for (; i < Base.K_NUM_FULL_DISTANCES; i++) - { - _distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + _tempPrices[i]; - } - } - _matchPriceCount = 0; - } - - private void FillAlignPrices() - { - for (UInt32 i = 0; i < Base.K_ALIGN_TABLE_SIZE; i++) - { - _alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i); - } - _alignPriceCount = 0; - } - - private static readonly string[] K_MATCH_FINDER_I_DS = - { - "BT2", - "BT4" - }; - - private static int FindMatchFinder(string s) - { - for (int m = 0; m < K_MATCH_FINDER_I_DS.Length; m++) - { - if (string.Equals(s, K_MATCH_FINDER_I_DS[m], StringComparison.OrdinalIgnoreCase)) - { - return m; - } - } - return -1; - } - - public void SetCoderProperties(CoderPropId[] propIDs, object[] properties) - { - for (UInt32 i = 0; i < properties.Length; i++) - { - object prop = properties[i]; - switch (propIDs[i]) - { - case CoderPropId.NumFastBytes: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - Int32 numFastBytes = (Int32)prop; - if (numFastBytes < 5 || numFastBytes > Base.K_MATCH_MAX_LEN) - { - throw new InvalidParamException(); - } - _numFastBytes = (UInt32)numFastBytes; - break; - } - case CoderPropId.Algorithm: - { - /* - if (!(prop is Int32)) - throw new InvalidParamException(); - Int32 maximize = (Int32)prop; - _fastMode = (maximize == 0); - _maxMode = (maximize >= 2); - */ - break; - } - case CoderPropId.MatchFinder: - { - if (!(prop is String)) - { - throw new InvalidParamException(); - } - EMatchFinderType matchFinderIndexPrev = _matchFinderType; - int m = FindMatchFinder(((string)prop)); - if (m < 0) - { - throw new InvalidParamException(); - } - _matchFinderType = (EMatchFinderType)m; - if (_matchFinder != null && matchFinderIndexPrev != _matchFinderType) - { - _dictionarySizePrev = 0xFFFFFFFF; - _matchFinder = null; - } - break; - } - case CoderPropId.DictionarySize: - { - const int kDicLogSizeMaxCompress = 30; - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - ; - Int32 dictionarySize = (Int32)prop; - if (dictionarySize < (UInt32)(1 << Base.K_DIC_LOG_SIZE_MIN) || - dictionarySize > (UInt32)(1 << kDicLogSizeMaxCompress)) - { - throw new InvalidParamException(); - } - _dictionarySize = (UInt32)dictionarySize; - int dicLogSize; - for (dicLogSize = 0; dicLogSize < (UInt32)kDicLogSizeMaxCompress; dicLogSize++) - { - if (dictionarySize <= ((UInt32)(1) << dicLogSize)) - { - break; - } - } - _distTableSize = (UInt32)dicLogSize * 2; - break; - } - case CoderPropId.PosStateBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - Int32 v = (Int32)prop; - if (v < 0 || v > (UInt32)Base.K_NUM_POS_STATES_BITS_ENCODING_MAX) - { - throw new InvalidParamException(); - } - _posStateBits = v; - _posStateMask = (((UInt32)1) << _posStateBits) - 1; - break; - } - case CoderPropId.LitPosBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - Int32 v = (Int32)prop; - if (v < 0 || v > Base.K_NUM_LIT_POS_STATES_BITS_ENCODING_MAX) - { - throw new InvalidParamException(); - } - _numLiteralPosStateBits = v; - break; - } - case CoderPropId.LitContextBits: - { - if (!(prop is Int32)) - { - throw new InvalidParamException(); - } - Int32 v = (Int32)prop; - if (v < 0 || v > Base.K_NUM_LIT_CONTEXT_BITS_MAX) - { - throw new InvalidParamException(); - } - ; - _numLiteralContextBits = v; - break; - } - case CoderPropId.EndMarker: - { - if (!(prop is Boolean)) - { - throw new InvalidParamException(); - } - SetWriteEndMarkerMode((Boolean)prop); - break; - } - default: - throw new InvalidParamException(); - } - } - } - - private uint _trainSize; - - public void SetTrainSize(uint trainSize) - { - _trainSize = trainSize; + _matchFinder.ReleaseStream(); } } + + private const int K_PROP_SIZE = 5; + private readonly byte[] _properties = new byte[K_PROP_SIZE]; + + public void WriteCoderProperties(Stream outStream) + { + WriteCoderProperties(_properties); + outStream.Write(_properties, 0, K_PROP_SIZE); + } + + public void WriteCoderProperties(Span span) + { + span[0] = (byte)( + (((_posStateBits * 5) + _numLiteralPosStateBits) * 9) + _numLiteralContextBits + ); + for (var i = 0; i < 4; i++) + { + span[1 + i] = (byte)((_dictionarySize >> (8 * i)) & 0xFF); + } + } + + private readonly uint[] _tempPrices = new uint[Base.K_NUM_FULL_DISTANCES]; + private uint _matchPriceCount; + + private void FillDistancesPrices() + { + for (var i = Base.K_START_POS_MODEL_INDEX; i < Base.K_NUM_FULL_DISTANCES; i++) + { + var posSlot = GetPosSlot(i); + var footerBits = (int)((posSlot >> 1) - 1); + var baseVal = ((2 | (posSlot & 1)) << footerBits); + _tempPrices[i] = BitTreeEncoder.ReverseGetPrice( + _posEncoders, + baseVal - posSlot - 1, + footerBits, + i - baseVal + ); + } + + for (uint lenToPosState = 0; lenToPosState < Base.K_NUM_LEN_TO_POS_STATES; lenToPosState++) + { + uint posSlot; + var encoder = _posSlotEncoder[lenToPosState]; + + var st = (lenToPosState << Base.K_NUM_POS_SLOT_BITS); + for (posSlot = 0; posSlot < _distTableSize; posSlot++) + { + _posSlotPrices[st + posSlot] = encoder.GetPrice(posSlot); + } + for (posSlot = Base.K_END_POS_MODEL_INDEX; posSlot < _distTableSize; posSlot++) + { + _posSlotPrices[st + posSlot] += ( + (((posSlot >> 1) - 1) - Base.K_NUM_ALIGN_BITS) + << BitEncoder.K_NUM_BIT_PRICE_SHIFT_BITS + ); + } + + var st2 = lenToPosState * Base.K_NUM_FULL_DISTANCES; + uint i; + for (i = 0; i < Base.K_START_POS_MODEL_INDEX; i++) + { + _distancesPrices[st2 + i] = _posSlotPrices[st + i]; + } + for (; i < Base.K_NUM_FULL_DISTANCES; i++) + { + _distancesPrices[st2 + i] = _posSlotPrices[st + GetPosSlot(i)] + _tempPrices[i]; + } + } + _matchPriceCount = 0; + } + + private void FillAlignPrices() + { + for (uint i = 0; i < Base.K_ALIGN_TABLE_SIZE; i++) + { + _alignPrices[i] = _posAlignEncoder.ReverseGetPrice(i); + } + _alignPriceCount = 0; + } + + private static readonly string[] K_MATCH_FINDER_I_DS = { "BT2", "BT4" }; + + private static int FindMatchFinder(string s) + { + for (var m = 0; m < K_MATCH_FINDER_I_DS.Length; m++) + { + if (string.Equals(s, K_MATCH_FINDER_I_DS[m], StringComparison.OrdinalIgnoreCase)) + { + return m; + } + } + return -1; + } + + public void SetCoderProperties(CoderPropId[] propIDs, object[] properties) + { + for (uint i = 0; i < properties.Length; i++) + { + var prop = properties[i]; + switch (propIDs[i]) + { + case CoderPropId.NumFastBytes: + { + if (prop is not int) + { + throw new InvalidParamException(); + } + var numFastBytes = (int)prop; + if (numFastBytes < 5 || numFastBytes > Base.K_MATCH_MAX_LEN) + { + throw new InvalidParamException(); + } + _numFastBytes = (uint)numFastBytes; + break; + } + case CoderPropId.Algorithm: + { + /* + if (!(prop is Int32)) + throw new InvalidParamException(); + Int32 maximize = (Int32)prop; + _fastMode = (maximize == 0); + _maxMode = (maximize >= 2); + */ + break; + } + case CoderPropId.MatchFinder: + { + if (prop is not string) + { + throw new InvalidParamException(); + } + var matchFinderIndexPrev = _matchFinderType; + var m = FindMatchFinder(((string)prop)); + if (m < 0) + { + throw new InvalidParamException(); + } + _matchFinderType = (EMatchFinderType)m; + if (_matchFinder != null && matchFinderIndexPrev != _matchFinderType) + { + _dictionarySizePrev = 0xFFFFFFFF; + _matchFinder = null; + } + break; + } + case CoderPropId.DictionarySize: + { + const int kDicLogSizeMaxCompress = 30; + if (prop is not int) + { + throw new InvalidParamException(); + } + ; + var dictionarySize = (int)prop; + if ( + dictionarySize < (uint)(1 << Base.K_DIC_LOG_SIZE_MIN) + || dictionarySize > (uint)(1 << kDicLogSizeMaxCompress) + ) + { + throw new InvalidParamException(); + } + _dictionarySize = (uint)dictionarySize; + int dicLogSize; + for (dicLogSize = 0; dicLogSize < (uint)kDicLogSizeMaxCompress; dicLogSize++) + { + if (dictionarySize <= ((uint)(1) << dicLogSize)) + { + break; + } + } + _distTableSize = (uint)dicLogSize * 2; + break; + } + case CoderPropId.PosStateBits: + { + if (prop is not int) + { + throw new InvalidParamException(); + } + var v = (int)prop; + if (v < 0 || v > (uint)Base.K_NUM_POS_STATES_BITS_ENCODING_MAX) + { + throw new InvalidParamException(); + } + _posStateBits = v; + _posStateMask = (((uint)1) << _posStateBits) - 1; + break; + } + case CoderPropId.LitPosBits: + { + if (prop is not int) + { + throw new InvalidParamException(); + } + var v = (int)prop; + if (v < 0 || v > Base.K_NUM_LIT_POS_STATES_BITS_ENCODING_MAX) + { + throw new InvalidParamException(); + } + _numLiteralPosStateBits = v; + break; + } + case CoderPropId.LitContextBits: + { + if (prop is not int) + { + throw new InvalidParamException(); + } + var v = (int)prop; + if (v < 0 || v > Base.K_NUM_LIT_CONTEXT_BITS_MAX) + { + throw new InvalidParamException(); + } + ; + _numLiteralContextBits = v; + break; + } + case CoderPropId.EndMarker: + { + if (prop is not bool) + { + throw new InvalidParamException(); + } + SetWriteEndMarkerMode((bool)prop); + break; + } + default: + throw new InvalidParamException(); + } + } + } + + private uint _trainSize; + + public void SetTrainSize(uint trainSize) => _trainSize = trainSize; } diff --git a/src/SharpCompress/Compressors/LZMA/LzmaEncoderProperties.cs b/src/SharpCompress/Compressors/LZMA/LzmaEncoderProperties.cs index de7fa6f1..17c43ed6 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaEncoderProperties.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaEncoderProperties.cs @@ -1,55 +1,45 @@ -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +public class LzmaEncoderProperties { - public class LzmaEncoderProperties + internal CoderPropId[] _propIDs; + internal object[] _properties; + + public LzmaEncoderProperties() : this(false) { } + + public LzmaEncoderProperties(bool eos) : this(eos, 1 << 20) { } + + public LzmaEncoderProperties(bool eos, int dictionary) : this(eos, dictionary, 32) { } + + public LzmaEncoderProperties(bool eos, int dictionary, int numFastBytes) { - internal CoderPropId[] _propIDs; - internal object[] _properties; + var posStateBits = 2; + var litContextBits = 3; + var litPosBits = 0; + var algorithm = 2; + var mf = "bt4"; - public LzmaEncoderProperties() - : this(false) + _propIDs = new[] { - } - - public LzmaEncoderProperties(bool eos) - : this(eos, 1 << 20) + CoderPropId.DictionarySize, + CoderPropId.PosStateBits, + CoderPropId.LitContextBits, + CoderPropId.LitPosBits, + CoderPropId.Algorithm, + CoderPropId.NumFastBytes, + CoderPropId.MatchFinder, + CoderPropId.EndMarker + }; + _properties = new object[] { - } - - public LzmaEncoderProperties(bool eos, int dictionary) - : this(eos, dictionary, 32) - { - } - - public LzmaEncoderProperties(bool eos, int dictionary, int numFastBytes) - { - int posStateBits = 2; - int litContextBits = 3; - int litPosBits = 0; - int algorithm = 2; - string mf = "bt4"; - - _propIDs = new[] - { - CoderPropId.DictionarySize, - CoderPropId.PosStateBits, - CoderPropId.LitContextBits, - CoderPropId.LitPosBits, - CoderPropId.Algorithm, - CoderPropId.NumFastBytes, - CoderPropId.MatchFinder, - CoderPropId.EndMarker - }; - _properties = new object[] - { - dictionary, - posStateBits, - litContextBits, - litPosBits, - algorithm, - numFastBytes, - mf, - eos - }; - } + dictionary, + posStateBits, + litContextBits, + litPosBits, + algorithm, + numFastBytes, + mf, + eos + }; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index 53c77719..01fddf17 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -5,316 +5,316 @@ using System.Buffers.Binary; using System.IO; using SharpCompress.Compressors.LZMA.LZ; -namespace SharpCompress.Compressors.LZMA +namespace SharpCompress.Compressors.LZMA; + +public class LzmaStream : Stream { - public class LzmaStream : Stream + private readonly Stream _inputStream; + private readonly long _inputSize; + private readonly long _outputSize; + + private readonly int _dictionarySize; + private readonly OutWindow _outWindow = new OutWindow(); + private readonly RangeCoder.Decoder _rangeDecoder = new RangeCoder.Decoder(); + private Decoder _decoder; + + private long _position; + private bool _endReached; + private long _availableBytes; + private long _rangeDecoderLimit; + private long _inputPosition; + + // LZMA2 + private readonly bool _isLzma2; + private bool _uncompressedChunk; + private bool _needDictReset = true; + private bool _needProps = true; + + private readonly Encoder _encoder; + private bool _isDisposed; + + public LzmaStream(byte[] properties, Stream inputStream) + : this(properties, inputStream, -1, -1, null, properties.Length < 5) { } + + public LzmaStream(byte[] properties, Stream inputStream, long inputSize) + : this(properties, inputStream, inputSize, -1, null, properties.Length < 5) { } + + public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize) + : this(properties, inputStream, inputSize, outputSize, null, properties.Length < 5) { } + + public LzmaStream( + byte[] properties, + Stream inputStream, + long inputSize, + long outputSize, + Stream presetDictionary, + bool isLzma2 + ) { - private readonly Stream _inputStream; - private readonly long _inputSize; - private readonly long _outputSize; + _inputStream = inputStream; + _inputSize = inputSize; + _outputSize = outputSize; + _isLzma2 = isLzma2; - private readonly int _dictionarySize; - private readonly OutWindow _outWindow = new OutWindow(); - private readonly RangeCoder.Decoder _rangeDecoder = new RangeCoder.Decoder(); - private Decoder _decoder; - - private long _position; - private bool _endReached; - private long _availableBytes; - private long _rangeDecoderLimit; - private long _inputPosition; - - // LZMA2 - private readonly bool _isLzma2; - private bool _uncompressedChunk; - private bool _needDictReset = true; - private bool _needProps = true; - - private readonly Encoder _encoder; - private bool _isDisposed; - - public LzmaStream(byte[] properties, Stream inputStream) - : this(properties, inputStream, -1, -1, null, properties.Length < 5) + if (!isLzma2) { - } - - public LzmaStream(byte[] properties, Stream inputStream, long inputSize) - : this(properties, inputStream, inputSize, -1, null, properties.Length < 5) - { - } - - public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize) - : this(properties, inputStream, inputSize, outputSize, null, properties.Length < 5) - { - } - - public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize, - Stream presetDictionary, bool isLzma2) - { - _inputStream = inputStream; - _inputSize = inputSize; - _outputSize = outputSize; - _isLzma2 = isLzma2; - - if (!isLzma2) - { - _dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1)); - _outWindow.Create(_dictionarySize); - if (presetDictionary != null) - { - _outWindow.Train(presetDictionary); - } - - _rangeDecoder.Init(inputStream); - - _decoder = new Decoder(); - _decoder.SetDecoderProperties(properties); - Properties = properties; - - _availableBytes = outputSize < 0 ? long.MaxValue : outputSize; - _rangeDecoderLimit = inputSize; - } - else - { - _dictionarySize = 2 | (properties[0] & 1); - _dictionarySize <<= (properties[0] >> 1) + 11; - - _outWindow.Create(_dictionarySize); - if (presetDictionary != null) - { - _outWindow.Train(presetDictionary); - _needDictReset = false; - } - - Properties = new byte[1]; - _availableBytes = 0; - } - } - - public LzmaStream(LzmaEncoderProperties properties, bool isLzma2, Stream outputStream) - : this(properties, isLzma2, null, outputStream) - { - } - - public LzmaStream(LzmaEncoderProperties properties, bool isLzma2, Stream presetDictionary, Stream outputStream) - { - _isLzma2 = isLzma2; - _availableBytes = 0; - _endReached = true; - - if (isLzma2) - { - throw new NotImplementedException(); - } - - _encoder = new Encoder(); - _encoder.SetCoderProperties(properties._propIDs, properties._properties); - byte[] prop = new byte[5]; - _encoder.WriteCoderProperties(prop); - Properties = prop; - - _encoder.SetStreams(null, outputStream, -1, -1); + _dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1)); + _outWindow.Create(_dictionarySize); if (presetDictionary != null) { - _encoder.Train(presetDictionary); + _outWindow.Train(presetDictionary); } + + _rangeDecoder.Init(inputStream); + + _decoder = new Decoder(); + _decoder.SetDecoderProperties(properties); + Properties = properties; + + _availableBytes = outputSize < 0 ? long.MaxValue : outputSize; + _rangeDecoderLimit = inputSize; } - - public override bool CanRead => _encoder == null; - - public override bool CanSeek => false; - - public override bool CanWrite => _encoder != null; - - public override void Flush() + else { - } + _dictionarySize = 2 | (properties[0] & 1); + _dictionarySize <<= (properties[0] >> 1) + 11; - protected override void Dispose(bool disposing) - { - if (_isDisposed) + _outWindow.Create(_dictionarySize); + if (presetDictionary != null) { - return; - } - _isDisposed = true; - if (disposing) - { - if (_encoder != null) - { - _position = _encoder.Code(null, true); - } - _inputStream?.Dispose(); - } - base.Dispose(disposing); - } - - public override long Length => _position + _availableBytes; - - public override long Position { get => _position; set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_endReached) - { - return 0; - } - - int total = 0; - while (total < count) - { - if (_availableBytes == 0) - { - if (_isLzma2) - { - DecodeChunkHeader(); - } - else - { - _endReached = true; - } - if (_endReached) - { - break; - } - } - - int toProcess = count - total; - if (toProcess > _availableBytes) - { - toProcess = (int)_availableBytes; - } - - _outWindow.SetLimit(toProcess); - if (_uncompressedChunk) - { - _inputPosition += _outWindow.CopyStream(_inputStream, toProcess); - } - else if (_decoder.Code(_dictionarySize, _outWindow, _rangeDecoder) - && _outputSize < 0) - { - _availableBytes = _outWindow.AvailableBytes; - } - - int read = _outWindow.Read(buffer, offset, toProcess); - total += read; - offset += read; - _position += read; - _availableBytes -= read; - - if (_availableBytes == 0 && !_uncompressedChunk) - { - _rangeDecoder.ReleaseStream(); - if (!_rangeDecoder.IsFinished || (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit)) - { - throw new DataErrorException(); - } - _inputPosition += _rangeDecoder._total; - if (_outWindow.HasPending) - { - throw new DataErrorException(); - } - } - } - - if (_endReached) - { - if (_inputSize >= 0 && _inputPosition != _inputSize) - { - throw new DataErrorException(); - } - if (_outputSize >= 0 && _position != _outputSize) - { - throw new DataErrorException(); - } - } - - return total; - } - - private void DecodeChunkHeader() - { - int control = _inputStream.ReadByte(); - _inputPosition++; - - if (control == 0x00) - { - _endReached = true; - return; - } - - if (control >= 0xE0 || control == 0x01) - { - _needProps = true; + _outWindow.Train(presetDictionary); _needDictReset = false; - _outWindow.Reset(); - } - else if (_needDictReset) - { - throw new DataErrorException(); } - if (control >= 0x80) - { - _uncompressedChunk = false; - - _availableBytes = (control & 0x1F) << 16; - _availableBytes += (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; - _inputPosition += 2; - - _rangeDecoderLimit = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; - _inputPosition += 2; - - if (control >= 0xC0) - { - _needProps = false; - Properties[0] = (byte)_inputStream.ReadByte(); - _inputPosition++; - - _decoder = new Decoder(); - _decoder.SetDecoderProperties(Properties); - } - else if (_needProps) - { - throw new DataErrorException(); - } - else if (control >= 0xA0) - { - _decoder = new Decoder(); - _decoder.SetDecoderProperties(Properties); - } - - _rangeDecoder.Init(_inputStream); - } - else if (control > 0x02) - { - throw new DataErrorException(); - } - else - { - _uncompressedChunk = true; - _availableBytes = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; - _inputPosition += 2; - } + Properties = new byte[1]; + _availableBytes = 0; } + } - public override long Seek(long offset, SeekOrigin origin) + public LzmaStream(LzmaEncoderProperties properties, bool isLzma2, Stream outputStream) + : this(properties, isLzma2, null, outputStream) { } + + public LzmaStream( + LzmaEncoderProperties properties, + bool isLzma2, + Stream presetDictionary, + Stream outputStream + ) + { + _isLzma2 = isLzma2; + _availableBytes = 0; + _endReached = true; + + if (isLzma2) { - throw new NotSupportedException(); + throw new NotImplementedException(); } - public override void SetLength(long value) + _encoder = new Encoder(); + _encoder.SetCoderProperties(properties._propIDs, properties._properties); + var prop = new byte[5]; + _encoder.WriteCoderProperties(prop); + Properties = prop; + + _encoder.SetStreams(null, outputStream, -1, -1); + if (presetDictionary != null) { - throw new NotSupportedException(); + _encoder.Train(presetDictionary); } + } - public override void Write(byte[] buffer, int offset, int count) + public override bool CanRead => _encoder == null; + + public override bool CanSeek => false; + + public override bool CanWrite => _encoder != null; + + public override void Flush() { } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + if (disposing) { if (_encoder != null) { - _position = _encoder.Code(new MemoryStream(buffer, offset, count), false); + _position = _encoder.Code(null, true); + } + _inputStream?.Dispose(); + } + base.Dispose(disposing); + } + + public override long Length => _position + _availableBytes; + + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_endReached) + { + return 0; + } + + var total = 0; + while (total < count) + { + if (_availableBytes == 0) + { + if (_isLzma2) + { + DecodeChunkHeader(); + } + else + { + _endReached = true; + } + if (_endReached) + { + break; + } + } + + var toProcess = count - total; + if (toProcess > _availableBytes) + { + toProcess = (int)_availableBytes; + } + + _outWindow.SetLimit(toProcess); + if (_uncompressedChunk) + { + _inputPosition += _outWindow.CopyStream(_inputStream, toProcess); + } + else if (_decoder.Code(_dictionarySize, _outWindow, _rangeDecoder) && _outputSize < 0) + { + _availableBytes = _outWindow.AvailableBytes; + } + + var read = _outWindow.Read(buffer, offset, toProcess); + total += read; + offset += read; + _position += read; + _availableBytes -= read; + + if (_availableBytes == 0 && !_uncompressedChunk) + { + _rangeDecoder.ReleaseStream(); + if ( + !_rangeDecoder.IsFinished + || (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit) + ) + { + throw new DataErrorException(); + } + _inputPosition += _rangeDecoder._total; + if (_outWindow.HasPending) + { + throw new DataErrorException(); + } } } - public byte[] Properties { get; } = new byte[5]; + if (_endReached) + { + if (_inputSize >= 0 && _inputPosition != _inputSize) + { + throw new DataErrorException(); + } + if (_outputSize >= 0 && _position != _outputSize) + { + throw new DataErrorException(); + } + } + + return total; } + + private void DecodeChunkHeader() + { + var control = _inputStream.ReadByte(); + _inputPosition++; + + if (control == 0x00) + { + _endReached = true; + return; + } + + if (control >= 0xE0 || control == 0x01) + { + _needProps = true; + _needDictReset = false; + _outWindow.Reset(); + } + else if (_needDictReset) + { + throw new DataErrorException(); + } + + if (control >= 0x80) + { + _uncompressedChunk = false; + + _availableBytes = (control & 0x1F) << 16; + _availableBytes += (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; + _inputPosition += 2; + + _rangeDecoderLimit = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; + _inputPosition += 2; + + if (control >= 0xC0) + { + _needProps = false; + Properties[0] = (byte)_inputStream.ReadByte(); + _inputPosition++; + + _decoder = new Decoder(); + _decoder.SetDecoderProperties(Properties); + } + else if (_needProps) + { + throw new DataErrorException(); + } + else if (control >= 0xA0) + { + _decoder = new Decoder(); + _decoder.SetDecoderProperties(Properties); + } + + _rangeDecoder.Init(_inputStream); + } + else if (control > 0x02) + { + throw new DataErrorException(); + } + else + { + _uncompressedChunk = true; + _availableBytes = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1; + _inputPosition += 2; + } + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + if (_encoder != null) + { + _position = _encoder.Code(new MemoryStream(buffer, offset, count), false); + } + } + + public byte[] Properties { get; } = new byte[5]; } diff --git a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoder.cs b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoder.cs index 9ea026a9..87906809 100644 --- a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoder.cs +++ b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoder.cs @@ -1,254 +1,228 @@ #nullable disable -using System; using System.IO; -namespace SharpCompress.Compressors.LZMA.RangeCoder +namespace SharpCompress.Compressors.LZMA.RangeCoder; + +internal class Encoder { - internal class Encoder + public const uint K_TOP_VALUE = (1 << 24); + + private Stream _stream; + + public ulong _low; + public uint _range; + private uint _cacheSize; + private byte _cache; + + //long StartPosition; + + public void SetStream(Stream stream) => _stream = stream; + + public void ReleaseStream() => _stream = null; + + public void Init() { - public const uint K_TOP_VALUE = (1 << 24); + //StartPosition = Stream.Position; - private Stream _stream; + _low = 0; + _range = 0xFFFFFFFF; + _cacheSize = 1; + _cache = 0; + } - public UInt64 _low; - public uint _range; - private uint _cacheSize; - private byte _cache; - - //long StartPosition; - - public void SetStream(Stream stream) + public void FlushData() + { + for (var i = 0; i < 5; i++) { - _stream = stream; - } - - public void ReleaseStream() - { - _stream = null; - } - - public void Init() - { - //StartPosition = Stream.Position; - - _low = 0; - _range = 0xFFFFFFFF; - _cacheSize = 1; - _cache = 0; - } - - public void FlushData() - { - for (int i = 0; i < 5; i++) - { - ShiftLow(); - } - } - - public void FlushStream() - { - _stream.Flush(); - } - - public void CloseStream() - { - _stream.Dispose(); - } - - public void Encode(uint start, uint size, uint total) - { - _low += start * (_range /= total); - _range *= size; - while (_range < K_TOP_VALUE) - { - _range <<= 8; - ShiftLow(); - } - } - - public void ShiftLow() - { - if ((uint)_low < 0xFF000000 || (uint)(_low >> 32) == 1) - { - byte temp = _cache; - do - { - _stream.WriteByte((byte)(temp + (_low >> 32))); - temp = 0xFF; - } - while (--_cacheSize != 0); - _cache = (byte)(((uint)_low) >> 24); - } - _cacheSize++; - _low = ((uint)_low) << 8; - } - - public void EncodeDirectBits(uint v, int numTotalBits) - { - for (int i = numTotalBits - 1; i >= 0; i--) - { - _range >>= 1; - if (((v >> i) & 1) == 1) - { - _low += _range; - } - if (_range < K_TOP_VALUE) - { - _range <<= 8; - ShiftLow(); - } - } - } - - public void EncodeBit(uint size0, int numTotalBits, uint symbol) - { - uint newBound = (_range >> numTotalBits) * size0; - if (symbol == 0) - { - _range = newBound; - } - else - { - _low += newBound; - _range -= newBound; - } - while (_range < K_TOP_VALUE) - { - _range <<= 8; - ShiftLow(); - } - } - - public long GetProcessedSizeAdd() - { - return -1; - - //return _cacheSize + Stream.Position - StartPosition + 4; - // (long)Stream.GetProcessedSize(); + ShiftLow(); } } - internal class Decoder + public void FlushStream() => _stream.Flush(); + + public void CloseStream() => _stream.Dispose(); + + public void Encode(uint start, uint size, uint total) { - public const uint K_TOP_VALUE = (1 << 24); - public uint _range; - public uint _code; - - // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); - public Stream _stream; - public long _total; - - public void Init(Stream stream) + _low += start * (_range /= total); + _range *= size; + while (_range < K_TOP_VALUE) { - // Stream.Init(stream); - _stream = stream; + _range <<= 8; + ShiftLow(); + } + } - _code = 0; - _range = 0xFFFFFFFF; - for (int i = 0; i < 5; i++) + public void ShiftLow() + { + if ((uint)_low < 0xFF000000 || (uint)(_low >> 32) == 1) + { + var temp = _cache; + do { - _code = (_code << 8) | (byte)_stream.ReadByte(); - } - _total = 5; + _stream.WriteByte((byte)(temp + (_low >> 32))); + temp = 0xFF; + } while (--_cacheSize != 0); + _cache = (byte)(((uint)_low) >> 24); } + _cacheSize++; + _low = ((uint)_low) << 8; + } - public void ReleaseStream() + public void EncodeDirectBits(uint v, int numTotalBits) + { + for (var i = numTotalBits - 1; i >= 0; i--) { - // Stream.ReleaseStream(); - _stream = null; - } - - public void CloseStream() - { - _stream.Dispose(); - } - - public void Normalize() - { - while (_range < K_TOP_VALUE) + _range >>= 1; + if (((v >> i) & 1) == 1) { - _code = (_code << 8) | (byte)_stream.ReadByte(); - _range <<= 8; - _total++; + _low += _range; } - } - - public void Normalize2() - { if (_range < K_TOP_VALUE) { - _code = (_code << 8) | (byte)_stream.ReadByte(); _range <<= 8; + ShiftLow(); + } + } + } + + public void EncodeBit(uint size0, int numTotalBits, uint symbol) + { + var newBound = (_range >> numTotalBits) * size0; + if (symbol == 0) + { + _range = newBound; + } + else + { + _low += newBound; + _range -= newBound; + } + while (_range < K_TOP_VALUE) + { + _range <<= 8; + ShiftLow(); + } + } + + public long GetProcessedSizeAdd() => -1; + + //return _cacheSize + Stream.Position - StartPosition + 4; + // (long)Stream.GetProcessedSize(); +} + +internal class Decoder +{ + public const uint K_TOP_VALUE = (1 << 24); + public uint _range; + public uint _code; + + // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); + public Stream _stream; + public long _total; + + public void Init(Stream stream) + { + // Stream.Init(stream); + _stream = stream; + + _code = 0; + _range = 0xFFFFFFFF; + for (var i = 0; i < 5; i++) + { + _code = (_code << 8) | (byte)_stream.ReadByte(); + } + _total = 5; + } + + public void ReleaseStream() => + // Stream.ReleaseStream(); + _stream = null; + + public void CloseStream() => _stream.Dispose(); + + public void Normalize() + { + while (_range < K_TOP_VALUE) + { + _code = (_code << 8) | (byte)_stream.ReadByte(); + _range <<= 8; + _total++; + } + } + + public void Normalize2() + { + if (_range < K_TOP_VALUE) + { + _code = (_code << 8) | (byte)_stream.ReadByte(); + _range <<= 8; + _total++; + } + } + + public uint GetThreshold(uint total) => _code / (_range /= total); + + public void Decode(uint start, uint size) + { + _code -= start * _range; + _range *= size; + Normalize(); + } + + public uint DecodeDirectBits(int numTotalBits) + { + var range = _range; + var code = _code; + uint result = 0; + for (var i = numTotalBits; i > 0; i--) + { + range >>= 1; + /* + result <<= 1; + if (code >= range) + { + code -= range; + result |= 1; + } + */ + var t = (code - range) >> 31; + code -= range & (t - 1); + result = (result << 1) | (1 - t); + + if (range < K_TOP_VALUE) + { + code = (code << 8) | (byte)_stream.ReadByte(); + range <<= 8; _total++; } } - - public uint GetThreshold(uint total) - { - return _code / (_range /= total); - } - - public void Decode(uint start, uint size) - { - _code -= start * _range; - _range *= size; - Normalize(); - } - - public uint DecodeDirectBits(int numTotalBits) - { - uint range = _range; - uint code = _code; - uint result = 0; - for (int i = numTotalBits; i > 0; i--) - { - range >>= 1; - /* - result <<= 1; - if (code >= range) - { - code -= range; - result |= 1; - } - */ - uint t = (code - range) >> 31; - code -= range & (t - 1); - result = (result << 1) | (1 - t); - - if (range < K_TOP_VALUE) - { - code = (code << 8) | (byte)_stream.ReadByte(); - range <<= 8; - _total++; - } - } - _range = range; - _code = code; - return result; - } - - public uint DecodeBit(uint size0, int numTotalBits) - { - uint newBound = (_range >> numTotalBits) * size0; - uint symbol; - if (_code < newBound) - { - symbol = 0; - _range = newBound; - } - else - { - symbol = 1; - _code -= newBound; - _range -= newBound; - } - Normalize(); - return symbol; - } - - public bool IsFinished => _code == 0; - - // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } + _range = range; + _code = code; + return result; } -} \ No newline at end of file + + public uint DecodeBit(uint size0, int numTotalBits) + { + var newBound = (_range >> numTotalBits) * size0; + uint symbol; + if (_code < newBound) + { + symbol = 0; + _range = newBound; + } + else + { + symbol = 1; + _code -= newBound; + _range -= newBound; + } + Normalize(); + return symbol; + } + + public bool IsFinished => _code == 0; + + // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } +} diff --git a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.cs b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.cs index 2ab53aff..dd93b526 100644 --- a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.cs +++ b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.cs @@ -1,140 +1,130 @@ -using System; +namespace SharpCompress.Compressors.LZMA.RangeCoder; -namespace SharpCompress.Compressors.LZMA.RangeCoder +internal struct BitEncoder { - internal struct BitEncoder + public const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; + public const uint K_BIT_MODEL_TOTAL = (1 << K_NUM_BIT_MODEL_TOTAL_BITS); + private const int K_NUM_MOVE_BITS = 5; + private const int K_NUM_MOVE_REDUCING_BITS = 2; + public const int K_NUM_BIT_PRICE_SHIFT_BITS = 6; + + private uint _prob; + + public void Init() => _prob = K_BIT_MODEL_TOTAL >> 1; + + public void UpdateModel(uint symbol) { - public const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; - public const uint K_BIT_MODEL_TOTAL = (1 << K_NUM_BIT_MODEL_TOTAL_BITS); - private const int K_NUM_MOVE_BITS = 5; - private const int K_NUM_MOVE_REDUCING_BITS = 2; - public const int K_NUM_BIT_PRICE_SHIFT_BITS = 6; - - private uint _prob; - - public void Init() + if (symbol == 0) { - _prob = K_BIT_MODEL_TOTAL >> 1; + _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; } - - public void UpdateModel(uint symbol) + else { - if (symbol == 0) - { - _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; - } - else - { - _prob -= (_prob) >> K_NUM_MOVE_BITS; - } - } - - public void Encode(Encoder encoder, uint symbol) - { - // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol); - // UpdateModel(symbol); - uint newBound = (encoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; - if (symbol == 0) - { - encoder._range = newBound; - _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; - } - else - { - encoder._low += newBound; - encoder._range -= newBound; - _prob -= (_prob) >> K_NUM_MOVE_BITS; - } - if (encoder._range < Encoder.K_TOP_VALUE) - { - encoder._range <<= 8; - encoder.ShiftLow(); - } - } - - private static readonly UInt32[] PROB_PRICES = new UInt32[K_BIT_MODEL_TOTAL >> K_NUM_MOVE_REDUCING_BITS]; - - static BitEncoder() - { - const int kNumBits = (K_NUM_BIT_MODEL_TOTAL_BITS - K_NUM_MOVE_REDUCING_BITS); - for (int i = kNumBits - 1; i >= 0; i--) - { - UInt32 start = (UInt32)1 << (kNumBits - i - 1); - UInt32 end = (UInt32)1 << (kNumBits - i); - for (UInt32 j = start; j < end; j++) - { - PROB_PRICES[j] = ((UInt32)i << K_NUM_BIT_PRICE_SHIFT_BITS) + - (((end - j) << K_NUM_BIT_PRICE_SHIFT_BITS) >> (kNumBits - i - 1)); - } - } - } - - public uint GetPrice(uint symbol) - { - return PROB_PRICES[(((_prob - symbol) ^ ((-(int)symbol))) & (K_BIT_MODEL_TOTAL - 1)) >> K_NUM_MOVE_REDUCING_BITS]; - } - - public uint GetPrice0() - { - return PROB_PRICES[_prob >> K_NUM_MOVE_REDUCING_BITS]; - } - - public uint GetPrice1() - { - return PROB_PRICES[(K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_REDUCING_BITS]; + _prob -= (_prob) >> K_NUM_MOVE_BITS; } } - internal struct BitDecoder + public void Encode(Encoder encoder, uint symbol) { - public const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; - public const uint K_BIT_MODEL_TOTAL = (1 << K_NUM_BIT_MODEL_TOTAL_BITS); - private const int K_NUM_MOVE_BITS = 5; - - private uint _prob; - - public void UpdateModel(int numMoveBits, uint symbol) + // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol); + // UpdateModel(symbol); + var newBound = (encoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; + if (symbol == 0) { - if (symbol == 0) - { - _prob += (K_BIT_MODEL_TOTAL - _prob) >> numMoveBits; - } - else - { - _prob -= (_prob) >> numMoveBits; - } + encoder._range = newBound; + _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; } - - public void Init() + else { - _prob = K_BIT_MODEL_TOTAL >> 1; - } - - public uint Decode(Decoder rangeDecoder) - { - uint newBound = (rangeDecoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; - if (rangeDecoder._code < newBound) - { - rangeDecoder._range = newBound; - _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; - if (rangeDecoder._range < Decoder.K_TOP_VALUE) - { - rangeDecoder._code = (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte(); - rangeDecoder._range <<= 8; - rangeDecoder._total++; - } - return 0; - } - rangeDecoder._range -= newBound; - rangeDecoder._code -= newBound; + encoder._low += newBound; + encoder._range -= newBound; _prob -= (_prob) >> K_NUM_MOVE_BITS; + } + if (encoder._range < Encoder.K_TOP_VALUE) + { + encoder._range <<= 8; + encoder.ShiftLow(); + } + } + + private static readonly uint[] PROB_PRICES = new uint[ + K_BIT_MODEL_TOTAL >> K_NUM_MOVE_REDUCING_BITS + ]; + + static BitEncoder() + { + const int kNumBits = (K_NUM_BIT_MODEL_TOTAL_BITS - K_NUM_MOVE_REDUCING_BITS); + for (var i = kNumBits - 1; i >= 0; i--) + { + var start = (uint)1 << (kNumBits - i - 1); + var end = (uint)1 << (kNumBits - i); + for (var j = start; j < end; j++) + { + PROB_PRICES[j] = + ((uint)i << K_NUM_BIT_PRICE_SHIFT_BITS) + + (((end - j) << K_NUM_BIT_PRICE_SHIFT_BITS) >> (kNumBits - i - 1)); + } + } + } + + public uint GetPrice(uint symbol) => + PROB_PRICES[ + (((_prob - symbol) ^ ((-(int)symbol))) & (K_BIT_MODEL_TOTAL - 1)) + >> K_NUM_MOVE_REDUCING_BITS + ]; + + public uint GetPrice0() => PROB_PRICES[_prob >> K_NUM_MOVE_REDUCING_BITS]; + + public uint GetPrice1() => PROB_PRICES[(K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_REDUCING_BITS]; +} + +internal struct BitDecoder +{ + public const int K_NUM_BIT_MODEL_TOTAL_BITS = 11; + public const uint K_BIT_MODEL_TOTAL = (1 << K_NUM_BIT_MODEL_TOTAL_BITS); + private const int K_NUM_MOVE_BITS = 5; + + private uint _prob; + + public void UpdateModel(int numMoveBits, uint symbol) + { + if (symbol == 0) + { + _prob += (K_BIT_MODEL_TOTAL - _prob) >> numMoveBits; + } + else + { + _prob -= (_prob) >> numMoveBits; + } + } + + public void Init() => _prob = K_BIT_MODEL_TOTAL >> 1; + + public uint Decode(Decoder rangeDecoder) + { + var newBound = (rangeDecoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob; + if (rangeDecoder._code < newBound) + { + rangeDecoder._range = newBound; + _prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; if (rangeDecoder._range < Decoder.K_TOP_VALUE) { - rangeDecoder._code = (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte(); + rangeDecoder._code = + (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte(); rangeDecoder._range <<= 8; rangeDecoder._total++; } - return 1; + return 0; } + rangeDecoder._range -= newBound; + rangeDecoder._code -= newBound; + _prob -= (_prob) >> K_NUM_MOVE_BITS; + if (rangeDecoder._range < Decoder.K_TOP_VALUE) + { + rangeDecoder._code = (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte(); + rangeDecoder._range <<= 8; + rangeDecoder._total++; + } + return 1; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.cs b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.cs index cc511603..b6a66e82 100644 --- a/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.cs +++ b/src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.cs @@ -1,163 +1,173 @@ -using System; +namespace SharpCompress.Compressors.LZMA.RangeCoder; -namespace SharpCompress.Compressors.LZMA.RangeCoder +internal readonly struct BitTreeEncoder { - internal struct BitTreeEncoder + private readonly BitEncoder[] _models; + private readonly int _numBitLevels; + + public BitTreeEncoder(int numBitLevels) { - private readonly BitEncoder[] _models; - private readonly int _numBitLevels; + _numBitLevels = numBitLevels; + _models = new BitEncoder[1 << numBitLevels]; + } - public BitTreeEncoder(int numBitLevels) + public void Init() + { + for (uint i = 1; i < (1 << _numBitLevels); i++) { - _numBitLevels = numBitLevels; - _models = new BitEncoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << _numBitLevels); i++) - { - _models[i].Init(); - } - } - - public void Encode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (int bitIndex = _numBitLevels; bitIndex > 0;) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - _models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - } - } - - public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol) - { - UInt32 m = 1; - for (UInt32 i = 0; i < _numBitLevels; i++) - { - UInt32 bit = symbol & 1; - _models[m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } - } - - public UInt32 GetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int bitIndex = _numBitLevels; bitIndex > 0;) - { - bitIndex--; - UInt32 bit = (symbol >> bitIndex) & 1; - price += _models[m].GetPrice(bit); - m = (m << 1) + bit; - } - return price; - } - - public UInt32 ReverseGetPrice(UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = _numBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += _models[m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static UInt32 ReverseGetPrice(BitEncoder[] models, UInt32 startIndex, - int numBitLevels, UInt32 symbol) - { - UInt32 price = 0; - UInt32 m = 1; - for (int i = numBitLevels; i > 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += models[startIndex + m].GetPrice(bit); - m = (m << 1) | bit; - } - return price; - } - - public static void ReverseEncode(BitEncoder[] models, UInt32 startIndex, - Encoder rangeEncoder, int numBitLevels, UInt32 symbol) - { - UInt32 m = 1; - for (int i = 0; i < numBitLevels; i++) - { - UInt32 bit = symbol & 1; - models[startIndex + m].Encode(rangeEncoder, bit); - m = (m << 1) | bit; - symbol >>= 1; - } + _models[i].Init(); } } - internal struct BitTreeDecoder + public void Encode(Encoder rangeEncoder, uint symbol) { - private readonly BitDecoder[] _models; - private readonly int _numBitLevels; - - public BitTreeDecoder(int numBitLevels) + uint m = 1; + for (var bitIndex = _numBitLevels; bitIndex > 0; ) { - _numBitLevels = numBitLevels; - _models = new BitDecoder[1 << numBitLevels]; - } - - public void Init() - { - for (uint i = 1; i < (1 << _numBitLevels); i++) - { - _models[i].Init(); - } - } - - public uint Decode(Decoder rangeDecoder) - { - uint m = 1; - for (int bitIndex = _numBitLevels; bitIndex > 0; bitIndex--) - { - m = (m << 1) + _models[m].Decode(rangeDecoder); - } - return m - ((uint)1 << _numBitLevels); - } - - public uint ReverseDecode(Decoder rangeDecoder) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < _numBitLevels; bitIndex++) - { - uint bit = _models[m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= (bit << bitIndex); - } - return symbol; - } - - public static uint ReverseDecode(BitDecoder[] models, UInt32 startIndex, - Decoder rangeDecoder, int numBitLevels) - { - uint m = 1; - uint symbol = 0; - for (int bitIndex = 0; bitIndex < numBitLevels; bitIndex++) - { - uint bit = models[startIndex + m].Decode(rangeDecoder); - m <<= 1; - m += bit; - symbol |= (bit << bitIndex); - } - return symbol; + bitIndex--; + var bit = (symbol >> bitIndex) & 1; + _models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; } } -} \ No newline at end of file + + public void ReverseEncode(Encoder rangeEncoder, uint symbol) + { + uint m = 1; + for (uint i = 0; i < _numBitLevels; i++) + { + var bit = symbol & 1; + _models[m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } + + public uint GetPrice(uint symbol) + { + uint price = 0; + uint m = 1; + for (var bitIndex = _numBitLevels; bitIndex > 0; ) + { + bitIndex--; + var bit = (symbol >> bitIndex) & 1; + price += _models[m].GetPrice(bit); + m = (m << 1) + bit; + } + return price; + } + + public uint ReverseGetPrice(uint symbol) + { + uint price = 0; + uint m = 1; + for (var i = _numBitLevels; i > 0; i--) + { + var bit = symbol & 1; + symbol >>= 1; + price += _models[m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static uint ReverseGetPrice( + BitEncoder[] models, + uint startIndex, + int numBitLevels, + uint symbol + ) + { + uint price = 0; + uint m = 1; + for (var i = numBitLevels; i > 0; i--) + { + var bit = symbol & 1; + symbol >>= 1; + price += models[startIndex + m].GetPrice(bit); + m = (m << 1) | bit; + } + return price; + } + + public static void ReverseEncode( + BitEncoder[] models, + uint startIndex, + Encoder rangeEncoder, + int numBitLevels, + uint symbol + ) + { + uint m = 1; + for (var i = 0; i < numBitLevels; i++) + { + var bit = symbol & 1; + models[startIndex + m].Encode(rangeEncoder, bit); + m = (m << 1) | bit; + symbol >>= 1; + } + } +} + +internal readonly struct BitTreeDecoder +{ + private readonly BitDecoder[] _models; + private readonly int _numBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + _numBitLevels = numBitLevels; + _models = new BitDecoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << _numBitLevels); i++) + { + _models[i].Init(); + } + } + + public uint Decode(Decoder rangeDecoder) + { + uint m = 1; + for (var bitIndex = _numBitLevels; bitIndex > 0; bitIndex--) + { + m = (m << 1) + _models[m].Decode(rangeDecoder); + } + return m - ((uint)1 << _numBitLevels); + } + + public uint ReverseDecode(Decoder rangeDecoder) + { + uint m = 1; + uint symbol = 0; + for (var bitIndex = 0; bitIndex < _numBitLevels; bitIndex++) + { + var bit = _models[m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + + public static uint ReverseDecode( + BitDecoder[] models, + uint startIndex, + Decoder rangeDecoder, + int numBitLevels + ) + { + uint m = 1; + uint symbol = 0; + for (var bitIndex = 0; bitIndex < numBitLevels; bitIndex++) + { + var bit = models[startIndex + m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } +} diff --git a/src/SharpCompress/Compressors/LZMA/Registry.cs b/src/SharpCompress/Compressors/LZMA/Registry.cs index 8495c4bc..6aba0c5d 100644 --- a/src/SharpCompress/Compressors/LZMA/Registry.cs +++ b/src/SharpCompress/Compressors/LZMA/Registry.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using SharpCompress.Common.SevenZip; @@ -8,49 +8,52 @@ using SharpCompress.Compressors.Filters; using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.Compressors.PPMd; -namespace SharpCompress.Compressors.LZMA -{ - internal static class DecoderRegistry - { - private const uint K_COPY = 0x0; - private const uint K_DELTA = 3; - private const uint K_LZMA2 = 0x21; - private const uint K_LZMA = 0x030101; - private const uint K_PPMD = 0x030401; - private const uint K_BCJ = 0x03030103; - private const uint K_BCJ2 = 0x0303011B; - private const uint K_DEFLATE = 0x040108; - private const uint K_B_ZIP2 = 0x040202; +namespace SharpCompress.Compressors.LZMA; - internal static Stream CreateDecoderStream(CMethodId id, Stream[] inStreams, byte[] info, IPasswordProvider pass, - long limit) +internal static class DecoderRegistry +{ + private const uint K_COPY = 0x0; + private const uint K_LZMA2 = 0x21; + private const uint K_LZMA = 0x030101; + private const uint K_PPMD = 0x030401; + private const uint K_BCJ = 0x03030103; + private const uint K_BCJ2 = 0x0303011B; + private const uint K_DEFLATE = 0x040108; + private const uint K_B_ZIP2 = 0x040202; + + internal static Stream CreateDecoderStream( + CMethodId id, + Stream[] inStreams, + byte[] info, + IPasswordProvider pass, + long limit + ) + { + switch (id._id) { - switch (id._id) - { - case K_COPY: - if (info != null) - { - throw new NotSupportedException(); - } - return inStreams.Single(); - case K_LZMA: - case K_LZMA2: - return new LzmaStream(info, inStreams.Single(), -1, limit); - case CMethodId.K_AES_ID: - return new AesDecoderStream(inStreams.Single(), info, pass, limit); - case K_BCJ: - return new BCJFilter(false, inStreams.Single()); - case K_BCJ2: - return new Bcj2DecoderStream(inStreams, info, limit); - case K_B_ZIP2: - return new BZip2Stream(inStreams.Single(), CompressionMode.Decompress, true); - case K_PPMD: - return new PpmdStream(new PpmdProperties(info), inStreams.Single(), false); - case K_DEFLATE: - return new DeflateStream(inStreams.Single(), CompressionMode.Decompress); - default: + case K_COPY: + if (info != null) + { throw new NotSupportedException(); - } + } + return inStreams.Single(); + case K_LZMA: + case K_LZMA2: + return new LzmaStream(info, inStreams.Single(), -1, limit); + case CMethodId.K_AES_ID: + return new AesDecoderStream(inStreams.Single(), info, pass, limit); + case K_BCJ: + return new BCJFilter(false, inStreams.Single()); + case K_BCJ2: + return new Bcj2DecoderStream(inStreams, info, limit); + case K_B_ZIP2: + return new BZip2Stream(inStreams.Single(), CompressionMode.Decompress, true); + case K_PPMD: + return new PpmdStream(new PpmdProperties(info), inStreams.Single(), false); + case K_DEFLATE: + return new DeflateStream(inStreams.Single(), CompressionMode.Decompress); + default: + throw new NotSupportedException(); } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs index 4410dd9a..344924be 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs @@ -1,84 +1,77 @@ using System; using System.IO; -namespace SharpCompress.Compressors.LZMA.Utilites +namespace SharpCompress.Compressors.LZMA.Utilites; + +internal class CrcBuilderStream : Stream { - internal class CrcBuilderStream : Stream + private readonly Stream _mTarget; + private uint _mCrc; + private bool _mFinished; + private bool _isDisposed; + + public CrcBuilderStream(Stream target) { - private readonly Stream _mTarget; - private uint _mCrc; - private bool _mFinished; - private bool _isDisposed; - - public CrcBuilderStream(Stream target) - { - _mTarget = target; - _mCrc = Crc.INIT_CRC; - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) - { - return; - } - _isDisposed = true; - _mTarget.Dispose(); - base.Dispose(disposing); - } - - public long Processed { get; private set; } - - public uint Finish() - { - if (!_mFinished) - { - _mFinished = true; - _mCrc = Crc.Finish(_mCrc); - } - - return _mCrc; - } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override void Flush() - { - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new InvalidOperationException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (_mFinished) - { - throw new InvalidOperationException("CRC calculation has been finished."); - } - - Processed += count; - _mCrc = Crc.Update(_mCrc, buffer, offset, count); - _mTarget.Write(buffer, offset, count); - } + _mTarget = target; + _mCrc = Crc.INIT_CRC; } -} \ No newline at end of file + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + _mTarget.Dispose(); + base.Dispose(disposing); + } + + public long Processed { get; private set; } + + public uint Finish() + { + if (!_mFinished) + { + _mFinished = true; + _mCrc = Crc.Finish(_mCrc); + } + + return _mCrc; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override void Flush() { } + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) => + throw new InvalidOperationException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + if (_mFinished) + { + throw new InvalidOperationException("CRC calculation has been finished."); + } + + Processed += count; + _mCrc = Crc.Update(_mCrc, buffer, offset, count); + _mTarget.Write(buffer, offset, count); + } +} diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs index d5e54728..c10efda1 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs @@ -2,109 +2,103 @@ using System; using System.Diagnostics; using System.IO; -namespace SharpCompress.Compressors.LZMA.Utilites +namespace SharpCompress.Compressors.LZMA.Utilites; + +[CLSCompliant(false)] +public class CrcCheckStream : Stream { - internal class CrcCheckStream : Stream + private readonly uint _mExpectedCrc; + private uint _mCurrentCrc; + private bool _mClosed; + + private readonly long[] _mBytes = new long[256]; + private long _mLength; + + public CrcCheckStream(uint crc) { - private readonly uint _mExpectedCrc; - private uint _mCurrentCrc; - private bool _mClosed; + _mExpectedCrc = crc; + _mCurrentCrc = Crc.INIT_CRC; + } - private readonly long[] _mBytes = new long[256]; - private long _mLength; - - public CrcCheckStream(uint crc) + protected override void Dispose(bool disposing) + { + //Nanook - is not equal here - _mCurrentCrc is yet to be negated + //if (_mCurrentCrc != _mExpectedCrc) + //{ + // throw new InvalidOperationException(); + //} + try { - _mExpectedCrc = crc; - _mCurrentCrc = Crc.INIT_CRC; - } - - protected override void Dispose(bool disposing) - { - //Nanook - is not equal here - _mCurrentCrc is yet to be negated - //if (_mCurrentCrc != _mExpectedCrc) - //{ - // throw new InvalidOperationException(); - //} - try + if (disposing && !_mClosed) { - if (disposing && !_mClosed) - { - _mClosed = true; - _mCurrentCrc = Crc.Finish(_mCurrentCrc); //now becomes equal + _mClosed = true; + _mCurrentCrc = Crc.Finish(_mCurrentCrc); //now becomes equal #if DEBUG - if (_mCurrentCrc == _mExpectedCrc) - { - Debug.WriteLine("CRC ok: " + _mExpectedCrc.ToString("x8")); - } - else - { - Debugger.Break(); - Debug.WriteLine("bad CRC"); - } + if (_mCurrentCrc == _mExpectedCrc) + { + Debug.WriteLine("CRC ok: " + _mExpectedCrc.ToString("x8")); + } + else + { + Debugger.Break(); + Debug.WriteLine("bad CRC"); + } - double lengthInv = 1.0 / _mLength; - double entropy = 0; - for (int i = 0; i < 256; i++) + var lengthInv = 1.0 / _mLength; + double entropy = 0; + for (var i = 0; i < 256; i++) + { + if (_mBytes[i] != 0) { - if (_mBytes[i] != 0) - { - double p = lengthInv * _mBytes[i]; - entropy -= p * Math.Log(p, 256); - } - } - Debug.WriteLine("entropy: " + (int)(entropy * 100) + "%"); -#endif - if (_mCurrentCrc != _mExpectedCrc) //moved test to here - { - throw new InvalidOperationException(); + var p = lengthInv * _mBytes[i]; + entropy -= p * Math.Log(p, 256); } } - } - finally - { - base.Dispose(disposing); + Debug.WriteLine("entropy: " + (int)(entropy * 100) + "%"); +#endif + if (_mCurrentCrc != _mExpectedCrc) //moved test to here + { + throw new InvalidOperationException(); + } } } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override void Flush() + finally { - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new InvalidOperationException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _mLength += count; - for (int i = 0; i < count; i++) - { - _mBytes[buffer[offset + i]]++; - } - - _mCurrentCrc = Crc.Update(_mCurrentCrc, buffer, offset, count); + base.Dispose(disposing); } } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override void Flush() { } + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) => + throw new InvalidOperationException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + _mLength += count; + for (var i = 0; i < count; i++) + { + _mBytes[buffer[offset + i]]++; + } + + _mCurrentCrc = Crc.Update(_mCurrentCrc, buffer, offset, count); + } } diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/IPasswordProvider.cs b/src/SharpCompress/Compressors/LZMA/Utilites/IPasswordProvider.cs index 51dc389e..2f0eb904 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/IPasswordProvider.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/IPasswordProvider.cs @@ -1,7 +1,6 @@ -namespace SharpCompress.Compressors.LZMA.Utilites +namespace SharpCompress.Compressors.LZMA.Utilites; + +internal interface IPasswordProvider { - internal interface IPasswordProvider - { - string CryptoGetTextPassword(); - } -} \ No newline at end of file + string CryptoGetTextPassword(); +} diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs b/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs index 1b64ccb6..19b0f374 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs @@ -1,92 +1,91 @@ -using System; +using System; using System.Diagnostics; using System.IO; -namespace SharpCompress.Compressors.LZMA.Utilites +namespace SharpCompress.Compressors.LZMA.Utilites; + +internal enum BlockType : byte { - internal enum BlockType : byte + #region Constants + + End = 0, + Header = 1, + ArchiveProperties = 2, + AdditionalStreamsInfo = 3, + MainStreamsInfo = 4, + FilesInfo = 5, + PackInfo = 6, + UnpackInfo = 7, + SubStreamsInfo = 8, + Size = 9, + Crc = 10, + Folder = 11, + CodersUnpackSize = 12, + NumUnpackStream = 13, + EmptyStream = 14, + EmptyFile = 15, + Anti = 16, + Name = 17, + CTime = 18, + ATime = 19, + MTime = 20, + WinAttributes = 21, + Comment = 22, + EncodedHeader = 23, + StartPos = 24, + Dummy = 25 + + #endregion +} + +internal static class Utils +{ + [Conditional("DEBUG")] + public static void Assert(bool expression) { - #region Constants + if (!expression) + { + if (Debugger.IsAttached) + { + Debugger.Break(); + } - End = 0, - Header = 1, - ArchiveProperties = 2, - AdditionalStreamsInfo = 3, - MainStreamsInfo = 4, - FilesInfo = 5, - PackInfo = 6, - UnpackInfo = 7, - SubStreamsInfo = 8, - Size = 9, - Crc = 10, - Folder = 11, - CodersUnpackSize = 12, - NumUnpackStream = 13, - EmptyStream = 14, - EmptyFile = 15, - Anti = 16, - Name = 17, - CTime = 18, - ATime = 19, - MTime = 20, - WinAttributes = 21, - Comment = 22, - EncodedHeader = 23, - StartPos = 24, - Dummy = 25 - - #endregion + throw new InvalidOperationException("Assertion failed."); + } } - internal static class Utils + public static void ReadExact(this Stream stream, byte[] buffer, int offset, int length) { - [Conditional("DEBUG")] - public static void Assert(bool expression) + if (stream is null) { - if (!expression) - { - if (Debugger.IsAttached) - { - Debugger.Break(); - } - - throw new InvalidOperationException("Assertion failed."); - } + throw new ArgumentNullException(nameof(stream)); } - public static void ReadExact(this Stream stream, byte[] buffer, int offset, int length) + if (buffer is null) { - if (stream is null) + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0 || offset > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (length < 0 || length > buffer.Length - offset) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + while (length > 0) + { + var fetched = stream.Read(buffer, offset, length); + if (fetched <= 0) { - throw new ArgumentNullException(nameof(stream)); + throw new EndOfStreamException(); } - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (length < 0 || length > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - while (length > 0) - { - int fetched = stream.Read(buffer, offset, length); - if (fetched <= 0) - { - throw new EndOfStreamException(); - } - - offset += fetched; - length -= fetched; - } + offset += fetched; + length -= fetched; } } } diff --git a/src/SharpCompress/Compressors/PPMd/H/FreqData.cs b/src/SharpCompress/Compressors/PPMd/H/FreqData.cs index f4537c11..5811b59b 100644 --- a/src/SharpCompress/Compressors/PPMd/H/FreqData.cs +++ b/src/SharpCompress/Compressors/PPMd/H/FreqData.cs @@ -2,68 +2,50 @@ using System; using System.Buffers.Binary; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class FreqData : Pointer { - internal class FreqData : Pointer + internal const int SIZE = 6; + + // struct FreqData + // { + // ushort SummFreq; + // STATE _PACK_ATTR * Stats; + // }; + + internal FreqData(byte[] memory) : base(memory) { } + + internal int SummFreq { - internal const int SIZE = 6; + get => BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; + set => BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); + } - // struct FreqData - // { - // ushort SummFreq; - // STATE _PACK_ATTR * Stats; - // }; + internal FreqData Initialize(byte[] mem) => Initialize(mem); - internal FreqData(byte[] memory) - : base(memory) - { - } + internal void IncrementSummFreq(int dSummFreq) => SummFreq += (short)dSummFreq; - internal int SummFreq - { - get => BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; - set => BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); - } + internal int GetStats() => BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 2)); - internal FreqData Initialize(byte[] mem) - { - return base.Initialize(mem); - } + internal virtual void SetStats(State state) => SetStats(state.Address); - internal void IncrementSummFreq(int dSummFreq) - { - SummFreq += (short)dSummFreq; - } + internal void SetStats(int state) => + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 2), state); - internal int GetStats() - { - return BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 2)); - } - - internal virtual void SetStats(State state) - { - SetStats(state.Address); - } - - internal void SetStats(int state) - { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 2), state); - } - - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("FreqData["); - buffer.Append("\n Address="); - buffer.Append(Address); - buffer.Append("\n size="); - buffer.Append(SIZE); - buffer.Append("\n summFreq="); - buffer.Append(SummFreq); - buffer.Append("\n stats="); - buffer.Append(GetStats()); - buffer.Append("\n]"); - return buffer.ToString(); - } + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("FreqData["); + buffer.Append("\n Address="); + buffer.Append(Address); + buffer.Append("\n size="); + buffer.Append(SIZE); + buffer.Append("\n summFreq="); + buffer.Append(SummFreq); + buffer.Append("\n stats="); + buffer.Append(GetStats()); + buffer.Append("\n]"); + return buffer.ToString(); } } diff --git a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs index 8f0e6867..3b2d22ba 100644 --- a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs +++ b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs @@ -6,912 +6,953 @@ using System.Text; using SharpCompress.Compressors.Rar; using Decoder = SharpCompress.Compressors.LZMA.RangeCoder.Decoder; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class ModelPpm { - internal class ModelPpm + private void InitBlock() { - private void InitBlock() + for (var i = 0; i < 25; i++) { - for (int i = 0; i < 25; i++) - { - _see2Cont[i] = new See2Context[16]; - } - for (int i2 = 0; i2 < 128; i2++) - { - _binSumm[i2] = new int[64]; - } + _see2Cont[i] = new See2Context[16]; + } + for (var i2 = 0; i2 < 128; i2++) + { + _binSumm[i2] = new int[64]; + } + } + + public SubAllocator SubAlloc { get; } = new SubAllocator(); + + public virtual See2Context DummySee2Cont => _dummySee2Cont; + + public virtual int InitRl => _initRl; + + public virtual int EscCount + { + get => _escCount; + set => _escCount = value & 0xff; + } + + public virtual int[] CharMask => _charMask; + + public virtual int NumMasked + { + get => _numMasked; + set => _numMasked = value; + } + + public virtual int PrevSuccess + { + get => _prevSuccess; + set => _prevSuccess = value & 0xff; + } + + public virtual int InitEsc + { + get => _initEsc; + set => _initEsc = value; + } + + public virtual int RunLength + { + get => _runLength; + set => _runLength = value; + } + + public virtual int HiBitsFlag + { + get => _hiBitsFlag; + set => _hiBitsFlag = value & 0xff; + } + + public virtual int[][] BinSumm => _binSumm; + + internal RangeCoder Coder { get; private set; } + + internal State FoundState { get; private set; } + + public virtual byte[] Heap => SubAlloc.Heap; + + public virtual int OrderFall => _orderFall; + + public const int MAX_O = 64; /* maximum allowed model order */ + + public const int INT_BITS = 7; + + public const int PERIOD_BITS = 7; + + //UPGRADE_NOTE: Final was removed from the declaration of 'TOT_BITS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int TOT_BITS = INT_BITS + PERIOD_BITS; + + //UPGRADE_NOTE: Final was removed from the declaration of 'INTERVAL '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int INTERVAL = 1 << INT_BITS; + + //UPGRADE_NOTE: Final was removed from the declaration of 'BIN_SCALE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int BIN_SCALE = 1 << TOT_BITS; + + public const int MAX_FREQ = 124; + + private readonly See2Context[][] _see2Cont = new See2Context[25][]; + + private See2Context _dummySee2Cont; + + private PpmContext _minContext; //medContext + + private PpmContext _maxContext; + + private int _numMasked, + _initEsc, + _orderFall, + _maxOrder, + _runLength, + _initRl; + + private readonly int[] _charMask = new int[256]; + + private readonly int[] _ns2Indx = new int[256]; + + private readonly int[] _ns2BsIndx = new int[256]; + + private readonly int[] _hb2Flag = new int[256]; + + // byte EscCount, PrevSuccess, HiBitsFlag; + private int _escCount, + _prevSuccess, + _hiBitsFlag; + + private readonly int[][] _binSumm = new int[128][]; // binary SEE-contexts + + private static readonly int[] INIT_BIN_ESC = + { + 0x3CDD, + 0x1F3F, + 0x59BF, + 0x48F3, + 0x64A1, + 0x5ABC, + 0x6632, + 0x6051 + }; + + // Temp fields + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState1 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState2 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState3 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState4 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempStateRef1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly StateRef _tempStateRef1 = new StateRef(); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempStateRef2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly StateRef _tempStateRef2 = new StateRef(); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly PpmContext _tempPpmContext1 = new PpmContext(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly PpmContext _tempPpmContext2 = new PpmContext(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly PpmContext _tempPpmContext3 = new PpmContext(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly PpmContext _tempPpmContext4 = new PpmContext(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'ps '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly int[] _ps = new int[MAX_O]; + + public ModelPpm() + { + InitBlock(); + _minContext = null; + _maxContext = null; + + //medContext = null; + } + + private void RestartModelRare() + { + new Span(_charMask).Clear(); + SubAlloc.InitSubAllocator(); + _initRl = -(_maxOrder < 12 ? _maxOrder : 12) - 1; + var addr = SubAlloc.AllocContext(); + _minContext.Address = addr; + _maxContext.Address = addr; + _minContext.SetSuffix(0); + _orderFall = _maxOrder; + _minContext.NumStats = 256; + _minContext.FreqData.SummFreq = _minContext.NumStats + 1; + + addr = SubAlloc.AllocUnits(256 / 2); + FoundState.Address = addr; + _minContext.FreqData.SetStats(addr); + + var state = new State(SubAlloc.Heap); + addr = _minContext.FreqData.GetStats(); + _runLength = _initRl; + _prevSuccess = 0; + for (var i = 0; i < 256; i++) + { + state.Address = addr + (i * State.SIZE); + state.Symbol = i; + state.Freq = 1; + state.SetSuccessor(0); } - public SubAllocator SubAlloc { get; } = new SubAllocator(); - - public virtual See2Context DummySee2Cont => _dummySee2Cont; - - public virtual int InitRl => _initRl; - - public virtual int EscCount { get => _escCount; set => _escCount = value & 0xff; } - - public virtual int[] CharMask => _charMask; - - public virtual int NumMasked { get => _numMasked; set => _numMasked = value; } - - public virtual int PrevSuccess { get => _prevSuccess; set => _prevSuccess = value & 0xff; } - - public virtual int InitEsc { get => _initEsc; set => _initEsc = value; } - - public virtual int RunLength { get => _runLength; set => _runLength = value; } - - public virtual int HiBitsFlag { get => _hiBitsFlag; set => _hiBitsFlag = value & 0xff; } - - public virtual int[][] BinSumm => _binSumm; - - internal RangeCoder Coder { get; private set; } - - internal State FoundState { get; private set; } - - public virtual byte[] Heap => SubAlloc.Heap; - - public virtual int OrderFall => _orderFall; - - public const int MAX_O = 64; /* maximum allowed model order */ - - public const int INT_BITS = 7; - - public const int PERIOD_BITS = 7; - - //UPGRADE_NOTE: Final was removed from the declaration of 'TOT_BITS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int TOT_BITS = INT_BITS + PERIOD_BITS; - - //UPGRADE_NOTE: Final was removed from the declaration of 'INTERVAL '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int INTERVAL = 1 << INT_BITS; - - //UPGRADE_NOTE: Final was removed from the declaration of 'BIN_SCALE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int BIN_SCALE = 1 << TOT_BITS; - - public const int MAX_FREQ = 124; - - private readonly See2Context[][] _see2Cont = new See2Context[25][]; - - private See2Context _dummySee2Cont; - - private PpmContext _minContext; //medContext - - private PpmContext _maxContext; - - private int _numMasked, _initEsc, _orderFall, _maxOrder, _runLength, _initRl; - - private readonly int[] _charMask = new int[256]; - - private readonly int[] _ns2Indx = new int[256]; - - private readonly int[] _ns2BsIndx = new int[256]; - - private readonly int[] _hb2Flag = new int[256]; - - // byte EscCount, PrevSuccess, HiBitsFlag; - private int _escCount, _prevSuccess, _hiBitsFlag; - - private readonly int[][] _binSumm = new int[128][]; // binary SEE-contexts - - private static readonly int[] INIT_BIN_ESC = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051 }; - - // Temp fields - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState1 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState2 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState3 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState4 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempStateRef1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly StateRef _tempStateRef1 = new StateRef(); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempStateRef2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly StateRef _tempStateRef2 = new StateRef(); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly PpmContext _tempPpmContext1 = new PpmContext(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly PpmContext _tempPpmContext2 = new PpmContext(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly PpmContext _tempPpmContext3 = new PpmContext(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempPPMContext4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly PpmContext _tempPpmContext4 = new PpmContext(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'ps '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly int[] _ps = new int[MAX_O]; - - public ModelPpm() + for (var i = 0; i < 128; i++) { - InitBlock(); - _minContext = null; - _maxContext = null; - - //medContext = null; - } - - private void RestartModelRare() - { - new Span(_charMask).Clear(); - SubAlloc.InitSubAllocator(); - _initRl = -(_maxOrder < 12 ? _maxOrder : 12) - 1; - int addr = SubAlloc.AllocContext(); - _minContext.Address = addr; - _maxContext.Address = addr; - _minContext.SetSuffix(0); - _orderFall = _maxOrder; - _minContext.NumStats = 256; - _minContext.FreqData.SummFreq = _minContext.NumStats + 1; - - addr = SubAlloc.AllocUnits(256 / 2); - FoundState.Address = addr; - _minContext.FreqData.SetStats(addr); - - State state = new State(SubAlloc.Heap); - addr = _minContext.FreqData.GetStats(); - _runLength = _initRl; - _prevSuccess = 0; - for (int i = 0; i < 256; i++) + for (var k = 0; k < 8; k++) { - state.Address = addr + i * State.SIZE; - state.Symbol = i; - state.Freq = 1; - state.SetSuccessor(0); - } - - for (int i = 0; i < 128; i++) - { - for (int k = 0; k < 8; k++) + for (var m = 0; m < 64; m += 8) { - for (int m = 0; m < 64; m += 8) - { - _binSumm[i][k + m] = BIN_SCALE - INIT_BIN_ESC[k] / (i + 2); - } - } - } - for (int i = 0; i < 25; i++) - { - for (int k = 0; k < 16; k++) - { - _see2Cont[i][k].Initialize(5 * i + 10); + _binSumm[i][k + m] = BIN_SCALE - (INIT_BIN_ESC[k] / (i + 2)); } } } - - private void StartModelRare(int maxOrder) + for (var i = 0; i < 25; i++) { - int i, k, m, step; - _escCount = 1; - _maxOrder = maxOrder; - RestartModelRare(); - - // Bug Fixed - _ns2BsIndx[0] = 0; - _ns2BsIndx[1] = 2; - for (int j = 0; j < 9; j++) + for (var k = 0; k < 16; k++) { - _ns2BsIndx[2 + j] = 4; + _see2Cont[i][k].Initialize((5 * i) + 10); } - for (int j = 0; j < 256 - 11; j++) - { - _ns2BsIndx[11 + j] = 6; - } - for (i = 0; i < 3; i++) - { - _ns2Indx[i] = i; - } - for (m = i, k = 1, step = 1; i < 256; i++) - { - _ns2Indx[i] = m; - if ((--k) == 0) - { - k = ++step; - m++; - } - } - for (int j = 0; j < 0x40; j++) - { - _hb2Flag[j] = 0; - } - for (int j = 0; j < 0x100 - 0x40; j++) - { - _hb2Flag[0x40 + j] = 0x08; - } - _dummySee2Cont.Shift = PERIOD_BITS; } + } - private void ClearMask() + private void StartModelRare(int maxOrder) + { + int i, + k, + m, + step; + _escCount = 1; + _maxOrder = maxOrder; + RestartModelRare(); + + // Bug Fixed + _ns2BsIndx[0] = 0; + _ns2BsIndx[1] = 2; + for (var j = 0; j < 9; j++) { - _escCount = 1; - new Span(_charMask).Clear(); + _ns2BsIndx[2 + j] = 4; } - - internal bool DecodeInit(IRarUnpack unpackRead, int escChar) + for (var j = 0; j < 256 - 11; j++) { - int maxOrder = unpackRead.Char & 0xff; - bool reset = ((maxOrder & 0x20) != 0); - - int maxMb = 0; - if (reset) - { - maxMb = unpackRead.Char; - } - else - { - if (SubAlloc.GetAllocatedMemory() == 0) - { - return (false); - } - } - if ((maxOrder & 0x40) != 0) - { - escChar = unpackRead.Char; - unpackRead.PpmEscChar = escChar; - } - Coder = new RangeCoder(unpackRead); - 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); - - //medContext = new PPMContext(Heap); - _maxContext = new PpmContext(Heap); - FoundState = new State(Heap); - _dummySee2Cont = new See2Context(); - for (int i = 0; i < 25; i++) - { - for (int j = 0; j < 16; j++) - { - _see2Cont[i][j] = new See2Context(); - } - } - StartModelRare(maxOrder); - } - return (_minContext.Address != 0); + _ns2BsIndx[11 + j] = 6; } - - public virtual int DecodeChar() + for (i = 0; i < 3; i++) { - // Debug - //subAlloc.dumpHeap(); - - if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) - { - return (-1); - } - - if (_minContext.NumStats != 1) - { - if (_minContext.FreqData.GetStats() <= SubAlloc.PText || - _minContext.FreqData.GetStats() > SubAlloc.HeapEnd) - { - return (-1); - } - if (!_minContext.DecodeSymbol1(this)) - { - return (-1); - } - } - else - { - _minContext.DecodeBinSymbol(this); - } - Coder.Decode(); - while (FoundState.Address == 0) - { - Coder.AriDecNormalize(); - do - { - _orderFall++; - _minContext.Address = _minContext.GetSuffix(); // =MinContext->Suffix; - if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) - { - return (-1); - } - } - while (_minContext.NumStats == _numMasked); - if (!_minContext.DecodeSymbol2(this)) - { - return (-1); - } - Coder.Decode(); - } - int symbol = FoundState.Symbol; - if ((_orderFall == 0) && FoundState.GetSuccessor() > SubAlloc.PText) - { - // MinContext=MaxContext=FoundState->Successor; - int addr = FoundState.GetSuccessor(); - _minContext.Address = addr; - _maxContext.Address = addr; - } - else - { - UpdateModel(); - - //this.foundState.Address=foundState.Address);//TODO just 4 debugging - if (_escCount == 0) - { - ClearMask(); - } - } - Coder.AriDecNormalize(); // ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead); - return (symbol); + _ns2Indx[i] = i; } - - public virtual See2Context[][] GetSee2Cont() + for (m = i, k = 1, step = 1; i < 256; i++) { - return _see2Cont; + _ns2Indx[i] = m; + if ((--k) == 0) + { + k = ++step; + m++; + } } - - public virtual void IncEscCount(int dEscCount) + for (var j = 0; j < 0x40; j++) { - EscCount = EscCount + dEscCount; + _hb2Flag[j] = 0; } - - public virtual void IncRunLength(int dRunLength) + for (var j = 0; j < 0x100 - 0x40; j++) { - RunLength = RunLength + dRunLength; + _hb2Flag[0x40 + j] = 0x08; } + _dummySee2Cont.Shift = PERIOD_BITS; + } - public virtual int[] GetHb2Flag() + private void ClearMask() + { + _escCount = 1; + new Span(_charMask).Clear(); + } + + internal bool DecodeInit(IRarUnpack unpackRead, int escChar) + { + var maxOrder = unpackRead.Char & 0xff; + var reset = ((maxOrder & 0x20) != 0); + + var maxMb = 0; + if (reset) { - return _hb2Flag; + maxMb = unpackRead.Char; } - - public virtual int[] GetNs2BsIndx() + else { - return _ns2BsIndx; + if (SubAlloc.GetAllocatedMemory() == 0) + { + return (false); + } } - - public virtual int[] GetNs2Indx() + if ((maxOrder & 0x40) != 0) { - return _ns2Indx; + escChar = unpackRead.Char; + unpackRead.PpmEscChar = escChar; } - - private int CreateSuccessors(bool skip, State p1) + Coder = new RangeCoder(unpackRead); + if (reset) { - //State upState = tempState1.Initialize(null); - StateRef upState = _tempStateRef2; - State tempState = _tempState1.Initialize(Heap); - - // PPM_CONTEXT* pc=MinContext, * UpBranch=FoundState->Successor; - PpmContext pc = _tempPpmContext1.Initialize(Heap); - pc.Address = _minContext.Address; - PpmContext upBranch = _tempPpmContext2.Initialize(Heap); - upBranch.Address = FoundState.GetSuccessor(); - - // STATE * p, * ps[MAX_O], ** pps=ps; - State p = _tempState2.Initialize(Heap); - int pps = 0; - - bool noLoop = false; - - if (!skip) + maxOrder = (maxOrder & 0x1f) + 1; + if (maxOrder > 16) { - _ps[pps++] = FoundState.Address; // *pps++ = FoundState; - if (pc.GetSuffix() == 0) - { - noLoop = true; - } + maxOrder = 16 + ((maxOrder - 16) * 3); } - if (!noLoop) - { - bool loopEntry = false; - if (p1.Address != 0) - { - p.Address = p1.Address; - pc.Address = pc.GetSuffix(); // =pc->Suffix; - loopEntry = true; - } - do - { - if (!loopEntry) - { - pc.Address = pc.GetSuffix(); // pc=pc->Suffix; - if (pc.NumStats != 1) - { - p.Address = pc.FreqData.GetStats(); // p=pc->U.Stats - if (p.Symbol != FoundState.Symbol) - { - do - { - p.IncrementAddress(); - } - while (p.Symbol != FoundState.Symbol); - } - } - else - { - p.Address = pc.GetOneState().Address; // p=&(pc->OneState); - } - } // LOOP_ENTRY: - loopEntry = false; - if (p.GetSuccessor() != upBranch.Address) - { - pc.Address = p.GetSuccessor(); // =p->Successor; - break; - } - _ps[pps++] = p.Address; - } - while (pc.GetSuffix() != 0); - } // NO_LOOP: - if (pps == 0) - { - return pc.Address; - } - upState.Symbol = Heap[upBranch.Address]; // UpState.Symbol=*(byte*) - - // UpBranch; - // UpState.Successor=(PPM_CONTEXT*) (((byte*) UpBranch)+1); - upState.SetSuccessor(upBranch.Address + 1); //TODO check if +1 necessary - if (pc.NumStats != 1) - { - if (pc.Address <= SubAlloc.PText) - { - return (0); - } - p.Address = pc.FreqData.GetStats(); - if (p.Symbol != upState.Symbol) - { - do - { - p.IncrementAddress(); - } - while (p.Symbol != upState.Symbol); - } - int cf = p.Freq - 1; - int s0 = pc.FreqData.SummFreq - pc.NumStats - cf; - - // UpState.Freq=1+((2*cf <= s0)?(5*cf > s0):((2*cf+3*s0-1)/(2*s0))); - upState.Freq = 1 + ((2 * cf <= s0) ? (5 * cf > s0 ? 1 : 0) : ((2 * cf + 3 * s0 - 1) / (2 * s0))); - } - else - { - upState.Freq = pc.GetOneState().Freq; // UpState.Freq=pc->OneState.Freq; - } - do - { - // pc = pc->createChild(this,*--pps,UpState); - tempState.Address = _ps[--pps]; - pc.Address = pc.CreateChild(this, tempState, upState); - if (pc.Address == 0) - { - return 0; - } - } - while (pps != 0); - return pc.Address; - } - - private void UpdateModelRestart() - { - RestartModelRare(); - _escCount = 0; - } - - private void UpdateModel() - { - //System.out.println("ModelPPM.updateModel()"); - // STATE fs = *FoundState, *p = NULL; - StateRef fs = _tempStateRef1; - fs.Values = FoundState; - State p = _tempState3.Initialize(Heap); - State tempState = _tempState4.Initialize(Heap); - - PpmContext pc = _tempPpmContext3.Initialize(Heap); - PpmContext successor = _tempPpmContext4.Initialize(Heap); - - int ns1, ns, cf, sf, s0; - pc.Address = _minContext.GetSuffix(); - if (fs.Freq < MAX_FREQ / 4 && pc.Address != 0) - { - if (pc.NumStats != 1) - { - p.Address = pc.FreqData.GetStats(); - if (p.Symbol != fs.Symbol) - { - do - { - p.IncrementAddress(); - } - while (p.Symbol != fs.Symbol); - tempState.Address = p.Address - State.SIZE; - if (p.Freq >= tempState.Freq) - { - State.PpmdSwap(p, tempState); - p.DecrementAddress(); - } - } - if (p.Freq < MAX_FREQ - 9) - { - p.IncrementFreq(2); - pc.FreqData.IncrementSummFreq(2); - } - } - else - { - p.Address = pc.GetOneState().Address; - if (p.Freq < 32) - { - p.IncrementFreq(1); - } - } - } - if (_orderFall == 0) - { - FoundState.SetSuccessor(CreateSuccessors(true, p)); - _minContext.Address = FoundState.GetSuccessor(); - _maxContext.Address = FoundState.GetSuccessor(); - if (_minContext.Address == 0) - { - UpdateModelRestart(); - return; - } - return; - } - SubAlloc.Heap[SubAlloc.PText] = (byte)fs.Symbol; - SubAlloc.IncPText(); - successor.Address = SubAlloc.PText; - if (SubAlloc.PText >= SubAlloc.FakeUnitsStart) - { - UpdateModelRestart(); - return; - } - - // // Debug - // subAlloc.dumpHeap(); - if (fs.GetSuccessor() != 0) - { - if (fs.GetSuccessor() <= SubAlloc.PText) - { - fs.SetSuccessor(CreateSuccessors(false, p)); - if (fs.GetSuccessor() == 0) - { - UpdateModelRestart(); - return; - } - } - if (--_orderFall == 0) - { - successor.Address = fs.GetSuccessor(); - if (_maxContext.Address != _minContext.Address) - { - SubAlloc.DecPText(1); - } - } - } - else - { - FoundState.SetSuccessor(successor.Address); - fs.SetSuccessor(_minContext); - } - - // // Debug - // subAlloc.dumpHeap(); - ns = _minContext.NumStats; - s0 = _minContext.FreqData.SummFreq - (ns) - (fs.Freq - 1); - for (pc.Address = _maxContext.Address; pc.Address != _minContext.Address; pc.Address = pc.GetSuffix()) - { - if ((ns1 = pc.NumStats) != 1) - { - if ((ns1 & 1) == 0) - { - //System.out.println(ns1); - pc.FreqData.SetStats(SubAlloc.ExpandUnits(pc.FreqData.GetStats(), Utility.URShift(ns1, 1))); - if (pc.FreqData.GetStats() == 0) - { - UpdateModelRestart(); - return; - } - } - - // bug fixed - // int sum = ((2 * ns1 < ns) ? 1 : 0) + - // 2 * ((4 * ((ns1 <= ns) ? 1 : 0)) & ((pc.getFreqData() - // .getSummFreq() <= 8 * ns1) ? 1 : 0)); - int sum = ((2 * ns1 < ns) ? 1 : 0) + - 2 * (((4 * ns1 <= ns) ? 1 : 0) & ((pc.FreqData.SummFreq <= 8 * ns1) ? 1 : 0)); - pc.FreqData.IncrementSummFreq(sum); - } - else - { - p.Address = SubAlloc.AllocUnits(1); - if (p.Address == 0) - { - UpdateModelRestart(); - return; - } - p.SetValues(pc.GetOneState()); - pc.FreqData.SetStats(p); - if (p.Freq < MAX_FREQ / 4 - 1) - { - p.IncrementFreq(p.Freq); - } - else - { - p.Freq = MAX_FREQ - 4; - } - pc.FreqData.SummFreq = (p.Freq + _initEsc + (ns > 3 ? 1 : 0)); - } - cf = 2 * fs.Freq * (pc.FreqData.SummFreq + 6); - sf = s0 + pc.FreqData.SummFreq; - if (cf < 6 * sf) - { - cf = 1 + (cf > sf ? 1 : 0) + (cf >= 4 * sf ? 1 : 0); - pc.FreqData.IncrementSummFreq(3); - } - else - { - cf = 4 + (cf >= 9 * sf ? 1 : 0) + (cf >= 12 * sf ? 1 : 0) + (cf >= 15 * sf ? 1 : 0); - pc.FreqData.IncrementSummFreq(cf); - } - p.Address = pc.FreqData.GetStats() + ns1 * State.SIZE; - p.SetSuccessor(successor); - p.Symbol = fs.Symbol; - p.Freq = cf; - pc.NumStats = ++ns1; - } - - int address = fs.GetSuccessor(); - _maxContext.Address = address; - _minContext.Address = address; - - //TODO-----debug - // int pos = minContext.getFreqData().getStats(); - // State a = new State(getHeap()); - // a.Address=pos); - // pos+=State.size; - // a.Address=pos); - //--dbg end - } - - // Debug - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("ModelPPM["); - buffer.Append("\n numMasked="); - buffer.Append(_numMasked); - buffer.Append("\n initEsc="); - buffer.Append(_initEsc); - buffer.Append("\n orderFall="); - buffer.Append(_orderFall); - buffer.Append("\n maxOrder="); - buffer.Append(_maxOrder); - buffer.Append("\n runLength="); - buffer.Append(_runLength); - buffer.Append("\n initRL="); - buffer.Append(_initRl); - buffer.Append("\n escCount="); - buffer.Append(_escCount); - buffer.Append("\n prevSuccess="); - buffer.Append(_prevSuccess); - buffer.Append("\n foundState="); - buffer.Append(FoundState); - buffer.Append("\n coder="); - buffer.Append(Coder); - buffer.Append("\n subAlloc="); - buffer.Append(SubAlloc); - buffer.Append("\n]"); - return buffer.ToString(); - } - - // Debug - // public void dumpHeap() { - // subAlloc.dumpHeap(); - // } - - internal bool DecodeInit(Stream stream, int maxOrder, int maxMemory) - { - if (stream != null) - { - Coder = new RangeCoder(stream); - } - if (maxOrder == 1) { SubAlloc.StopSubAllocator(); return (false); } - SubAlloc.StartSubAllocator(maxMemory); + SubAlloc.StartSubAllocator((maxMb + 1) << 20); _minContext = new PpmContext(Heap); //medContext = new PPMContext(Heap); _maxContext = new PpmContext(Heap); FoundState = new State(Heap); _dummySee2Cont = new See2Context(); - for (int i = 0; i < 25; i++) + for (var i = 0; i < 25; i++) { - for (int j = 0; j < 16; j++) + for (var j = 0; j < 16; j++) { _see2Cont[i][j] = new See2Context(); } } StartModelRare(maxOrder); + } + return (_minContext.Address != 0); + } - return (_minContext.Address != 0); + public virtual int DecodeChar() + { + // Debug + //subAlloc.dumpHeap(); + + if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) + { + return (-1); } - internal void NextContext() + if (_minContext.NumStats != 1) { - int addr = FoundState.GetSuccessor(); - if (_orderFall == 0 && addr > SubAlloc.PText) + if ( + _minContext.FreqData.GetStats() <= SubAlloc.PText + || _minContext.FreqData.GetStats() > SubAlloc.HeapEnd + ) { - _minContext.Address = addr; - _maxContext.Address = addr; + return (-1); + } + if (!_minContext.DecodeSymbol1(this)) + { + return (-1); + } + } + else + { + _minContext.DecodeBinSymbol(this); + } + Coder.Decode(); + while (FoundState.Address == 0) + { + Coder.AriDecNormalize(); + do + { + _orderFall++; + _minContext.Address = _minContext.GetSuffix(); // =MinContext->Suffix; + if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) + { + return (-1); + } + } while (_minContext.NumStats == _numMasked); + if (!_minContext.DecodeSymbol2(this)) + { + return (-1); + } + Coder.Decode(); + } + var symbol = FoundState.Symbol; + if ((_orderFall == 0) && FoundState.GetSuccessor() > SubAlloc.PText) + { + // MinContext=MaxContext=FoundState->Successor; + var addr = FoundState.GetSuccessor(); + _minContext.Address = addr; + _maxContext.Address = addr; + } + else + { + UpdateModel(); + + //this.foundState.Address=foundState.Address);//TODO just 4 debugging + if (_escCount == 0) + { + ClearMask(); + } + } + Coder.AriDecNormalize(); // ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead); + return (symbol); + } + + public virtual See2Context[][] GetSee2Cont() => _see2Cont; + + public virtual void IncEscCount(int dEscCount) => EscCount += dEscCount; + + public virtual void IncRunLength(int dRunLength) => RunLength += dRunLength; + + public virtual int[] GetHb2Flag() => _hb2Flag; + + public virtual int[] GetNs2BsIndx() => _ns2BsIndx; + + public virtual int[] GetNs2Indx() => _ns2Indx; + + private int CreateSuccessors(bool skip, State p1) + { + //State upState = tempState1.Initialize(null); + var upState = _tempStateRef2; + var tempState = _tempState1.Initialize(Heap); + + // PPM_CONTEXT* pc=MinContext, * UpBranch=FoundState->Successor; + var pc = _tempPpmContext1.Initialize(Heap); + pc.Address = _minContext.Address; + var upBranch = _tempPpmContext2.Initialize(Heap); + upBranch.Address = FoundState.GetSuccessor(); + + // STATE * p, * ps[MAX_O], ** pps=ps; + var p = _tempState2.Initialize(Heap); + var pps = 0; + + var noLoop = false; + + if (!skip) + { + _ps[pps++] = FoundState.Address; // *pps++ = FoundState; + if (pc.GetSuffix() == 0) + { + noLoop = true; + } + } + if (!noLoop) + { + var loopEntry = false; + if (p1.Address != 0) + { + p.Address = p1.Address; + pc.Address = pc.GetSuffix(); // =pc->Suffix; + loopEntry = true; + } + do + { + if (!loopEntry) + { + pc.Address = pc.GetSuffix(); // pc=pc->Suffix; + if (pc.NumStats != 1) + { + p.Address = pc.FreqData.GetStats(); // p=pc->U.Stats + if (p.Symbol != FoundState.Symbol) + { + do + { + p.IncrementAddress(); + } while (p.Symbol != FoundState.Symbol); + } + } + else + { + p.Address = pc.GetOneState().Address; // p=&(pc->OneState); + } + } // LOOP_ENTRY: + loopEntry = false; + if (p.GetSuccessor() != upBranch.Address) + { + pc.Address = p.GetSuccessor(); // =p->Successor; + break; + } + _ps[pps++] = p.Address; + } while (pc.GetSuffix() != 0); + } // NO_LOOP: + if (pps == 0) + { + return pc.Address; + } + upState.Symbol = Heap[upBranch.Address]; // UpState.Symbol=*(byte*) + + // UpBranch; + // UpState.Successor=(PPM_CONTEXT*) (((byte*) UpBranch)+1); + upState.SetSuccessor(upBranch.Address + 1); //TODO check if +1 necessary + if (pc.NumStats != 1) + { + if (pc.Address <= SubAlloc.PText) + { + return (0); + } + p.Address = pc.FreqData.GetStats(); + if (p.Symbol != upState.Symbol) + { + do + { + p.IncrementAddress(); + } while (p.Symbol != upState.Symbol); + } + var cf = p.Freq - 1; + var s0 = pc.FreqData.SummFreq - pc.NumStats - cf; + + // UpState.Freq=1+((2*cf <= s0)?(5*cf > s0):((2*cf+3*s0-1)/(2*s0))); + upState.Freq = + 1 + + ((2 * cf <= s0) ? (5 * cf > s0 ? 1 : 0) : (((2 * cf) + (3 * s0) - 1) / (2 * s0))); + } + else + { + upState.Freq = pc.GetOneState().Freq; // UpState.Freq=pc->OneState.Freq; + } + do + { + // pc = pc->createChild(this,*--pps,UpState); + tempState.Address = _ps[--pps]; + pc.Address = pc.CreateChild(this, tempState, upState); + if (pc.Address == 0) + { + return 0; + } + } while (pps != 0); + return pc.Address; + } + + private void UpdateModelRestart() + { + RestartModelRare(); + _escCount = 0; + } + + private void UpdateModel() + { + //System.out.println("ModelPPM.updateModel()"); + // STATE fs = *FoundState, *p = NULL; + var fs = _tempStateRef1; + fs.Values = FoundState; + var p = _tempState3.Initialize(Heap); + var tempState = _tempState4.Initialize(Heap); + + var pc = _tempPpmContext3.Initialize(Heap); + var successor = _tempPpmContext4.Initialize(Heap); + + int ns1, + ns, + cf, + sf, + s0; + pc.Address = _minContext.GetSuffix(); + if (fs.Freq < MAX_FREQ / 4 && pc.Address != 0) + { + if (pc.NumStats != 1) + { + p.Address = pc.FreqData.GetStats(); + if (p.Symbol != fs.Symbol) + { + do + { + p.IncrementAddress(); + } while (p.Symbol != fs.Symbol); + tempState.Address = p.Address - State.SIZE; + if (p.Freq >= tempState.Freq) + { + State.PpmdSwap(p, tempState); + p.DecrementAddress(); + } + } + if (p.Freq < MAX_FREQ - 9) + { + p.IncrementFreq(2); + pc.FreqData.IncrementSummFreq(2); + } } else { - UpdateModel(); + p.Address = pc.GetOneState().Address; + if (p.Freq < 32) + { + p.IncrementFreq(1); + } } } - - public int DecodeChar(Decoder decoder) + if (_orderFall == 0) { - if (_minContext.NumStats != 1) + FoundState.SetSuccessor(CreateSuccessors(true, p)); + _minContext.Address = FoundState.GetSuccessor(); + _maxContext.Address = FoundState.GetSuccessor(); + if (_minContext.Address == 0) { - State s = _tempState1.Initialize(Heap); - s.Address = _minContext.FreqData.GetStats(); - int i; - int count, hiCnt; - if ((count = (int)decoder.GetThreshold((uint)_minContext.FreqData.SummFreq)) < (hiCnt = s.Freq)) + UpdateModelRestart(); + return; + } + return; + } + SubAlloc.Heap[SubAlloc.PText] = (byte)fs.Symbol; + SubAlloc.IncPText(); + successor.Address = SubAlloc.PText; + if (SubAlloc.PText >= SubAlloc.FakeUnitsStart) + { + UpdateModelRestart(); + return; + } + + // // Debug + // subAlloc.dumpHeap(); + if (fs.GetSuccessor() != 0) + { + if (fs.GetSuccessor() <= SubAlloc.PText) + { + fs.SetSuccessor(CreateSuccessors(false, p)); + if (fs.GetSuccessor() == 0) { - byte symbol; - decoder.Decode(0, (uint)s.Freq); - symbol = (byte)s.Symbol; - _minContext.update1_0(this, s.Address); - NextContext(); - return symbol; + UpdateModelRestart(); + return; } - _prevSuccess = 0; - i = _minContext.NumStats - 1; - do + } + if (--_orderFall == 0) + { + successor.Address = fs.GetSuccessor(); + if (_maxContext.Address != _minContext.Address) { - s.IncrementAddress(); - if ((hiCnt += s.Freq) > count) + SubAlloc.DecPText(1); + } + } + } + else + { + FoundState.SetSuccessor(successor.Address); + fs.SetSuccessor(_minContext); + } + + // // Debug + // subAlloc.dumpHeap(); + ns = _minContext.NumStats; + s0 = _minContext.FreqData.SummFreq - (ns) - (fs.Freq - 1); + for ( + pc.Address = _maxContext.Address; + pc.Address != _minContext.Address; + pc.Address = pc.GetSuffix() + ) + { + if ((ns1 = pc.NumStats) != 1) + { + if ((ns1 & 1) == 0) + { + //System.out.println(ns1); + pc.FreqData.SetStats( + SubAlloc.ExpandUnits(pc.FreqData.GetStats(), Utility.URShift(ns1, 1)) + ); + if (pc.FreqData.GetStats() == 0) { - byte symbol; - decoder.Decode((uint)(hiCnt - s.Freq), (uint)s.Freq); - symbol = (byte)s.Symbol; - _minContext.Update1(this, s.Address); - NextContext(); - return symbol; + UpdateModelRestart(); + return; } } - while (--i > 0); - if (count >= _minContext.FreqData.SummFreq) - { - return -2; - } - _hiBitsFlag = _hb2Flag[FoundState.Symbol]; - decoder.Decode((uint)hiCnt, (uint)(_minContext.FreqData.SummFreq - hiCnt)); - for (i = 0; i < 256; i++) - { - _charMask[i] = -1; - } - _charMask[s.Symbol] = 0; - i = _minContext.NumStats - 1; - do - { - s.DecrementAddress(); - _charMask[s.Symbol] = 0; - } - while (--i > 0); + + // bug fixed + // int sum = ((2 * ns1 < ns) ? 1 : 0) + + // 2 * ((4 * ((ns1 <= ns) ? 1 : 0)) & ((pc.getFreqData() + // .getSummFreq() <= 8 * ns1) ? 1 : 0)); + var sum = + ((2 * ns1 < ns) ? 1 : 0) + + ( + 2 + * (((4 * ns1 <= ns) ? 1 : 0) & ((pc.FreqData.SummFreq <= 8 * ns1) ? 1 : 0)) + ); + pc.FreqData.IncrementSummFreq(sum); } else { - State rs = _tempState1.Initialize(Heap); - rs.Address = _minContext.GetOneState().Address; - _hiBitsFlag = GetHb2Flag()[FoundState.Symbol]; - int off1 = rs.Freq - 1; - int off2 = _minContext.GetArrayIndex(this, rs); - int bs = _binSumm[off1][off2]; - if (decoder.DecodeBit((uint)bs, 14) == 0) + p.Address = SubAlloc.AllocUnits(1); + if (p.Address == 0) { - byte symbol; - _binSumm[off1][off2] = (bs + INTERVAL - _minContext.GetMean(bs, PERIOD_BITS, 2)) & 0xFFFF; - FoundState.Address = rs.Address; - symbol = (byte)rs.Symbol; - rs.IncrementFreq((rs.Freq < 128) ? 1 : 0); - _prevSuccess = 1; - IncRunLength(1); - NextContext(); - return symbol; + UpdateModelRestart(); + return; } - bs = (bs - _minContext.GetMean(bs, PERIOD_BITS, 2)) & 0xFFFF; - _binSumm[off1][off2] = bs; - _initEsc = PpmContext.EXP_ESCAPE[Utility.URShift(bs, 10)]; - int i; - for (i = 0; i < 256; i++) + p.SetValues(pc.GetOneState()); + pc.FreqData.SetStats(p); + if (p.Freq < (MAX_FREQ / 4) - 1) { - _charMask[i] = -1; + p.IncrementFreq(p.Freq); } - _charMask[rs.Symbol] = 0; - _prevSuccess = 0; + else + { + p.Freq = MAX_FREQ - 4; + } + pc.FreqData.SummFreq = (p.Freq + _initEsc + (ns > 3 ? 1 : 0)); } - for (; ; ) + cf = 2 * fs.Freq * (pc.FreqData.SummFreq + 6); + sf = s0 + pc.FreqData.SummFreq; + if (cf < 6 * sf) { - State s = _tempState1.Initialize(Heap); - int i; - int freqSum, count, hiCnt; - See2Context see; - int num, numMasked = _minContext.NumStats; - do - { - _orderFall++; - _minContext.Address = _minContext.GetSuffix(); - if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) - { - return -1; - } - } - while (_minContext.NumStats == numMasked); - hiCnt = 0; - s.Address = _minContext.FreqData.GetStats(); - i = 0; - num = _minContext.NumStats - numMasked; - do - { - int k = _charMask[s.Symbol]; - hiCnt += s.Freq & k; - _minContext._ps[i] = s.Address; - s.IncrementAddress(); - i -= k; - } - while (i != num); + cf = 1 + (cf > sf ? 1 : 0) + (cf >= 4 * sf ? 1 : 0); + pc.FreqData.IncrementSummFreq(3); + } + else + { + cf = 4 + (cf >= 9 * sf ? 1 : 0) + (cf >= 12 * sf ? 1 : 0) + (cf >= 15 * sf ? 1 : 0); + pc.FreqData.IncrementSummFreq(cf); + } + p.Address = pc.FreqData.GetStats() + (ns1 * State.SIZE); + p.SetSuccessor(successor); + p.Symbol = fs.Symbol; + p.Freq = cf; + pc.NumStats = ++ns1; + } - see = _minContext.MakeEscFreq(this, numMasked, out freqSum); - freqSum += hiCnt; - count = (int)decoder.GetThreshold((uint)freqSum); + var address = fs.GetSuccessor(); + _maxContext.Address = address; + _minContext.Address = address; - if (count < hiCnt) + //TODO-----debug + // int pos = minContext.getFreqData().getStats(); + // State a = new State(getHeap()); + // a.Address=pos); + // pos+=State.size; + // a.Address=pos); + //--dbg end + } + + // Debug + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("ModelPPM["); + buffer.Append("\n numMasked="); + buffer.Append(_numMasked); + buffer.Append("\n initEsc="); + buffer.Append(_initEsc); + buffer.Append("\n orderFall="); + buffer.Append(_orderFall); + buffer.Append("\n maxOrder="); + buffer.Append(_maxOrder); + buffer.Append("\n runLength="); + buffer.Append(_runLength); + buffer.Append("\n initRL="); + buffer.Append(_initRl); + buffer.Append("\n escCount="); + buffer.Append(_escCount); + buffer.Append("\n prevSuccess="); + buffer.Append(_prevSuccess); + buffer.Append("\n foundState="); + buffer.Append(FoundState); + buffer.Append("\n coder="); + buffer.Append(Coder); + buffer.Append("\n subAlloc="); + buffer.Append(SubAlloc); + buffer.Append("\n]"); + return buffer.ToString(); + } + + // Debug + // public void dumpHeap() { + // subAlloc.dumpHeap(); + // } + + internal bool DecodeInit(Stream stream, int maxOrder, int maxMemory) + { + if (stream != null) + { + Coder = new RangeCoder(stream); + } + + if (maxOrder == 1) + { + SubAlloc.StopSubAllocator(); + return (false); + } + SubAlloc.StartSubAllocator(maxMemory); + _minContext = new PpmContext(Heap); + + //medContext = 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); + } + + internal void NextContext() + { + var addr = FoundState.GetSuccessor(); + if (_orderFall == 0 && addr > SubAlloc.PText) + { + _minContext.Address = addr; + _maxContext.Address = addr; + } + else + { + UpdateModel(); + } + } + + public int DecodeChar(Decoder decoder) + { + if (_minContext.NumStats != 1) + { + var s = _tempState1.Initialize(Heap); + s.Address = _minContext.FreqData.GetStats(); + int i; + int count, + hiCnt; + if ( + (count = (int)decoder.GetThreshold((uint)_minContext.FreqData.SummFreq)) + < (hiCnt = s.Freq) + ) + { + byte symbol; + decoder.Decode(0, (uint)s.Freq); + symbol = (byte)s.Symbol; + _minContext.update1_0(this, s.Address); + NextContext(); + return symbol; + } + _prevSuccess = 0; + i = _minContext.NumStats - 1; + do + { + s.IncrementAddress(); + if ((hiCnt += s.Freq) > count) { byte symbol; - State ps = _tempState2.Initialize(Heap); - for (hiCnt = 0, i = 0, ps.Address = _minContext._ps[i]; - (hiCnt += ps.Freq) <= count; - i++, ps.Address = _minContext._ps[i]) - { - ; - } - s.Address = ps.Address; decoder.Decode((uint)(hiCnt - s.Freq), (uint)s.Freq); - see.Update(); symbol = (byte)s.Symbol; - _minContext.Update2(this, s.Address); - UpdateModel(); + _minContext.Update1(this, s.Address); + NextContext(); return symbol; } - if (count >= freqSum) - { - return -2; - } - decoder.Decode((uint)hiCnt, (uint)(freqSum - hiCnt)); - see.Summ = see.Summ + freqSum; - do - { - s.Address = _minContext._ps[--i]; - _charMask[s.Symbol] = 0; - } - while (i != 0); + } while (--i > 0); + if (count >= _minContext.FreqData.SummFreq) + { + return -2; } + _hiBitsFlag = _hb2Flag[FoundState.Symbol]; + decoder.Decode((uint)hiCnt, (uint)(_minContext.FreqData.SummFreq - hiCnt)); + for (i = 0; i < 256; i++) + { + _charMask[i] = -1; + } + _charMask[s.Symbol] = 0; + i = _minContext.NumStats - 1; + do + { + s.DecrementAddress(); + _charMask[s.Symbol] = 0; + } while (--i > 0); + } + else + { + var rs = _tempState1.Initialize(Heap); + rs.Address = _minContext.GetOneState().Address; + _hiBitsFlag = GetHb2Flag()[FoundState.Symbol]; + var off1 = rs.Freq - 1; + var off2 = _minContext.GetArrayIndex(this, rs); + var bs = _binSumm[off1][off2]; + if (decoder.DecodeBit((uint)bs, 14) == 0) + { + byte symbol; + _binSumm[off1][off2] = + (bs + INTERVAL - _minContext.GetMean(bs, PERIOD_BITS, 2)) & 0xFFFF; + FoundState.Address = rs.Address; + symbol = (byte)rs.Symbol; + rs.IncrementFreq((rs.Freq < 128) ? 1 : 0); + _prevSuccess = 1; + IncRunLength(1); + NextContext(); + return symbol; + } + bs = (bs - _minContext.GetMean(bs, PERIOD_BITS, 2)) & 0xFFFF; + _binSumm[off1][off2] = bs; + _initEsc = PpmContext.EXP_ESCAPE[Utility.URShift(bs, 10)]; + int i; + for (i = 0; i < 256; i++) + { + _charMask[i] = -1; + } + _charMask[rs.Symbol] = 0; + _prevSuccess = 0; + } + for (; ; ) + { + var s = _tempState1.Initialize(Heap); + int i; + int count, + hiCnt; + See2Context see; + int num, + numMasked = _minContext.NumStats; + do + { + _orderFall++; + _minContext.Address = _minContext.GetSuffix(); + if (_minContext.Address <= SubAlloc.PText || _minContext.Address > SubAlloc.HeapEnd) + { + return -1; + } + } while (_minContext.NumStats == numMasked); + hiCnt = 0; + s.Address = _minContext.FreqData.GetStats(); + i = 0; + num = _minContext.NumStats - numMasked; + do + { + var k = _charMask[s.Symbol]; + hiCnt += s.Freq & k; + _minContext._ps[i] = s.Address; + s.IncrementAddress(); + i -= k; + } while (i != num); + + see = _minContext.MakeEscFreq(this, numMasked, out var freqSum); + freqSum += hiCnt; + count = (int)decoder.GetThreshold((uint)freqSum); + + if (count < hiCnt) + { + byte symbol; + var ps = _tempState2.Initialize(Heap); + for ( + hiCnt = 0, i = 0, ps.Address = _minContext._ps[i]; + (hiCnt += ps.Freq) <= count; + i++, ps.Address = _minContext._ps[i] + ) + { + ; + } + s.Address = ps.Address; + decoder.Decode((uint)(hiCnt - s.Freq), (uint)s.Freq); + see.Update(); + symbol = (byte)s.Symbol; + _minContext.Update2(this, s.Address); + UpdateModel(); + return symbol; + } + if (count >= freqSum) + { + return -2; + } + decoder.Decode((uint)hiCnt, (uint)(freqSum - hiCnt)); + see.Summ += freqSum; + do + { + s.Address = _minContext._ps[--i]; + _charMask[s.Symbol] = 0; + } while (i != 0); } } } diff --git a/src/SharpCompress/Compressors/PPMd/H/PPMContext.cs b/src/SharpCompress/Compressors/PPMd/H/PPMContext.cs index 83340887..a573aa8d 100644 --- a/src/SharpCompress/Compressors/PPMd/H/PPMContext.cs +++ b/src/SharpCompress/Compressors/PPMd/H/PPMContext.cs @@ -4,566 +4,548 @@ using System; using System.Buffers.Binary; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class PpmContext : Pointer { - internal class PpmContext : Pointer + internal FreqData FreqData { - internal FreqData FreqData + get => _freqData; + set { - get => _freqData; - set - { - _freqData.SummFreq = value.SummFreq; - _freqData.SetStats(value.GetStats()); - } - } - - public virtual int NumStats - { - get - { - if (Memory != null) - { - _numStats = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; - } - return _numStats; - } - - set - { - _numStats = value & 0xffff; - if (Memory != null) - { - BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); - } - } - } - - //UPGRADE_NOTE: Final was removed from the declaration of 'unionSize '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - //UPGRADE_NOTE: The initialization of 'unionSize' was moved to static method 'SharpCompress.Unpack.PPM.PPMContext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - private static readonly int UNION_SIZE; - - //UPGRADE_NOTE: Final was removed from the declaration of 'size '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int SIZE = 2 + UNION_SIZE + 4; // 12 - - // ushort NumStats; - private int _numStats; // determines if feqData or onstate is used - - // (1==onestate) - - //UPGRADE_NOTE: Final was removed from the declaration of 'freqData '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly FreqData _freqData; // -\ - - // |-> union - //UPGRADE_NOTE: Final was removed from the declaration of 'oneState '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _oneState; // -/ - - private int _suffix; // pointer ppmcontext - - //UPGRADE_NOTE: Final was removed from the declaration of 'ExpEscape'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int[] EXP_ESCAPE = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; - - // Temp fields - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState1 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState2 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState3 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState4 = new State(null); - - //UPGRADE_NOTE: Final was removed from the declaration of 'tempState5 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly State _tempState5 = new State(null); - private PpmContext _tempPpmContext; - - //UPGRADE_NOTE: Final was removed from the declaration of 'ps '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - internal int[] _ps = new int[256]; - - public PpmContext(byte[] memory) - : base(memory) - { - _oneState = new State(memory); - _freqData = new FreqData(memory); - } - - internal PpmContext Initialize(byte[] mem) - { - _oneState.Initialize(mem); - _freqData.Initialize(mem); - return base.Initialize(mem); - } - - internal State GetOneState() - { - return _oneState; - } - - internal void SetOneState(StateRef oneState) - { - _oneState.SetValues(oneState); - } - - internal int GetSuffix() - { - if (Memory != null) - { - _suffix = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 8)); - } - return _suffix; - } - - internal void SetSuffix(PpmContext suffix) - { - SetSuffix(suffix.Address); - } - - internal void SetSuffix(int suffix) - { - _suffix = suffix; - if (Memory != null) - { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 8), suffix); - } - } - - internal override int Address - { - get => base.Address; - set - { - base.Address = value; - _oneState.Address = value + 2; - _freqData.Address = value + 2; - } - } - - private PpmContext GetTempPpmContext(byte[] memory) - { - if (_tempPpmContext is null) - { - _tempPpmContext = new PpmContext(null); - } - return _tempPpmContext.Initialize(memory); - } - - internal int CreateChild(ModelPpm model, State pStats, StateRef firstState) - { - PpmContext pc = GetTempPpmContext(model.SubAlloc.Heap); - pc.Address = model.SubAlloc.AllocContext(); - if (pc != null) - { - pc.NumStats = 1; - pc.SetOneState(firstState); - pc.SetSuffix(this); - pStats.SetSuccessor(pc); - } - return pc.Address; - } - - internal void Rescale(ModelPpm model) - { - int oldNs = NumStats, i = NumStats - 1, adder, escFreq; - - // STATE* p1, * p; - State p1 = new State(model.Heap); - State p = new State(model.Heap); - State temp = new State(model.Heap); - - for (p.Address = model.FoundState.Address; p.Address != _freqData.GetStats(); p.DecrementAddress()) - { - temp.Address = p.Address - State.SIZE; - State.PpmdSwap(p, temp); - } - temp.Address = _freqData.GetStats(); - temp.IncrementFreq(4); - _freqData.IncrementSummFreq(4); - escFreq = _freqData.SummFreq - p.Freq; - adder = (model.OrderFall != 0) ? 1 : 0; - p.Freq = Utility.URShift((p.Freq + adder), 1); - _freqData.SummFreq = p.Freq; - do - { - p.IncrementAddress(); - escFreq -= p.Freq; - p.Freq = Utility.URShift((p.Freq + adder), 1); - _freqData.IncrementSummFreq(p.Freq); - temp.Address = p.Address - State.SIZE; - if (p.Freq > temp.Freq) - { - p1.Address = p.Address; - StateRef tmp = new StateRef(); - tmp.Values = p1; - State temp2 = new State(model.Heap); - State temp3 = new State(model.Heap); - do - { - // p1[0]=p1[-1]; - temp2.Address = p1.Address - State.SIZE; - p1.SetValues(temp2); - p1.DecrementAddress(); - temp3.Address = p1.Address - State.SIZE; - } - while (p1.Address != _freqData.GetStats() && tmp.Freq > temp3.Freq); - p1.SetValues(tmp); - } - } - while (--i != 0); - if (p.Freq == 0) - { - do - { - i++; - p.DecrementAddress(); - } - while (p.Freq == 0); - escFreq += i; - NumStats = NumStats - i; - if (NumStats == 1) - { - StateRef tmp = new StateRef(); - temp.Address = _freqData.GetStats(); - tmp.Values = temp; - - // STATE tmp=*U.Stats; - do - { - // tmp.Freq-=(tmp.Freq >> 1) - tmp.DecrementFreq(Utility.URShift(tmp.Freq, 1)); - escFreq = Utility.URShift(escFreq, 1); - } - while (escFreq > 1); - model.SubAlloc.FreeUnits(_freqData.GetStats(), Utility.URShift((oldNs + 1), 1)); - _oneState.SetValues(tmp); - model.FoundState.Address = _oneState.Address; - return; - } - } - escFreq -= Utility.URShift(escFreq, 1); - _freqData.IncrementSummFreq(escFreq); - int n0 = Utility.URShift((oldNs + 1), 1), n1 = Utility.URShift((NumStats + 1), 1); - if (n0 != n1) - { - _freqData.SetStats(model.SubAlloc.ShrinkUnits(_freqData.GetStats(), n0, n1)); - } - model.FoundState.Address = _freqData.GetStats(); - } - - internal int GetArrayIndex(ModelPpm model, State rs) - { - PpmContext tempSuffix = GetTempPpmContext(model.SubAlloc.Heap); - tempSuffix.Address = GetSuffix(); - int ret = 0; - ret += model.PrevSuccess; - ret += model.GetNs2BsIndx()[tempSuffix.NumStats - 1]; - ret += model.HiBitsFlag + 2 * model.GetHb2Flag()[rs.Symbol]; - ret += ((Utility.URShift(model.RunLength, 26)) & 0x20); - return ret; - } - - internal int GetMean(int summ, int shift, int round) - { - return (Utility.URShift((summ + (1 << (shift - round))), (shift))); - } - - internal void DecodeBinSymbol(ModelPpm model) - { - State rs = _tempState1.Initialize(model.Heap); - rs.Address = _oneState.Address; // State& - model.HiBitsFlag = model.GetHb2Flag()[model.FoundState.Symbol]; - int off1 = rs.Freq - 1; - int off2 = GetArrayIndex(model, rs); - int bs = model.BinSumm[off1][off2]; - if (model.Coder.GetCurrentShiftCount(ModelPpm.TOT_BITS) < bs) - { - model.FoundState.Address = rs.Address; - rs.IncrementFreq((rs.Freq < 128) ? 1 : 0); - model.Coder.SubRange.LowCount = 0; - model.Coder.SubRange.HighCount = bs; - bs = ((bs + ModelPpm.INTERVAL - GetMean(bs, ModelPpm.PERIOD_BITS, 2)) & 0xffff); - model.BinSumm[off1][off2] = bs; - model.PrevSuccess = 1; - model.IncRunLength(1); - } - else - { - model.Coder.SubRange.LowCount = bs; - bs = (bs - GetMean(bs, ModelPpm.PERIOD_BITS, 2)) & 0xFFFF; - model.BinSumm[off1][off2] = bs; - model.Coder.SubRange.HighCount = ModelPpm.BIN_SCALE; - model.InitEsc = EXP_ESCAPE[Utility.URShift(bs, 10)]; - model.NumMasked = 1; - model.CharMask[rs.Symbol] = model.EscCount; - model.PrevSuccess = 0; - model.FoundState.Address = 0; - } - - //int a = 0;//TODO just 4 debugging - } - - // public static void ppmdSwap(ModelPPM model, StatePtr state1, StatePtr state2) - // { - // byte[] bytes = model.getSubAlloc().getHeap(); - // int p1 = state1.Address; - // int p2 = state2.Address; - // - // for (int i = 0; i < StatePtr.size; i++) { - // byte temp = bytes[p1+i]; - // bytes[p1+i] = bytes[p2+i]; - // bytes[p2+i] = temp; - // } - // state1.Address=p1); - // state2.Address=p2); - // } - - internal void Update1(ModelPpm model, int p) - { - model.FoundState.Address = p; - model.FoundState.IncrementFreq(4); - _freqData.IncrementSummFreq(4); - State p0 = _tempState3.Initialize(model.Heap); - State p1 = _tempState4.Initialize(model.Heap); - p0.Address = p; - p1.Address = p - State.SIZE; - if (p0.Freq > p1.Freq) - { - State.PpmdSwap(p0, p1); - model.FoundState.Address = p1.Address; - if (p1.Freq > ModelPpm.MAX_FREQ) - { - Rescale(model); - } - } - } - - internal void update1_0(ModelPpm model, int p) - { - model.FoundState.Address = p; - model.PrevSuccess = 2 * model.FoundState.Freq > _freqData.SummFreq ? 1 : 0; - model.IncRunLength(model.PrevSuccess); - _freqData.IncrementSummFreq(4); - model.FoundState.IncrementFreq(4); - if (model.FoundState.Freq > ModelPpm.MAX_FREQ) - { - Rescale(model); - } - } - - internal bool DecodeSymbol2(ModelPpm model) - { - long count; - int hiCnt, i = NumStats - model.NumMasked; - See2Context psee2C = MakeEscFreq2(model, i); - RangeCoder coder = model.Coder; - - // STATE* ps[256], ** pps=ps, * p=U.Stats-1; - State p = _tempState1.Initialize(model.Heap); - State temp = _tempState2.Initialize(model.Heap); - p.Address = _freqData.GetStats() - State.SIZE; - int pps = 0; - hiCnt = 0; - - do - { - do - { - p.IncrementAddress(); // p++; - } - while (model.CharMask[p.Symbol] == model.EscCount); - hiCnt += p.Freq; - _ps[pps++] = p.Address; - } - while (--i != 0); - coder.SubRange.IncScale(hiCnt); - count = coder.CurrentCount; - if (count >= coder.SubRange.Scale) - { - return false; - } - pps = 0; - p.Address = _ps[pps]; - if (count < hiCnt) - { - hiCnt = 0; - while ((hiCnt += p.Freq) <= count) - { - p.Address = _ps[++pps]; // p=*++pps; - } - coder.SubRange.HighCount = hiCnt; - coder.SubRange.LowCount = hiCnt - p.Freq; - psee2C.Update(); - Update2(model, p.Address); - } - else - { - coder.SubRange.LowCount = hiCnt; - coder.SubRange.HighCount = coder.SubRange.Scale; - i = NumStats - model.NumMasked; // ->NumMasked; - pps--; - do - { - temp.Address = _ps[++pps]; // (*++pps) - model.CharMask[temp.Symbol] = model.EscCount; - } - while (--i != 0); - psee2C.IncSumm((int)coder.SubRange.Scale); - model.NumMasked = NumStats; - } - return (true); - } - - internal void Update2(ModelPpm model, int p) - { - State temp = _tempState5.Initialize(model.Heap); - temp.Address = p; - model.FoundState.Address = p; - model.FoundState.IncrementFreq(4); - _freqData.IncrementSummFreq(4); - if (temp.Freq > ModelPpm.MAX_FREQ) - { - Rescale(model); - } - model.IncEscCount(1); - model.RunLength = model.InitRl; - } - - private See2Context MakeEscFreq2(ModelPpm model, int diff) - { - See2Context psee2C; - int numStats = NumStats; - if (numStats != 256) - { - PpmContext suff = GetTempPpmContext(model.Heap); - suff.Address = GetSuffix(); - int idx1 = model.GetNs2Indx()[diff - 1]; - int idx2 = 0; - idx2 += ((diff < suff.NumStats - numStats) ? 1 : 0); - idx2 += 2 * ((_freqData.SummFreq < 11 * numStats) ? 1 : 0); - idx2 += 4 * ((model.NumMasked > diff) ? 1 : 0); - idx2 += model.HiBitsFlag; - psee2C = model.GetSee2Cont()[idx1][idx2]; - model.Coder.SubRange.Scale = psee2C.Mean; - } - else - { - psee2C = model.DummySee2Cont; - model.Coder.SubRange.Scale = 1; - } - return psee2C; - } - - internal See2Context MakeEscFreq(ModelPpm model, int numMasked, out int escFreq) - { - See2Context psee2C; - int numStats = NumStats; - int nonMasked = numStats - numMasked; - if (numStats != 256) - { - PpmContext suff = GetTempPpmContext(model.Heap); - suff.Address = GetSuffix(); - int idx1 = model.GetNs2Indx()[nonMasked - 1]; - int idx2 = 0; - idx2 += ((nonMasked < suff.NumStats - numStats) ? 1 : 0); - idx2 += 2 * ((_freqData.SummFreq < 11 * numStats) ? 1 : 0); - idx2 += 4 * ((numMasked > nonMasked) ? 1 : 0); - idx2 += model.HiBitsFlag; - psee2C = model.GetSee2Cont()[idx1][idx2]; - escFreq = psee2C.Mean; - } - else - { - psee2C = model.DummySee2Cont; - escFreq = 1; - } - return psee2C; - } - - internal bool DecodeSymbol1(ModelPpm model) - { - RangeCoder coder = model.Coder; - coder.SubRange.Scale = _freqData.SummFreq; - State p = new State(model.Heap); - p.Address = _freqData.GetStats(); - int i, hiCnt; - long count = coder.CurrentCount; - if (count >= coder.SubRange.Scale) - { - return false; - } - if (count < (hiCnt = p.Freq)) - { - coder.SubRange.HighCount = hiCnt; - model.PrevSuccess = (2 * hiCnt > coder.SubRange.Scale) ? 1 : 0; - model.IncRunLength(model.PrevSuccess); - hiCnt += 4; - model.FoundState.Address = p.Address; - model.FoundState.Freq = hiCnt; - _freqData.IncrementSummFreq(4); - if (hiCnt > ModelPpm.MAX_FREQ) - { - Rescale(model); - } - coder.SubRange.LowCount = 0; - return true; - } - if (model.FoundState.Address == 0) - { - return (false); - } - model.PrevSuccess = 0; - int numStats = NumStats; - i = numStats - 1; - while ((hiCnt += p.IncrementAddress().Freq) <= count) - { - if (--i == 0) - { - model.HiBitsFlag = model.GetHb2Flag()[model.FoundState.Symbol]; - coder.SubRange.LowCount = hiCnt; - model.CharMask[p.Symbol] = model.EscCount; - model.NumMasked = numStats; - i = numStats - 1; - model.FoundState.Address = 0; - do - { - model.CharMask[p.DecrementAddress().Symbol] = model.EscCount; - } - while (--i != 0); - coder.SubRange.HighCount = coder.SubRange.Scale; - return (true); - } - } - coder.SubRange.LowCount = hiCnt - p.Freq; - coder.SubRange.HighCount = hiCnt; - Update1(model, p.Address); - return (true); - } - - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("PPMContext["); - buffer.Append("\n Address="); - buffer.Append(Address); - buffer.Append("\n size="); - buffer.Append(SIZE); - buffer.Append("\n numStats="); - buffer.Append(NumStats); - buffer.Append("\n Suffix="); - buffer.Append(GetSuffix()); - buffer.Append("\n freqData="); - buffer.Append(_freqData); - buffer.Append("\n oneState="); - buffer.Append(_oneState); - buffer.Append("\n]"); - return buffer.ToString(); - } - - static PpmContext() - { - UNION_SIZE = Math.Max(FreqData.SIZE, State.SIZE); + _freqData.SummFreq = value.SummFreq; + _freqData.SetStats(value.GetStats()); } } + + public virtual int NumStats + { + get + { + if (Memory != null) + { + _numStats = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; + } + return _numStats; + } + set + { + _numStats = value & 0xffff; + if (Memory != null) + { + BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); + } + } + } + + //UPGRADE_NOTE: Final was removed from the declaration of 'unionSize '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + //UPGRADE_NOTE: The initialization of 'unionSize' was moved to static method 'SharpCompress.Unpack.PPM.PPMContext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" + private static readonly int UNION_SIZE; + + //UPGRADE_NOTE: Final was removed from the declaration of 'size '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int SIZE = 2 + UNION_SIZE + 4; // 12 + + // ushort NumStats; + private int _numStats; // determines if feqData or onstate is used + + // (1==onestate) + + //UPGRADE_NOTE: Final was removed from the declaration of 'freqData '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly FreqData _freqData; // -\ + + // |-> union + //UPGRADE_NOTE: Final was removed from the declaration of 'oneState '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _oneState; // -/ + + private int _suffix; // pointer ppmcontext + + //UPGRADE_NOTE: Final was removed from the declaration of 'ExpEscape'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int[] EXP_ESCAPE = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; + + // Temp fields + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState1 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState1 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState2 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState2 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState3 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState3 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState4 = new State(null); + + //UPGRADE_NOTE: Final was removed from the declaration of 'tempState5 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly State _tempState5 = new State(null); + private PpmContext _tempPpmContext; + + //UPGRADE_NOTE: Final was removed from the declaration of 'ps '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + internal int[] _ps = new int[256]; + + public PpmContext(byte[] memory) : base(memory) + { + _oneState = new State(memory); + _freqData = new FreqData(memory); + } + + internal PpmContext Initialize(byte[] mem) + { + _oneState.Initialize(mem); + _freqData.Initialize(mem); + return Initialize(mem); + } + + internal State GetOneState() => _oneState; + + internal void SetOneState(StateRef oneState) => _oneState.SetValues(oneState); + + internal int GetSuffix() + { + if (Memory != null) + { + _suffix = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 8)); + } + return _suffix; + } + + internal void SetSuffix(PpmContext suffix) => SetSuffix(suffix.Address); + + internal void SetSuffix(int suffix) + { + _suffix = suffix; + if (Memory != null) + { + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 8), suffix); + } + } + + internal override int Address + { + get => base.Address; + set + { + base.Address = value; + _oneState.Address = value + 2; + _freqData.Address = value + 2; + } + } + + private PpmContext GetTempPpmContext(byte[] memory) + { + _tempPpmContext ??= new PpmContext(null); + return _tempPpmContext.Initialize(memory); + } + + internal int CreateChild(ModelPpm model, State pStats, StateRef firstState) + { + var pc = GetTempPpmContext(model.SubAlloc.Heap); + pc.Address = model.SubAlloc.AllocContext(); + if (pc != null) + { + pc.NumStats = 1; + pc.SetOneState(firstState); + pc.SetSuffix(this); + pStats.SetSuccessor(pc); + } + return pc.Address; + } + + internal void Rescale(ModelPpm model) + { + int oldNs = NumStats, + i = NumStats - 1, + adder, + escFreq; + + // STATE* p1, * p; + var p1 = new State(model.Heap); + var p = new State(model.Heap); + var temp = new State(model.Heap); + + for ( + p.Address = model.FoundState.Address; + p.Address != _freqData.GetStats(); + p.DecrementAddress() + ) + { + temp.Address = p.Address - State.SIZE; + State.PpmdSwap(p, temp); + } + temp.Address = _freqData.GetStats(); + temp.IncrementFreq(4); + _freqData.IncrementSummFreq(4); + escFreq = _freqData.SummFreq - p.Freq; + adder = (model.OrderFall != 0) ? 1 : 0; + p.Freq = Utility.URShift((p.Freq + adder), 1); + _freqData.SummFreq = p.Freq; + do + { + p.IncrementAddress(); + escFreq -= p.Freq; + p.Freq = Utility.URShift((p.Freq + adder), 1); + _freqData.IncrementSummFreq(p.Freq); + temp.Address = p.Address - State.SIZE; + if (p.Freq > temp.Freq) + { + p1.Address = p.Address; + var tmp = new StateRef(); + tmp.Values = p1; + var temp2 = new State(model.Heap); + var temp3 = new State(model.Heap); + do + { + // p1[0]=p1[-1]; + temp2.Address = p1.Address - State.SIZE; + p1.SetValues(temp2); + p1.DecrementAddress(); + temp3.Address = p1.Address - State.SIZE; + } while (p1.Address != _freqData.GetStats() && tmp.Freq > temp3.Freq); + p1.SetValues(tmp); + } + } while (--i != 0); + if (p.Freq == 0) + { + do + { + i++; + p.DecrementAddress(); + } while (p.Freq == 0); + escFreq += i; + NumStats -= i; + if (NumStats == 1) + { + var tmp = new StateRef(); + temp.Address = _freqData.GetStats(); + tmp.Values = temp; + + // STATE tmp=*U.Stats; + do + { + // tmp.Freq-=(tmp.Freq >> 1) + tmp.DecrementFreq(Utility.URShift(tmp.Freq, 1)); + escFreq = Utility.URShift(escFreq, 1); + } while (escFreq > 1); + model.SubAlloc.FreeUnits(_freqData.GetStats(), Utility.URShift((oldNs + 1), 1)); + _oneState.SetValues(tmp); + model.FoundState.Address = _oneState.Address; + return; + } + } + escFreq -= Utility.URShift(escFreq, 1); + _freqData.IncrementSummFreq(escFreq); + int n0 = Utility.URShift((oldNs + 1), 1), + n1 = Utility.URShift((NumStats + 1), 1); + if (n0 != n1) + { + _freqData.SetStats(model.SubAlloc.ShrinkUnits(_freqData.GetStats(), n0, n1)); + } + model.FoundState.Address = _freqData.GetStats(); + } + + internal int GetArrayIndex(ModelPpm model, State rs) + { + var tempSuffix = GetTempPpmContext(model.SubAlloc.Heap); + tempSuffix.Address = GetSuffix(); + var ret = 0; + ret += model.PrevSuccess; + ret += model.GetNs2BsIndx()[tempSuffix.NumStats - 1]; + ret += model.HiBitsFlag + (2 * model.GetHb2Flag()[rs.Symbol]); + ret += ((Utility.URShift(model.RunLength, 26)) & 0x20); + return ret; + } + + internal int GetMean(int summ, int shift, int round) => + (Utility.URShift((summ + (1 << (shift - round))), (shift))); + + internal void DecodeBinSymbol(ModelPpm model) + { + var rs = _tempState1.Initialize(model.Heap); + rs.Address = _oneState.Address; // State& + model.HiBitsFlag = model.GetHb2Flag()[model.FoundState.Symbol]; + var off1 = rs.Freq - 1; + var off2 = GetArrayIndex(model, rs); + var bs = model.BinSumm[off1][off2]; + if (model.Coder.GetCurrentShiftCount(ModelPpm.TOT_BITS) < bs) + { + model.FoundState.Address = rs.Address; + rs.IncrementFreq((rs.Freq < 128) ? 1 : 0); + model.Coder.SubRange.LowCount = 0; + model.Coder.SubRange.HighCount = bs; + bs = ((bs + ModelPpm.INTERVAL - GetMean(bs, ModelPpm.PERIOD_BITS, 2)) & 0xffff); + model.BinSumm[off1][off2] = bs; + model.PrevSuccess = 1; + model.IncRunLength(1); + } + else + { + model.Coder.SubRange.LowCount = bs; + bs = (bs - GetMean(bs, ModelPpm.PERIOD_BITS, 2)) & 0xFFFF; + model.BinSumm[off1][off2] = bs; + model.Coder.SubRange.HighCount = ModelPpm.BIN_SCALE; + model.InitEsc = EXP_ESCAPE[Utility.URShift(bs, 10)]; + model.NumMasked = 1; + model.CharMask[rs.Symbol] = model.EscCount; + model.PrevSuccess = 0; + model.FoundState.Address = 0; + } + + //int a = 0;//TODO just 4 debugging + } + + // public static void ppmdSwap(ModelPPM model, StatePtr state1, StatePtr state2) + // { + // byte[] bytes = model.getSubAlloc().getHeap(); + // int p1 = state1.Address; + // int p2 = state2.Address; + // + // for (int i = 0; i < StatePtr.size; i++) { + // byte temp = bytes[p1+i]; + // bytes[p1+i] = bytes[p2+i]; + // bytes[p2+i] = temp; + // } + // state1.Address=p1); + // state2.Address=p2); + // } + + internal void Update1(ModelPpm model, int p) + { + model.FoundState.Address = p; + model.FoundState.IncrementFreq(4); + _freqData.IncrementSummFreq(4); + var p0 = _tempState3.Initialize(model.Heap); + var p1 = _tempState4.Initialize(model.Heap); + p0.Address = p; + p1.Address = p - State.SIZE; + if (p0.Freq > p1.Freq) + { + State.PpmdSwap(p0, p1); + model.FoundState.Address = p1.Address; + if (p1.Freq > ModelPpm.MAX_FREQ) + { + Rescale(model); + } + } + } + + internal void update1_0(ModelPpm model, int p) + { + model.FoundState.Address = p; + model.PrevSuccess = 2 * model.FoundState.Freq > _freqData.SummFreq ? 1 : 0; + model.IncRunLength(model.PrevSuccess); + _freqData.IncrementSummFreq(4); + model.FoundState.IncrementFreq(4); + if (model.FoundState.Freq > ModelPpm.MAX_FREQ) + { + Rescale(model); + } + } + + internal bool DecodeSymbol2(ModelPpm model) + { + long count; + int hiCnt, + i = NumStats - model.NumMasked; + var psee2C = MakeEscFreq2(model, i); + var coder = model.Coder; + + // STATE* ps[256], ** pps=ps, * p=U.Stats-1; + var p = _tempState1.Initialize(model.Heap); + var temp = _tempState2.Initialize(model.Heap); + p.Address = _freqData.GetStats() - State.SIZE; + var pps = 0; + hiCnt = 0; + + do + { + do + { + p.IncrementAddress(); // p++; + } while (model.CharMask[p.Symbol] == model.EscCount); + hiCnt += p.Freq; + _ps[pps++] = p.Address; + } while (--i != 0); + coder.SubRange.IncScale(hiCnt); + count = coder.CurrentCount; + if (count >= coder.SubRange.Scale) + { + return false; + } + pps = 0; + p.Address = _ps[pps]; + if (count < hiCnt) + { + hiCnt = 0; + while ((hiCnt += p.Freq) <= count) + { + p.Address = _ps[++pps]; // p=*++pps; + } + coder.SubRange.HighCount = hiCnt; + coder.SubRange.LowCount = hiCnt - p.Freq; + psee2C.Update(); + Update2(model, p.Address); + } + else + { + coder.SubRange.LowCount = hiCnt; + coder.SubRange.HighCount = coder.SubRange.Scale; + i = NumStats - model.NumMasked; // ->NumMasked; + pps--; + do + { + temp.Address = _ps[++pps]; // (*++pps) + model.CharMask[temp.Symbol] = model.EscCount; + } while (--i != 0); + psee2C.IncSumm((int)coder.SubRange.Scale); + model.NumMasked = NumStats; + } + return (true); + } + + internal void Update2(ModelPpm model, int p) + { + var temp = _tempState5.Initialize(model.Heap); + temp.Address = p; + model.FoundState.Address = p; + model.FoundState.IncrementFreq(4); + _freqData.IncrementSummFreq(4); + if (temp.Freq > ModelPpm.MAX_FREQ) + { + Rescale(model); + } + model.IncEscCount(1); + model.RunLength = model.InitRl; + } + + private See2Context MakeEscFreq2(ModelPpm model, int diff) + { + See2Context psee2C; + var numStats = NumStats; + if (numStats != 256) + { + var suff = GetTempPpmContext(model.Heap); + suff.Address = GetSuffix(); + var idx1 = model.GetNs2Indx()[diff - 1]; + var idx2 = 0; + idx2 += ((diff < suff.NumStats - numStats) ? 1 : 0); + idx2 += 2 * ((_freqData.SummFreq < 11 * numStats) ? 1 : 0); + idx2 += 4 * ((model.NumMasked > diff) ? 1 : 0); + idx2 += model.HiBitsFlag; + psee2C = model.GetSee2Cont()[idx1][idx2]; + model.Coder.SubRange.Scale = psee2C.Mean; + } + else + { + psee2C = model.DummySee2Cont; + model.Coder.SubRange.Scale = 1; + } + return psee2C; + } + + internal See2Context MakeEscFreq(ModelPpm model, int numMasked, out int escFreq) + { + See2Context psee2C; + var numStats = NumStats; + var nonMasked = numStats - numMasked; + if (numStats != 256) + { + var suff = GetTempPpmContext(model.Heap); + suff.Address = GetSuffix(); + var idx1 = model.GetNs2Indx()[nonMasked - 1]; + var idx2 = 0; + idx2 += ((nonMasked < suff.NumStats - numStats) ? 1 : 0); + idx2 += 2 * ((_freqData.SummFreq < 11 * numStats) ? 1 : 0); + idx2 += 4 * ((numMasked > nonMasked) ? 1 : 0); + idx2 += model.HiBitsFlag; + psee2C = model.GetSee2Cont()[idx1][idx2]; + escFreq = psee2C.Mean; + } + else + { + psee2C = model.DummySee2Cont; + escFreq = 1; + } + return psee2C; + } + + internal bool DecodeSymbol1(ModelPpm model) + { + var coder = model.Coder; + coder.SubRange.Scale = _freqData.SummFreq; + var p = new State(model.Heap); + p.Address = _freqData.GetStats(); + int i, + hiCnt; + long count = coder.CurrentCount; + if (count >= coder.SubRange.Scale) + { + return false; + } + if (count < (hiCnt = p.Freq)) + { + coder.SubRange.HighCount = hiCnt; + model.PrevSuccess = (2 * hiCnt > coder.SubRange.Scale) ? 1 : 0; + model.IncRunLength(model.PrevSuccess); + hiCnt += 4; + model.FoundState.Address = p.Address; + model.FoundState.Freq = hiCnt; + _freqData.IncrementSummFreq(4); + if (hiCnt > ModelPpm.MAX_FREQ) + { + Rescale(model); + } + coder.SubRange.LowCount = 0; + return true; + } + if (model.FoundState.Address == 0) + { + return (false); + } + model.PrevSuccess = 0; + var numStats = NumStats; + i = numStats - 1; + while ((hiCnt += p.IncrementAddress().Freq) <= count) + { + if (--i == 0) + { + model.HiBitsFlag = model.GetHb2Flag()[model.FoundState.Symbol]; + coder.SubRange.LowCount = hiCnt; + model.CharMask[p.Symbol] = model.EscCount; + model.NumMasked = numStats; + i = numStats - 1; + model.FoundState.Address = 0; + do + { + model.CharMask[p.DecrementAddress().Symbol] = model.EscCount; + } while (--i != 0); + coder.SubRange.HighCount = coder.SubRange.Scale; + return (true); + } + } + coder.SubRange.LowCount = hiCnt - p.Freq; + coder.SubRange.HighCount = hiCnt; + Update1(model, p.Address); + return (true); + } + + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("PPMContext["); + buffer.Append("\n Address="); + buffer.Append(Address); + buffer.Append("\n size="); + buffer.Append(SIZE); + buffer.Append("\n numStats="); + buffer.Append(NumStats); + buffer.Append("\n Suffix="); + buffer.Append(GetSuffix()); + buffer.Append("\n freqData="); + buffer.Append(_freqData); + buffer.Append("\n oneState="); + buffer.Append(_oneState); + buffer.Append("\n]"); + return buffer.ToString(); + } + + static PpmContext() => UNION_SIZE = Math.Max(FreqData.SIZE, State.SIZE); } diff --git a/src/SharpCompress/Compressors/PPMd/H/Pointer.cs b/src/SharpCompress/Compressors/PPMd/H/Pointer.cs index 98f0a9cb..b304b359 100644 --- a/src/SharpCompress/Compressors/PPMd/H/Pointer.cs +++ b/src/SharpCompress/Compressors/PPMd/H/Pointer.cs @@ -1,27 +1,22 @@ #nullable disable -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal abstract class Pointer { - internal abstract class Pointer + /// Initialize the object with the array (may be null) + /// the byte array + /// + internal Pointer(byte[] mem) => Memory = mem; + + internal byte[] Memory { get; private set; } + + internal virtual int Address { get; set; } + + protected T Initialize(byte[] mem) where T : Pointer { - /// Initialize the object with the array (may be null) - /// the byte array - /// - internal Pointer(byte[] mem) - { - Memory = mem; - } - - internal byte[] Memory { get; private set; } - - internal virtual int Address { get; set; } - - protected T Initialize(byte[] mem) - where T : Pointer - { - Memory = mem; - Address = 0; - return this as T; - } + Memory = mem; + Address = 0; + return this as T; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs index ce778e19..6659cb83 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs @@ -1,157 +1,168 @@ #nullable disable -using System; using System.IO; using System.Text; using SharpCompress.Compressors.Rar; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class RangeCoder { - internal class RangeCoder + internal const int TOP = 1 << 24; + internal const int BOT = 1 << 15; + internal const long UINT_MASK = 0xFFFFffffL; + + // uint low, code, range; + private long _low, + _code, + _range; + private readonly IRarUnpack _unpackRead; + private readonly Stream _stream; + + internal RangeCoder(IRarUnpack unpackRead) { - internal const int TOP = 1 << 24; - internal const int BOT = 1 << 15; - internal const long UINT_MASK = 0xFFFFffffL; + _unpackRead = unpackRead; + Init(); + } - // uint low, code, range; - private long _low, _code, _range; - private readonly IRarUnpack _unpackRead; - private readonly Stream _stream; + internal RangeCoder(Stream stream) + { + _stream = stream; + Init(); + } - internal RangeCoder(IRarUnpack unpackRead) + private void Init() + { + SubRange = new SubRange(); + + _low = _code = 0L; + _range = 0xFFFFffffL; + for (var i = 0; i < 4; i++) { - _unpackRead = unpackRead; - Init(); - } - - internal RangeCoder(Stream stream) - { - _stream = stream; - Init(); - } - - private void Init() - { - SubRange = new SubRange(); - - _low = _code = 0L; - _range = 0xFFFFffffL; - for (int i = 0; i < 4; i++) - { - _code = ((_code << 8) | Char) & UINT_MASK; - } - } - - internal int CurrentCount - { - get - { - _range = (_range / SubRange.Scale) & UINT_MASK; - return (int)((_code - _low) / (_range)); - } - } - - private long Char - { - get - { - if (_unpackRead != null) - { - return (_unpackRead.Char); - } - if (_stream != null) - { - return _stream.ReadByte(); - } - return -1; - } - } - - internal SubRange SubRange { get; private set; } - - internal long GetCurrentShiftCount(int shift) - { - _range = Utility.URShift(_range, shift); - return ((_code - _low) / (_range)) & UINT_MASK; - } - - internal void Decode() - { - _low = (_low + (_range * SubRange.LowCount)) & UINT_MASK; - _range = (_range * (SubRange.HighCount - SubRange.LowCount)) & UINT_MASK; - } - - internal void AriDecNormalize() - { - // while ((low ^ (low + range)) < TOP || range < BOT && ((range = -low & (BOT - 1)) != 0 ? true : true)) - // { - // code = ((code << 8) | unpackRead.getChar()&0xff)&uintMask; - // range = (range << 8)&uintMask; - // low = (low << 8)&uintMask; - // } - - // Rewrote for clarity - bool c2 = false; - while ((_low ^ (_low + _range)) < TOP || (c2 = _range < BOT)) - { - if (c2) - { - _range = (-_low & (BOT - 1)) & UINT_MASK; - c2 = false; - } - _code = ((_code << 8) | Char) & UINT_MASK; - _range = (_range << 8) & UINT_MASK; - _low = (_low << 8) & UINT_MASK; - } - } - - // Debug - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("RangeCoder["); - buffer.Append("\n low="); - buffer.Append(_low); - buffer.Append("\n code="); - buffer.Append(_code); - buffer.Append("\n range="); - buffer.Append(_range); - buffer.Append("\n subrange="); - buffer.Append(SubRange); - buffer.Append(']'); - return buffer.ToString(); + _code = ((_code << 8) | Char) & UINT_MASK; } } - internal class SubRange + internal int CurrentCount { - // uint LowCount, HighCount, scale; - private long _lowCount, _highCount, _scale; - - internal void IncScale(int dScale) + get { - Scale = Scale + dScale; + _range = (_range / SubRange.Scale) & UINT_MASK; + return (int)((_code - _low) / (_range)); } + } - internal long HighCount { get => _highCount; set => _highCount = value & RangeCoder.UINT_MASK; } - - internal long LowCount { get => _lowCount & RangeCoder.UINT_MASK; set => _lowCount = value & RangeCoder.UINT_MASK; } - - internal long Scale { get => _scale; set => _scale = value & RangeCoder.UINT_MASK; } - - // Debug - public override String ToString() + private long Char + { + get { - StringBuilder buffer = new StringBuilder(); - buffer.Append("SubRange["); - buffer.Append("\n lowCount="); - buffer.Append(_lowCount); - buffer.Append("\n highCount="); - buffer.Append(_highCount); - buffer.Append("\n scale="); - buffer.Append(_scale); - buffer.Append(']'); - return buffer.ToString(); + if (_unpackRead != null) + { + return (_unpackRead.Char); + } + if (_stream != null) + { + return _stream.ReadByte(); + } + return -1; } } + + internal SubRange SubRange { get; private set; } + + internal long GetCurrentShiftCount(int shift) + { + _range = Utility.URShift(_range, shift); + return ((_code - _low) / (_range)) & UINT_MASK; + } + + internal void Decode() + { + _low = (_low + (_range * SubRange.LowCount)) & UINT_MASK; + _range = (_range * (SubRange.HighCount - SubRange.LowCount)) & UINT_MASK; + } + + internal void AriDecNormalize() + { + // while ((low ^ (low + range)) < TOP || range < BOT && ((range = -low & (BOT - 1)) != 0 ? true : true)) + // { + // code = ((code << 8) | unpackRead.getChar()&0xff)&uintMask; + // range = (range << 8)&uintMask; + // low = (low << 8)&uintMask; + // } + + // Rewrote for clarity + var c2 = false; + while ((_low ^ (_low + _range)) < TOP || (c2 = _range < BOT)) + { + if (c2) + { + _range = (-_low & (BOT - 1)) & UINT_MASK; + c2 = false; + } + _code = ((_code << 8) | Char) & UINT_MASK; + _range = (_range << 8) & UINT_MASK; + _low = (_low << 8) & UINT_MASK; + } + } + + // Debug + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("RangeCoder["); + buffer.Append("\n low="); + buffer.Append(_low); + buffer.Append("\n code="); + buffer.Append(_code); + buffer.Append("\n range="); + buffer.Append(_range); + buffer.Append("\n subrange="); + buffer.Append(SubRange); + buffer.Append(']'); + return buffer.ToString(); + } +} + +internal class SubRange +{ + // uint LowCount, HighCount, scale; + private long _lowCount, + _highCount, + _scale; + + internal void IncScale(int dScale) => Scale += dScale; + + internal long HighCount + { + get => _highCount; + set => _highCount = value & RangeCoder.UINT_MASK; + } + + internal long LowCount + { + get => _lowCount & RangeCoder.UINT_MASK; + set => _lowCount = value & RangeCoder.UINT_MASK; + } + + internal long Scale + { + get => _scale; + set => _scale = value & RangeCoder.UINT_MASK; + } + + // Debug + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("SubRange["); + buffer.Append("\n lowCount="); + buffer.Append(_lowCount); + buffer.Append("\n highCount="); + buffer.Append(_highCount); + buffer.Append("\n scale="); + buffer.Append(_scale); + buffer.Append(']'); + return buffer.ToString(); + } } diff --git a/src/SharpCompress/Compressors/PPMd/H/RarMemBlock.cs b/src/SharpCompress/Compressors/PPMd/H/RarMemBlock.cs index 2c63811f..9fe3958a 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RarMemBlock.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RarMemBlock.cs @@ -1,127 +1,118 @@ using System; using System.Buffers.Binary; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class RarMemBlock : Pointer { - internal class RarMemBlock : Pointer + public const int SIZE = 12; + + private int _stamp, + _nu; + + private int _next, + _prev; // Pointer RarMemBlock + + public RarMemBlock(byte[] memory) : base(memory) { } + + internal int Stamp { - public const int SIZE = 12; - - private int _stamp, _nu; - - private int _next, _prev; // Pointer RarMemBlock - - public RarMemBlock(byte[] memory) - : base(memory) - { - } - - internal int Stamp - { - get - { - if (Memory != null) - { - _stamp = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; - } - return _stamp; - } - - set - { - _stamp = value; - if (Memory != null) - { - BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); - } - } - } - - internal void InsertAt(RarMemBlock p) - { - RarMemBlock temp = new RarMemBlock(Memory); - SetPrev(p.Address); - temp.Address = GetPrev(); - SetNext(temp.GetNext()); // prev.getNext(); - temp.SetNext(this); // prev.setNext(this); - temp.Address = GetNext(); - temp.SetPrev(this); // next.setPrev(this); - } - - internal void Remove() - { - RarMemBlock temp = new RarMemBlock(Memory); - temp.Address = GetPrev(); - temp.SetNext(GetNext()); // prev.setNext(next); - temp.Address = GetNext(); - temp.SetPrev(GetPrev()); // next.setPrev(prev); - - // next = -1; - // prev = -1; - } - - internal int GetNext() + get { if (Memory != null) { - _next = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 4)); + _stamp = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address)) & 0xffff; } - return _next; + return _stamp; } - - internal void SetNext(RarMemBlock next) + set { - SetNext(next.Address); - } - - internal void SetNext(int next) - { - _next = next; + _stamp = value; if (Memory != null) { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 4), next); - } - } - - internal int GetNu() - { - if (Memory != null) - { - _nu = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address + 2)) & 0xffff; - } - return _nu; - } - - internal void SetNu(int nu) - { - _nu = nu & 0xffff; - if (Memory != null) - { - BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address + 2), (short)nu); - } - } - - internal int GetPrev() - { - if (Memory != null) - { - _prev = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 8)); - } - return _prev; - } - - internal void SetPrev(RarMemBlock prev) - { - SetPrev(prev.Address); - } - - internal void SetPrev(int prev) - { - _prev = prev; - if (Memory != null) - { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 8), prev); + BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address), (short)value); } } } + + internal void InsertAt(RarMemBlock p) + { + var temp = new RarMemBlock(Memory); + SetPrev(p.Address); + temp.Address = GetPrev(); + SetNext(temp.GetNext()); // prev.getNext(); + temp.SetNext(this); // prev.setNext(this); + temp.Address = GetNext(); + temp.SetPrev(this); // next.setPrev(this); + } + + internal void Remove() + { + var temp = new RarMemBlock(Memory); + temp.Address = GetPrev(); + temp.SetNext(GetNext()); // prev.setNext(next); + temp.Address = GetNext(); + temp.SetPrev(GetPrev()); // next.setPrev(prev); + + // next = -1; + // prev = -1; + } + + internal int GetNext() + { + if (Memory != null) + { + _next = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 4)); + } + return _next; + } + + internal void SetNext(RarMemBlock next) => SetNext(next.Address); + + internal void SetNext(int next) + { + _next = next; + if (Memory != null) + { + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 4), next); + } + } + + internal int GetNu() + { + if (Memory != null) + { + _nu = BinaryPrimitives.ReadInt16LittleEndian(Memory.AsSpan(Address + 2)) & 0xffff; + } + return _nu; + } + + internal void SetNu(int nu) + { + _nu = nu & 0xffff; + if (Memory != null) + { + BinaryPrimitives.WriteInt16LittleEndian(Memory.AsSpan(Address + 2), (short)nu); + } + } + + internal int GetPrev() + { + if (Memory != null) + { + _prev = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 8)); + } + return _prev; + } + + internal void SetPrev(RarMemBlock prev) => SetPrev(prev.Address); + + internal void SetPrev(int prev) + { + _prev = prev; + if (Memory != null) + { + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 8), prev); + } + } } diff --git a/src/SharpCompress/Compressors/PPMd/H/RarNode.cs b/src/SharpCompress/Compressors/PPMd/H/RarNode.cs index f6db5cf7..614e62cb 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RarNode.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RarNode.cs @@ -2,54 +2,47 @@ using System; using System.Buffers.Binary; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class RarNode : Pointer { - internal class RarNode : Pointer + private int _next; //rarnode pointer + + public const int SIZE = 4; + + public RarNode(byte[] memory) : base(memory) { } + + internal int GetNext() { - private int _next; //rarnode pointer - - public const int SIZE = 4; - - public RarNode(byte[] memory) - : base(memory) + if (Memory != null) { + _next = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address)); } + return _next; + } - internal int GetNext() - { - if (Memory != null) - { - _next = BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address)); - } - return _next; - } + internal void SetNext(RarNode next) => SetNext(next.Address); - internal void SetNext(RarNode next) + internal void SetNext(int next) + { + _next = next; + if (Memory != null) { - SetNext(next.Address); - } - - internal void SetNext(int next) - { - _next = next; - if (Memory != null) - { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address), next); - } - } - - public override string ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("State["); - buffer.Append("\n Address="); - buffer.Append(Address); - buffer.Append("\n size="); - buffer.Append(SIZE); - buffer.Append("\n next="); - buffer.Append(GetNext()); - buffer.Append("\n]"); - return buffer.ToString(); + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address), next); } } + + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("State["); + buffer.Append("\n Address="); + buffer.Append(Address); + buffer.Append("\n size="); + buffer.Append(SIZE); + buffer.Append("\n next="); + buffer.Append(GetNext()); + buffer.Append("\n]"); + return buffer.ToString(); + } } diff --git a/src/SharpCompress/Compressors/PPMd/H/SEE2Context.cs b/src/SharpCompress/Compressors/PPMd/H/SEE2Context.cs index c97f0836..0278294a 100644 --- a/src/SharpCompress/Compressors/PPMd/H/SEE2Context.cs +++ b/src/SharpCompress/Compressors/PPMd/H/SEE2Context.cs @@ -1,75 +1,82 @@ -using System; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class See2Context { - internal class See2Context + public virtual int Mean { - public virtual int Mean + get { - get - { - int retVal = Utility.URShift(_summ, _shift); - _summ -= retVal; - return retVal + ((retVal == 0) ? 1 : 0); - } - } - - public virtual int Count { get => _count; set => _count = value & 0xff; } - - public virtual int Shift { get => _shift; set => _shift = value & 0xff; } - - public virtual int Summ { get => _summ; set => _summ = value & 0xffff; } - - public const int SIZE = 4; - - // ushort Summ; - private int _summ; - - // byte Shift; - private int _shift; - - // byte Count; - private int _count; - - public void Initialize(int initVal) - { - _shift = (ModelPpm.PERIOD_BITS - 4) & 0xff; - _summ = (initVal << _shift) & 0xffff; - _count = 4; - } - - public virtual void Update() - { - if (_shift < ModelPpm.PERIOD_BITS && --_count == 0) - { - _summ += _summ; - _count = (3 << _shift++); - } - _summ &= 0xffff; - _count &= 0xff; - _shift &= 0xff; - } - - public virtual void IncSumm(int dSumm) - { - Summ = Summ + dSumm; - } - - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("SEE2Context["); - buffer.Append("\n size="); - buffer.Append(SIZE); - buffer.Append("\n summ="); - buffer.Append(_summ); - buffer.Append("\n shift="); - buffer.Append(_shift); - buffer.Append("\n count="); - buffer.Append(_count); - buffer.Append("\n]"); - return buffer.ToString(); + var retVal = Utility.URShift(_summ, _shift); + _summ -= retVal; + return retVal + ((retVal == 0) ? 1 : 0); } } -} \ No newline at end of file + + public virtual int Count + { + get => _count; + set => _count = value & 0xff; + } + + public virtual int Shift + { + get => _shift; + set => _shift = value & 0xff; + } + + public virtual int Summ + { + get => _summ; + set => _summ = value & 0xffff; + } + + public const int SIZE = 4; + + // ushort Summ; + private int _summ; + + // byte Shift; + private int _shift; + + // byte Count; + private int _count; + + public void Initialize(int initVal) + { + _shift = (ModelPpm.PERIOD_BITS - 4) & 0xff; + _summ = (initVal << _shift) & 0xffff; + _count = 4; + } + + public virtual void Update() + { + if (_shift < ModelPpm.PERIOD_BITS && --_count == 0) + { + _summ += _summ; + _count = (3 << _shift++); + } + _summ &= 0xffff; + _count &= 0xff; + _shift &= 0xff; + } + + public virtual void IncSumm(int dSumm) => Summ += dSumm; + + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("SEE2Context["); + buffer.Append("\n size="); + buffer.Append(SIZE); + buffer.Append("\n summ="); + buffer.Append(_summ); + buffer.Append("\n shift="); + buffer.Append(_shift); + buffer.Append("\n count="); + buffer.Append(_count); + buffer.Append("\n]"); + return buffer.ToString(); + } +} diff --git a/src/SharpCompress/Compressors/PPMd/H/State.cs b/src/SharpCompress/Compressors/PPMd/H/State.cs index e9d1a48c..dc901c2e 100644 --- a/src/SharpCompress/Compressors/PPMd/H/State.cs +++ b/src/SharpCompress/Compressors/PPMd/H/State.cs @@ -2,97 +2,88 @@ using System; using System.Buffers.Binary; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal sealed class State : Pointer { - internal sealed class State : Pointer + internal const int SIZE = 6; + + internal State(byte[]? memory) : base(memory) { } + + internal int Symbol { - internal const int SIZE = 6; + get => Memory[Address] & 0xff; + set => Memory[Address] = (byte)value; + } - internal State(byte[]? memory) - : base(memory) + internal int Freq + { + get => Memory[Address + 1] & 0xff; + set => Memory[Address + 1] = (byte)value; + } + + internal State Initialize(byte[] mem) => Initialize(mem); + + internal void IncrementFreq(int dFreq) => + Memory[Address + 1] = (byte)(Memory[Address + 1] + dFreq); + + internal int GetSuccessor() => + BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 2)); + + internal void SetSuccessor(PpmContext successor) => SetSuccessor(successor.Address); + + internal void SetSuccessor(int successor) => + BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 2), successor); + + internal void SetValues(StateRef state) + { + Symbol = state.Symbol; + Freq = state.Freq; + SetSuccessor(state.GetSuccessor()); + } + + internal void SetValues(State ptr) => + Array.Copy(ptr.Memory, ptr.Address, Memory, Address, SIZE); + + internal State DecrementAddress() + { + Address -= SIZE; + return this; + } + + internal State IncrementAddress() + { + Address += SIZE; + return this; + } + + internal static void PpmdSwap(State ptr1, State ptr2) + { + byte[] mem1 = ptr1.Memory, + mem2 = ptr2.Memory; + for (int i = 0, pos1 = ptr1.Address, pos2 = ptr2.Address; i < SIZE; i++, pos1++, pos2++) { - } - - internal int Symbol { get => Memory[Address] & 0xff; set => Memory[Address] = (byte)value; } - - internal int Freq { get => Memory[Address + 1] & 0xff; set => Memory[Address + 1] = (byte)value; } - - internal State Initialize(byte[] mem) - { - return base.Initialize(mem); - } - - internal void IncrementFreq(int dFreq) - { - Memory[Address + 1] = (byte)(Memory[Address + 1] + dFreq); - } - - internal int GetSuccessor() - { - return BinaryPrimitives.ReadInt32LittleEndian(Memory.AsSpan(Address + 2)); - } - - internal void SetSuccessor(PpmContext successor) - { - SetSuccessor(successor.Address); - } - - internal void SetSuccessor(int successor) - { - BinaryPrimitives.WriteInt32LittleEndian(Memory.AsSpan(Address + 2), successor); - } - - internal void SetValues(StateRef state) - { - Symbol = state.Symbol; - Freq = state.Freq; - SetSuccessor(state.GetSuccessor()); - } - - internal void SetValues(State ptr) - { - Array.Copy(ptr.Memory, ptr.Address, Memory, Address, SIZE); - } - - internal State DecrementAddress() - { - Address = Address - SIZE; - return this; - } - - internal State IncrementAddress() - { - Address = Address + SIZE; - return this; - } - - internal static void PpmdSwap(State ptr1, State ptr2) - { - byte[] mem1 = ptr1.Memory, mem2 = ptr2.Memory; - for (int i = 0, pos1 = ptr1.Address, pos2 = ptr2.Address; i < SIZE; i++, pos1++, pos2++) - { - byte temp = mem1[pos1]; - mem1[pos1] = mem2[pos2]; - mem2[pos2] = temp; - } - } - - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("State["); - buffer.Append("\n Address="); - buffer.Append(Address); - buffer.Append("\n size="); - buffer.Append(SIZE); - buffer.Append("\n symbol="); - buffer.Append(Symbol); - buffer.Append("\n freq="); - buffer.Append(Freq); - buffer.Append("\n successor="); - buffer.Append(GetSuccessor()); - buffer.Append("\n]"); - return buffer.ToString(); + var temp = mem1[pos1]; + mem1[pos1] = mem2[pos2]; + mem2[pos2] = temp; } } + + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("State["); + buffer.Append("\n Address="); + buffer.Append(Address); + buffer.Append("\n size="); + buffer.Append(SIZE); + buffer.Append("\n symbol="); + buffer.Append(Symbol); + buffer.Append("\n freq="); + buffer.Append(Freq); + buffer.Append("\n successor="); + buffer.Append(GetSuccessor()); + buffer.Append("\n]"); + return buffer.ToString(); + } } diff --git a/src/SharpCompress/Compressors/PPMd/H/StateRef.cs b/src/SharpCompress/Compressors/PPMd/H/StateRef.cs index f1e8e90f..8ef56a09 100644 --- a/src/SharpCompress/Compressors/PPMd/H/StateRef.cs +++ b/src/SharpCompress/Compressors/PPMd/H/StateRef.cs @@ -1,67 +1,58 @@ -using System; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class StateRef { - internal class StateRef + private int _symbol; + + private int _freq; + + private int _successor; // pointer ppmcontext + + internal int Symbol { - private int _symbol; + get => _symbol; + set => _symbol = value & 0xff; + } - private int _freq; + internal int Freq + { + get => _freq; + set => _freq = value & 0xff; + } - private int _successor; // pointer ppmcontext - - internal int Symbol { get => _symbol; set => _symbol = value & 0xff; } - - internal int Freq { get => _freq; set => _freq = value & 0xff; } - - internal State Values + internal State Values + { + set { - set - { - Freq = value.Freq; - SetSuccessor(value.GetSuccessor()); - Symbol = value.Symbol; - } - } - - public virtual void IncrementFreq(int dFreq) - { - _freq = (_freq + dFreq) & 0xff; - } - - public virtual void DecrementFreq(int dFreq) - { - _freq = (_freq - dFreq) & 0xff; - } - - public virtual int GetSuccessor() - { - return _successor; - } - - public virtual void SetSuccessor(PpmContext successor) - { - SetSuccessor(successor.Address); - } - - public virtual void SetSuccessor(int successor) - { - _successor = successor; - } - - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("State["); - buffer.Append("\n symbol="); - buffer.Append(Symbol); - buffer.Append("\n freq="); - buffer.Append(Freq); - buffer.Append("\n successor="); - buffer.Append(GetSuccessor()); - buffer.Append("\n]"); - return buffer.ToString(); + Freq = value.Freq; + SetSuccessor(value.GetSuccessor()); + Symbol = value.Symbol; } } -} \ No newline at end of file + + public virtual void IncrementFreq(int dFreq) => _freq = (_freq + dFreq) & 0xff; + + public virtual void DecrementFreq(int dFreq) => _freq = (_freq - dFreq) & 0xff; + + public virtual int GetSuccessor() => _successor; + + public virtual void SetSuccessor(PpmContext successor) => SetSuccessor(successor.Address); + + public virtual void SetSuccessor(int successor) => _successor = successor; + + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("State["); + buffer.Append("\n symbol="); + buffer.Append(Symbol); + buffer.Append("\n freq="); + buffer.Append(Freq); + buffer.Append("\n successor="); + buffer.Append(GetSuccessor()); + buffer.Append("\n]"); + return buffer.ToString(); + } +} diff --git a/src/SharpCompress/Compressors/PPMd/H/SubAllocator.cs b/src/SharpCompress/Compressors/PPMd/H/SubAllocator.cs index 39a808d5..e507f3c3 100644 --- a/src/SharpCompress/Compressors/PPMd/H/SubAllocator.cs +++ b/src/SharpCompress/Compressors/PPMd/H/SubAllocator.cs @@ -3,451 +3,440 @@ using System; using System.Text; -namespace SharpCompress.Compressors.PPMd.H +namespace SharpCompress.Compressors.PPMd.H; + +internal class SubAllocator { - internal class SubAllocator + public virtual int FakeUnitsStart { - public virtual int FakeUnitsStart { get => _fakeUnitsStart; set => _fakeUnitsStart = value; } + get => _fakeUnitsStart; + set => _fakeUnitsStart = value; + } - public virtual int HeapEnd => _heapEnd; + public virtual int HeapEnd => _heapEnd; - public virtual int PText { get => _pText; set => _pText = value; } + public virtual int PText + { + get => _pText; + set => _pText = value; + } - public virtual int UnitsStart { get => _unitsStart; set => _unitsStart = value; } + public virtual int UnitsStart + { + get => _unitsStart; + set => _unitsStart = value; + } - public virtual byte[] Heap => _heap; + public virtual byte[] Heap => _heap; - //UPGRADE_NOTE: Final was removed from the declaration of 'N4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public const int N1 = 4; - public const int N2 = 4; - public const int N3 = 4; - public static readonly int N4 = (128 + 3 - 1 * N1 - 2 * N2 - 3 * N3) / 4; + //UPGRADE_NOTE: Final was removed from the declaration of 'N4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public const int N1 = 4; + public const int N2 = 4; + public const int N3 = 4; + public static readonly int N4 = (128 + 3 - (1 * N1) - (2 * N2) - (3 * N3)) / 4; - //UPGRADE_NOTE: Final was removed from the declaration of 'N_INDEXES '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int N_INDEXES = N1 + N2 + N3 + N4; + //UPGRADE_NOTE: Final was removed from the declaration of 'N_INDEXES '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int N_INDEXES = N1 + N2 + N3 + N4; - //UPGRADE_NOTE: Final was removed from the declaration of 'UNIT_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - //UPGRADE_NOTE: The initialization of 'UNIT_SIZE' was moved to static method 'SharpCompress.Unpack.PPM.SubAllocator'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" - public static readonly int UNIT_SIZE; + //UPGRADE_NOTE: Final was removed from the declaration of 'UNIT_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + //UPGRADE_NOTE: The initialization of 'UNIT_SIZE' was moved to static method 'SharpCompress.Unpack.PPM.SubAllocator'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'" + public static readonly int UNIT_SIZE; - public const int FIXED_UNIT_SIZE = 12; + public const int FIXED_UNIT_SIZE = 12; - private int _subAllocatorSize; + private int _subAllocatorSize; - // byte Indx2Units[N_INDEXES], Units2Indx[128], GlueCount; - private readonly int[] _indx2Units = new int[N_INDEXES]; - private readonly int[] _units2Indx = new int[128]; - private int _glueCount; + // byte Indx2Units[N_INDEXES], Units2Indx[128], GlueCount; + private readonly int[] _indx2Units = new int[N_INDEXES]; + private readonly int[] _units2Indx = new int[128]; + private int _glueCount; - // byte *HeapStart,*LoUnit, *HiUnit; - private int _heapStart, _loUnit, _hiUnit; + // byte *HeapStart,*LoUnit, *HiUnit; + private int _heapStart, + _loUnit, + _hiUnit; - //UPGRADE_NOTE: Final was removed from the declaration of 'freeList '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - private readonly RarNode[] _freeList = new RarNode[N_INDEXES]; + //UPGRADE_NOTE: Final was removed from the declaration of 'freeList '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private readonly RarNode[] _freeList = new RarNode[N_INDEXES]; - // byte *pText, *UnitsStart,*HeapEnd,*FakeUnitsStart; - private int _pText, _unitsStart, _heapEnd, _fakeUnitsStart; + // byte *pText, *UnitsStart,*HeapEnd,*FakeUnitsStart; + private int _pText, + _unitsStart, + _heapEnd, + _fakeUnitsStart; - private byte[] _heap; + private byte[] _heap; - private int _freeListPos; + private int _freeListPos; - private int _tempMemBlockPos; + private int _tempMemBlockPos; - // Temp fields - private RarNode _tempRarNode; - private RarMemBlock _tempRarMemBlock1; - private RarMemBlock _tempRarMemBlock2; - private RarMemBlock _tempRarMemBlock3; + // Temp fields + private RarNode _tempRarNode; + private RarMemBlock _tempRarMemBlock1; + private RarMemBlock _tempRarMemBlock2; + private RarMemBlock _tempRarMemBlock3; - public SubAllocator() + public SubAllocator() => Clean(); + + public virtual void Clean() => _subAllocatorSize = 0; + + private void InsertNode(int p, int indx) + { + var temp = _tempRarNode; + temp.Address = p; + temp.SetNext(_freeList[indx].GetNext()); + _freeList[indx].SetNext(temp); + } + + public virtual void IncPText() => _pText++; + + private int RemoveNode(int indx) + { + var retVal = _freeList[indx].GetNext(); + var temp = _tempRarNode; + temp.Address = retVal; + _freeList[indx].SetNext(temp.GetNext()); + return retVal; + } + + private int U2B(int nu) => UNIT_SIZE * nu; + + /* memblockptr */ + + private int MbPtr(int basePtr, int items) => (basePtr + U2B(items)); + + private void SplitBlock(int pv, int oldIndx, int newIndx) + { + int i, + uDiff = _indx2Units[oldIndx] - _indx2Units[newIndx]; + var p = pv + U2B(_indx2Units[newIndx]); + if (_indx2Units[i = _units2Indx[uDiff - 1]] != uDiff) { - Clean(); + InsertNode(p, --i); + p += U2B(i = _indx2Units[i]); + uDiff -= i; } + InsertNode(p, _units2Indx[uDiff - 1]); + } - public virtual void Clean() + public virtual void StopSubAllocator() + { + if (_subAllocatorSize != 0) { _subAllocatorSize = 0; - } - private void InsertNode(int p, int indx) - { - RarNode temp = _tempRarNode; - temp.Address = p; - temp.SetNext(_freeList[indx].GetNext()); - _freeList[indx].SetNext(temp); - } - - public virtual void IncPText() - { - _pText++; - } - - private int RemoveNode(int indx) - { - int retVal = _freeList[indx].GetNext(); - RarNode temp = _tempRarNode; - temp.Address = retVal; - _freeList[indx].SetNext(temp.GetNext()); - return retVal; - } - - private int U2B(int nu) - { - return UNIT_SIZE * nu; - } - - /* memblockptr */ - - private int MbPtr(int basePtr, int items) - { - return (basePtr + U2B(items)); - } - - private void SplitBlock(int pv, int oldIndx, int newIndx) - { - int i, uDiff = _indx2Units[oldIndx] - _indx2Units[newIndx]; - int p = pv + U2B(_indx2Units[newIndx]); - if (_indx2Units[i = _units2Indx[uDiff - 1]] != uDiff) - { - InsertNode(p, --i); - p += U2B(i = _indx2Units[i]); - uDiff -= i; - } - InsertNode(p, _units2Indx[uDiff - 1]); - } - - public virtual void StopSubAllocator() - { - if (_subAllocatorSize != 0) - { - _subAllocatorSize = 0; - - //ArrayFactory.BYTES_FACTORY.recycle(heap); - _heap = null; - _heapStart = 1; - - // rarfree(HeapStart); - // Free temp fields - _tempRarNode = null; - _tempRarMemBlock1 = null; - _tempRarMemBlock2 = null; - _tempRarMemBlock3 = null; - } - } - - public virtual int GetAllocatedMemory() - { - return _subAllocatorSize; - } - - public virtual bool StartSubAllocator(int saSize) - { - int t = saSize; - if (_subAllocatorSize == t) - { - return true; - } - StopSubAllocator(); - int allocSize = t / FIXED_UNIT_SIZE * UNIT_SIZE + UNIT_SIZE; - - // adding space for freelist (needed for poiters) - // 1+ for null pointer - int realAllocSize = 1 + allocSize + 4 * N_INDEXES; - - // adding space for an additional memblock - _tempMemBlockPos = realAllocSize; - realAllocSize += RarMemBlock.SIZE; - - _heap = new byte[realAllocSize]; + //ArrayFactory.BYTES_FACTORY.recycle(heap); + _heap = null; _heapStart = 1; - _heapEnd = _heapStart + allocSize - UNIT_SIZE; - _subAllocatorSize = t; - // Bug fixed - _freeListPos = _heapStart + allocSize; + // rarfree(HeapStart); + // Free temp fields + _tempRarNode = null; + _tempRarMemBlock1 = null; + _tempRarMemBlock2 = null; + _tempRarMemBlock3 = null; + } + } - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" - //assert(realAllocSize - tempMemBlockPos == RarMemBlock.size): realAllocSize - //+ + tempMemBlockPos + + RarMemBlock.size; - - // Init freeList - for (int i = 0, pos = _freeListPos; i < _freeList.Length; i++, pos += RarNode.SIZE) - { - _freeList[i] = new RarNode(_heap); - _freeList[i].Address = pos; - } - - // Init temp fields - _tempRarNode = new RarNode(_heap); - _tempRarMemBlock1 = new RarMemBlock(_heap); - _tempRarMemBlock2 = new RarMemBlock(_heap); - _tempRarMemBlock3 = new RarMemBlock(_heap); + public virtual int GetAllocatedMemory() => _subAllocatorSize; + public virtual bool StartSubAllocator(int saSize) + { + var t = saSize; + if (_subAllocatorSize == t) + { return true; } + StopSubAllocator(); + var allocSize = (t / FIXED_UNIT_SIZE * UNIT_SIZE) + UNIT_SIZE; - private void GlueFreeBlocks() + // adding space for freelist (needed for poiters) + // 1+ for null pointer + var realAllocSize = 1 + allocSize + (4 * N_INDEXES); + + // adding space for an additional memblock + _tempMemBlockPos = realAllocSize; + realAllocSize += RarMemBlock.SIZE; + + _heap = new byte[realAllocSize]; + _heapStart = 1; + _heapEnd = _heapStart + allocSize - UNIT_SIZE; + _subAllocatorSize = t; + + // Bug fixed + _freeListPos = _heapStart + allocSize; + + //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + //assert(realAllocSize - tempMemBlockPos == RarMemBlock.size): realAllocSize + //+ + tempMemBlockPos + + RarMemBlock.size; + + // Init freeList + for (int i = 0, pos = _freeListPos; i < _freeList.Length; i++, pos += RarNode.SIZE) { - RarMemBlock s0 = _tempRarMemBlock1; - s0.Address = _tempMemBlockPos; - RarMemBlock p = _tempRarMemBlock2; - RarMemBlock p1 = _tempRarMemBlock3; - int i, k, sz; - if (_loUnit != _hiUnit) - { - _heap[_loUnit] = 0; - } - for (i = 0, s0.SetPrev(s0), s0.SetNext(s0); i < N_INDEXES; i++) - { - while (_freeList[i].GetNext() != 0) - { - p.Address = RemoveNode(i); // =(RAR_MEM_BLK*)RemoveNode(i); - p.InsertAt(s0); // p->insertAt(&s0); - p.Stamp = 0xFFFF; // p->Stamp=0xFFFF; - p.SetNu(_indx2Units[i]); // p->NU=Indx2Units[i]; - } - } - for (p.Address = s0.GetNext(); p.Address != s0.Address; p.Address = p.GetNext()) - { - // while ((p1=MBPtr(p,p->NU))->Stamp == 0xFFFF && int(p->NU)+p1->NU - // < 0x10000) - // Bug fixed - p1.Address = MbPtr(p.Address, p.GetNu()); - while (p1.Stamp == 0xFFFF && p.GetNu() + p1.GetNu() < 0x10000) - { - p1.Remove(); - p.SetNu(p.GetNu() + p1.GetNu()); // ->NU += p1->NU; - p1.Address = MbPtr(p.Address, p.GetNu()); - } - } + _freeList[i] = new RarNode(_heap); + _freeList[i].Address = pos; + } - // while ((p=s0.next) != &s0) + // Init temp fields + _tempRarNode = new RarNode(_heap); + _tempRarMemBlock1 = new RarMemBlock(_heap); + _tempRarMemBlock2 = new RarMemBlock(_heap); + _tempRarMemBlock3 = new RarMemBlock(_heap); + + return true; + } + + private void GlueFreeBlocks() + { + var s0 = _tempRarMemBlock1; + s0.Address = _tempMemBlockPos; + var p = _tempRarMemBlock2; + var p1 = _tempRarMemBlock3; + int i, + k, + sz; + if (_loUnit != _hiUnit) + { + _heap[_loUnit] = 0; + } + for (i = 0, s0.SetPrev(s0), s0.SetNext(s0); i < N_INDEXES; i++) + { + while (_freeList[i].GetNext() != 0) + { + p.Address = RemoveNode(i); // =(RAR_MEM_BLK*)RemoveNode(i); + p.InsertAt(s0); // p->insertAt(&s0); + p.Stamp = 0xFFFF; // p->Stamp=0xFFFF; + p.SetNu(_indx2Units[i]); // p->NU=Indx2Units[i]; + } + } + for (p.Address = s0.GetNext(); p.Address != s0.Address; p.Address = p.GetNext()) + { + // while ((p1=MBPtr(p,p->NU))->Stamp == 0xFFFF && int(p->NU)+p1->NU + // < 0x10000) // Bug fixed + p1.Address = MbPtr(p.Address, p.GetNu()); + while (p1.Stamp == 0xFFFF && p.GetNu() + p1.GetNu() < 0x10000) + { + p1.Remove(); + p.SetNu(p.GetNu() + p1.GetNu()); // ->NU += p1->NU; + p1.Address = MbPtr(p.Address, p.GetNu()); + } + } + + // while ((p=s0.next) != &s0) + // Bug fixed + p.Address = s0.GetNext(); + while (p.Address != s0.Address) + { + for (p.Remove(), sz = p.GetNu(); sz > 128; sz -= 128, p.Address = MbPtr(p.Address, 128)) + { + InsertNode(p.Address, N_INDEXES - 1); + } + if (_indx2Units[i = _units2Indx[sz - 1]] != sz) + { + k = sz - _indx2Units[--i]; + InsertNode(MbPtr(p.Address, sz - k), k - 1); + } + InsertNode(p.Address, i); p.Address = s0.GetNext(); - while (p.Address != s0.Address) - { - for (p.Remove(), sz = p.GetNu(); sz > 128; sz -= 128, p.Address = MbPtr(p.Address, 128)) - { - InsertNode(p.Address, N_INDEXES - 1); - } - if (_indx2Units[i = _units2Indx[sz - 1]] != sz) - { - k = sz - _indx2Units[--i]; - InsertNode(MbPtr(p.Address, sz - k), k - 1); - } - InsertNode(p.Address, i); - p.Address = s0.GetNext(); - } } + } - private int AllocUnitsRare(int indx) + private int AllocUnitsRare(int indx) + { + if (_glueCount == 0) { - if (_glueCount == 0) - { - _glueCount = 255; - GlueFreeBlocks(); - if (_freeList[indx].GetNext() != 0) - { - return RemoveNode(indx); - } - } - int i = indx; - do - { - if (++i == N_INDEXES) - { - _glueCount--; - i = U2B(_indx2Units[indx]); - int j = FIXED_UNIT_SIZE * _indx2Units[indx]; - if (_fakeUnitsStart - _pText > j) - { - _fakeUnitsStart -= j; - _unitsStart -= i; - return _unitsStart; - } - return (0); - } - } - while (_freeList[i].GetNext() == 0); - int retVal = RemoveNode(i); - SplitBlock(retVal, i, indx); - return retVal; - } - - public virtual int AllocUnits(int nu) - { - int indx = _units2Indx[nu - 1]; + _glueCount = 255; + GlueFreeBlocks(); if (_freeList[indx].GetNext() != 0) { return RemoveNode(indx); } - int retVal = _loUnit; - _loUnit += U2B(_indx2Units[indx]); - if (_loUnit <= _hiUnit) - { - return retVal; - } - _loUnit -= U2B(_indx2Units[indx]); - return AllocUnitsRare(indx); } - - public virtual int AllocContext() + var i = indx; + do { - if (_hiUnit != _loUnit) + if (++i == N_INDEXES) { - return (_hiUnit -= UNIT_SIZE); + _glueCount--; + i = U2B(_indx2Units[indx]); + var j = FIXED_UNIT_SIZE * _indx2Units[indx]; + if (_fakeUnitsStart - _pText > j) + { + _fakeUnitsStart -= j; + _unitsStart -= i; + return _unitsStart; + } + return (0); } - if (_freeList[0].GetNext() != 0) - { - return RemoveNode(0); - } - return AllocUnitsRare(0); + } while (_freeList[i].GetNext() == 0); + var retVal = RemoveNode(i); + SplitBlock(retVal, i, indx); + return retVal; + } + + public virtual int AllocUnits(int nu) + { + var indx = _units2Indx[nu - 1]; + if (_freeList[indx].GetNext() != 0) + { + return RemoveNode(indx); } - - public virtual int ExpandUnits(int oldPtr, int oldNu) + var retVal = _loUnit; + _loUnit += U2B(_indx2Units[indx]); + if (_loUnit <= _hiUnit) { - int i0 = _units2Indx[oldNu - 1]; - int i1 = _units2Indx[oldNu - 1 + 1]; - if (i0 == i1) - { - return oldPtr; - } - int ptr = AllocUnits(oldNu + 1); - if (ptr != 0) - { - // memcpy(ptr,OldPtr,U2B(OldNU)); - Array.Copy(_heap, oldPtr, _heap, ptr, U2B(oldNu)); - InsertNode(oldPtr, i0); - } - return ptr; + return retVal; } + _loUnit -= U2B(_indx2Units[indx]); + return AllocUnitsRare(indx); + } - public virtual int ShrinkUnits(int oldPtr, int oldNu, int newNu) + public virtual int AllocContext() + { + if (_hiUnit != _loUnit) { - // System.out.println("SubAllocator.shrinkUnits(" + OldPtr + ", " + - // OldNU + ", " + NewNU + ")"); - int i0 = _units2Indx[oldNu - 1]; - int i1 = _units2Indx[newNu - 1]; - if (i0 == i1) - { - return oldPtr; - } - if (_freeList[i1].GetNext() != 0) - { - int ptr = RemoveNode(i1); + return (_hiUnit -= UNIT_SIZE); + } + if (_freeList[0].GetNext() != 0) + { + return RemoveNode(0); + } + return AllocUnitsRare(0); + } - // memcpy(ptr,OldPtr,U2B(NewNU)); - // for (int i = 0; i < U2B(NewNU); i++) { - // heap[ptr + i] = heap[OldPtr + i]; - // } - Array.Copy(_heap, oldPtr, _heap, ptr, U2B(newNu)); - InsertNode(oldPtr, i0); - return ptr; - } - SplitBlock(oldPtr, i0, i1); + public virtual int ExpandUnits(int oldPtr, int oldNu) + { + var i0 = _units2Indx[oldNu - 1]; + var i1 = _units2Indx[oldNu - 1 + 1]; + if (i0 == i1) + { return oldPtr; } - - public virtual void FreeUnits(int ptr, int oldNu) + var ptr = AllocUnits(oldNu + 1); + if (ptr != 0) { - InsertNode(ptr, _units2Indx[oldNu - 1]); + // memcpy(ptr,OldPtr,U2B(OldNU)); + Array.Copy(_heap, oldPtr, _heap, ptr, U2B(oldNu)); + InsertNode(oldPtr, i0); + } + return ptr; + } + + public virtual int ShrinkUnits(int oldPtr, int oldNu, int newNu) + { + // System.out.println("SubAllocator.shrinkUnits(" + OldPtr + ", " + + // OldNU + ", " + NewNU + ")"); + var i0 = _units2Indx[oldNu - 1]; + var i1 = _units2Indx[newNu - 1]; + if (i0 == i1) + { + return oldPtr; + } + if (_freeList[i1].GetNext() != 0) + { + var ptr = RemoveNode(i1); + + // memcpy(ptr,OldPtr,U2B(NewNU)); + // for (int i = 0; i < U2B(NewNU); i++) { + // heap[ptr + i] = heap[OldPtr + i]; + // } + Array.Copy(_heap, oldPtr, _heap, ptr, U2B(newNu)); + InsertNode(oldPtr, i0); + return ptr; + } + SplitBlock(oldPtr, i0, i1); + return oldPtr; + } + + public virtual void FreeUnits(int ptr, int oldNu) => InsertNode(ptr, _units2Indx[oldNu - 1]); + + public virtual void DecPText(int dPText) => PText -= dPText; + + public virtual void InitSubAllocator() + { + int i, + k; + new Span(_heap, _freeListPos, SizeOfFreeList()).Clear(); + + _pText = _heapStart; + + var size2 = FIXED_UNIT_SIZE * (_subAllocatorSize / 8 / FIXED_UNIT_SIZE * 7); + var realSize2 = size2 / FIXED_UNIT_SIZE * UNIT_SIZE; + var size1 = _subAllocatorSize - size2; + var realSize1 = (size1 / FIXED_UNIT_SIZE * UNIT_SIZE) + (size1 % FIXED_UNIT_SIZE); + _hiUnit = _heapStart + _subAllocatorSize; + _loUnit = _unitsStart = _heapStart + realSize1; + _fakeUnitsStart = _heapStart + size1; + _hiUnit = _loUnit + realSize2; + + for (i = 0, k = 1; i < N1; i++, k += 1) + { + _indx2Units[i] = k & 0xff; + } + for (k++; i < N1 + N2; i++, k += 2) + { + _indx2Units[i] = k & 0xff; + } + for (k++; i < N1 + N2 + N3; i++, k += 3) + { + _indx2Units[i] = k & 0xff; + } + for (k++; i < (N1 + N2 + N3 + N4); i++, k += 4) + { + _indx2Units[i] = k & 0xff; } - public virtual void DecPText(int dPText) + for (_glueCount = 0, k = 0, i = 0; k < 128; k++) { - PText = PText - dPText; - } - - public virtual void InitSubAllocator() - { - int i, k; - new Span(_heap, _freeListPos, SizeOfFreeList()).Clear(); - - _pText = _heapStart; - - int size2 = FIXED_UNIT_SIZE * (_subAllocatorSize / 8 / FIXED_UNIT_SIZE * 7); - int realSize2 = size2 / FIXED_UNIT_SIZE * UNIT_SIZE; - int size1 = _subAllocatorSize - size2; - int realSize1 = size1 / FIXED_UNIT_SIZE * UNIT_SIZE + size1 % FIXED_UNIT_SIZE; - _hiUnit = _heapStart + _subAllocatorSize; - _loUnit = _unitsStart = _heapStart + realSize1; - _fakeUnitsStart = _heapStart + size1; - _hiUnit = _loUnit + realSize2; - - for (i = 0, k = 1; i < N1; i++, k += 1) - { - _indx2Units[i] = k & 0xff; - } - for (k++; i < N1 + N2; i++, k += 2) - { - _indx2Units[i] = k & 0xff; - } - for (k++; i < N1 + N2 + N3; i++, k += 3) - { - _indx2Units[i] = k & 0xff; - } - for (k++; i < (N1 + N2 + N3 + N4); i++, k += 4) - { - _indx2Units[i] = k & 0xff; - } - - for (_glueCount = 0, k = 0, i = 0; k < 128; k++) - { - i += ((_indx2Units[i] < (k + 1)) ? 1 : 0); - _units2Indx[k] = i & 0xff; - } - } - - private int SizeOfFreeList() - { - return _freeList.Length * RarNode.SIZE; - } - - // Debug - // public void dumpHeap() { - // File file = new File("P:\\test\\heapdumpj"); - // OutputStream out = null; - // try { - // out = new FileOutputStream(file); - // out.write(heap, heapStart, heapEnd - heapStart); - // out.flush(); - // System.out.println("Heap dumped to " + file.getAbsolutePath()); - // } - // catch (IOException e) { - // e.printStackTrace(); - // } - // finally { - // FileUtil.close(out); - // } - // } - - // Debug - public override String ToString() - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("SubAllocator["); - buffer.Append("\n subAllocatorSize="); - buffer.Append(_subAllocatorSize); - buffer.Append("\n glueCount="); - buffer.Append(_glueCount); - buffer.Append("\n heapStart="); - buffer.Append(_heapStart); - buffer.Append("\n loUnit="); - buffer.Append(_loUnit); - buffer.Append("\n hiUnit="); - buffer.Append(_hiUnit); - buffer.Append("\n pText="); - buffer.Append(_pText); - buffer.Append("\n unitsStart="); - buffer.Append(_unitsStart); - buffer.Append("\n]"); - return buffer.ToString(); - } - - static SubAllocator() - { - UNIT_SIZE = Math.Max(PpmContext.SIZE, RarMemBlock.SIZE); + i += ((_indx2Units[i] < (k + 1)) ? 1 : 0); + _units2Indx[k] = i & 0xff; } } + + private int SizeOfFreeList() => _freeList.Length * RarNode.SIZE; + + // Debug + // public void dumpHeap() { + // File file = new File("P:\\test\\heapdumpj"); + // OutputStream out = null; + // try { + // out = new FileOutputStream(file); + // out.write(heap, heapStart, heapEnd - heapStart); + // out.flush(); + // System.out.println("Heap dumped to " + file.getAbsolutePath()); + // } + // catch (IOException e) { + // e.printStackTrace(); + // } + // finally { + // FileUtil.close(out); + // } + // } + + // Debug + public override string ToString() + { + var buffer = new StringBuilder(); + buffer.Append("SubAllocator["); + buffer.Append("\n subAllocatorSize="); + buffer.Append(_subAllocatorSize); + buffer.Append("\n glueCount="); + buffer.Append(_glueCount); + buffer.Append("\n heapStart="); + buffer.Append(_heapStart); + buffer.Append("\n loUnit="); + buffer.Append(_loUnit); + buffer.Append("\n hiUnit="); + buffer.Append(_hiUnit); + buffer.Append("\n pText="); + buffer.Append(_pText); + buffer.Append("\n unitsStart="); + buffer.Append(_unitsStart); + buffer.Append("\n]"); + return buffer.ToString(); + } + + static SubAllocator() => UNIT_SIZE = Math.Max(PpmContext.SIZE, RarMemBlock.SIZE); } diff --git a/src/SharpCompress/Compressors/PPMd/I1/Allocator.cs b/src/SharpCompress/Compressors/PPMd/I1/Allocator.cs index 269e44c1..7b677a93 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/Allocator.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/Allocator.cs @@ -1,491 +1,495 @@ #nullable disable -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// Allocate a single, large array and then provide sections of this array to callers. Callers are provided with +/// instances of (which simply contain a single address value, representing a location +/// in the large array). Callers can then cast to one of the following structures (all +/// of which also simply contain a single address value): +internal class Allocator { - /// Allocate a single, large array and then provide sections of this array to callers. Callers are provided with - /// instances of (which simply contain a single address value, representing a location - /// in the large array). Callers can then cast to one of the following structures (all - /// of which also simply contain a single address value): - internal class Allocator + private const uint UNIT_SIZE = 12; + private const uint LOCAL_OFFSET = 4; // reserve the first four bytes for Pointer.Zero + private const uint NODE_OFFSET = LOCAL_OFFSET + MemoryNode.SIZE; // reserve space for a single memory node + + private const uint HEAP_OFFSET = NODE_OFFSET + (INDEX_COUNT * MemoryNode.SIZE); + + // reserve space for the array of memory nodes + + private const uint N1 = 4; + private const uint N2 = 4; + private const uint N3 = 4; + private const uint N4 = (128 + 3 - (1 * N1) - (2 * N2) - (3 * N3)) / 4; + private const uint INDEX_COUNT = N1 + N2 + N3 + N4; + + private static readonly byte[] INDEX_TO_UNITS; + private static readonly byte[] UNITS_TO_INDEX; + + public uint _allocatorSize; + public uint _glueCount; + public Pointer _baseUnit; + public Pointer _lowUnit; + public Pointer _highUnit; + public Pointer _text; + public Pointer _heap; + public MemoryNode[] _memoryNodes; + + public byte[] _memory; + + /// + /// Initializes static read-only arrays used by the . + /// + static Allocator() { - private const uint UNIT_SIZE = 12; - private const uint LOCAL_OFFSET = 4; // reserve the first four bytes for Pointer.Zero - private const uint NODE_OFFSET = LOCAL_OFFSET + MemoryNode.SIZE; // reserve space for a single memory node + // Construct the static index to units lookup array. It will contain the following values. + // + // 1 2 3 4 6 8 10 12 15 18 21 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 104 108 + // 112 116 120 124 128 - private const uint HEAP_OFFSET = NODE_OFFSET + INDEX_COUNT * MemoryNode.SIZE; + uint index; + uint unitCount; - // reserve space for the array of memory nodes + INDEX_TO_UNITS = new byte[INDEX_COUNT]; - private const uint N1 = 4; - private const uint N2 = 4; - private const uint N3 = 4; - private const uint N4 = (128 + 3 - 1 * N1 - 2 * N2 - 3 * N3) / 4; - private const uint INDEX_COUNT = N1 + N2 + N3 + N4; - - private static readonly byte[] INDEX_TO_UNITS; - private static readonly byte[] UNITS_TO_INDEX; - - public uint _allocatorSize; - public uint _glueCount; - public Pointer _baseUnit; - public Pointer _lowUnit; - public Pointer _highUnit; - public Pointer _text; - public Pointer _heap; - public MemoryNode[] _memoryNodes; - - public byte[] _memory; - - /// - /// Initializes static read-only arrays used by the . - /// - static Allocator() + for (index = 0, unitCount = 1; index < N1; index++, unitCount += 1) { - // Construct the static index to units lookup array. It will contain the following values. - // - // 1 2 3 4 6 8 10 12 15 18 21 24 28 32 36 40 44 48 52 56 60 64 68 72 76 80 84 88 92 96 100 104 108 - // 112 116 120 124 128 - - uint index; - uint unitCount; - - INDEX_TO_UNITS = new byte[INDEX_COUNT]; - - for (index = 0, unitCount = 1; index < N1; index++, unitCount += 1) - { - INDEX_TO_UNITS[index] = (byte)unitCount; - } - - for (unitCount++; index < N1 + N2; index++, unitCount += 2) - { - INDEX_TO_UNITS[index] = (byte)unitCount; - } - - for (unitCount++; index < N1 + N2 + N3; index++, unitCount += 3) - { - INDEX_TO_UNITS[index] = (byte)unitCount; - } - - for (unitCount++; index < N1 + N2 + N3 + N4; index++, unitCount += 4) - { - INDEX_TO_UNITS[index] = (byte)unitCount; - } - - // Construct the static units to index lookup array. It will contain the following values. - // - // 00 01 02 03 04 04 05 05 06 06 07 07 08 08 08 09 09 09 10 10 10 11 11 11 12 12 12 12 13 13 13 13 - // 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 - // 22 22 22 22 23 23 23 23 24 24 24 24 25 25 25 25 26 26 26 26 27 27 27 27 28 28 28 28 29 29 29 29 - // 30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33 34 34 34 34 35 35 35 35 36 36 36 36 37 37 37 37 - - UNITS_TO_INDEX = new byte[128]; - - for (unitCount = index = 0; unitCount < 128; unitCount++) - { - index += (uint)((INDEX_TO_UNITS[index] < unitCount + 1) ? 1 : 0); - UNITS_TO_INDEX[unitCount] = (byte)index; - } + INDEX_TO_UNITS[index] = (byte)unitCount; } - #region Public Methods - - public Allocator() + for (unitCount++; index < N1 + N2; index++, unitCount += 2) { - _memoryNodes = new MemoryNode[INDEX_COUNT]; + INDEX_TO_UNITS[index] = (byte)unitCount; } - /// - /// Initialize or reset the memory allocator (so that the single, large array can be re-used without destroying - /// and re-creating it). - /// - public void Initialize() + for (unitCount++; index < N1 + N2 + N3; index++, unitCount += 3) { - for (int index = 0; index < INDEX_COUNT; index++) - { - _memoryNodes[index] = new MemoryNode((uint)(NODE_OFFSET + index * MemoryNode.SIZE), _memory); - _memoryNodes[index].Stamp = 0; - _memoryNodes[index].Next = MemoryNode.ZERO; - _memoryNodes[index].UnitCount = 0; - } - - _text = _heap; - - uint difference = UNIT_SIZE * (_allocatorSize / 8 / UNIT_SIZE * 7); - - _highUnit = _heap + _allocatorSize; - _lowUnit = _highUnit - difference; - _baseUnit = _highUnit - difference; - - _glueCount = 0; + INDEX_TO_UNITS[index] = (byte)unitCount; } - /// - /// Start the allocator (create a single, large array of bytes). - /// - /// - /// Note that .NET will create that array on the large object heap (because it is so large). - /// - /// - public void Start(int allocatorSize) + for (unitCount++; index < N1 + N2 + N3 + N4; index++, unitCount += 4) { - uint size = (uint)allocatorSize; - if (_allocatorSize != size) + INDEX_TO_UNITS[index] = (byte)unitCount; + } + + // Construct the static units to index lookup array. It will contain the following values. + // + // 00 01 02 03 04 04 05 05 06 06 07 07 08 08 08 09 09 09 10 10 10 11 11 11 12 12 12 12 13 13 13 13 + // 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 + // 22 22 22 22 23 23 23 23 24 24 24 24 25 25 25 25 26 26 26 26 27 27 27 27 28 28 28 28 29 29 29 29 + // 30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33 34 34 34 34 35 35 35 35 36 36 36 36 37 37 37 37 + + UNITS_TO_INDEX = new byte[128]; + + for (unitCount = index = 0; unitCount < 128; unitCount++) + { + index += (uint)((INDEX_TO_UNITS[index] < unitCount + 1) ? 1 : 0); + UNITS_TO_INDEX[unitCount] = (byte)index; + } + } + + #region Public Methods + + public Allocator() => _memoryNodes = new MemoryNode[INDEX_COUNT]; + + /// + /// Initialize or reset the memory allocator (so that the single, large array can be re-used without destroying + /// and re-creating it). + /// + public void Initialize() + { + for (var index = 0; index < INDEX_COUNT; index++) + { + _memoryNodes[index] = new MemoryNode( + (uint)(NODE_OFFSET + (index * MemoryNode.SIZE)), + _memory + ); + _memoryNodes[index].Stamp = 0; + _memoryNodes[index].Next = MemoryNode.ZERO; + _memoryNodes[index].UnitCount = 0; + } + + _text = _heap; + + var difference = UNIT_SIZE * (_allocatorSize / 8 / UNIT_SIZE * 7); + + _highUnit = _heap + _allocatorSize; + _lowUnit = _highUnit - difference; + _baseUnit = _highUnit - difference; + + _glueCount = 0; + } + + /// + /// Start the allocator (create a single, large array of bytes). + /// + /// + /// Note that .NET will create that array on the large object heap (because it is so large). + /// + /// + public void Start(int allocatorSize) + { + var size = (uint)allocatorSize; + if (_allocatorSize != size) + { + Stop(); + _memory = new byte[HEAP_OFFSET + size]; // the single, large array of bytes + _heap = new Pointer(HEAP_OFFSET, _memory); // reserve bytes in the range 0 .. HeapOffset - 1 + _allocatorSize = size; + } + } + + /// + /// Stop the allocator (free the single, large array of bytes). This can safely be called multiple times (without + /// intervening calls to ). + /// + /// + /// Because the array is on the large object heap it may not be freed immediately. + /// + public void Stop() + { + if (_allocatorSize != 0) + { + _allocatorSize = 0; + _memory = null; + _heap = Pointer.ZERO; + } + } + + /// + /// Determine how much memory (from the single, large array) is currenly in use. + /// + /// + public uint GetMemoryUsed() + { + var memoryUsed = _allocatorSize - (_highUnit - _lowUnit) - (_baseUnit - _text); + for (uint index = 0; index < INDEX_COUNT; index++) + { + memoryUsed -= UNIT_SIZE * INDEX_TO_UNITS[index] * _memoryNodes[index].Stamp; + } + return memoryUsed; + } + + /// + /// Allocate a given number of units from the single, large array. Each unit is bytes + /// in size. + /// + /// + /// + public Pointer AllocateUnits(uint unitCount) + { + uint index = UNITS_TO_INDEX[unitCount - 1]; + if (_memoryNodes[index].Available) + { + return _memoryNodes[index].Remove(); + } + + var allocatedBlock = _lowUnit; + _lowUnit += INDEX_TO_UNITS[index] * UNIT_SIZE; + if (_lowUnit <= _highUnit) + { + return allocatedBlock; + } + + _lowUnit -= INDEX_TO_UNITS[index] * UNIT_SIZE; + return AllocateUnitsRare(index); + } + + /// + /// Allocate enough space for a PpmContext instance in the single, large array. + /// + /// + public Pointer AllocateContext() + { + if (_highUnit != _lowUnit) + { + return (_highUnit -= UNIT_SIZE); + } + if (_memoryNodes[0].Available) + { + return _memoryNodes[0].Remove(); + } + return AllocateUnitsRare(0); + } + + /// + /// Increase the size of an existing allocation (represented by a ). + /// + /// + /// + /// + public Pointer ExpandUnits(Pointer oldPointer, uint oldUnitCount) + { + uint oldIndex = UNITS_TO_INDEX[oldUnitCount - 1]; + uint newIndex = UNITS_TO_INDEX[oldUnitCount]; + + if (oldIndex == newIndex) + { + return oldPointer; + } + + var pointer = AllocateUnits(oldUnitCount + 1); + + if (pointer != Pointer.ZERO) + { + CopyUnits(pointer, oldPointer, oldUnitCount); + _memoryNodes[oldIndex].Insert(oldPointer, oldUnitCount); + } + + return pointer; + } + + /// + /// Decrease the size of an existing allocation (represented by a ). + /// + /// + /// + /// + /// + public Pointer ShrinkUnits(Pointer oldPointer, uint oldUnitCount, uint newUnitCount) + { + uint oldIndex = UNITS_TO_INDEX[oldUnitCount - 1]; + uint newIndex = UNITS_TO_INDEX[newUnitCount - 1]; + + if (oldIndex == newIndex) + { + return oldPointer; + } + + if (_memoryNodes[newIndex].Available) + { + Pointer pointer = _memoryNodes[newIndex].Remove(); + CopyUnits(pointer, oldPointer, newUnitCount); + _memoryNodes[oldIndex].Insert(oldPointer, INDEX_TO_UNITS[oldIndex]); + return pointer; + } + SplitBlock(oldPointer, oldIndex, newIndex); + return oldPointer; + } + + /// + /// Free previously allocated space (the location and amount of space to free must be specified by using + /// a to indicate the location and a number of units to indicate the amount). + /// + /// + /// + public void FreeUnits(Pointer pointer, uint unitCount) + { + uint index = UNITS_TO_INDEX[unitCount - 1]; + _memoryNodes[index].Insert(pointer, INDEX_TO_UNITS[index]); + } + + public void SpecialFreeUnits(Pointer pointer) + { + if (pointer != _baseUnit) + { + _memoryNodes[0].Insert(pointer, 1); + } + else + { + MemoryNode memoryNode = pointer; + memoryNode.Stamp = uint.MaxValue; + _baseUnit += UNIT_SIZE; + } + } + + public Pointer MoveUnitsUp(Pointer oldPointer, uint unitCount) + { + uint index = UNITS_TO_INDEX[unitCount - 1]; + + if (oldPointer > _baseUnit + (16 * 1024) || oldPointer > _memoryNodes[index].Next) + { + return oldPointer; + } + + Pointer pointer = _memoryNodes[index].Remove(); + CopyUnits(pointer, oldPointer, unitCount); + unitCount = INDEX_TO_UNITS[index]; + + if (oldPointer != _baseUnit) + { + _memoryNodes[index].Insert(oldPointer, unitCount); + } + else + { + _baseUnit += unitCount * UNIT_SIZE; + } + + return pointer; + } + + /// + /// Expand the space allocated (in the single, large array) for the bytes of the data (ie. the "text") that is + /// being encoded or decoded. + /// + public void ExpandText() + { + MemoryNode memoryNode; + var counts = new uint[INDEX_COUNT]; + + while ((memoryNode = _baseUnit).Stamp == uint.MaxValue) + { + _baseUnit = memoryNode + memoryNode.UnitCount; + counts[UNITS_TO_INDEX[memoryNode.UnitCount - 1]]++; + memoryNode.Stamp = 0; + } + + for (uint index = 0; index < INDEX_COUNT; index++) + { + for (memoryNode = _memoryNodes[index]; counts[index] != 0; memoryNode = memoryNode.Next) { - Stop(); - _memory = new byte[HEAP_OFFSET + size]; // the single, large array of bytes - _heap = new Pointer(HEAP_OFFSET, _memory); // reserve bytes in the range 0 .. HeapOffset - 1 - _allocatorSize = size; + while (memoryNode.Next.Stamp == 0) + { + memoryNode.Unlink(); + _memoryNodes[index].Stamp--; + if (--counts[index] == 0) + { + break; + } + } } } + } - /// - /// Stop the allocator (free the single, large array of bytes). This can safely be called multiple times (without - /// intervening calls to ). - /// - /// - /// Because the array is on the large object heap it may not be freed immediately. - /// - public void Stop() - { - if (_allocatorSize != 0) - { - _allocatorSize = 0; - _memory = null; - _heap = Pointer.ZERO; - } - } + #endregion - /// - /// Determine how much memory (from the single, large array) is currenly in use. - /// - /// - public uint GetMemoryUsed() - { - uint memoryUsed = _allocatorSize - (_highUnit - _lowUnit) - (_baseUnit - _text); - for (uint index = 0; index < INDEX_COUNT; index++) - { - memoryUsed -= UNIT_SIZE * INDEX_TO_UNITS[index] * _memoryNodes[index].Stamp; - } - return memoryUsed; - } + #region Private Methods - /// - /// Allocate a given number of units from the single, large array. Each unit is bytes - /// in size. - /// - /// - /// - public Pointer AllocateUnits(uint unitCount) + private Pointer AllocateUnitsRare(uint index) + { + if (_glueCount == 0) { - uint index = UNITS_TO_INDEX[unitCount - 1]; + GlueFreeBlocks(); if (_memoryNodes[index].Available) { return _memoryNodes[index].Remove(); } - - Pointer allocatedBlock = _lowUnit; - _lowUnit += INDEX_TO_UNITS[index] * UNIT_SIZE; - if (_lowUnit <= _highUnit) - { - return allocatedBlock; - } - - _lowUnit -= INDEX_TO_UNITS[index] * UNIT_SIZE; - return AllocateUnitsRare(index); } - /// - /// Allocate enough space for a PpmContext instance in the single, large array. - /// - /// - public Pointer AllocateContext() + var oldIndex = index; + do { - if (_highUnit != _lowUnit) + if (++oldIndex == INDEX_COUNT) { - return (_highUnit -= UNIT_SIZE); + _glueCount--; + oldIndex = INDEX_TO_UNITS[index] * UNIT_SIZE; + return (_baseUnit - _text > oldIndex) ? (_baseUnit -= oldIndex) : Pointer.ZERO; } - if (_memoryNodes[0].Available) - { - return _memoryNodes[0].Remove(); - } - return AllocateUnitsRare(0); - } + } while (!_memoryNodes[oldIndex].Available); - /// - /// Increase the size of an existing allocation (represented by a ). - /// - /// - /// - /// - public Pointer ExpandUnits(Pointer oldPointer, uint oldUnitCount) - { - uint oldIndex = UNITS_TO_INDEX[oldUnitCount - 1]; - uint newIndex = UNITS_TO_INDEX[oldUnitCount]; - - if (oldIndex == newIndex) - { - return oldPointer; - } - - Pointer pointer = AllocateUnits(oldUnitCount + 1); - - if (pointer != Pointer.ZERO) - { - CopyUnits(pointer, oldPointer, oldUnitCount); - _memoryNodes[oldIndex].Insert(oldPointer, oldUnitCount); - } - - return pointer; - } - - /// - /// Decrease the size of an existing allocation (represented by a ). - /// - /// - /// - /// - /// - public Pointer ShrinkUnits(Pointer oldPointer, uint oldUnitCount, uint newUnitCount) - { - uint oldIndex = UNITS_TO_INDEX[oldUnitCount - 1]; - uint newIndex = UNITS_TO_INDEX[newUnitCount - 1]; - - if (oldIndex == newIndex) - { - return oldPointer; - } - - if (_memoryNodes[newIndex].Available) - { - Pointer pointer = _memoryNodes[newIndex].Remove(); - CopyUnits(pointer, oldPointer, newUnitCount); - _memoryNodes[oldIndex].Insert(oldPointer, INDEX_TO_UNITS[oldIndex]); - return pointer; - } - SplitBlock(oldPointer, oldIndex, newIndex); - return oldPointer; - } - - /// - /// Free previously allocated space (the location and amount of space to free must be specified by using - /// a to indicate the location and a number of units to indicate the amount). - /// - /// - /// - public void FreeUnits(Pointer pointer, uint unitCount) - { - uint index = UNITS_TO_INDEX[unitCount - 1]; - _memoryNodes[index].Insert(pointer, INDEX_TO_UNITS[index]); - } - - public void SpecialFreeUnits(Pointer pointer) - { - if (pointer != _baseUnit) - { - _memoryNodes[0].Insert(pointer, 1); - } - else - { - MemoryNode memoryNode = pointer; - memoryNode.Stamp = uint.MaxValue; - _baseUnit += UNIT_SIZE; - } - } - - public Pointer MoveUnitsUp(Pointer oldPointer, uint unitCount) - { - uint index = UNITS_TO_INDEX[unitCount - 1]; - - if (oldPointer > _baseUnit + 16 * 1024 || oldPointer > _memoryNodes[index].Next) - { - return oldPointer; - } - - Pointer pointer = _memoryNodes[index].Remove(); - CopyUnits(pointer, oldPointer, unitCount); - unitCount = INDEX_TO_UNITS[index]; - - if (oldPointer != _baseUnit) - { - _memoryNodes[index].Insert(oldPointer, unitCount); - } - else - { - _baseUnit += unitCount * UNIT_SIZE; - } - - return pointer; - } - - /// - /// Expand the space allocated (in the single, large array) for the bytes of the data (ie. the "text") that is - /// being encoded or decoded. - /// - public void ExpandText() - { - MemoryNode memoryNode; - uint[] counts = new uint[INDEX_COUNT]; - - while ((memoryNode = _baseUnit).Stamp == uint.MaxValue) - { - _baseUnit = memoryNode + memoryNode.UnitCount; - counts[UNITS_TO_INDEX[memoryNode.UnitCount - 1]]++; - memoryNode.Stamp = 0; - } - - for (uint index = 0; index < INDEX_COUNT; index++) - { - for (memoryNode = _memoryNodes[index]; counts[index] != 0; memoryNode = memoryNode.Next) - { - while (memoryNode.Next.Stamp == 0) - { - memoryNode.Unlink(); - _memoryNodes[index].Stamp--; - if (--counts[index] == 0) - { - break; - } - } - } - } - } - - #endregion - - #region Private Methods - - private Pointer AllocateUnitsRare(uint index) - { - if (_glueCount == 0) - { - GlueFreeBlocks(); - if (_memoryNodes[index].Available) - { - return _memoryNodes[index].Remove(); - } - } - - uint oldIndex = index; - do - { - if (++oldIndex == INDEX_COUNT) - { - _glueCount--; - oldIndex = INDEX_TO_UNITS[index] * UNIT_SIZE; - return (_baseUnit - _text > oldIndex) ? (_baseUnit -= oldIndex) : Pointer.ZERO; - } - } - while (!_memoryNodes[oldIndex].Available); - - Pointer allocatedBlock = _memoryNodes[oldIndex].Remove(); - SplitBlock(allocatedBlock, oldIndex, index); - return allocatedBlock; - } - - private void SplitBlock(Pointer pointer, uint oldIndex, uint newIndex) - { - uint unitCountDifference = (uint)(INDEX_TO_UNITS[oldIndex] - INDEX_TO_UNITS[newIndex]); - Pointer newPointer = pointer + INDEX_TO_UNITS[newIndex] * UNIT_SIZE; - - uint index = UNITS_TO_INDEX[unitCountDifference - 1]; - if (INDEX_TO_UNITS[index] != unitCountDifference) - { - uint unitCount = INDEX_TO_UNITS[--index]; - _memoryNodes[index].Insert(newPointer, unitCount); - newPointer += unitCount * UNIT_SIZE; - unitCountDifference -= unitCount; - } - - _memoryNodes[UNITS_TO_INDEX[unitCountDifference - 1]].Insert(newPointer, unitCountDifference); - } - - private void GlueFreeBlocks() - { - MemoryNode memoryNode = new MemoryNode(LOCAL_OFFSET, _memory); - memoryNode.Stamp = 0; - memoryNode.Next = MemoryNode.ZERO; - memoryNode.UnitCount = 0; - - MemoryNode memoryNode0; - MemoryNode memoryNode1; - MemoryNode memoryNode2; - - if (_lowUnit != _highUnit) - { - _lowUnit[0] = 0; - } - - // Find all unused memory nodes. - - memoryNode1 = memoryNode; - for (uint index = 0; index < INDEX_COUNT; index++) - { - while (_memoryNodes[index].Available) - { - memoryNode0 = _memoryNodes[index].Remove(); - if (memoryNode0.UnitCount != 0) - { - while ((memoryNode2 = memoryNode0 + memoryNode0.UnitCount).Stamp == uint.MaxValue) - { - memoryNode0.UnitCount = memoryNode0.UnitCount + memoryNode2.UnitCount; - memoryNode2.UnitCount = 0; - } - memoryNode1.Link(memoryNode0); - memoryNode1 = memoryNode0; - } - } - } - - // Coalesce the memory represented by the unused memory nodes. - - while (memoryNode.Available) - { - memoryNode0 = memoryNode.Remove(); - uint unitCount = memoryNode0.UnitCount; - if (unitCount != 0) - { - for (; unitCount > 128; unitCount -= 128, memoryNode0 += 128) - { - _memoryNodes[INDEX_COUNT - 1].Insert(memoryNode0, 128); - } - - uint index = UNITS_TO_INDEX[unitCount - 1]; - if (INDEX_TO_UNITS[index] != unitCount) - { - uint unitCountDifference = unitCount - INDEX_TO_UNITS[--index]; - _memoryNodes[unitCountDifference - 1].Insert(memoryNode0 + (unitCount - unitCountDifference), - unitCountDifference); - } - - _memoryNodes[index].Insert(memoryNode0, INDEX_TO_UNITS[index]); - } - } - - _glueCount = 1 << 13; - } - - private void CopyUnits(Pointer target, Pointer source, uint unitCount) - { - do - { - target[0] = source[0]; - target[1] = source[1]; - target[2] = source[2]; - target[3] = source[3]; - target[4] = source[4]; - target[5] = source[5]; - target[6] = source[6]; - target[7] = source[7]; - target[8] = source[8]; - target[9] = source[9]; - target[10] = source[10]; - target[11] = source[11]; - target += UNIT_SIZE; - source += UNIT_SIZE; - } - while (--unitCount != 0); - } - - #endregion + Pointer allocatedBlock = _memoryNodes[oldIndex].Remove(); + SplitBlock(allocatedBlock, oldIndex, index); + return allocatedBlock; } -} \ No newline at end of file + + private void SplitBlock(Pointer pointer, uint oldIndex, uint newIndex) + { + var unitCountDifference = (uint)(INDEX_TO_UNITS[oldIndex] - INDEX_TO_UNITS[newIndex]); + var newPointer = pointer + (INDEX_TO_UNITS[newIndex] * UNIT_SIZE); + + uint index = UNITS_TO_INDEX[unitCountDifference - 1]; + if (INDEX_TO_UNITS[index] != unitCountDifference) + { + uint unitCount = INDEX_TO_UNITS[--index]; + _memoryNodes[index].Insert(newPointer, unitCount); + newPointer += unitCount * UNIT_SIZE; + unitCountDifference -= unitCount; + } + + _memoryNodes[UNITS_TO_INDEX[unitCountDifference - 1]].Insert( + newPointer, + unitCountDifference + ); + } + + private void GlueFreeBlocks() + { + var memoryNode = new MemoryNode(LOCAL_OFFSET, _memory); + memoryNode.Stamp = 0; + memoryNode.Next = MemoryNode.ZERO; + memoryNode.UnitCount = 0; + + MemoryNode memoryNode0; + MemoryNode memoryNode1; + MemoryNode memoryNode2; + + if (_lowUnit != _highUnit) + { + _lowUnit[0] = 0; + } + + // Find all unused memory nodes. + + memoryNode1 = memoryNode; + for (uint index = 0; index < INDEX_COUNT; index++) + { + while (_memoryNodes[index].Available) + { + memoryNode0 = _memoryNodes[index].Remove(); + if (memoryNode0.UnitCount != 0) + { + while ( + (memoryNode2 = memoryNode0 + memoryNode0.UnitCount).Stamp == uint.MaxValue + ) + { + memoryNode0.UnitCount += memoryNode2.UnitCount; + memoryNode2.UnitCount = 0; + } + memoryNode1.Link(memoryNode0); + memoryNode1 = memoryNode0; + } + } + } + + // Coalesce the memory represented by the unused memory nodes. + + while (memoryNode.Available) + { + memoryNode0 = memoryNode.Remove(); + var unitCount = memoryNode0.UnitCount; + if (unitCount != 0) + { + for (; unitCount > 128; unitCount -= 128, memoryNode0 += 128) + { + _memoryNodes[INDEX_COUNT - 1].Insert(memoryNode0, 128); + } + + uint index = UNITS_TO_INDEX[unitCount - 1]; + if (INDEX_TO_UNITS[index] != unitCount) + { + var unitCountDifference = unitCount - INDEX_TO_UNITS[--index]; + _memoryNodes[unitCountDifference - 1].Insert( + memoryNode0 + (unitCount - unitCountDifference), + unitCountDifference + ); + } + + _memoryNodes[index].Insert(memoryNode0, INDEX_TO_UNITS[index]); + } + } + + _glueCount = 1 << 13; + } + + private void CopyUnits(Pointer target, Pointer source, uint unitCount) + { + do + { + target[0] = source[0]; + target[1] = source[1]; + target[2] = source[2]; + target[3] = source[3]; + target[4] = source[4]; + target[5] = source[5]; + target[6] = source[6]; + target[7] = source[7]; + target[8] = source[8]; + target[9] = source[9]; + target[10] = source[10]; + target[11] = source[11]; + target += UNIT_SIZE; + source += UNIT_SIZE; + } while (--unitCount != 0); + } + + #endregion +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/Coder.cs b/src/SharpCompress/Compressors/PPMd/I1/Coder.cs index a5c0d862..b033a6f8 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/Coder.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/Coder.cs @@ -4,101 +4,99 @@ using System.IO; #endregion -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// A simple range coder. +/// +/// +/// Note that in most cases fields are used rather than properties for performance reasons (for example, +/// is a field rather than a property). +/// +internal class Coder { - /// - /// A simple range coder. - /// - /// - /// Note that in most cases fields are used rather than properties for performance reasons (for example, - /// is a field rather than a property). - /// - internal class Coder + private const uint RANGE_TOP = 1 << 24; + private const uint RANGE_BOTTOM = 1 << 15; + private uint _low; + private uint _code; + private uint _range; + + public uint _lowCount; + public uint _highCount; + public uint _scale; + + public void RangeEncoderInitialize() { - private const uint RANGE_TOP = 1 << 24; - private const uint RANGE_BOTTOM = 1 << 15; - private uint _low; - private uint _code; - private uint _range; + _low = 0; + _range = uint.MaxValue; + } - public uint _lowCount; - public uint _highCount; - public uint _scale; - - public void RangeEncoderInitialize() + public void RangeEncoderNormalize(Stream stream) + { + while ( + (_low ^ (_low + _range)) < RANGE_TOP + || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true) + ) { - _low = 0; - _range = uint.MaxValue; - } - - public void RangeEncoderNormalize(Stream stream) - { - while ((_low ^ (_low + _range)) < RANGE_TOP || - _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)) - { - stream.WriteByte((byte)(_low >> 24)); - _range <<= 8; - _low <<= 8; - } - } - - public void RangeEncodeSymbol() - { - _low += _lowCount * (_range /= _scale); - _range *= _highCount - _lowCount; - } - - public void RangeShiftEncodeSymbol(int rangeShift) - { - _low += _lowCount * (_range >>= rangeShift); - _range *= _highCount - _lowCount; - } - - public void RangeEncoderFlush(Stream stream) - { - for (uint index = 0; index < 4; index++) - { - stream.WriteByte((byte)(_low >> 24)); - _low <<= 8; - } - } - - public void RangeDecoderInitialize(Stream stream) - { - _low = 0; - _code = 0; - _range = uint.MaxValue; - for (uint index = 0; index < 4; index++) - { - _code = (_code << 8) | (byte)stream.ReadByte(); - } - } - - public void RangeDecoderNormalize(Stream stream) - { - while ((_low ^ (_low + _range)) < RANGE_TOP || - _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)) - { - _code = (_code << 8) | (byte)stream.ReadByte(); - _range <<= 8; - _low <<= 8; - } - } - - public uint RangeGetCurrentCount() - { - return (_code - _low) / (_range /= _scale); - } - - public uint RangeGetCurrentShiftCount(int rangeShift) - { - return (_code - _low) / (_range >>= rangeShift); - } - - public void RangeRemoveSubrange() - { - _low += _range * _lowCount; - _range *= _highCount - _lowCount; + stream.WriteByte((byte)(_low >> 24)); + _range <<= 8; + _low <<= 8; } } -} \ No newline at end of file + + public void RangeEncodeSymbol() + { + _low += _lowCount * (_range /= _scale); + _range *= _highCount - _lowCount; + } + + public void RangeShiftEncodeSymbol(int rangeShift) + { + _low += _lowCount * (_range >>= rangeShift); + _range *= _highCount - _lowCount; + } + + public void RangeEncoderFlush(Stream stream) + { + for (uint index = 0; index < 4; index++) + { + stream.WriteByte((byte)(_low >> 24)); + _low <<= 8; + } + } + + public void RangeDecoderInitialize(Stream stream) + { + _low = 0; + _code = 0; + _range = uint.MaxValue; + for (uint index = 0; index < 4; index++) + { + _code = (_code << 8) | (byte)stream.ReadByte(); + } + } + + public void RangeDecoderNormalize(Stream stream) + { + while ( + (_low ^ (_low + _range)) < RANGE_TOP + || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true) + ) + { + _code = (_code << 8) | (byte)stream.ReadByte(); + _range <<= 8; + _low <<= 8; + } + } + + public uint RangeGetCurrentCount() => (_code - _low) / (_range /= _scale); + + public uint RangeGetCurrentShiftCount(int rangeShift) => + (_code - _low) / (_range >>= rangeShift); + + public void RangeRemoveSubrange() + { + _low += _range * _lowCount; + _range *= _highCount - _lowCount; + } +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/MemoryNode.cs b/src/SharpCompress/Compressors/PPMd/I1/MemoryNode.cs index aee8b181..5e978c5c 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/MemoryNode.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/MemoryNode.cs @@ -1,243 +1,240 @@ #nullable disable -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// A structure containing a single address. The address represents a location in the +/// array. That location in the array contains information itself describing a section +/// of the array (ie. a block of memory). +/// +/// +/// +/// This must be a structure rather than a class because several places in the associated code assume that +/// is a value type (meaning that assignment creates a completely new copy of +/// the instance rather than just copying a reference to the same instance). +/// +/// +/// MemoryNode +/// 4 Stamp +/// 4 Next +/// 4 UnitCount +/// +/// +/// Note that is a field rather than a property for performance reasons. +/// +/// +internal struct MemoryNode { + public uint _address; + public byte[] _memory; + public static readonly MemoryNode ZERO = new MemoryNode(0, null); + public const int SIZE = 12; + /// - /// A structure containing a single address. The address represents a location in the - /// array. That location in the array contains information itself describing a section - /// of the array (ie. a block of memory). + /// Initializes a new instance of the structure. /// - /// - /// - /// This must be a structure rather than a class because several places in the associated code assume that - /// is a value type (meaning that assignment creates a completely new copy of - /// the instance rather than just copying a reference to the same instance). - /// - /// - /// MemoryNode - /// 4 Stamp - /// 4 Next - /// 4 UnitCount - /// - /// - /// Note that is a field rather than a property for performance reasons. - /// - /// - internal struct MemoryNode + public MemoryNode(uint address, byte[] memory) { - public uint _address; - public byte[] _memory; - public static readonly MemoryNode ZERO = new MemoryNode(0, null); - public const int SIZE = 12; + _address = address; + _memory = memory; + } - /// - /// Initializes a new instance of the structure. - /// - public MemoryNode(uint address, byte[] memory) + /// + /// Gets or sets the stamp. + /// + public uint Stamp + { + get => + _memory[_address] + | (((uint)_memory[_address + 1]) << 8) + | (((uint)_memory[_address + 2]) << 16) + | (((uint)_memory[_address + 3]) << 24); + set { - _address = address; - _memory = memory; - } - - /// - /// Gets or sets the stamp. - /// - public uint Stamp - { - get => _memory[_address] | ((uint)_memory[_address + 1]) << 8 | ((uint)_memory[_address + 2]) << 16 | - ((uint)_memory[_address + 3]) << 24; - set - { - _memory[_address] = (byte)value; - _memory[_address + 1] = (byte)(value >> 8); - _memory[_address + 2] = (byte)(value >> 16); - _memory[_address + 3] = (byte)(value >> 24); - } - } - - /// - /// Gets or sets the next memory node. - /// - public MemoryNode Next - { - get => new MemoryNode( - _memory[_address + 4] | ((uint)_memory[_address + 5]) << 8 | - ((uint)_memory[_address + 6]) << 16 | ((uint)_memory[_address + 7]) << 24, _memory); - set - { - _memory[_address + 4] = (byte)value._address; - _memory[_address + 5] = (byte)(value._address >> 8); - _memory[_address + 6] = (byte)(value._address >> 16); - _memory[_address + 7] = (byte)(value._address >> 24); - } - } - - /// - /// Gets or sets the unit count. - /// - public uint UnitCount - { - get => _memory[_address + 8] | ((uint)_memory[_address + 9]) << 8 | - ((uint)_memory[_address + 10]) << 16 | ((uint)_memory[_address + 11]) << 24; - set - { - _memory[_address + 8] = (byte)value; - _memory[_address + 9] = (byte)(value >> 8); - _memory[_address + 10] = (byte)(value >> 16); - _memory[_address + 11] = (byte)(value >> 24); - } - } - - /// - /// Gets whether there is a next memory node available. - /// - public bool Available => Next._address != 0; - - /// - /// Link in the provided memory node. - /// - /// - public void Link(MemoryNode memoryNode) - { - memoryNode.Next = Next; - Next = memoryNode; - } - - /// - /// Unlink this memory node. - /// - public void Unlink() - { - Next = Next.Next; - } - - /// - /// Insert the memory node into the linked list. - /// - /// - /// - public void Insert(MemoryNode memoryNode, uint unitCount) - { - Link(memoryNode); - memoryNode.Stamp = uint.MaxValue; - memoryNode.UnitCount = unitCount; - Stamp++; - } - - /// - /// Remove this memory node from the linked list. - /// - /// - public MemoryNode Remove() - { - MemoryNode next = Next; - Unlink(); - Stamp--; - return next; - } - - /// - /// Allow a pointer to be implicitly converted to a memory node. - /// - /// - /// - public static implicit operator MemoryNode(Pointer pointer) - { - return new MemoryNode(pointer._address, pointer._memory); - } - - /// - /// Allow pointer-like addition on a memory node. - /// - /// - /// - /// - public static MemoryNode operator +(MemoryNode memoryNode, int offset) - { - memoryNode._address = (uint)(memoryNode._address + offset * SIZE); - return memoryNode; - } - - /// - /// Allow pointer-like addition on a memory node. - /// - /// - /// - /// - public static MemoryNode operator +(MemoryNode memoryNode, uint offset) - { - memoryNode._address += offset * SIZE; - return memoryNode; - } - - /// - /// Allow pointer-like subtraction on a memory node. - /// - /// - /// - /// - public static MemoryNode operator -(MemoryNode memoryNode, int offset) - { - memoryNode._address = (uint)(memoryNode._address - offset * SIZE); - return memoryNode; - } - - /// - /// Allow pointer-like subtraction on a memory node. - /// - /// - /// - /// - public static MemoryNode operator -(MemoryNode memoryNode, uint offset) - { - memoryNode._address -= offset * SIZE; - return memoryNode; - } - - /// - /// Compare two memory nodes. - /// - /// - /// - /// - public static bool operator ==(MemoryNode memoryNode1, MemoryNode memoryNode2) - { - return memoryNode1._address == memoryNode2._address; - } - - /// - /// Compare two memory nodes. - /// - /// - /// - /// - public static bool operator !=(MemoryNode memoryNode1, MemoryNode memoryNode2) - { - return memoryNode1._address != memoryNode2._address; - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// true if obj and this instance are the same type and represent the same value; otherwise, false. - /// Another object to compare to. - public override bool Equals(object obj) - { - if (obj is MemoryNode) - { - MemoryNode memoryNode = (MemoryNode)obj; - return memoryNode._address == _address; - } - return base.Equals(obj); - } - - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return _address.GetHashCode(); + _memory[_address] = (byte)value; + _memory[_address + 1] = (byte)(value >> 8); + _memory[_address + 2] = (byte)(value >> 16); + _memory[_address + 3] = (byte)(value >> 24); } } -} \ No newline at end of file + + /// + /// Gets or sets the next memory node. + /// + public MemoryNode Next + { + get => + new MemoryNode( + _memory[_address + 4] + | (((uint)_memory[_address + 5]) << 8) + | (((uint)_memory[_address + 6]) << 16) + | (((uint)_memory[_address + 7]) << 24), + _memory + ); + set + { + _memory[_address + 4] = (byte)value._address; + _memory[_address + 5] = (byte)(value._address >> 8); + _memory[_address + 6] = (byte)(value._address >> 16); + _memory[_address + 7] = (byte)(value._address >> 24); + } + } + + /// + /// Gets or sets the unit count. + /// + public uint UnitCount + { + get => + _memory[_address + 8] + | (((uint)_memory[_address + 9]) << 8) + | (((uint)_memory[_address + 10]) << 16) + | (((uint)_memory[_address + 11]) << 24); + set + { + _memory[_address + 8] = (byte)value; + _memory[_address + 9] = (byte)(value >> 8); + _memory[_address + 10] = (byte)(value >> 16); + _memory[_address + 11] = (byte)(value >> 24); + } + } + + /// + /// Gets whether there is a next memory node available. + /// + public bool Available => Next._address != 0; + + /// + /// Link in the provided memory node. + /// + /// + public void Link(MemoryNode memoryNode) + { + memoryNode.Next = Next; + Next = memoryNode; + } + + /// + /// Unlink this memory node. + /// + public void Unlink() => Next = Next.Next; + + /// + /// Insert the memory node into the linked list. + /// + /// + /// + public void Insert(MemoryNode memoryNode, uint unitCount) + { + Link(memoryNode); + memoryNode.Stamp = uint.MaxValue; + memoryNode.UnitCount = unitCount; + Stamp++; + } + + /// + /// Remove this memory node from the linked list. + /// + /// + public MemoryNode Remove() + { + var next = Next; + Unlink(); + Stamp--; + return next; + } + + /// + /// Allow a pointer to be implicitly converted to a memory node. + /// + /// + /// + public static implicit operator MemoryNode(Pointer pointer) => + new MemoryNode(pointer._address, pointer._memory); + + /// + /// Allow pointer-like addition on a memory node. + /// + /// + /// + /// + public static MemoryNode operator +(MemoryNode memoryNode, int offset) + { + memoryNode._address = (uint)(memoryNode._address + (offset * SIZE)); + return memoryNode; + } + + /// + /// Allow pointer-like addition on a memory node. + /// + /// + /// + /// + public static MemoryNode operator +(MemoryNode memoryNode, uint offset) + { + memoryNode._address += offset * SIZE; + return memoryNode; + } + + /// + /// Allow pointer-like subtraction on a memory node. + /// + /// + /// + /// + public static MemoryNode operator -(MemoryNode memoryNode, int offset) + { + memoryNode._address = (uint)(memoryNode._address - (offset * SIZE)); + return memoryNode; + } + + /// + /// Allow pointer-like subtraction on a memory node. + /// + /// + /// + /// + public static MemoryNode operator -(MemoryNode memoryNode, uint offset) + { + memoryNode._address -= offset * SIZE; + return memoryNode; + } + + /// + /// Compare two memory nodes. + /// + /// + /// + /// + public static bool operator ==(MemoryNode memoryNode1, MemoryNode memoryNode2) => + memoryNode1._address == memoryNode2._address; + + /// + /// Compare two memory nodes. + /// + /// + /// + /// + public static bool operator !=(MemoryNode memoryNode1, MemoryNode memoryNode2) => + memoryNode1._address != memoryNode2._address; + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// true if obj and this instance are the same type and represent the same value; otherwise, false. + /// Another object to compare to. + public override bool Equals(object obj) + { + if (obj is MemoryNode memoryNode) + { + return memoryNode._address == _address; + } + return base.Equals(obj); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => _address.GetHashCode(); +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/Model.cs b/src/SharpCompress/Compressors/PPMd/I1/Model.cs index 347057eb..786cb0e7 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/Model.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/Model.cs @@ -6,916 +6,946 @@ using System.IO; // This is a port of Dmitry Shkarin's PPMd Variant I Revision 1. // Ported by Michael Bone (mjbone03@yahoo.com.au). -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// The model. +/// +internal partial class Model { - /// - /// The model. - /// - internal partial class Model + public const uint SIGNATURE = 0x84acaf8fU; + public const char VARIANT = 'I'; + public const int MAXIMUM_ORDER = 16; // maximum allowed model order + + private const byte UPPER_FREQUENCY = 5; + private const byte INTERVAL_BIT_COUNT = 7; + private const byte PERIOD_BIT_COUNT = 7; + private const byte TOTAL_BIT_COUNT = INTERVAL_BIT_COUNT + PERIOD_BIT_COUNT; + private const uint INTERVAL = 1 << INTERVAL_BIT_COUNT; + private const uint BINARY_SCALE = 1 << TOTAL_BIT_COUNT; + private const uint MAXIMUM_FREQUENCY = 124; + private const uint ORDER_BOUND = 9; + + private readonly See2Context[,] _see2Contexts; + private readonly See2Context _emptySee2Context; + private PpmContext _maximumContext; + private readonly ushort[,] _binarySummary = new ushort[25, 64]; // binary SEE-contexts + private readonly byte[] _numberStatisticsToBinarySummaryIndex = new byte[256]; + private readonly byte[] _probabilities = new byte[260]; + private readonly byte[] _characterMask = new byte[256]; + private byte _escapeCount; + private int _modelOrder; + private int _orderFall; + private int _initialEscape; + private int _initialRunLength; + private int _runLength; + private byte _previousSuccess; + private byte _numberMasked; + private ModelRestorationMethod _method; + private PpmState _foundState; // found next state transition + + private Allocator _allocator; + private Coder _coder; + private PpmContext _minimumContext; + private byte _numberStatistics; + private readonly PpmState[] _decodeStates = new PpmState[256]; + + private static readonly ushort[] INITIAL_BINARY_ESCAPES = { - public const uint SIGNATURE = 0x84acaf8fU; - public const char VARIANT = 'I'; - public const int MAXIMUM_ORDER = 16; // maximum allowed model order + 0x3CDD, + 0x1F3F, + 0x59BF, + 0x48F3, + 0x64A1, + 0x5ABC, + 0x6632, + 0x6051 + }; - private const byte UPPER_FREQUENCY = 5; - private const byte INTERVAL_BIT_COUNT = 7; - private const byte PERIOD_BIT_COUNT = 7; - private const byte TOTAL_BIT_COUNT = INTERVAL_BIT_COUNT + PERIOD_BIT_COUNT; - private const uint INTERVAL = 1 << INTERVAL_BIT_COUNT; - private const uint BINARY_SCALE = 1 << TOTAL_BIT_COUNT; - private const uint MAXIMUM_FREQUENCY = 124; - private const uint ORDER_BOUND = 9; + private static ReadOnlySpan EXPONENTIAL_ESCAPES => + new byte[] { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; - private readonly See2Context[,] _see2Contexts; - private readonly See2Context _emptySee2Context; - private PpmContext _maximumContext; - private readonly ushort[,] _binarySummary = new ushort[25, 64]; // binary SEE-contexts - private readonly byte[] _numberStatisticsToBinarySummaryIndex = new byte[256]; - private readonly byte[] _probabilities = new byte[260]; - private readonly byte[] _characterMask = new byte[256]; - private byte _escapeCount; - private int _modelOrder; - private int _orderFall; - private int _initialEscape; - private int _initialRunLength; - private int _runLength; - private byte _previousSuccess; - private byte _numberMasked; - private ModelRestorationMethod _method; - private PpmState _foundState; // found next state transition + #region Public Methods - private Allocator _allocator; - private Coder _coder; - private PpmContext _minimumContext; - private byte _numberStatistics; - private readonly PpmState[] _decodeStates = new PpmState[256]; + public Model() + { + // Construct the conversion table for number statistics. Initially it will contain the following values. + // + // 0 2 4 4 4 4 4 4 4 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 + // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - private static readonly ushort[] INITIAL_BINARY_ESCAPES = + _numberStatisticsToBinarySummaryIndex[0] = 2 * 0; + _numberStatisticsToBinarySummaryIndex[1] = 2 * 1; + for (var index = 2; index < 11; index++) { - 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, - 0x6051 - }; - - private static ReadOnlySpan EXPONENTIAL_ESCAPES => new byte[] { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; - - #region Public Methods - - public Model() + _numberStatisticsToBinarySummaryIndex[index] = 2 * 2; + } + for (var index = 11; index < 256; index++) { - // Construct the conversion table for number statistics. Initially it will contain the following values. - // - // 0 2 4 4 4 4 4 4 4 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - - _numberStatisticsToBinarySummaryIndex[0] = 2 * 0; - _numberStatisticsToBinarySummaryIndex[1] = 2 * 1; - for (int index = 2; index < 11; index++) - { - _numberStatisticsToBinarySummaryIndex[index] = 2 * 2; - } - for (int index = 11; index < 256; index++) - { - _numberStatisticsToBinarySummaryIndex[index] = 2 * 3; - } - - // Construct the probability table. Initially it will contain the following values (depending on the value of - // the upper frequency). - // - // 00 01 02 03 04 05 06 06 07 07 07 08 08 08 08 09 09 09 09 09 10 10 10 10 10 10 11 11 11 11 11 11 - // 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15 - // 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 - // 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 - // 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 - // 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 - // 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 - // 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 - // 26 26 27 27 - - uint count = 1; - uint step = 1; - uint probability = UPPER_FREQUENCY; - - for (int index = 0; index < UPPER_FREQUENCY; index++) - { - _probabilities[index] = (byte)index; - } - - for (int index = UPPER_FREQUENCY; index < 260; index++) - { - _probabilities[index] = (byte)probability; - count--; - if (count == 0) - { - step++; - count = step; - probability++; - } - } - - // Create the context array. - - _see2Contexts = new See2Context[24, 32]; - for (int index1 = 0; index1 < 24; index1++) - { - for (int index2 = 0; index2 < 32; index2++) - { - _see2Contexts[index1, index2] = new See2Context(); - } - } - - // Set the signature (identifying the algorithm). - - _emptySee2Context = new See2Context(); - _emptySee2Context._summary = (ushort)(SIGNATURE & 0x0000ffff); - _emptySee2Context._shift = (byte)((SIGNATURE >> 16) & 0x000000ff); - _emptySee2Context._count = (byte)(SIGNATURE >> 24); + _numberStatisticsToBinarySummaryIndex[index] = 2 * 3; } - /// - /// Encode (ie. compress) a given source stream, writing the encoded result to the target stream. - /// - public void Encode(Stream target, Stream source, PpmdProperties properties) + // Construct the probability table. Initially it will contain the following values (depending on the value of + // the upper frequency). + // + // 00 01 02 03 04 05 06 06 07 07 07 08 08 08 08 09 09 09 09 09 10 10 10 10 10 10 11 11 11 11 11 11 + // 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15 + // 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 + // 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 + // 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 + // 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 + // 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 + // 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 + // 26 26 27 27 + + uint count = 1; + uint step = 1; + uint probability = UPPER_FREQUENCY; + + for (var index = 0; index < UPPER_FREQUENCY; index++) { - if (target is null) - { - throw new ArgumentNullException(nameof(target)); - } - - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - EncodeStart(properties); - EncodeBlock(target, source, true); + _probabilities[index] = (byte)index; } - internal Coder EncodeStart(PpmdProperties properties) + for (int index = UPPER_FREQUENCY; index < 260; index++) { - _allocator = properties._allocator; - _coder = new Coder(); - _coder.RangeEncoderInitialize(); - StartModel(properties.ModelOrder, properties.RestorationMethod); - return _coder; - } - - internal void EncodeBlock(Stream target, Stream source, bool final) - { - while (true) + _probabilities[index] = (byte)probability; + count--; + if (count == 0) { - _minimumContext = _maximumContext; - _numberStatistics = _minimumContext.NumberStatistics; - - int c = source.ReadByte(); - if (c < 0 && !final) - { - return; - } - - if (_numberStatistics != 0) - { - EncodeSymbol1(c, _minimumContext); - _coder.RangeEncodeSymbol(); - } - else - { - EncodeBinarySymbol(c, _minimumContext); - _coder.RangeShiftEncodeSymbol(TOTAL_BIT_COUNT); - } - - while (_foundState == PpmState.ZERO) - { - _coder.RangeEncoderNormalize(target); - do - { - _orderFall++; - _minimumContext = _minimumContext.Suffix; - if (_minimumContext == PpmContext.ZERO) - { - goto StopEncoding; - } - } - while (_minimumContext.NumberStatistics == _numberMasked); - EncodeSymbol2(c, _minimumContext); - _coder.RangeEncodeSymbol(); - } - - if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit) - { - _maximumContext = _foundState.Successor; - } - else - { - UpdateModel(_minimumContext); - if (_escapeCount == 0) - { - ClearMask(); - } - } - - _coder.RangeEncoderNormalize(target); - } - - StopEncoding: - _coder.RangeEncoderFlush(target); - } - - /// - /// Dencode (ie. decompress) a given source stream, writing the decoded result to the target stream. - /// - public void Decode(Stream target, Stream source, PpmdProperties properties) - { - if (target is null) - { - throw new ArgumentNullException(nameof(target)); - } - - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - DecodeStart(source, properties); - byte[] buffer = new byte[65536]; - int read; - while ((read = DecodeBlock(source, buffer, 0, buffer.Length)) != 0) - { - target.Write(buffer, 0, read); + step++; + count = step; + probability++; } } - internal Coder DecodeStart(Stream source, PpmdProperties properties) + // Create the context array. + + _see2Contexts = new See2Context[24, 32]; + for (var index1 = 0; index1 < 24; index1++) + { + for (var index2 = 0; index2 < 32; index2++) + { + _see2Contexts[index1, index2] = new See2Context(); + } + } + + // Set the signature (identifying the algorithm). + + _emptySee2Context = new See2Context(); + _emptySee2Context._summary = (ushort)(SIGNATURE & 0x0000ffff); + _emptySee2Context._shift = (byte)((SIGNATURE >> 16) & 0x000000ff); + _emptySee2Context._count = (byte)(SIGNATURE >> 24); + } + + /// + /// Encode (ie. compress) a given source stream, writing the encoded result to the target stream. + /// + public void Encode(Stream target, Stream source, PpmdProperties properties) + { + if (target is null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + EncodeStart(properties); + EncodeBlock(target, source, true); + } + + internal Coder EncodeStart(PpmdProperties properties) + { + _allocator = properties._allocator; + _coder = new Coder(); + _coder.RangeEncoderInitialize(); + StartModel(properties.ModelOrder, properties.RestorationMethod); + return _coder; + } + + internal void EncodeBlock(Stream target, Stream source, bool final) + { + while (true) { - _allocator = properties._allocator; - _coder = new Coder(); - _coder.RangeDecoderInitialize(source); - StartModel(properties.ModelOrder, properties.RestorationMethod); _minimumContext = _maximumContext; _numberStatistics = _minimumContext.NumberStatistics; - return _coder; - } - internal int DecodeBlock(Stream source, byte[] buffer, int offset, int count) - { - if (_minimumContext == PpmContext.ZERO) + var c = source.ReadByte(); + if (c < 0 && !final) { - return 0; - } - - int total = 0; - while (total < count) - { - if (_numberStatistics != 0) - { - DecodeSymbol1(_minimumContext); - } - else - { - DecodeBinarySymbol(_minimumContext); - } - - _coder.RangeRemoveSubrange(); - - while (_foundState == PpmState.ZERO) - { - _coder.RangeDecoderNormalize(source); - do - { - _orderFall++; - _minimumContext = _minimumContext.Suffix; - if (_minimumContext == PpmContext.ZERO) - { - goto StopDecoding; - } - } - while (_minimumContext.NumberStatistics == _numberMasked); - DecodeSymbol2(_minimumContext); - _coder.RangeRemoveSubrange(); - } - - buffer[offset] = _foundState.Symbol; - offset++; - total++; - - if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit) - { - _maximumContext = _foundState.Successor; - } - else - { - UpdateModel(_minimumContext); - if (_escapeCount == 0) - { - ClearMask(); - } - } - - _minimumContext = _maximumContext; - _numberStatistics = _minimumContext.NumberStatistics; - _coder.RangeDecoderNormalize(source); - } - - StopDecoding: - return total; - } - - #endregion - - #region Private Methods - - /// - /// Initialise the model (unless the model order is set to 1 in which case the model should be cleared so that - /// the statistics are carried over, allowing "solid" mode compression). - /// - private void StartModel(int modelOrder, ModelRestorationMethod modelRestorationMethod) - { - Array.Clear(_characterMask, 0, _characterMask.Length); - _escapeCount = 1; - - // Compress in "solid" mode if the model order value is set to 1 (this will examine the current PPM context - // structures to determine the value of orderFall). - - if (modelOrder < 2) - { - _orderFall = _modelOrder; - for (PpmContext context = _maximumContext; context.Suffix != PpmContext.ZERO; context = context.Suffix) - { - _orderFall--; - } return; } - _modelOrder = modelOrder; - _orderFall = modelOrder; - _method = modelRestorationMethod; - _allocator.Initialize(); - _initialRunLength = -((modelOrder < 12) ? modelOrder : 12) - 1; - _runLength = _initialRunLength; - - // Allocate the context structure. - - _maximumContext = _allocator.AllocateContext(); - _maximumContext.Suffix = PpmContext.ZERO; - _maximumContext.NumberStatistics = 255; - _maximumContext.SummaryFrequency = (ushort)(_maximumContext.NumberStatistics + 2); - _maximumContext.Statistics = _allocator.AllocateUnits(256 / 2); - - // allocates enough space for 256 PPM states (each is 6 bytes) - - _previousSuccess = 0; - for (int index = 0; index < 256; index++) + if (_numberStatistics != 0) { - PpmState state = _maximumContext.Statistics[index]; - state.Symbol = (byte)index; - state.Frequency = 1; - state.Successor = PpmContext.ZERO; - } - - uint probability = 0; - for (int index1 = 0; probability < 25; probability++) - { - while (_probabilities[index1] == probability) - { - index1++; - } - for (int index2 = 0; index2 < 8; index2++) - { - _binarySummary[probability, index2] = - (ushort)(BINARY_SCALE - INITIAL_BINARY_ESCAPES[index2] / (index1 + 1)); - } - for (int index2 = 8; index2 < 64; index2 += 8) - { - for (int index3 = 0; index3 < 8; index3++) - { - _binarySummary[probability, index2 + index3] = _binarySummary[probability, index3]; - } - } - } - - probability = 0; - for (uint index1 = 0; probability < 24; probability++) - { - while (_probabilities[index1 + 3] == probability + 3) - { - index1++; - } - for (int index2 = 0; index2 < 32; index2++) - { - _see2Contexts[probability, index2].Initialize(2 * index1 + 5); - } - } - } - - private void UpdateModel(PpmContext minimumContext) - { - PpmState state = PpmState.ZERO; - PpmContext successor; - PpmContext currentContext = _maximumContext; - uint numberStatistics; - uint ns1; - uint cf; - uint sf; - uint s0; - uint foundStateFrequency = _foundState.Frequency; - byte foundStateSymbol = _foundState.Symbol; - byte symbol; - byte flag; - - PpmContext foundStateSuccessor = _foundState.Successor; - PpmContext context = minimumContext.Suffix; - - if ((foundStateFrequency < MAXIMUM_FREQUENCY / 4) && (context != PpmContext.ZERO)) - { - if (context.NumberStatistics != 0) - { - state = context.Statistics; - if (state.Symbol != foundStateSymbol) - { - do - { - symbol = state[1].Symbol; - state++; - } - while (symbol != foundStateSymbol); - if (state[0].Frequency >= state[-1].Frequency) - { - Swap(state[0], state[-1]); - state--; - } - } - cf = (uint)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 2 : 0); - state.Frequency += (byte)cf; - context.SummaryFrequency += (byte)cf; - } - else - { - state = context.FirstState; - state.Frequency += (byte)((state.Frequency < 32) ? 1 : 0); - } - } - - if (_orderFall == 0 && foundStateSuccessor != PpmContext.ZERO) - { - _foundState.Successor = CreateSuccessors(true, state, minimumContext); - if (_foundState.Successor == PpmContext.ZERO) - { - goto RestartModel; - } - _maximumContext = _foundState.Successor; - return; - } - - _allocator._text[0] = foundStateSymbol; - _allocator._text++; - successor = _allocator._text; - - if (_allocator._text >= _allocator._baseUnit) - { - goto RestartModel; - } - - if (foundStateSuccessor != PpmContext.ZERO) - { - if (foundStateSuccessor < _allocator._baseUnit) - { - foundStateSuccessor = CreateSuccessors(false, state, minimumContext); - } + EncodeSymbol1(c, _minimumContext); + _coder.RangeEncodeSymbol(); } else { - foundStateSuccessor = ReduceOrder(state, minimumContext); + EncodeBinarySymbol(c, _minimumContext); + _coder.RangeShiftEncodeSymbol(TOTAL_BIT_COUNT); } - if (foundStateSuccessor == PpmContext.ZERO) + while (_foundState == PpmState.ZERO) + { + _coder.RangeEncoderNormalize(target); + do + { + _orderFall++; + _minimumContext = _minimumContext.Suffix; + if (_minimumContext == PpmContext.ZERO) + { + goto StopEncoding; + } + } while (_minimumContext.NumberStatistics == _numberMasked); + EncodeSymbol2(c, _minimumContext); + _coder.RangeEncodeSymbol(); + } + + if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit) + { + _maximumContext = _foundState.Successor; + } + else + { + UpdateModel(_minimumContext); + if (_escapeCount == 0) + { + ClearMask(); + } + } + + _coder.RangeEncoderNormalize(target); + } + + StopEncoding: + _coder.RangeEncoderFlush(target); + } + + /// + /// Dencode (ie. decompress) a given source stream, writing the decoded result to the target stream. + /// + public void Decode(Stream target, Stream source, PpmdProperties properties) + { + if (target is null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + DecodeStart(source, properties); + var buffer = new byte[65536]; + int read; + while ((read = DecodeBlock(source, buffer, 0, buffer.Length)) != 0) + { + target.Write(buffer, 0, read); + } + } + + internal Coder DecodeStart(Stream source, PpmdProperties properties) + { + _allocator = properties._allocator; + _coder = new Coder(); + _coder.RangeDecoderInitialize(source); + StartModel(properties.ModelOrder, properties.RestorationMethod); + _minimumContext = _maximumContext; + _numberStatistics = _minimumContext.NumberStatistics; + return _coder; + } + + internal int DecodeBlock(Stream source, byte[] buffer, int offset, int count) + { + if (_minimumContext == PpmContext.ZERO) + { + return 0; + } + + var total = 0; + while (total < count) + { + if (_numberStatistics != 0) + { + DecodeSymbol1(_minimumContext); + } + else + { + DecodeBinarySymbol(_minimumContext); + } + + _coder.RangeRemoveSubrange(); + + while (_foundState == PpmState.ZERO) + { + _coder.RangeDecoderNormalize(source); + do + { + _orderFall++; + _minimumContext = _minimumContext.Suffix; + if (_minimumContext == PpmContext.ZERO) + { + goto StopDecoding; + } + } while (_minimumContext.NumberStatistics == _numberMasked); + DecodeSymbol2(_minimumContext); + _coder.RangeRemoveSubrange(); + } + + buffer[offset] = _foundState.Symbol; + offset++; + total++; + + if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit) + { + _maximumContext = _foundState.Successor; + } + else + { + UpdateModel(_minimumContext); + if (_escapeCount == 0) + { + ClearMask(); + } + } + + _minimumContext = _maximumContext; + _numberStatistics = _minimumContext.NumberStatistics; + _coder.RangeDecoderNormalize(source); + } + + StopDecoding: + return total; + } + + #endregion + + #region Private Methods + + /// + /// Initialise the model (unless the model order is set to 1 in which case the model should be cleared so that + /// the statistics are carried over, allowing "solid" mode compression). + /// + private void StartModel(int modelOrder, ModelRestorationMethod modelRestorationMethod) + { + Array.Clear(_characterMask, 0, _characterMask.Length); + _escapeCount = 1; + + // Compress in "solid" mode if the model order value is set to 1 (this will examine the current PPM context + // structures to determine the value of orderFall). + + if (modelOrder < 2) + { + _orderFall = _modelOrder; + for ( + var context = _maximumContext; + context.Suffix != PpmContext.ZERO; + context = context.Suffix + ) + { + _orderFall--; + } + return; + } + + _modelOrder = modelOrder; + _orderFall = modelOrder; + _method = modelRestorationMethod; + _allocator.Initialize(); + _initialRunLength = -((modelOrder < 12) ? modelOrder : 12) - 1; + _runLength = _initialRunLength; + + // Allocate the context structure. + + _maximumContext = _allocator.AllocateContext(); + _maximumContext.Suffix = PpmContext.ZERO; + _maximumContext.NumberStatistics = 255; + _maximumContext.SummaryFrequency = (ushort)(_maximumContext.NumberStatistics + 2); + _maximumContext.Statistics = _allocator.AllocateUnits(256 / 2); + + // allocates enough space for 256 PPM states (each is 6 bytes) + + _previousSuccess = 0; + for (var index = 0; index < 256; index++) + { + var state = _maximumContext.Statistics[index]; + state.Symbol = (byte)index; + state.Frequency = 1; + state.Successor = PpmContext.ZERO; + } + + uint probability = 0; + for (var index1 = 0; probability < 25; probability++) + { + while (_probabilities[index1] == probability) + { + index1++; + } + for (var index2 = 0; index2 < 8; index2++) + { + _binarySummary[probability, index2] = (ushort)( + BINARY_SCALE - (INITIAL_BINARY_ESCAPES[index2] / (index1 + 1)) + ); + } + for (var index2 = 8; index2 < 64; index2 += 8) + { + for (var index3 = 0; index3 < 8; index3++) + { + _binarySummary[probability, index2 + index3] = _binarySummary[ + probability, + index3 + ]; + } + } + } + + probability = 0; + for (uint index1 = 0; probability < 24; probability++) + { + while (_probabilities[index1 + 3] == probability + 3) + { + index1++; + } + for (var index2 = 0; index2 < 32; index2++) + { + _see2Contexts[probability, index2].Initialize((2 * index1) + 5); + } + } + } + + private void UpdateModel(PpmContext minimumContext) + { + var state = PpmState.ZERO; + PpmContext successor; + var currentContext = _maximumContext; + uint numberStatistics; + uint ns1; + uint cf; + uint sf; + uint s0; + uint foundStateFrequency = _foundState.Frequency; + var foundStateSymbol = _foundState.Symbol; + byte symbol; + byte flag; + + var foundStateSuccessor = _foundState.Successor; + var context = minimumContext.Suffix; + + if ((foundStateFrequency < MAXIMUM_FREQUENCY / 4) && (context != PpmContext.ZERO)) + { + if (context.NumberStatistics != 0) + { + state = context.Statistics; + if (state.Symbol != foundStateSymbol) + { + do + { + symbol = state[1].Symbol; + state++; + } while (symbol != foundStateSymbol); + if (state[0].Frequency >= state[-1].Frequency) + { + Swap(state[0], state[-1]); + state--; + } + } + cf = (uint)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 2 : 0); + state.Frequency += (byte)cf; + context.SummaryFrequency += (byte)cf; + } + else + { + state = context.FirstState; + state.Frequency += (byte)((state.Frequency < 32) ? 1 : 0); + } + } + + if (_orderFall == 0 && foundStateSuccessor != PpmContext.ZERO) + { + _foundState.Successor = CreateSuccessors(true, state, minimumContext); + if (_foundState.Successor == PpmContext.ZERO) { goto RestartModel; } + _maximumContext = _foundState.Successor; + return; + } - if (--_orderFall == 0) - { - successor = foundStateSuccessor; - _allocator._text -= (_maximumContext != minimumContext) ? 1 : 0; - } - else if (_method > ModelRestorationMethod.Freeze) - { - successor = foundStateSuccessor; - _allocator._text = _allocator._heap; - _orderFall = 0; - } + _allocator._text[0] = foundStateSymbol; + _allocator._text++; + successor = _allocator._text; - numberStatistics = minimumContext.NumberStatistics; - s0 = minimumContext.SummaryFrequency - numberStatistics - foundStateFrequency; - flag = (byte)((foundStateSymbol >= 0x40) ? 0x08 : 0x00); - for (; currentContext != minimumContext; currentContext = currentContext.Suffix) + if (_allocator._text >= _allocator._baseUnit) + { + goto RestartModel; + } + + if (foundStateSuccessor != PpmContext.ZERO) + { + if (foundStateSuccessor < _allocator._baseUnit) { - ns1 = currentContext.NumberStatistics; - if (ns1 != 0) + foundStateSuccessor = CreateSuccessors(false, state, minimumContext); + } + } + else + { + foundStateSuccessor = ReduceOrder(state, minimumContext); + } + + if (foundStateSuccessor == PpmContext.ZERO) + { + goto RestartModel; + } + + if (--_orderFall == 0) + { + successor = foundStateSuccessor; + _allocator._text -= (_maximumContext != minimumContext) ? 1 : 0; + } + else if (_method > ModelRestorationMethod.Freeze) + { + successor = foundStateSuccessor; + _allocator._text = _allocator._heap; + _orderFall = 0; + } + + numberStatistics = minimumContext.NumberStatistics; + s0 = minimumContext.SummaryFrequency - numberStatistics - foundStateFrequency; + flag = (byte)((foundStateSymbol >= 0x40) ? 0x08 : 0x00); + for (; currentContext != minimumContext; currentContext = currentContext.Suffix) + { + ns1 = currentContext.NumberStatistics; + if (ns1 != 0) + { + if ((ns1 & 1) != 0) { - if ((ns1 & 1) != 0) - { - state = _allocator.ExpandUnits(currentContext.Statistics, (ns1 + 1) >> 1); - if (state == PpmState.ZERO) - { - goto RestartModel; - } - currentContext.Statistics = state; - } - currentContext.SummaryFrequency += (ushort)((3 * ns1 + 1 < numberStatistics) ? 1 : 0); - } - else - { - state = _allocator.AllocateUnits(1); + state = _allocator.ExpandUnits(currentContext.Statistics, (ns1 + 1) >> 1); if (state == PpmState.ZERO) { goto RestartModel; } - Copy(state, currentContext.FirstState); currentContext.Statistics = state; - if (state.Frequency < MAXIMUM_FREQUENCY / 4 - 1) - { - state.Frequency += state.Frequency; - } - else - { - state.Frequency = (byte)(MAXIMUM_FREQUENCY - 4); - } - currentContext.SummaryFrequency = - (ushort)(state.Frequency + _initialEscape + ((numberStatistics > 2) ? 1 : 0)); } - - cf = (uint)(2 * foundStateFrequency * (currentContext.SummaryFrequency + 6)); - sf = s0 + currentContext.SummaryFrequency; - - if (cf < 6 * sf) + currentContext.SummaryFrequency += (ushort)( + ((3 * ns1) + 1 < numberStatistics) ? 1 : 0 + ); + } + else + { + state = _allocator.AllocateUnits(1); + if (state == PpmState.ZERO) { - cf = (uint)(1 + ((cf > sf) ? 1 : 0) + ((cf >= 4 * sf) ? 1 : 0)); - currentContext.SummaryFrequency += 4; + goto RestartModel; + } + Copy(state, currentContext.FirstState); + currentContext.Statistics = state; + if (state.Frequency < (MAXIMUM_FREQUENCY / 4) - 1) + { + state.Frequency += state.Frequency; } else { - cf = (uint)(4 + ((cf > 9 * sf) ? 1 : 0) + ((cf > 12 * sf) ? 1 : 0) + ((cf > 15 * sf) ? 1 : 0)); - currentContext.SummaryFrequency += (ushort)cf; + state.Frequency = (byte)(MAXIMUM_FREQUENCY - 4); } - - state = currentContext.Statistics + (++currentContext.NumberStatistics); - state.Successor = successor; - state.Symbol = foundStateSymbol; - state.Frequency = (byte)cf; - currentContext.Flags |= flag; + currentContext.SummaryFrequency = (ushort)( + state.Frequency + _initialEscape + ((numberStatistics > 2) ? 1 : 0) + ); } - _maximumContext = foundStateSuccessor; - return; + cf = (uint)(2 * foundStateFrequency * (currentContext.SummaryFrequency + 6)); + sf = s0 + currentContext.SummaryFrequency; - RestartModel: - RestoreModel(currentContext, minimumContext, foundStateSuccessor); + if (cf < 6 * sf) + { + cf = (uint)(1 + ((cf > sf) ? 1 : 0) + ((cf >= 4 * sf) ? 1 : 0)); + currentContext.SummaryFrequency += 4; + } + else + { + cf = (uint)( + 4 + + ((cf > 9 * sf) ? 1 : 0) + + ((cf > 12 * sf) ? 1 : 0) + + ((cf > 15 * sf) ? 1 : 0) + ); + currentContext.SummaryFrequency += (ushort)cf; + } + + state = currentContext.Statistics + (++currentContext.NumberStatistics); + state.Successor = successor; + state.Symbol = foundStateSymbol; + state.Frequency = (byte)cf; + currentContext.Flags |= flag; } - private PpmContext CreateSuccessors(bool skip, PpmState state, PpmContext context) + _maximumContext = foundStateSuccessor; + return; + + RestartModel: + RestoreModel(currentContext, minimumContext, foundStateSuccessor); + } + + private PpmContext CreateSuccessors(bool skip, PpmState state, PpmContext context) + { + var upBranch = _foundState.Successor; + var states = new PpmState[MAXIMUM_ORDER]; + uint stateIndex = 0; + var symbol = _foundState.Symbol; + + if (!skip) { - PpmContext upBranch = _foundState.Successor; - PpmState[] states = new PpmState[MAXIMUM_ORDER]; - uint stateIndex = 0; - byte symbol = _foundState.Symbol; - - if (!skip) + states[stateIndex++] = _foundState; + if (context.Suffix == PpmContext.ZERO) { - states[stateIndex++] = _foundState; - if (context.Suffix == PpmContext.ZERO) - { - goto NoLoop; - } + goto NoLoop; + } + } + + var gotoLoopEntry = false; + if (state != PpmState.ZERO) + { + context = context.Suffix; + gotoLoopEntry = true; + } + + do + { + if (gotoLoopEntry) + { + gotoLoopEntry = false; + goto LoopEntry; } - bool gotoLoopEntry = false; - if (state != PpmState.ZERO) + context = context.Suffix; + if (context.NumberStatistics != 0) { - context = context.Suffix; - gotoLoopEntry = true; - } - - do - { - if (gotoLoopEntry) + byte temporary; + state = context.Statistics; + if (state.Symbol != symbol) { - gotoLoopEntry = false; - goto LoopEntry; - } - - context = context.Suffix; - if (context.NumberStatistics != 0) - { - byte temporary; - state = context.Statistics; - if (state.Symbol != symbol) + do { - do - { - temporary = state[1].Symbol; - state++; - } - while (temporary != symbol); - } - temporary = (byte)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 1 : 0); - state.Frequency += temporary; - context.SummaryFrequency += temporary; - } - else - { - state = context.FirstState; - state.Frequency += - (byte)(((context.Suffix.NumberStatistics == 0) ? 1 : 0) & ((state.Frequency < 24) ? 1 : 0)); + temporary = state[1].Symbol; + state++; + } while (temporary != symbol); } + temporary = (byte)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 1 : 0); + state.Frequency += temporary; + context.SummaryFrequency += temporary; + } + else + { + state = context.FirstState; + state.Frequency += (byte)( + ((context.Suffix.NumberStatistics == 0) ? 1 : 0) + & ((state.Frequency < 24) ? 1 : 0) + ); + } LoopEntry: - if (state.Successor != upBranch) - { - context = state.Successor; - break; - } - states[stateIndex++] = state; + if (state.Successor != upBranch) + { + context = state.Successor; + break; } - while (context.Suffix != PpmContext.ZERO); + states[stateIndex++] = state; + } while (context.Suffix != PpmContext.ZERO); NoLoop: - if (stateIndex == 0) + if (stateIndex == 0) + { + return context; + } + + byte localNumberStatistics = 0; + var localFlags = (byte)((symbol >= 0x40) ? 0x10 : 0x00); + symbol = upBranch.NumberStatistics; + var localSymbol = symbol; + byte localFrequency; + PpmContext localSuccessor = ((Pointer)upBranch) + 1; + localFlags |= (byte)((symbol >= 0x40) ? 0x08 : 0x00); + + if (context.NumberStatistics != 0) + { + state = context.Statistics; + if (state.Symbol != symbol) { + byte temporary; + do + { + temporary = state[1].Symbol; + state++; + } while (temporary != symbol); + } + var cf = (uint)(state.Frequency - 1); + var s0 = (uint)(context.SummaryFrequency - context.NumberStatistics - cf); + localFrequency = (byte)( + 1 + ((2 * cf <= s0) ? (uint)((5 * cf > s0) ? 1 : 0) : ((cf + (2 * s0) - 3) / s0)) + ); + } + else + { + localFrequency = context.FirstStateFrequency; + } + + do + { + PpmContext currentContext = _allocator.AllocateContext(); + if (currentContext == PpmContext.ZERO) + { + return PpmContext.ZERO; + } + currentContext.NumberStatistics = localNumberStatistics; + currentContext.Flags = localFlags; + currentContext.FirstStateSymbol = localSymbol; + currentContext.FirstStateFrequency = localFrequency; + currentContext.FirstStateSuccessor = localSuccessor; + currentContext.Suffix = context; + context = currentContext; + states[--stateIndex].Successor = context; + } while (stateIndex != 0); + + return context; + } + + private PpmContext ReduceOrder(PpmState state, PpmContext context) + { + PpmState currentState; + var states = new PpmState[MAXIMUM_ORDER]; + uint stateIndex = 0; + var currentContext = context; + PpmContext upBranch = _allocator._text; + byte temporary; + var symbol = _foundState.Symbol; + + states[stateIndex++] = _foundState; + _foundState.Successor = upBranch; + _orderFall++; + + var gotoLoopEntry = false; + if (state != PpmState.ZERO) + { + context = context.Suffix; + gotoLoopEntry = true; + } + + while (true) + { + if (gotoLoopEntry) + { + gotoLoopEntry = false; + goto LoopEntry; + } + + if (context.Suffix == PpmContext.ZERO) + { + if (_method > ModelRestorationMethod.Freeze) + { + do + { + states[--stateIndex].Successor = context; + } while (stateIndex != 0); + _allocator._text = _allocator._heap + 1; + _orderFall = 1; + } return context; } - byte localNumberStatistics = 0; - byte localFlags = (byte)((symbol >= 0x40) ? 0x10 : 0x00); - symbol = upBranch.NumberStatistics; - byte localSymbol = symbol; - byte localFrequency; - PpmContext localSuccessor = ((Pointer)upBranch) + 1; - localFlags |= (byte)((symbol >= 0x40) ? 0x08 : 0x00); - + context = context.Suffix; if (context.NumberStatistics != 0) { state = context.Statistics; if (state.Symbol != symbol) { - byte temporary; do { temporary = state[1].Symbol; state++; - } - while (temporary != symbol); + } while (temporary != symbol); } - uint cf = (uint)(state.Frequency - 1); - uint s0 = (uint)(context.SummaryFrequency - context.NumberStatistics - cf); - localFrequency = (byte)(1 + ((2 * cf <= s0) ? (uint)((5 * cf > s0) ? 1 : 0) : ((cf + 2 * s0 - 3) / s0))); + temporary = (byte)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 2 : 0); + state.Frequency += temporary; + context.SummaryFrequency += temporary; } else { - localFrequency = context.FirstStateFrequency; + state = context.FirstState; + state.Frequency += (byte)((state.Frequency < 32) ? 1 : 0); + } + + LoopEntry: + if (state.Successor != PpmContext.ZERO) + { + break; + } + states[stateIndex++] = state; + state.Successor = upBranch; + _orderFall++; + } + + if (_method > ModelRestorationMethod.Freeze) + { + context = state.Successor; + do + { + states[--stateIndex].Successor = context; + } while (stateIndex != 0); + _allocator._text = _allocator._heap + 1; + _orderFall = 1; + return context; + } + if (state.Successor <= upBranch) + { + currentState = _foundState; + _foundState = state; + state.Successor = CreateSuccessors(false, PpmState.ZERO, context); + _foundState = currentState; + } + + if (_orderFall == 1 && currentContext == _maximumContext) + { + _foundState.Successor = state.Successor; + _allocator._text--; + } + + return state.Successor; + } + + private void RestoreModel( + PpmContext context, + PpmContext minimumContext, + PpmContext foundStateSuccessor + ) + { + PpmContext currentContext; + + _allocator._text = _allocator._heap; + for ( + currentContext = _maximumContext; + currentContext != context; + currentContext = currentContext.Suffix + ) + { + if (--currentContext.NumberStatistics == 0) + { + currentContext.Flags = (byte)( + (currentContext.Flags & 0x10) + + ((currentContext.Statistics.Symbol >= 0x40) ? 0x08 : 0x00) + ); + var state = currentContext.Statistics; + Copy(currentContext.FirstState, state); + _allocator.SpecialFreeUnits(state); + currentContext.FirstStateFrequency = (byte)( + (currentContext.FirstStateFrequency + 11) >> 3 + ); + } + else + { + Refresh((uint)((currentContext.NumberStatistics + 3) >> 1), false, currentContext); + } + } + + for (; currentContext != minimumContext; currentContext = currentContext.Suffix) + { + if (currentContext.NumberStatistics == 0) + { + currentContext.FirstStateFrequency -= (byte)( + currentContext.FirstStateFrequency >> 1 + ); + } + else if ( + (currentContext.SummaryFrequency += 4) > 128 + (4 * currentContext.NumberStatistics) + ) + { + Refresh((uint)((currentContext.NumberStatistics + 2) >> 1), true, currentContext); + } + } + + if (_method > ModelRestorationMethod.Freeze) + { + _maximumContext = foundStateSuccessor; + _allocator._glueCount += (uint)(((_allocator._memoryNodes[1].Stamp & 1) == 0) ? 1 : 0); + } + else if (_method == ModelRestorationMethod.Freeze) + { + while (_maximumContext.Suffix != PpmContext.ZERO) + { + _maximumContext = _maximumContext.Suffix; + } + + RemoveBinaryContexts(0, _maximumContext); + _method++; + _allocator._glueCount = 0; + _orderFall = _modelOrder; + } + else if ( + _method == ModelRestorationMethod.Restart + || _allocator.GetMemoryUsed() < (_allocator._allocatorSize >> 1) + ) + { + StartModel(_modelOrder, _method); + _escapeCount = 0; + } + else + { + while (_maximumContext.Suffix != PpmContext.ZERO) + { + _maximumContext = _maximumContext.Suffix; } do { - PpmContext currentContext = _allocator.AllocateContext(); - if (currentContext == PpmContext.ZERO) - { - return PpmContext.ZERO; - } - currentContext.NumberStatistics = localNumberStatistics; - currentContext.Flags = localFlags; - currentContext.FirstStateSymbol = localSymbol; - currentContext.FirstStateFrequency = localFrequency; - currentContext.FirstStateSuccessor = localSuccessor; - currentContext.Suffix = context; - context = currentContext; - states[--stateIndex].Successor = context; - } - while (stateIndex != 0); + CutOff(0, _maximumContext); + _allocator.ExpandText(); + } while (_allocator.GetMemoryUsed() > 3 * (_allocator._allocatorSize >> 2)); - return context; + _allocator._glueCount = 0; + _orderFall = _modelOrder; } - - private PpmContext ReduceOrder(PpmState state, PpmContext context) - { - PpmState currentState; - PpmState[] states = new PpmState[MAXIMUM_ORDER]; - uint stateIndex = 0; - PpmContext currentContext = context; - PpmContext upBranch = _allocator._text; - byte temporary; - byte symbol = _foundState.Symbol; - - states[stateIndex++] = _foundState; - _foundState.Successor = upBranch; - _orderFall++; - - bool gotoLoopEntry = false; - if (state != PpmState.ZERO) - { - context = context.Suffix; - gotoLoopEntry = true; - } - - while (true) - { - if (gotoLoopEntry) - { - gotoLoopEntry = false; - goto LoopEntry; - } - - if (context.Suffix == PpmContext.ZERO) - { - if (_method > ModelRestorationMethod.Freeze) - { - do - { - states[--stateIndex].Successor = context; - } - while (stateIndex != 0); - _allocator._text = _allocator._heap + 1; - _orderFall = 1; - } - return context; - } - - context = context.Suffix; - if (context.NumberStatistics != 0) - { - state = context.Statistics; - if (state.Symbol != symbol) - { - do - { - temporary = state[1].Symbol; - state++; - } - while (temporary != symbol); - } - temporary = (byte)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 2 : 0); - state.Frequency += temporary; - context.SummaryFrequency += temporary; - } - else - { - state = context.FirstState; - state.Frequency += (byte)((state.Frequency < 32) ? 1 : 0); - } - - LoopEntry: - if (state.Successor != PpmContext.ZERO) - { - break; - } - states[stateIndex++] = state; - state.Successor = upBranch; - _orderFall++; - } - - if (_method > ModelRestorationMethod.Freeze) - { - context = state.Successor; - do - { - states[--stateIndex].Successor = context; - } - while (stateIndex != 0); - _allocator._text = _allocator._heap + 1; - _orderFall = 1; - return context; - } - if (state.Successor <= upBranch) - { - currentState = _foundState; - _foundState = state; - state.Successor = CreateSuccessors(false, PpmState.ZERO, context); - _foundState = currentState; - } - - if (_orderFall == 1 && currentContext == _maximumContext) - { - _foundState.Successor = state.Successor; - _allocator._text--; - } - - return state.Successor; - } - - private void RestoreModel(PpmContext context, PpmContext minimumContext, PpmContext foundStateSuccessor) - { - PpmContext currentContext; - - _allocator._text = _allocator._heap; - for (currentContext = _maximumContext; currentContext != context; currentContext = currentContext.Suffix) - { - if (--currentContext.NumberStatistics == 0) - { - currentContext.Flags = - (byte) - ((currentContext.Flags & 0x10) + ((currentContext.Statistics.Symbol >= 0x40) ? 0x08 : 0x00)); - PpmState state = currentContext.Statistics; - Copy(currentContext.FirstState, state); - _allocator.SpecialFreeUnits(state); - currentContext.FirstStateFrequency = (byte)((currentContext.FirstStateFrequency + 11) >> 3); - } - else - { - Refresh((uint)((currentContext.NumberStatistics + 3) >> 1), false, currentContext); - } - } - - for (; currentContext != minimumContext; currentContext = currentContext.Suffix) - { - if (currentContext.NumberStatistics == 0) - { - currentContext.FirstStateFrequency -= (byte)(currentContext.FirstStateFrequency >> 1); - } - else if ((currentContext.SummaryFrequency += 4) > 128 + 4 * currentContext.NumberStatistics) - { - Refresh((uint)((currentContext.NumberStatistics + 2) >> 1), true, currentContext); - } - } - - if (_method > ModelRestorationMethod.Freeze) - { - _maximumContext = foundStateSuccessor; - _allocator._glueCount += (uint)(((_allocator._memoryNodes[1].Stamp & 1) == 0) ? 1 : 0); - } - else if (_method == ModelRestorationMethod.Freeze) - { - while (_maximumContext.Suffix != PpmContext.ZERO) - { - _maximumContext = _maximumContext.Suffix; - } - - RemoveBinaryContexts(0, _maximumContext); - _method = _method + 1; - _allocator._glueCount = 0; - _orderFall = _modelOrder; - } - else if (_method == ModelRestorationMethod.Restart || - _allocator.GetMemoryUsed() < (_allocator._allocatorSize >> 1)) - { - StartModel(_modelOrder, _method); - _escapeCount = 0; - } - else - { - while (_maximumContext.Suffix != PpmContext.ZERO) - { - _maximumContext = _maximumContext.Suffix; - } - - do - { - CutOff(0, _maximumContext); - _allocator.ExpandText(); - } - while (_allocator.GetMemoryUsed() > 3 * (_allocator._allocatorSize >> 2)); - - _allocator._glueCount = 0; - _orderFall = _modelOrder; - } - } - - private static void Swap(PpmState state1, PpmState state2) - { - byte swapSymbol = state1.Symbol; - byte swapFrequency = state1.Frequency; - PpmContext swapSuccessor = state1.Successor; - - state1.Symbol = state2.Symbol; - state1.Frequency = state2.Frequency; - state1.Successor = state2.Successor; - - state2.Symbol = swapSymbol; - state2.Frequency = swapFrequency; - state2.Successor = swapSuccessor; - } - - private static void Copy(PpmState state1, PpmState state2) - { - state1.Symbol = state2.Symbol; - state1.Frequency = state2.Frequency; - state1.Successor = state2.Successor; - } - - private static int Mean(int sum, int shift, int round) - { - return (sum + (1 << (shift - round))) >> shift; - } - - private void ClearMask() - { - _escapeCount = 1; - Array.Clear(_characterMask, 0, _characterMask.Length); - } - - #endregion } -} \ No newline at end of file + + private static void Swap(PpmState state1, PpmState state2) + { + var swapSymbol = state1.Symbol; + var swapFrequency = state1.Frequency; + var swapSuccessor = state1.Successor; + + state1.Symbol = state2.Symbol; + state1.Frequency = state2.Frequency; + state1.Successor = state2.Successor; + + state2.Symbol = swapSymbol; + state2.Frequency = swapFrequency; + state2.Successor = swapSuccessor; + } + + private static void Copy(PpmState state1, PpmState state2) + { + state1.Symbol = state2.Symbol; + state1.Frequency = state2.Frequency; + state1.Successor = state2.Successor; + } + + private static int Mean(int sum, int shift, int round) => + (sum + (1 << (shift - round))) >> shift; + + private void ClearMask() + { + _escapeCount = 1; + Array.Clear(_characterMask, 0, _characterMask.Length); + } + + #endregion +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/ModelRestorationMethod.cs b/src/SharpCompress/Compressors/PPMd/I1/ModelRestorationMethod.cs index 720e9d99..0a504c99 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/ModelRestorationMethod.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/ModelRestorationMethod.cs @@ -4,26 +4,25 @@ #endregion -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// The method used to adjust the model when the memory limit is reached. +/// +internal enum ModelRestorationMethod { /// - /// The method used to adjust the model when the memory limit is reached. + /// Restart the model from scratch (this is the default). /// - internal enum ModelRestorationMethod - { - /// - /// Restart the model from scratch (this is the default). - /// - Restart = 0, + Restart = 0, - /// - /// Cut off the model (nearly twice as slow). - /// - CutOff = 1, + /// + /// Cut off the model (nearly twice as slow). + /// + CutOff = 1, - /// - /// Freeze the context tree (in some cases may result in poor compression). - /// - Freeze = 2 - } -} \ No newline at end of file + /// + /// Freeze the context tree (in some cases may result in poor compression). + /// + Freeze = 2 +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/Pointer.cs b/src/SharpCompress/Compressors/PPMd/I1/Pointer.cs index b17343e7..72c557ba 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/Pointer.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/Pointer.cs @@ -2,362 +2,357 @@ using System; -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// A structure containing a single address representing a position in the array. This +/// is intended to mimic the behaviour of a pointer in C/C++. +/// +/// +/// +/// This must be a structure rather than a class because several places in the associated code assume that +/// is a value type (meaning that assignment creates a completely new copy of the +/// instance rather than just copying a reference to the same instance). +/// +/// +/// Note that is a field rather than a property for performance reasons. +/// +/// +internal struct Pointer { + public uint _address; + public byte[] _memory; + public static readonly Pointer ZERO = new Pointer(0, null); + public const int SIZE = 1; + /// - /// A structure containing a single address representing a position in the array. This - /// is intended to mimic the behaviour of a pointer in C/C++. + /// Initializes a new instance of the structure. /// - /// - /// - /// This must be a structure rather than a class because several places in the associated code assume that - /// is a value type (meaning that assignment creates a completely new copy of the - /// instance rather than just copying a reference to the same instance). - /// - /// - /// Note that is a field rather than a property for performance reasons. - /// - /// - internal struct Pointer + public Pointer(uint address, byte[] memory) { - public uint _address; - public byte[] _memory; - public static readonly Pointer ZERO = new Pointer(0, null); - public const int SIZE = 1; + _address = address; + _memory = memory; + } - /// - /// Initializes a new instance of the structure. - /// - public Pointer(uint address, byte[] memory) - { - _address = address; - _memory = memory; - } - - /// - /// Gets or sets the byte at the given . - /// - /// - /// - public byte this[int offset] - { - get - { -#if DEBUG - if (_address == 0) - { - throw new InvalidOperationException("The pointer being indexed is a null pointer."); - } -#endif - return _memory[_address + offset]; - } - set - { -#if DEBUG - if (_address == 0) - { - throw new InvalidOperationException("The pointer being indexed is a null pointer."); - } -#endif - _memory[_address + offset] = value; - } - } - - /// - /// Allow a to be implicitly converted to a . - /// - /// - /// - public static implicit operator Pointer(MemoryNode memoryNode) - { - return new Pointer(memoryNode._address, memoryNode._memory); - } - - /// - /// Allow a to be implicitly converted to a . - /// - /// - /// - public static implicit operator Pointer(Model.PpmContext context) - { - return new Pointer(context._address, context._memory); - } - - /// - /// Allow a to be implicitly converted to a . - /// - /// - /// - public static implicit operator Pointer(PpmState state) - { - return new Pointer(state._address, state._memory); - } - - /// - /// Increase the address of a pointer by the given number of bytes. - /// - /// - /// - /// - public static Pointer operator +(Pointer pointer, int offset) + /// + /// Gets or sets the byte at the given . + /// + /// + /// + public byte this[int offset] + { + get { #if DEBUG - if (pointer._address == 0) + if (_address == 0) { - throw new InvalidOperationException("The pointer is a null pointer."); + throw new InvalidOperationException("The pointer being indexed is a null pointer."); } #endif - pointer._address = (uint)(pointer._address + offset); - return pointer; + return _memory[_address + offset]; } - - /// - /// Increase the address of a pointer by the given number of bytes. - /// - /// - /// - /// - public static Pointer operator +(Pointer pointer, uint offset) + set { #if DEBUG - if (pointer._address == 0) + if (_address == 0) { - throw new InvalidOperationException("The pointer is a null pointer."); + throw new InvalidOperationException("The pointer being indexed is a null pointer."); } #endif - pointer._address += offset; - return pointer; - } - - /// - /// Increment the address of a pointer. - /// - /// - /// - public static Pointer operator ++(Pointer pointer) - { -#if DEBUG - if (pointer._address == 0) - { - throw new InvalidOperationException("The pointer being incremented is a null pointer."); - } -#endif - pointer._address++; - return pointer; - } - - /// - /// Decrease the address of a pointer by the given number of bytes. - /// - /// - /// - /// - public static Pointer operator -(Pointer pointer, int offset) - { -#if DEBUG - if (pointer._address == 0) - { - throw new InvalidOperationException("The pointer is a null pointer."); - } -#endif - pointer._address = (uint)(pointer._address - offset); - return pointer; - } - - /// - /// Decrease the address of a pointer by the given number of bytes. - /// - /// - /// - /// - public static Pointer operator -(Pointer pointer, uint offset) - { -#if DEBUG - if (pointer._address == 0) - { - throw new InvalidOperationException("The pointer is a null pointer."); - } -#endif - pointer._address -= offset; - return pointer; - } - - /// - /// Decrement the address of a pointer. - /// - /// - /// - public static Pointer operator --(Pointer pointer) - { -#if DEBUG - if (pointer._address == 0) - { - throw new InvalidOperationException("The pointer being decremented is a null pointer."); - } -#endif - pointer._address--; - return pointer; - } - - /// - /// Subtract two pointers. - /// - /// - /// - /// The number of bytes between the two pointers. - public static uint operator -(Pointer pointer1, Pointer pointer2) - { -#if DEBUG - if (pointer1._address == 0) - { - throw new InvalidOperationException( - "The pointer to the left of the subtraction operator is a null pointer."); - } - if (pointer2._address == 0) - { - throw new InvalidOperationException( - "The pointer to the right of the subtraction operator is a null pointer."); - } -#endif - return pointer1._address - pointer2._address; - } - - /// - /// Compare pointers. - /// - /// - /// - /// - public static bool operator <(Pointer pointer1, Pointer pointer2) - { -#if DEBUG - if (pointer1._address == 0) - { - throw new InvalidOperationException( - "The pointer to the left of the less than operator is a null pointer."); - } - if (pointer2._address == 0) - { - throw new InvalidOperationException( - "The pointer to the right of the less than operator is a null pointer."); - } -#endif - return pointer1._address < pointer2._address; - } - - /// - /// Compare two pointers. - /// - /// - /// - /// - public static bool operator <=(Pointer pointer1, Pointer pointer2) - { -#if DEBUG - if (pointer1._address == 0) - { - throw new InvalidOperationException( - "The pointer to the left of the less than or equal to operator is a null pointer."); - } - if (pointer2._address == 0) - { - throw new InvalidOperationException( - "The pointer to the right of the less than or equal to operator is a null pointer."); - } -#endif - return pointer1._address <= pointer2._address; - } - - /// - /// Compare two pointers. - /// - /// - /// - /// - public static bool operator >(Pointer pointer1, Pointer pointer2) - { -#if DEBUG - if (pointer1._address == 0) - { - throw new InvalidOperationException( - "The pointer to the left of the greater than operator is a null pointer."); - } - if (pointer2._address == 0) - { - throw new InvalidOperationException( - "The pointer to the right of the greater than operator is a null pointer."); - } -#endif - return pointer1._address > pointer2._address; - } - - /// - /// Compare two pointers. - /// - /// - /// - /// - public static bool operator >=(Pointer pointer1, Pointer pointer2) - { -#if DEBUG - if (pointer1._address == 0) - { - throw new InvalidOperationException( - "The pointer to the left of the greater than or equal to operator is a null pointer."); - } - if (pointer2._address == 0) - { - throw new InvalidOperationException( - "The pointer to the right of the greater than or equal to operator is a null pointer."); - } -#endif - return pointer1._address >= pointer2._address; - } - - /// - /// Compare two pointers. - /// - /// - /// - /// - public static bool operator ==(Pointer pointer1, Pointer pointer2) - { - return pointer1._address == pointer2._address; - } - - /// - /// Compare two pointers. - /// - /// - /// - /// - public static bool operator !=(Pointer pointer1, Pointer pointer2) - { - return pointer1._address != pointer2._address; - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// true if obj and this instance are the same type and represent the same value; otherwise, false. - /// Another object to compare to. - public override bool Equals(object obj) - { - if (obj is Pointer) - { - Pointer pointer = (Pointer)obj; - return pointer._address == _address; - } - return base.Equals(obj); - } - - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return _address.GetHashCode(); + _memory[_address + offset] = value; } } -} \ No newline at end of file + + /// + /// Allow a to be implicitly converted to a . + /// + /// + /// + public static implicit operator Pointer(MemoryNode memoryNode) => + new Pointer(memoryNode._address, memoryNode._memory); + + /// + /// Allow a to be implicitly converted to a . + /// + /// + /// + public static implicit operator Pointer(Model.PpmContext context) => + new Pointer(context._address, context._memory); + + /// + /// Allow a to be implicitly converted to a . + /// + /// + /// + public static implicit operator Pointer(PpmState state) => + new Pointer(state._address, state._memory); + + /// + /// Increase the address of a pointer by the given number of bytes. + /// + /// + /// + /// + public static Pointer operator +(Pointer pointer, int offset) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer is a null pointer."); + } +#endif + pointer._address = (uint)(pointer._address + offset); + return pointer; + } + + /// + /// Increase the address of a pointer by the given number of bytes. + /// + /// + /// + /// + public static Pointer operator +(Pointer pointer, uint offset) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer is a null pointer."); + } +#endif + pointer._address += offset; + return pointer; + } + + /// + /// Increment the address of a pointer. + /// + /// + /// + public static Pointer operator ++(Pointer pointer) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer being incremented is a null pointer."); + } +#endif + pointer._address++; + return pointer; + } + + /// + /// Decrease the address of a pointer by the given number of bytes. + /// + /// + /// + /// + public static Pointer operator -(Pointer pointer, int offset) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer is a null pointer."); + } +#endif + pointer._address = (uint)(pointer._address - offset); + return pointer; + } + + /// + /// Decrease the address of a pointer by the given number of bytes. + /// + /// + /// + /// + public static Pointer operator -(Pointer pointer, uint offset) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer is a null pointer."); + } +#endif + pointer._address -= offset; + return pointer; + } + + /// + /// Decrement the address of a pointer. + /// + /// + /// + public static Pointer operator --(Pointer pointer) + { +#if DEBUG + if (pointer._address == 0) + { + throw new InvalidOperationException("The pointer being decremented is a null pointer."); + } +#endif + pointer._address--; + return pointer; + } + + /// + /// Subtract two pointers. + /// + /// + /// + /// The number of bytes between the two pointers. + public static uint operator -(Pointer pointer1, Pointer pointer2) + { +#if DEBUG + if (pointer1._address == 0) + { + throw new InvalidOperationException( + "The pointer to the left of the subtraction operator is a null pointer." + ); + } + if (pointer2._address == 0) + { + throw new InvalidOperationException( + "The pointer to the right of the subtraction operator is a null pointer." + ); + } +#endif + return pointer1._address - pointer2._address; + } + + /// + /// Compare pointers. + /// + /// + /// + /// + public static bool operator <(Pointer pointer1, Pointer pointer2) + { +#if DEBUG + if (pointer1._address == 0) + { + throw new InvalidOperationException( + "The pointer to the left of the less than operator is a null pointer." + ); + } + if (pointer2._address == 0) + { + throw new InvalidOperationException( + "The pointer to the right of the less than operator is a null pointer." + ); + } +#endif + return pointer1._address < pointer2._address; + } + + /// + /// Compare two pointers. + /// + /// + /// + /// + public static bool operator <=(Pointer pointer1, Pointer pointer2) + { +#if DEBUG + if (pointer1._address == 0) + { + throw new InvalidOperationException( + "The pointer to the left of the less than or equal to operator is a null pointer." + ); + } + if (pointer2._address == 0) + { + throw new InvalidOperationException( + "The pointer to the right of the less than or equal to operator is a null pointer." + ); + } +#endif + return pointer1._address <= pointer2._address; + } + + /// + /// Compare two pointers. + /// + /// + /// + /// + public static bool operator >(Pointer pointer1, Pointer pointer2) + { +#if DEBUG + if (pointer1._address == 0) + { + throw new InvalidOperationException( + "The pointer to the left of the greater than operator is a null pointer." + ); + } + if (pointer2._address == 0) + { + throw new InvalidOperationException( + "The pointer to the right of the greater than operator is a null pointer." + ); + } +#endif + return pointer1._address > pointer2._address; + } + + /// + /// Compare two pointers. + /// + /// + /// + /// + public static bool operator >=(Pointer pointer1, Pointer pointer2) + { +#if DEBUG + if (pointer1._address == 0) + { + throw new InvalidOperationException( + "The pointer to the left of the greater than or equal to operator is a null pointer." + ); + } + if (pointer2._address == 0) + { + throw new InvalidOperationException( + "The pointer to the right of the greater than or equal to operator is a null pointer." + ); + } +#endif + return pointer1._address >= pointer2._address; + } + + /// + /// Compare two pointers. + /// + /// + /// + /// + public static bool operator ==(Pointer pointer1, Pointer pointer2) => + pointer1._address == pointer2._address; + + /// + /// Compare two pointers. + /// + /// + /// + /// + public static bool operator !=(Pointer pointer1, Pointer pointer2) => + pointer1._address != pointer2._address; + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// true if obj and this instance are the same type and represent the same value; otherwise, false. + /// Another object to compare to. + public override bool Equals(object obj) + { + if (obj is Pointer pointer) + { + return pointer._address == _address; + } + return base.Equals(obj); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => _address.GetHashCode(); +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/PpmContext.cs b/src/SharpCompress/Compressors/PPMd/I1/PpmContext.cs index f75cbd3a..a37d278d 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/PpmContext.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/PpmContext.cs @@ -1,747 +1,751 @@ #nullable disable -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// The PPM context structure. This is tightly coupled with . +/// +/// +/// +/// This must be a structure rather than a class because several places in the associated code assume that +/// is a value type (meaning that assignment creates a completely new copy of +/// the instance rather than just copying a reference to the same instance). +/// +/// +internal partial class Model { /// - /// The PPM context structure. This is tightly coupled with . + /// The structure which represents the current PPM context. This is 12 bytes in size. /// - /// - /// - /// This must be a structure rather than a class because several places in the associated code assume that - /// is a value type (meaning that assignment creates a completely new copy of - /// the instance rather than just copying a reference to the same instance). - /// - /// - internal partial class Model + internal struct PpmContext { + public uint _address; + public byte[] _memory; + public static readonly PpmContext ZERO = new PpmContext(0, null); + public const int SIZE = 12; + /// - /// The structure which represents the current PPM context. This is 12 bytes in size. + /// Initializes a new instance of the structure. /// - internal struct PpmContext + public PpmContext(uint address, byte[] memory) { - public uint _address; - public byte[] _memory; - public static readonly PpmContext ZERO = new PpmContext(0, null); - public const int SIZE = 12; + _address = address; + _memory = memory; + } - /// - /// Initializes a new instance of the structure. - /// - public PpmContext(uint address, byte[] memory) + /// + /// Gets or sets the number statistics. + /// + public byte NumberStatistics + { + get => _memory[_address]; + set => _memory[_address] = value; + } + + /// + /// Gets or sets the flags. + /// + public byte Flags + { + get => _memory[_address + 1]; + set => _memory[_address + 1] = value; + } + + /// + /// Gets or sets the summary frequency. + /// + public ushort SummaryFrequency + { + get => (ushort)(_memory[_address + 2] | (_memory[_address + 3] << 8)); + set { - _address = address; - _memory = memory; - } - - /// - /// Gets or sets the number statistics. - /// - public byte NumberStatistics { get => _memory[_address]; set => _memory[_address] = value; } - - /// - /// Gets or sets the flags. - /// - public byte Flags { get => _memory[_address + 1]; set => _memory[_address + 1] = value; } - - /// - /// Gets or sets the summary frequency. - /// - public ushort SummaryFrequency - { - get => (ushort)(_memory[_address + 2] | _memory[_address + 3] << 8); - set - { - _memory[_address + 2] = (byte)value; - _memory[_address + 3] = (byte)(value >> 8); - } - } - - /// - /// Gets or sets the statistics. - /// - public PpmState Statistics - { - get => new PpmState( - _memory[_address + 4] | ((uint)_memory[_address + 5]) << 8 | - ((uint)_memory[_address + 6]) << 16 | ((uint)_memory[_address + 7]) << 24, _memory); - set - { - _memory[_address + 4] = (byte)value._address; - _memory[_address + 5] = (byte)(value._address >> 8); - _memory[_address + 6] = (byte)(value._address >> 16); - _memory[_address + 7] = (byte)(value._address >> 24); - } - } - - /// - /// Gets or sets the suffix. - /// - public PpmContext Suffix - { - get => new PpmContext( - _memory[_address + 8] | ((uint)_memory[_address + 9]) << 8 | - ((uint)_memory[_address + 10]) << 16 | ((uint)_memory[_address + 11]) << 24, _memory); - set - { - _memory[_address + 8] = (byte)value._address; - _memory[_address + 9] = (byte)(value._address >> 8); - _memory[_address + 10] = (byte)(value._address >> 16); - _memory[_address + 11] = (byte)(value._address >> 24); - } - } - - /// - /// The first PPM state associated with the PPM context. - /// - /// - /// - /// The first PPM state overlaps this PPM context instance (the context.SummaryFrequency and context.Statistics members - /// of PpmContext use 6 bytes and so can therefore fit into the space used by the Symbol, Frequency and - /// Successor members of PpmState, since they also add up to 6 bytes). - /// - /// - /// PpmContext (context.SummaryFrequency and context.Statistics use 6 bytes) - /// 1 context.NumberStatistics - /// 1 context.Flags - /// 2 context.SummaryFrequency - /// 4 context.Statistics (pointer to PpmState) - /// 4 context.Suffix (pointer to PpmContext) - /// - /// - /// PpmState (total of 6 bytes) - /// 1 Symbol - /// 1 Frequency - /// 4 Successor (pointer to PpmContext) - /// - /// - /// - public PpmState FirstState => new PpmState(_address + 2, _memory); - - /// - /// Gets or sets the symbol of the first PPM state. This is provided for convenience. The same - /// information can be obtained using the Symbol property on the PPM state provided by the - /// property. - /// - public byte FirstStateSymbol { get => _memory[_address + 2]; set => _memory[_address + 2] = value; } - - /// - /// Gets or sets the frequency of the first PPM state. This is provided for convenience. The same - /// information can be obtained using the Frequency property on the PPM state provided by the - ///context.FirstState property. - /// - public byte FirstStateFrequency { get => _memory[_address + 3]; set => _memory[_address + 3] = value; } - - /// - /// Gets or sets the successor of the first PPM state. This is provided for convenience. The same - /// information can be obtained using the Successor property on the PPM state provided by the - /// - public PpmContext FirstStateSuccessor - { - get => new PpmContext( - _memory[_address + 4] | ((uint)_memory[_address + 5]) << 8 | - ((uint)_memory[_address + 6]) << 16 | ((uint)_memory[_address + 7]) << 24, _memory); - set - { - _memory[_address + 4] = (byte)value._address; - _memory[_address + 5] = (byte)(value._address >> 8); - _memory[_address + 6] = (byte)(value._address >> 16); - _memory[_address + 7] = (byte)(value._address >> 24); - } - } - - /// - /// Allow a pointer to be implicitly converted to a PPM context. - /// - /// - /// - public static implicit operator PpmContext(Pointer pointer) - { - return new PpmContext(pointer._address, pointer._memory); - } - - /// - /// Allow pointer-like addition on a PPM context. - /// - /// - /// - /// - public static PpmContext operator +(PpmContext context, int offset) - { - context._address = (uint)(context._address + offset * SIZE); - return context; - } - - /// - /// Allow pointer-like subtraction on a PPM context. - /// - /// - /// - /// - public static PpmContext operator -(PpmContext context, int offset) - { - context._address = (uint)(context._address - offset * SIZE); - return context; - } - - /// - /// Compare two PPM contexts. - /// - /// - /// - /// - public static bool operator <=(PpmContext context1, PpmContext context2) - { - return context1._address <= context2._address; - } - - /// - /// Compare two PPM contexts. - /// - /// - /// - /// - public static bool operator >=(PpmContext context1, PpmContext context2) - { - return context1._address >= context2._address; - } - - /// - /// Compare two PPM contexts. - /// - /// - /// - /// - public static bool operator ==(PpmContext context1, PpmContext context2) - { - return context1._address == context2._address; - } - - /// - /// Compare two PPM contexts. - /// - /// - /// - /// - public static bool operator !=(PpmContext context1, PpmContext context2) - { - return context1._address != context2._address; - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// true if obj and this instance are the same type and represent the same value; otherwise, false. - /// Another object to compare to. - public override bool Equals(object obj) - { - if (obj is PpmContext) - { - PpmContext context = (PpmContext)obj; - return context._address == _address; - } - return base.Equals(obj); - } - - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return _address.GetHashCode(); + _memory[_address + 2] = (byte)value; + _memory[_address + 3] = (byte)(value >> 8); } } - private void EncodeBinarySymbol(int symbol, PpmContext context) + /// + /// Gets or sets the statistics. + /// + public PpmState Statistics { - PpmState state = context.FirstState; - int index1 = _probabilities[state.Frequency - 1]; - int index2 = _numberStatisticsToBinarySummaryIndex[context.Suffix.NumberStatistics] + _previousSuccess + - context.Flags + ((_runLength >> 26) & 0x20); - - if (state.Symbol == symbol) + get => + new PpmState( + _memory[_address + 4] + | (((uint)_memory[_address + 5]) << 8) + | (((uint)_memory[_address + 6]) << 16) + | (((uint)_memory[_address + 7]) << 24), + _memory + ); + set { - _foundState = state; - state.Frequency += (byte)((state.Frequency < 196) ? 1 : 0); - _coder._lowCount = 0; - _coder._highCount = _binarySummary[index1, index2]; - _binarySummary[index1, index2] += - (ushort)(INTERVAL - Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2)); - _previousSuccess = 1; - _runLength++; - } - else - { - _coder._lowCount = _binarySummary[index1, index2]; - _binarySummary[index1, index2] -= (ushort)Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2); - _coder._highCount = BINARY_SCALE; - _initialEscape = EXPONENTIAL_ESCAPES[_binarySummary[index1, index2] >> 10]; - _characterMask[state.Symbol] = _escapeCount; - _previousSuccess = 0; - _numberMasked = 0; - _foundState = PpmState.ZERO; + _memory[_address + 4] = (byte)value._address; + _memory[_address + 5] = (byte)(value._address >> 8); + _memory[_address + 6] = (byte)(value._address >> 16); + _memory[_address + 7] = (byte)(value._address >> 24); } } - private void EncodeSymbol1(int symbol, PpmContext context) + /// + /// Gets or sets the suffix. + /// + public PpmContext Suffix { - uint lowCount; - uint index = context.Statistics.Symbol; - PpmState state = context.Statistics; - _coder._scale = context.SummaryFrequency; - if (index == symbol) + get => + new PpmContext( + _memory[_address + 8] + | (((uint)_memory[_address + 9]) << 8) + | (((uint)_memory[_address + 10]) << 16) + | (((uint)_memory[_address + 11]) << 24), + _memory + ); + set { - _coder._highCount = state.Frequency; - _previousSuccess = (byte)((2 * _coder._highCount >= _coder._scale) ? 1 : 0); - _foundState = state; - _foundState.Frequency += 4; - context.SummaryFrequency += 4; - _runLength += _previousSuccess; - if (state.Frequency > MAXIMUM_FREQUENCY) - { - Rescale(context); - } - _coder._lowCount = 0; - return; + _memory[_address + 8] = (byte)value._address; + _memory[_address + 9] = (byte)(value._address >> 8); + _memory[_address + 10] = (byte)(value._address >> 16); + _memory[_address + 11] = (byte)(value._address >> 24); } + } - lowCount = state.Frequency; - index = context.NumberStatistics; + /// + /// The first PPM state associated with the PPM context. + /// + /// + /// + /// The first PPM state overlaps this PPM context instance (the context.SummaryFrequency and context.Statistics members + /// of PpmContext use 6 bytes and so can therefore fit into the space used by the Symbol, Frequency and + /// Successor members of PpmState, since they also add up to 6 bytes). + /// + /// + /// PpmContext (context.SummaryFrequency and context.Statistics use 6 bytes) + /// 1 context.NumberStatistics + /// 1 context.Flags + /// 2 context.SummaryFrequency + /// 4 context.Statistics (pointer to PpmState) + /// 4 context.Suffix (pointer to PpmContext) + /// + /// + /// PpmState (total of 6 bytes) + /// 1 Symbol + /// 1 Frequency + /// 4 Successor (pointer to PpmContext) + /// + /// + /// + public PpmState FirstState => new PpmState(_address + 2, _memory); + + /// + /// Gets or sets the symbol of the first PPM state. This is provided for convenience. The same + /// information can be obtained using the Symbol property on the PPM state provided by the + /// property. + /// + public byte FirstStateSymbol + { + get => _memory[_address + 2]; + set => _memory[_address + 2] = value; + } + + /// + /// Gets or sets the frequency of the first PPM state. This is provided for convenience. The same + /// information can be obtained using the Frequency property on the PPM state provided by the + ///context.FirstState property. + /// + public byte FirstStateFrequency + { + get => _memory[_address + 3]; + set => _memory[_address + 3] = value; + } + + /// + /// Gets or sets the successor of the first PPM state. This is provided for convenience. The same + /// information can be obtained using the Successor property on the PPM state provided by the + /// + public PpmContext FirstStateSuccessor + { + get => + new PpmContext( + _memory[_address + 4] + | (((uint)_memory[_address + 5]) << 8) + | (((uint)_memory[_address + 6]) << 16) + | (((uint)_memory[_address + 7]) << 24), + _memory + ); + set + { + _memory[_address + 4] = (byte)value._address; + _memory[_address + 5] = (byte)(value._address >> 8); + _memory[_address + 6] = (byte)(value._address >> 16); + _memory[_address + 7] = (byte)(value._address >> 24); + } + } + + /// + /// Allow a pointer to be implicitly converted to a PPM context. + /// + /// + /// + public static implicit operator PpmContext(Pointer pointer) => + new PpmContext(pointer._address, pointer._memory); + + /// + /// Allow pointer-like addition on a PPM context. + /// + /// + /// + /// + public static PpmContext operator +(PpmContext context, int offset) + { + context._address = (uint)(context._address + (offset * SIZE)); + return context; + } + + /// + /// Allow pointer-like subtraction on a PPM context. + /// + /// + /// + /// + public static PpmContext operator -(PpmContext context, int offset) + { + context._address = (uint)(context._address - (offset * SIZE)); + return context; + } + + /// + /// Compare two PPM contexts. + /// + /// + /// + /// + public static bool operator <=(PpmContext context1, PpmContext context2) => + context1._address <= context2._address; + + /// + /// Compare two PPM contexts. + /// + /// + /// + /// + public static bool operator >=(PpmContext context1, PpmContext context2) => + context1._address >= context2._address; + + /// + /// Compare two PPM contexts. + /// + /// + /// + /// + public static bool operator ==(PpmContext context1, PpmContext context2) => + context1._address == context2._address; + + /// + /// Compare two PPM contexts. + /// + /// + /// + /// + public static bool operator !=(PpmContext context1, PpmContext context2) => + context1._address != context2._address; + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// true if obj and this instance are the same type and represent the same value; otherwise, false. + /// Another object to compare to. + public override bool Equals(object obj) + { + if (obj is PpmContext context) + { + return context._address == _address; + } + return base.Equals(obj); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => _address.GetHashCode(); + } + + private void EncodeBinarySymbol(int symbol, PpmContext context) + { + var state = context.FirstState; + int index1 = _probabilities[state.Frequency - 1]; + var index2 = + _numberStatisticsToBinarySummaryIndex[context.Suffix.NumberStatistics] + + _previousSuccess + + context.Flags + + ((_runLength >> 26) & 0x20); + + if (state.Symbol == symbol) + { + _foundState = state; + state.Frequency += (byte)((state.Frequency < 196) ? 1 : 0); + _coder._lowCount = 0; + _coder._highCount = _binarySummary[index1, index2]; + _binarySummary[index1, index2] += (ushort)( + INTERVAL - Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2) + ); + _previousSuccess = 1; + _runLength++; + } + else + { + _coder._lowCount = _binarySummary[index1, index2]; + _binarySummary[index1, index2] -= (ushort)Mean( + _binarySummary[index1, index2], + PERIOD_BIT_COUNT, + 2 + ); + _coder._highCount = BINARY_SCALE; + _initialEscape = EXPONENTIAL_ESCAPES[_binarySummary[index1, index2] >> 10]; + _characterMask[state.Symbol] = _escapeCount; _previousSuccess = 0; - while ((++state).Symbol != symbol) - { - lowCount += state.Frequency; - if (--index == 0) - { - _coder._lowCount = lowCount; - _characterMask[state.Symbol] = _escapeCount; - _numberMasked = context.NumberStatistics; - index = context.NumberStatistics; - _foundState = PpmState.ZERO; - do - { - _characterMask[(--state).Symbol] = _escapeCount; - } - while (--index != 0); - _coder._highCount = _coder._scale; - return; - } - } - _coder._highCount = (_coder._lowCount = lowCount) + state.Frequency; - Update1(state, context); + _numberMasked = 0; + _foundState = PpmState.ZERO; } + } - private void EncodeSymbol2(int symbol, PpmContext context) - { - See2Context see2Context = MakeEscapeFrequency(context); - uint currentSymbol; - uint lowCount = 0; - uint index = (uint)(context.NumberStatistics - _numberMasked); - PpmState state = context.Statistics - 1; - - do - { - do - { - currentSymbol = state[1].Symbol; - state++; - } - while (_characterMask[currentSymbol] == _escapeCount); - _characterMask[currentSymbol] = _escapeCount; - if (currentSymbol == symbol) - { - goto SymbolFound; - } - lowCount += state.Frequency; - } - while (--index != 0); - - _coder._lowCount = lowCount; - _coder._scale += _coder._lowCount; - _coder._highCount = _coder._scale; - see2Context._summary += (ushort)_coder._scale; - _numberMasked = context.NumberStatistics; - return; - - SymbolFound: - _coder._lowCount = lowCount; - lowCount += state.Frequency; - _coder._highCount = lowCount; - for (PpmState p1 = state; --index != 0;) - { - do - { - currentSymbol = p1[1].Symbol; - p1++; - } - while (_characterMask[currentSymbol] == _escapeCount); - lowCount += p1.Frequency; - } - _coder._scale += lowCount; - see2Context.Update(); - Update2(state, context); - } - - private void DecodeBinarySymbol(PpmContext context) - { - PpmState state = context.FirstState; - int index1 = _probabilities[state.Frequency - 1]; - int index2 = _numberStatisticsToBinarySummaryIndex[context.Suffix.NumberStatistics] + _previousSuccess + - context.Flags + ((_runLength >> 26) & 0x20); - - if (_coder.RangeGetCurrentShiftCount(TOTAL_BIT_COUNT) < _binarySummary[index1, index2]) - { - _foundState = state; - state.Frequency += (byte)((state.Frequency < 196) ? 1 : 0); - _coder._lowCount = 0; - _coder._highCount = _binarySummary[index1, index2]; - _binarySummary[index1, index2] += - (ushort)(INTERVAL - Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2)); - _previousSuccess = 1; - _runLength++; - } - else - { - _coder._lowCount = _binarySummary[index1, index2]; - _binarySummary[index1, index2] -= (ushort)Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2); - _coder._highCount = BINARY_SCALE; - _initialEscape = EXPONENTIAL_ESCAPES[_binarySummary[index1, index2] >> 10]; - _characterMask[state.Symbol] = _escapeCount; - _previousSuccess = 0; - _numberMasked = 0; - _foundState = PpmState.ZERO; - } - } - - private void DecodeSymbol1(PpmContext context) - { - uint index; - uint count; - uint highCount = context.Statistics.Frequency; - PpmState state = context.Statistics; - _coder._scale = context.SummaryFrequency; - - count = _coder.RangeGetCurrentCount(); - if (count < highCount) - { - _coder._highCount = highCount; - _previousSuccess = (byte)((2 * _coder._highCount >= _coder._scale) ? 1 : 0); - _foundState = state; - highCount += 4; - _foundState.Frequency = (byte)highCount; - context.SummaryFrequency += 4; - _runLength += _previousSuccess; - if (highCount > MAXIMUM_FREQUENCY) - { - Rescale(context); - } - _coder._lowCount = 0; - return; - } - - index = context.NumberStatistics; - _previousSuccess = 0; - while ((highCount += (++state).Frequency) <= count) - { - if (--index == 0) - { - _coder._lowCount = highCount; - _characterMask[state.Symbol] = _escapeCount; - _numberMasked = context.NumberStatistics; - index = context.NumberStatistics; - _foundState = PpmState.ZERO; - do - { - _characterMask[(--state).Symbol] = _escapeCount; - } - while (--index != 0); - _coder._highCount = _coder._scale; - return; - } - } - _coder._highCount = highCount; - _coder._lowCount = _coder._highCount - state.Frequency; - Update1(state, context); - } - - private void DecodeSymbol2(PpmContext context) - { - See2Context see2Context = MakeEscapeFrequency(context); - uint currentSymbol; - uint count; - uint highCount = 0; - uint index = (uint)(context.NumberStatistics - _numberMasked); - uint stateIndex = 0; - PpmState state = context.Statistics - 1; - - do - { - do - { - currentSymbol = state[1].Symbol; - state++; - } - while (_characterMask[currentSymbol] == _escapeCount); - highCount += state.Frequency; - _decodeStates[stateIndex++] = state; - - // note that decodeStates is a static array that is re-used on each call to this method (for performance reasons) - } - while (--index != 0); - - _coder._scale += highCount; - count = _coder.RangeGetCurrentCount(); - stateIndex = 0; - state = _decodeStates[stateIndex]; - if (count < highCount) - { - highCount = 0; - while ((highCount += state.Frequency) <= count) - { - state = _decodeStates[++stateIndex]; - } - _coder._highCount = highCount; - _coder._lowCount = _coder._highCount - state.Frequency; - see2Context.Update(); - Update2(state, context); - } - else - { - _coder._lowCount = highCount; - _coder._highCount = _coder._scale; - index = (uint)(context.NumberStatistics - _numberMasked); - _numberMasked = context.NumberStatistics; - do - { - _characterMask[_decodeStates[stateIndex].Symbol] = _escapeCount; - stateIndex++; - } - while (--index != 0); - see2Context._summary += (ushort)_coder._scale; - } - } - - private void Update1(PpmState state, PpmContext context) - { - _foundState = state; - _foundState.Frequency += 4; - context.SummaryFrequency += 4; - if (state[0].Frequency > state[-1].Frequency) - { - Swap(state[0], state[-1]); - _foundState = --state; - if (state.Frequency > MAXIMUM_FREQUENCY) - { - Rescale(context); - } - } - } - - private void Update2(PpmState state, PpmContext context) + private void EncodeSymbol1(int symbol, PpmContext context) + { + uint lowCount; + uint index = context.Statistics.Symbol; + var state = context.Statistics; + _coder._scale = context.SummaryFrequency; + if (index == symbol) { + _coder._highCount = state.Frequency; + _previousSuccess = (byte)((2 * _coder._highCount >= _coder._scale) ? 1 : 0); _foundState = state; _foundState.Frequency += 4; context.SummaryFrequency += 4; + _runLength += _previousSuccess; if (state.Frequency > MAXIMUM_FREQUENCY) { Rescale(context); } - _escapeCount++; - _runLength = _initialRunLength; + _coder._lowCount = 0; + return; } - private See2Context MakeEscapeFrequency(PpmContext context) + lowCount = state.Frequency; + index = context.NumberStatistics; + _previousSuccess = 0; + while ((++state).Symbol != symbol) { - uint numberStatistics = (uint)2 * context.NumberStatistics; - See2Context see2Context; - - if (context.NumberStatistics != 0xff) + lowCount += state.Frequency; + if (--index == 0) { - // Note that context.Flags is always in the range 0 .. 28 (this ensures that the index used for the second - // dimension of the see2Contexts array is always in the range 0 .. 31). - - numberStatistics = context.Suffix.NumberStatistics; - int index1 = _probabilities[context.NumberStatistics + 2] - 3; - int index2 = ((context.SummaryFrequency > 11 * (context.NumberStatistics + 1)) ? 1 : 0) + - ((2 * context.NumberStatistics < numberStatistics + _numberMasked) ? 2 : 0) + context.Flags; - see2Context = _see2Contexts[index1, index2]; - _coder._scale = see2Context.Mean(); - } - else - { - see2Context = _emptySee2Context; - _coder._scale = 1; - } - - return see2Context; - } - - private void Rescale(PpmContext context) - { - uint oldUnitCount; - int adder; - uint escapeFrequency; - uint index = context.NumberStatistics; - - byte localSymbol; - byte localFrequency; - PpmContext localSuccessor; - PpmState p1; - PpmState state; - - for (state = _foundState; state != context.Statistics; state--) - { - Swap(state[0], state[-1]); - } - - state.Frequency += 4; - context.SummaryFrequency += 4; - escapeFrequency = (uint)(context.SummaryFrequency - state.Frequency); - adder = (_orderFall != 0 || _method > ModelRestorationMethod.Freeze) ? 1 : 0; - state.Frequency = (byte)((state.Frequency + adder) >> 1); - context.SummaryFrequency = state.Frequency; - - do - { - escapeFrequency -= (++state).Frequency; - state.Frequency = (byte)((state.Frequency + adder) >> 1); - context.SummaryFrequency += state.Frequency; - if (state[0].Frequency > state[-1].Frequency) - { - p1 = state; - localSymbol = p1.Symbol; - localFrequency = p1.Frequency; - localSuccessor = p1.Successor; - do - { - Copy(p1[0], p1[-1]); - } - while (localFrequency > (--p1)[-1].Frequency); - p1.Symbol = localSymbol; - p1.Frequency = localFrequency; - p1.Successor = localSuccessor; - } - } - while (--index != 0); - - if (state.Frequency == 0) - { - do - { - index++; - } - while ((--state).Frequency == 0); - - escapeFrequency += index; - oldUnitCount = (uint)((context.NumberStatistics + 2) >> 1); - context.NumberStatistics -= (byte)index; - if (context.NumberStatistics == 0) - { - localSymbol = context.Statistics.Symbol; - localFrequency = context.Statistics.Frequency; - localSuccessor = context.Statistics.Successor; - localFrequency = (byte)((2 * localFrequency + escapeFrequency - 1) / escapeFrequency); - if (localFrequency > MAXIMUM_FREQUENCY / 3) - { - localFrequency = (byte)(MAXIMUM_FREQUENCY / 3); - } - _allocator.FreeUnits(context.Statistics, oldUnitCount); - context.FirstStateSymbol = localSymbol; - context.FirstStateFrequency = localFrequency; - context.FirstStateSuccessor = localSuccessor; - context.Flags = (byte)((context.Flags & 0x10) + ((localSymbol >= 0x40) ? 0x08 : 0x00)); - _foundState = context.FirstState; - return; - } - - context.Statistics = _allocator.ShrinkUnits(context.Statistics, oldUnitCount, - (uint)((context.NumberStatistics + 2) >> 1)); - context.Flags &= 0xf7; + _coder._lowCount = lowCount; + _characterMask[state.Symbol] = _escapeCount; + _numberMasked = context.NumberStatistics; index = context.NumberStatistics; - state = context.Statistics; - context.Flags |= (byte)((state.Symbol >= 0x40) ? 0x08 : 0x00); + _foundState = PpmState.ZERO; do { - context.Flags |= (byte)(((++state).Symbol >= 0x40) ? 0x08 : 0x00); - } - while (--index != 0); + _characterMask[(--state).Symbol] = _escapeCount; + } while (--index != 0); + _coder._highCount = _coder._scale; + return; } - - escapeFrequency -= (escapeFrequency >> 1); - context.SummaryFrequency += (ushort)escapeFrequency; - context.Flags |= 0x04; - _foundState = context.Statistics; } + _coder._highCount = (_coder._lowCount = lowCount) + state.Frequency; + Update1(state, context); + } - private void Refresh(uint oldUnitCount, bool scale, PpmContext context) + private void EncodeSymbol2(int symbol, PpmContext context) + { + var see2Context = MakeEscapeFrequency(context); + uint currentSymbol; + uint lowCount = 0; + var index = (uint)(context.NumberStatistics - _numberMasked); + var state = context.Statistics - 1; + + do { - int index = context.NumberStatistics; - int escapeFrequency; - int scaleValue = (scale ? 1 : 0); - - context.Statistics = _allocator.ShrinkUnits(context.Statistics, oldUnitCount, (uint)((index + 2) >> 1)); - PpmState statistics = context.Statistics; - context.Flags = - (byte)((context.Flags & (0x10 + (scale ? 0x04 : 0x00))) + ((statistics.Symbol >= 0x40) ? 0x08 : 0x00)); - escapeFrequency = context.SummaryFrequency - statistics.Frequency; - statistics.Frequency = (byte)((statistics.Frequency + scaleValue) >> scaleValue); - context.SummaryFrequency = statistics.Frequency; - do { - escapeFrequency -= (++statistics).Frequency; - statistics.Frequency = (byte)((statistics.Frequency + scaleValue) >> scaleValue); - context.SummaryFrequency += statistics.Frequency; - context.Flags |= (byte)((statistics.Symbol >= 0x40) ? 0x08 : 0x00); + currentSymbol = state[1].Symbol; + state++; + } while (_characterMask[currentSymbol] == _escapeCount); + _characterMask[currentSymbol] = _escapeCount; + if (currentSymbol == symbol) + { + goto SymbolFound; } - while (--index != 0); + lowCount += state.Frequency; + } while (--index != 0); - escapeFrequency = (escapeFrequency + scaleValue) >> scaleValue; - context.SummaryFrequency += (ushort)escapeFrequency; + _coder._lowCount = lowCount; + _coder._scale += _coder._lowCount; + _coder._highCount = _coder._scale; + see2Context._summary += (ushort)_coder._scale; + _numberMasked = context.NumberStatistics; + return; + + SymbolFound: + _coder._lowCount = lowCount; + lowCount += state.Frequency; + _coder._highCount = lowCount; + for (var p1 = state; --index != 0; ) + { + do + { + currentSymbol = p1[1].Symbol; + p1++; + } while (_characterMask[currentSymbol] == _escapeCount); + lowCount += p1.Frequency; + } + _coder._scale += lowCount; + see2Context.Update(); + Update2(state, context); + } + + private void DecodeBinarySymbol(PpmContext context) + { + var state = context.FirstState; + int index1 = _probabilities[state.Frequency - 1]; + var index2 = + _numberStatisticsToBinarySummaryIndex[context.Suffix.NumberStatistics] + + _previousSuccess + + context.Flags + + ((_runLength >> 26) & 0x20); + + if (_coder.RangeGetCurrentShiftCount(TOTAL_BIT_COUNT) < _binarySummary[index1, index2]) + { + _foundState = state; + state.Frequency += (byte)((state.Frequency < 196) ? 1 : 0); + _coder._lowCount = 0; + _coder._highCount = _binarySummary[index1, index2]; + _binarySummary[index1, index2] += (ushort)( + INTERVAL - Mean(_binarySummary[index1, index2], PERIOD_BIT_COUNT, 2) + ); + _previousSuccess = 1; + _runLength++; + } + else + { + _coder._lowCount = _binarySummary[index1, index2]; + _binarySummary[index1, index2] -= (ushort)Mean( + _binarySummary[index1, index2], + PERIOD_BIT_COUNT, + 2 + ); + _coder._highCount = BINARY_SCALE; + _initialEscape = EXPONENTIAL_ESCAPES[_binarySummary[index1, index2] >> 10]; + _characterMask[state.Symbol] = _escapeCount; + _previousSuccess = 0; + _numberMasked = 0; + _foundState = PpmState.ZERO; + } + } + + private void DecodeSymbol1(PpmContext context) + { + uint index; + uint count; + uint highCount = context.Statistics.Frequency; + var state = context.Statistics; + _coder._scale = context.SummaryFrequency; + + count = _coder.RangeGetCurrentCount(); + if (count < highCount) + { + _coder._highCount = highCount; + _previousSuccess = (byte)((2 * _coder._highCount >= _coder._scale) ? 1 : 0); + _foundState = state; + highCount += 4; + _foundState.Frequency = (byte)highCount; + context.SummaryFrequency += 4; + _runLength += _previousSuccess; + if (highCount > MAXIMUM_FREQUENCY) + { + Rescale(context); + } + _coder._lowCount = 0; + return; } - private PpmContext CutOff(int order, PpmContext context) + index = context.NumberStatistics; + _previousSuccess = 0; + while ((highCount += (++state).Frequency) <= count) { - int index; - PpmState state; + if (--index == 0) + { + _coder._lowCount = highCount; + _characterMask[state.Symbol] = _escapeCount; + _numberMasked = context.NumberStatistics; + index = context.NumberStatistics; + _foundState = PpmState.ZERO; + do + { + _characterMask[(--state).Symbol] = _escapeCount; + } while (--index != 0); + _coder._highCount = _coder._scale; + return; + } + } + _coder._highCount = highCount; + _coder._lowCount = _coder._highCount - state.Frequency; + Update1(state, context); + } + private void DecodeSymbol2(PpmContext context) + { + var see2Context = MakeEscapeFrequency(context); + uint currentSymbol; + uint count; + uint highCount = 0; + var index = (uint)(context.NumberStatistics - _numberMasked); + uint stateIndex = 0; + var state = context.Statistics - 1; + + do + { + do + { + currentSymbol = state[1].Symbol; + state++; + } while (_characterMask[currentSymbol] == _escapeCount); + highCount += state.Frequency; + _decodeStates[stateIndex++] = state; + + // note that decodeStates is a static array that is re-used on each call to this method (for performance reasons) + } while (--index != 0); + + _coder._scale += highCount; + count = _coder.RangeGetCurrentCount(); + stateIndex = 0; + state = _decodeStates[stateIndex]; + if (count < highCount) + { + highCount = 0; + while ((highCount += state.Frequency) <= count) + { + state = _decodeStates[++stateIndex]; + } + _coder._highCount = highCount; + _coder._lowCount = _coder._highCount - state.Frequency; + see2Context.Update(); + Update2(state, context); + } + else + { + _coder._lowCount = highCount; + _coder._highCount = _coder._scale; + index = (uint)(context.NumberStatistics - _numberMasked); + _numberMasked = context.NumberStatistics; + do + { + _characterMask[_decodeStates[stateIndex].Symbol] = _escapeCount; + stateIndex++; + } while (--index != 0); + see2Context._summary += (ushort)_coder._scale; + } + } + + private void Update1(PpmState state, PpmContext context) + { + _foundState = state; + _foundState.Frequency += 4; + context.SummaryFrequency += 4; + if (state[0].Frequency > state[-1].Frequency) + { + Swap(state[0], state[-1]); + _foundState = --state; + if (state.Frequency > MAXIMUM_FREQUENCY) + { + Rescale(context); + } + } + } + + private void Update2(PpmState state, PpmContext context) + { + _foundState = state; + _foundState.Frequency += 4; + context.SummaryFrequency += 4; + if (state.Frequency > MAXIMUM_FREQUENCY) + { + Rescale(context); + } + _escapeCount++; + _runLength = _initialRunLength; + } + + private See2Context MakeEscapeFrequency(PpmContext context) + { + var numberStatistics = (uint)2 * context.NumberStatistics; + See2Context see2Context; + + if (context.NumberStatistics != 0xff) + { + // Note that context.Flags is always in the range 0 .. 28 (this ensures that the index used for the second + // dimension of the see2Contexts array is always in the range 0 .. 31). + + numberStatistics = context.Suffix.NumberStatistics; + var index1 = _probabilities[context.NumberStatistics + 2] - 3; + var index2 = + ((context.SummaryFrequency > 11 * (context.NumberStatistics + 1)) ? 1 : 0) + + ((2 * context.NumberStatistics < numberStatistics + _numberMasked) ? 2 : 0) + + context.Flags; + see2Context = _see2Contexts[index1, index2]; + _coder._scale = see2Context.Mean(); + } + else + { + see2Context = _emptySee2Context; + _coder._scale = 1; + } + + return see2Context; + } + + private void Rescale(PpmContext context) + { + uint oldUnitCount; + int adder; + uint escapeFrequency; + uint index = context.NumberStatistics; + + byte localSymbol; + byte localFrequency; + PpmContext localSuccessor; + PpmState p1; + PpmState state; + + for (state = _foundState; state != context.Statistics; state--) + { + Swap(state[0], state[-1]); + } + + state.Frequency += 4; + context.SummaryFrequency += 4; + escapeFrequency = (uint)(context.SummaryFrequency - state.Frequency); + adder = (_orderFall != 0 || _method > ModelRestorationMethod.Freeze) ? 1 : 0; + state.Frequency = (byte)((state.Frequency + adder) >> 1); + context.SummaryFrequency = state.Frequency; + + do + { + escapeFrequency -= (++state).Frequency; + state.Frequency = (byte)((state.Frequency + adder) >> 1); + context.SummaryFrequency += state.Frequency; + if (state[0].Frequency > state[-1].Frequency) + { + p1 = state; + localSymbol = p1.Symbol; + localFrequency = p1.Frequency; + localSuccessor = p1.Successor; + do + { + Copy(p1[0], p1[-1]); + } while (localFrequency > (--p1)[-1].Frequency); + p1.Symbol = localSymbol; + p1.Frequency = localFrequency; + p1.Successor = localSuccessor; + } + } while (--index != 0); + + if (state.Frequency == 0) + { + do + { + index++; + } while ((--state).Frequency == 0); + + escapeFrequency += index; + oldUnitCount = (uint)((context.NumberStatistics + 2) >> 1); + context.NumberStatistics -= (byte)index; if (context.NumberStatistics == 0) { - state = context.FirstState; - if ((Pointer)state.Successor >= _allocator._baseUnit) + localSymbol = context.Statistics.Symbol; + localFrequency = context.Statistics.Frequency; + localSuccessor = context.Statistics.Successor; + localFrequency = (byte)( + ((2 * localFrequency) + escapeFrequency - 1) / escapeFrequency + ); + if (localFrequency > MAXIMUM_FREQUENCY / 3) { - if (order < _modelOrder) - { - state.Successor = CutOff(order + 1, state.Successor); - } - else - { - state.Successor = PpmContext.ZERO; - } - - if (state.Successor == PpmContext.ZERO && order > ORDER_BOUND) - { - _allocator.SpecialFreeUnits(context); - return PpmContext.ZERO; - } - - return context; + localFrequency = (byte)(MAXIMUM_FREQUENCY / 3); } - _allocator.SpecialFreeUnits(context); - return PpmContext.ZERO; + _allocator.FreeUnits(context.Statistics, oldUnitCount); + context.FirstStateSymbol = localSymbol; + context.FirstStateFrequency = localFrequency; + context.FirstStateSuccessor = localSuccessor; + context.Flags = (byte)( + (context.Flags & 0x10) + ((localSymbol >= 0x40) ? 0x08 : 0x00) + ); + _foundState = context.FirstState; + return; } - uint unitCount = (uint)((context.NumberStatistics + 2) >> 1); - context.Statistics = _allocator.MoveUnitsUp(context.Statistics, unitCount); + context.Statistics = _allocator.ShrinkUnits( + context.Statistics, + oldUnitCount, + (uint)((context.NumberStatistics + 2) >> 1) + ); + context.Flags &= 0xf7; index = context.NumberStatistics; - for (state = context.Statistics + index; state >= context.Statistics; state--) + state = context.Statistics; + context.Flags |= (byte)((state.Symbol >= 0x40) ? 0x08 : 0x00); + do { - if (state.Successor < _allocator._baseUnit) - { - state.Successor = PpmContext.ZERO; - Swap(state, context.Statistics[index--]); - } - else if (order < _modelOrder) + context.Flags |= (byte)(((++state).Symbol >= 0x40) ? 0x08 : 0x00); + } while (--index != 0); + } + + escapeFrequency -= (escapeFrequency >> 1); + context.SummaryFrequency += (ushort)escapeFrequency; + context.Flags |= 0x04; + _foundState = context.Statistics; + } + + private void Refresh(uint oldUnitCount, bool scale, PpmContext context) + { + int index = context.NumberStatistics; + int escapeFrequency; + var scaleValue = (scale ? 1 : 0); + + context.Statistics = _allocator.ShrinkUnits( + context.Statistics, + oldUnitCount, + (uint)((index + 2) >> 1) + ); + var statistics = context.Statistics; + context.Flags = (byte)( + (context.Flags & (0x10 + (scale ? 0x04 : 0x00))) + + ((statistics.Symbol >= 0x40) ? 0x08 : 0x00) + ); + escapeFrequency = context.SummaryFrequency - statistics.Frequency; + statistics.Frequency = (byte)((statistics.Frequency + scaleValue) >> scaleValue); + context.SummaryFrequency = statistics.Frequency; + + do + { + escapeFrequency -= (++statistics).Frequency; + statistics.Frequency = (byte)((statistics.Frequency + scaleValue) >> scaleValue); + context.SummaryFrequency += statistics.Frequency; + context.Flags |= (byte)((statistics.Symbol >= 0x40) ? 0x08 : 0x00); + } while (--index != 0); + + escapeFrequency = (escapeFrequency + scaleValue) >> scaleValue; + context.SummaryFrequency += (ushort)escapeFrequency; + } + + private PpmContext CutOff(int order, PpmContext context) + { + int index; + PpmState state; + + if (context.NumberStatistics == 0) + { + state = context.FirstState; + if ((Pointer)state.Successor >= _allocator._baseUnit) + { + if (order < _modelOrder) { state.Successor = CutOff(order + 1, state.Successor); } @@ -749,69 +753,107 @@ namespace SharpCompress.Compressors.PPMd.I1 { state.Successor = PpmContext.ZERO; } - } - if (index != context.NumberStatistics && order != 0) - { - context.NumberStatistics = (byte)index; - state = context.Statistics; - if (index < 0) + if (state.Successor == PpmContext.ZERO && order > ORDER_BOUND) { - _allocator.FreeUnits(state, unitCount); _allocator.SpecialFreeUnits(context); return PpmContext.ZERO; } - if (index == 0) - { - context.Flags = (byte)((context.Flags & 0x10) + ((state.Symbol >= 0x40) ? 0x08 : 0x00)); - Copy(context.FirstState, state); - _allocator.FreeUnits(state, unitCount); - context.FirstStateFrequency = (byte)((context.FirstStateFrequency + 11) >> 3); - } - else - { - Refresh(unitCount, context.SummaryFrequency > 16 * index, context); - } - } - return context; - } - - private PpmContext RemoveBinaryContexts(int order, PpmContext context) - { - if (context.NumberStatistics == 0) - { - PpmState state = context.FirstState; - if ((Pointer)state.Successor >= _allocator._baseUnit && order < _modelOrder) - { - state.Successor = RemoveBinaryContexts(order + 1, state.Successor); - } - else - { - state.Successor = PpmContext.ZERO; - } - if ((state.Successor == PpmContext.ZERO) && - (context.Suffix.NumberStatistics == 0 || context.Suffix.Flags == 0xff)) - { - _allocator.FreeUnits(context, 1); - return PpmContext.ZERO; - } return context; } + _allocator.SpecialFreeUnits(context); + return PpmContext.ZERO; + } - for (PpmState state = context.Statistics + context.NumberStatistics; state >= context.Statistics; state--) + var unitCount = (uint)((context.NumberStatistics + 2) >> 1); + context.Statistics = _allocator.MoveUnitsUp(context.Statistics, unitCount); + index = context.NumberStatistics; + for (state = context.Statistics + index; state >= context.Statistics; state--) + { + if (state.Successor < _allocator._baseUnit) { - if ((Pointer)state.Successor >= _allocator._baseUnit && order < _modelOrder) - { - state.Successor = RemoveBinaryContexts(order + 1, state.Successor); - } - else - { - state.Successor = PpmContext.ZERO; - } + state.Successor = PpmContext.ZERO; + Swap(state, context.Statistics[index--]); } + else if (order < _modelOrder) + { + state.Successor = CutOff(order + 1, state.Successor); + } + else + { + state.Successor = PpmContext.ZERO; + } + } + if (index != context.NumberStatistics && order != 0) + { + context.NumberStatistics = (byte)index; + state = context.Statistics; + if (index < 0) + { + _allocator.FreeUnits(state, unitCount); + _allocator.SpecialFreeUnits(context); + return PpmContext.ZERO; + } + if (index == 0) + { + context.Flags = (byte)( + (context.Flags & 0x10) + ((state.Symbol >= 0x40) ? 0x08 : 0x00) + ); + Copy(context.FirstState, state); + _allocator.FreeUnits(state, unitCount); + context.FirstStateFrequency = (byte)((context.FirstStateFrequency + 11) >> 3); + } + else + { + Refresh(unitCount, context.SummaryFrequency > 16 * index, context); + } + } + + return context; + } + + private PpmContext RemoveBinaryContexts(int order, PpmContext context) + { + if (context.NumberStatistics == 0) + { + var state = context.FirstState; + if ((Pointer)state.Successor >= _allocator._baseUnit && order < _modelOrder) + { + state.Successor = RemoveBinaryContexts(order + 1, state.Successor); + } + else + { + state.Successor = PpmContext.ZERO; + } + if ( + (state.Successor == PpmContext.ZERO) + && (context.Suffix.NumberStatistics == 0 || context.Suffix.Flags == 0xff) + ) + { + _allocator.FreeUnits(context, 1); + return PpmContext.ZERO; + } return context; } + + for ( + var state = context.Statistics + context.NumberStatistics; + state >= context.Statistics; + state-- + ) + { + if ((Pointer)state.Successor >= _allocator._baseUnit && order < _modelOrder) + { + state.Successor = RemoveBinaryContexts(order + 1, state.Successor); + } + else + { + state.Successor = PpmContext.ZERO; + } + } + + return context; } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/PpmState.cs b/src/SharpCompress/Compressors/PPMd/I1/PpmState.cs index dc72009d..757afd00 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/PpmState.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/PpmState.cs @@ -1,193 +1,191 @@ #nullable disable -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// PPM state. +/// +/// +/// +/// This must be a structure rather than a class because several places in the associated code assume that +/// is a value type (meaning that assignment creates a completely new copy of the +/// instance rather than just copying a reference to the same instance). +/// +/// +/// Note that is a field rather than a property for performance reasons. +/// +/// +internal struct PpmState { + public uint _address; + public byte[] _memory; + public static readonly PpmState ZERO = new PpmState(0, null); + public const int SIZE = 6; + /// - /// PPM state. + /// Initializes a new instance of the structure. /// - /// - /// - /// This must be a structure rather than a class because several places in the associated code assume that - /// is a value type (meaning that assignment creates a completely new copy of the - /// instance rather than just copying a reference to the same instance). - /// - /// - /// Note that is a field rather than a property for performance reasons. - /// - /// - internal struct PpmState + public PpmState(uint address, byte[] memory) { - public uint _address; - public byte[] _memory; - public static readonly PpmState ZERO = new PpmState(0, null); - public const int SIZE = 6; + _address = address; + _memory = memory; + } - /// - /// Initializes a new instance of the structure. - /// - public PpmState(uint address, byte[] memory) + /// + /// Gets or sets the symbol. + /// + public byte Symbol + { + get => _memory[_address]; + set => _memory[_address] = value; + } + + /// + /// Gets or sets the frequency. + /// + public byte Frequency + { + get => _memory[_address + 1]; + set => _memory[_address + 1] = value; + } + + /// + /// Gets or sets the successor. + /// + public Model.PpmContext Successor + { + get => + new Model.PpmContext( + _memory[_address + 2] + | (((uint)_memory[_address + 3]) << 8) + | (((uint)_memory[_address + 4]) << 16) + | (((uint)_memory[_address + 5]) << 24), + _memory + ); + set { - _address = address; - _memory = memory; - } - - /// - /// Gets or sets the symbol. - /// - public byte Symbol { get => _memory[_address]; set => _memory[_address] = value; } - - /// - /// Gets or sets the frequency. - /// - public byte Frequency { get => _memory[_address + 1]; set => _memory[_address + 1] = value; } - - /// - /// Gets or sets the successor. - /// - public Model.PpmContext Successor - { - get => new Model.PpmContext( - _memory[_address + 2] | ((uint)_memory[_address + 3]) << 8 | - ((uint)_memory[_address + 4]) << 16 | ((uint)_memory[_address + 5]) << 24, _memory); - set - { - _memory[_address + 2] = (byte)value._address; - _memory[_address + 3] = (byte)(value._address >> 8); - _memory[_address + 4] = (byte)(value._address >> 16); - _memory[_address + 5] = (byte)(value._address >> 24); - } - } - - /// - /// Gets the at the relative to this - /// . - /// - /// - /// - public PpmState this[int offset] => new PpmState((uint)(_address + offset * SIZE), _memory); - - /// - /// Allow a pointer to be implicitly converted to a PPM state. - /// - /// - /// - public static implicit operator PpmState(Pointer pointer) - { - return new PpmState(pointer._address, pointer._memory); - } - - /// - /// Allow pointer-like addition on a PPM state. - /// - /// - /// - /// - public static PpmState operator +(PpmState state, int offset) - { - state._address = (uint)(state._address + offset * SIZE); - return state; - } - - /// - /// Allow pointer-like incrementing on a PPM state. - /// - /// - /// - public static PpmState operator ++(PpmState state) - { - state._address += SIZE; - return state; - } - - /// - /// Allow pointer-like subtraction on a PPM state. - /// - /// - /// - /// - public static PpmState operator -(PpmState state, int offset) - { - state._address = (uint)(state._address - offset * SIZE); - return state; - } - - /// - /// Allow pointer-like decrementing on a PPM state. - /// - /// - /// - public static PpmState operator --(PpmState state) - { - state._address -= SIZE; - return state; - } - - /// - /// Compare two PPM states. - /// - /// - /// - /// - public static bool operator <=(PpmState state1, PpmState state2) - { - return state1._address <= state2._address; - } - - /// - /// Compare two PPM states. - /// - /// - /// - /// - public static bool operator >=(PpmState state1, PpmState state2) - { - return state1._address >= state2._address; - } - - /// - /// Compare two PPM states. - /// - /// - /// - /// - public static bool operator ==(PpmState state1, PpmState state2) - { - return state1._address == state2._address; - } - - /// - /// Compare two PPM states. - /// - /// - /// - /// - public static bool operator !=(PpmState state1, PpmState state2) - { - return state1._address != state2._address; - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// true if obj and this instance are the same type and represent the same value; otherwise, false. - /// Another object to compare to. - public override bool Equals(object obj) - { - if (obj is PpmState) - { - PpmState state = (PpmState)obj; - return state._address == _address; - } - return base.Equals(obj); - } - - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return _address.GetHashCode(); + _memory[_address + 2] = (byte)value._address; + _memory[_address + 3] = (byte)(value._address >> 8); + _memory[_address + 4] = (byte)(value._address >> 16); + _memory[_address + 5] = (byte)(value._address >> 24); } } -} \ No newline at end of file + + /// + /// Gets the at the relative to this + /// . + /// + /// + /// + public PpmState this[int offset] => new PpmState((uint)(_address + (offset * SIZE)), _memory); + + /// + /// Allow a pointer to be implicitly converted to a PPM state. + /// + /// + /// + public static implicit operator PpmState(Pointer pointer) => + new PpmState(pointer._address, pointer._memory); + + /// + /// Allow pointer-like addition on a PPM state. + /// + /// + /// + /// + public static PpmState operator +(PpmState state, int offset) + { + state._address = (uint)(state._address + (offset * SIZE)); + return state; + } + + /// + /// Allow pointer-like incrementing on a PPM state. + /// + /// + /// + public static PpmState operator ++(PpmState state) + { + state._address += SIZE; + return state; + } + + /// + /// Allow pointer-like subtraction on a PPM state. + /// + /// + /// + /// + public static PpmState operator -(PpmState state, int offset) + { + state._address = (uint)(state._address - (offset * SIZE)); + return state; + } + + /// + /// Allow pointer-like decrementing on a PPM state. + /// + /// + /// + public static PpmState operator --(PpmState state) + { + state._address -= SIZE; + return state; + } + + /// + /// Compare two PPM states. + /// + /// + /// + /// + public static bool operator <=(PpmState state1, PpmState state2) => + state1._address <= state2._address; + + /// + /// Compare two PPM states. + /// + /// + /// + /// + public static bool operator >=(PpmState state1, PpmState state2) => + state1._address >= state2._address; + + /// + /// Compare two PPM states. + /// + /// + /// + /// + public static bool operator ==(PpmState state1, PpmState state2) => + state1._address == state2._address; + + /// + /// Compare two PPM states. + /// + /// + /// + /// + public static bool operator !=(PpmState state1, PpmState state2) => + state1._address != state2._address; + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// true if obj and this instance are the same type and represent the same value; otherwise, false. + /// Another object to compare to. + public override bool Equals(object obj) + { + if (obj is PpmState state) + { + return state._address == _address; + } + return base.Equals(obj); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => _address.GetHashCode(); +} diff --git a/src/SharpCompress/Compressors/PPMd/I1/See2Context.cs b/src/SharpCompress/Compressors/PPMd/I1/See2Context.cs index 3029d85b..bc52e432 100644 --- a/src/SharpCompress/Compressors/PPMd/I1/See2Context.cs +++ b/src/SharpCompress/Compressors/PPMd/I1/See2Context.cs @@ -4,52 +4,51 @@ #endregion -namespace SharpCompress.Compressors.PPMd.I1 +namespace SharpCompress.Compressors.PPMd.I1; + +/// +/// SEE2 (secondary escape estimation) contexts for PPM contexts with masked symbols. +/// +/// +/// +/// This must be a class rather than a structure because MakeEscapeFrequency returns a See2Context +/// instance from the see2Contexts array. The caller (for example, EncodeSymbol2) then updates the +/// returned See2Context instance and expects the updates to be reflected in the see2Contexts array. +/// This would not happen if this were a structure. +/// +/// +/// Note that in most cases fields are used rather than properties for performance reasons (for example, +/// is a field rather than a property). +/// +/// +internal class See2Context { - /// - /// SEE2 (secondary escape estimation) contexts for PPM contexts with masked symbols. - /// - /// - /// - /// This must be a class rather than a structure because MakeEscapeFrequency returns a See2Context - /// instance from the see2Contexts array. The caller (for example, EncodeSymbol2) then updates the - /// returned See2Context instance and expects the updates to be reflected in the see2Contexts array. - /// This would not happen if this were a structure. - /// - /// - /// Note that in most cases fields are used rather than properties for performance reasons (for example, - /// is a field rather than a property). - /// - /// - internal class See2Context + private const byte PERIOD_BIT_COUNT = 7; + + public ushort _summary; + public byte _shift; + public byte _count; + + public void Initialize(uint initialValue) { - private const byte PERIOD_BIT_COUNT = 7; + _shift = PERIOD_BIT_COUNT - 4; + _summary = (ushort)(initialValue << _shift); + _count = 7; + } - public ushort _summary; - public byte _shift; - public byte _count; + public uint Mean() + { + var value = (uint)(_summary >> _shift); + _summary = (ushort)(_summary - value); + return (uint)(value + ((value == 0) ? 1 : 0)); + } - public void Initialize(uint initialValue) + public void Update() + { + if (_shift < PERIOD_BIT_COUNT && --_count == 0) { - _shift = PERIOD_BIT_COUNT - 4; - _summary = (ushort)(initialValue << _shift); - _count = 7; - } - - public uint Mean() - { - uint value = (uint)(_summary >> _shift); - _summary = (ushort)(_summary - value); - return (uint)(value + ((value == 0) ? 1 : 0)); - } - - public void Update() - { - if (_shift < PERIOD_BIT_COUNT && --_count == 0) - { - _summary += _summary; - _count = (byte)(3 << _shift++); - } + _summary += _summary; + _count = (byte)(3 << _shift++); } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs b/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs index 08eef12c..e75d88b8 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdProperties.cs @@ -1,82 +1,82 @@ -using System; +using System; using System.Buffers.Binary; using SharpCompress.Compressors.PPMd.I1; -namespace SharpCompress.Compressors.PPMd +namespace SharpCompress.Compressors.PPMd; + +public class PpmdProperties { - public class PpmdProperties + private int _allocatorSize; + internal Allocator? _allocator; + + public PpmdProperties() : this(16 << 20, 6) { } + + public PpmdProperties(int allocatorSize, int modelOrder) + : this(allocatorSize, modelOrder, ModelRestorationMethod.Restart) { } + + internal PpmdProperties( + int allocatorSize, + int modelOrder, + ModelRestorationMethod modelRestorationMethod + ) { + AllocatorSize = allocatorSize; + ModelOrder = modelOrder; + RestorationMethod = modelRestorationMethod; + } - private int _allocatorSize; - internal Allocator? _allocator; + public int ModelOrder { get; } + public PpmdVersion Version { get; } = PpmdVersion.I1; + internal ModelRestorationMethod RestorationMethod { get; } - public PpmdProperties() - : this(16 << 20, 6) + public PpmdProperties(byte[] properties) : this(properties.AsSpan()) { } + + public PpmdProperties(ReadOnlySpan properties) + { + if (properties.Length == 2) { + var props = BinaryPrimitives.ReadUInt16LittleEndian(properties); + AllocatorSize = (((props >> 4) & 0xff) + 1) << 20; + ModelOrder = (props & 0x0f) + 1; + RestorationMethod = (ModelRestorationMethod)(props >> 12); } - - public PpmdProperties(int allocatorSize, int modelOrder) - : this(allocatorSize, modelOrder, ModelRestorationMethod.Restart) + else if (properties.Length == 5) { + Version = PpmdVersion.H7Z; + AllocatorSize = BinaryPrimitives.ReadInt32LittleEndian(properties.Slice(1)); + ModelOrder = properties[0]; } + } - internal PpmdProperties(int allocatorSize, int modelOrder, ModelRestorationMethod modelRestorationMethod) + public int AllocatorSize + { + get => _allocatorSize; + set { - AllocatorSize = allocatorSize; - ModelOrder = modelOrder; - RestorationMethod = modelRestorationMethod; - } - - public int ModelOrder { get; } - public PpmdVersion Version { get; } = PpmdVersion.I1; - internal ModelRestorationMethod RestorationMethod { get; } - - public PpmdProperties(byte[] properties) : this(properties.AsSpan()) - { - } - - public PpmdProperties(ReadOnlySpan properties) - { - if (properties.Length == 2) + _allocatorSize = value; + if (Version == PpmdVersion.I1) { - ushort props = BinaryPrimitives.ReadUInt16LittleEndian(properties); - AllocatorSize = (((props >> 4) & 0xff) + 1) << 20; - ModelOrder = (props & 0x0f) + 1; - RestorationMethod = (ModelRestorationMethod)(props >> 12); - } - else if (properties.Length == 5) - { - Version = PpmdVersion.H7Z; - AllocatorSize = BinaryPrimitives.ReadInt32LittleEndian(properties.Slice(1)); - ModelOrder = properties[0]; - } - } + _allocator ??= new Allocator(); - public int AllocatorSize - { - get => _allocatorSize; - set - { - _allocatorSize = value; - if (Version == PpmdVersion.I1) - { - _allocator ??= new Allocator(); - - _allocator.Start(_allocatorSize); - } - } - } - - public byte[] Properties - { - get - { - byte[] bytes = new byte[2]; - BinaryPrimitives.WriteUInt16LittleEndian( - bytes, - (ushort)((ModelOrder - 1) + (((AllocatorSize >> 20) - 1) << 4) + ((ushort)RestorationMethod << 12))); - return bytes; + _allocator.Start(_allocatorSize); } } } + + public byte[] Properties + { + get + { + var bytes = new byte[2]; + BinaryPrimitives.WriteUInt16LittleEndian( + bytes, + (ushort)( + (ModelOrder - 1) + + (((AllocatorSize >> 20) - 1) << 4) + + ((ushort)RestorationMethod << 12) + ) + ); + return bytes; + } + } } diff --git a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs index 33715c3a..bdc4d65a 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdStream.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdStream.cs @@ -6,139 +6,134 @@ using SharpCompress.Compressors.LZMA.RangeCoder; using SharpCompress.Compressors.PPMd.H; using SharpCompress.Compressors.PPMd.I1; -namespace SharpCompress.Compressors.PPMd +namespace SharpCompress.Compressors.PPMd; + +public class PpmdStream : Stream { - public class PpmdStream : Stream + private readonly PpmdProperties _properties; + private readonly Stream _stream; + private readonly bool _compress; + private readonly Model _model; + private readonly ModelPpm _modelH; + private readonly Decoder _decoder; + private long _position; + private bool _isDisposed; + + public PpmdStream(PpmdProperties properties, Stream stream, bool compress) { - private readonly PpmdProperties _properties; - private readonly Stream _stream; - private readonly bool _compress; - private readonly Model _model; - private readonly ModelPpm _modelH; - private readonly Decoder _decoder; - private long _position; - private bool _isDisposed; + _properties = properties; + _stream = stream; + _compress = compress; - public PpmdStream(PpmdProperties properties, Stream stream, bool compress) + if (properties.Version == PpmdVersion.I1) { - _properties = properties; - _stream = stream; - _compress = compress; - - if (properties.Version == PpmdVersion.I1) + _model = new Model(); + if (compress) { - _model = new Model(); - if (compress) - { - _model.EncodeStart(properties); - } - else - { - _model.DecodeStart(stream, properties); - } + _model.EncodeStart(properties); } - if (properties.Version == PpmdVersion.H) + else { - _modelH = new ModelPpm(); - if (compress) - { - throw new NotImplementedException(); - } - _modelH.DecodeInit(stream, properties.ModelOrder, properties.AllocatorSize); - } - if (properties.Version == PpmdVersion.H7Z) - { - _modelH = new ModelPpm(); - if (compress) - { - throw new NotImplementedException(); - } - _modelH.DecodeInit(null, properties.ModelOrder, properties.AllocatorSize); - _decoder = new Decoder(); - _decoder.Init(stream); + _model.DecodeStart(stream, properties); } } - - public override bool CanRead => !_compress; - - public override bool CanSeek => false; - - public override bool CanWrite => _compress; - - public override void Flush() + if (properties.Version == PpmdVersion.H) { + _modelH = new ModelPpm(); + if (compress) + { + throw new NotImplementedException(); + } + _modelH.DecodeInit(stream, properties.ModelOrder, properties.AllocatorSize); } - - protected override void Dispose(bool isDisposing) + if (properties.Version == PpmdVersion.H7Z) { - if (_isDisposed) + _modelH = new ModelPpm(); + if (compress) { - return; - } - _isDisposed = true; - if (isDisposing) - { - if (_compress) - { - _model.EncodeBlock(_stream, new MemoryStream(), true); - } - } - base.Dispose(isDisposing); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => _position; set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_compress) - { - return 0; - } - int size = 0; - if (_properties.Version == PpmdVersion.I1) - { - size = _model.DecodeBlock(_stream, buffer, offset, count); - } - if (_properties.Version == PpmdVersion.H) - { - int c; - while (size < count && (c = _modelH.DecodeChar()) >= 0) - { - buffer[offset++] = (byte)c; - size++; - } - } - if (_properties.Version == PpmdVersion.H7Z) - { - int c; - while (size < count && (c = _modelH.DecodeChar(_decoder)) >= 0) - { - buffer[offset++] = (byte)c; - size++; - } - } - _position += size; - return size; - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (_compress) - { - _model.EncodeBlock(_stream, new MemoryStream(buffer, offset, count), false); + throw new NotImplementedException(); } + _modelH.DecodeInit(null, properties.ModelOrder, properties.AllocatorSize); + _decoder = new Decoder(); + _decoder.Init(stream); } } -} \ No newline at end of file + + public override bool CanRead => !_compress; + + public override bool CanSeek => false; + + public override bool CanWrite => _compress; + + public override void Flush() { } + + protected override void Dispose(bool isDisposing) + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + if (isDisposing) + { + if (_compress) + { + _model.EncodeBlock(_stream, new MemoryStream(), true); + } + } + base.Dispose(isDisposing); + } + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => _position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_compress) + { + return 0; + } + var size = 0; + if (_properties.Version == PpmdVersion.I1) + { + size = _model.DecodeBlock(_stream, buffer, offset, count); + } + if (_properties.Version == PpmdVersion.H) + { + int c; + while (size < count && (c = _modelH.DecodeChar()) >= 0) + { + buffer[offset++] = (byte)c; + size++; + } + } + if (_properties.Version == PpmdVersion.H7Z) + { + int c; + while (size < count && (c = _modelH.DecodeChar(_decoder)) >= 0) + { + buffer[offset++] = (byte)c; + size++; + } + } + _position += size; + return size; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + if (_compress) + { + _model.EncodeBlock(_stream, new MemoryStream(buffer, offset, count), false); + } + } +} diff --git a/src/SharpCompress/Compressors/PPMd/PpmdVersion.cs b/src/SharpCompress/Compressors/PPMd/PpmdVersion.cs index 8ab1aa38..791034ac 100644 --- a/src/SharpCompress/Compressors/PPMd/PpmdVersion.cs +++ b/src/SharpCompress/Compressors/PPMd/PpmdVersion.cs @@ -1,9 +1,8 @@ -namespace SharpCompress.Compressors.PPMd +namespace SharpCompress.Compressors.PPMd; + +public enum PpmdVersion { - public enum PpmdVersion - { - H, - H7Z, - I1 - } -} \ No newline at end of file + H, + H7Z, + I1 +} diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs index 0cc86a78..651e2c8a 100644 --- a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -1,18 +1,17 @@ -using System.IO; +using System.IO; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar; + +internal interface IRarUnpack { - internal interface IRarUnpack - { - void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); - void DoUnpack(); + void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream); + void DoUnpack(); - // eg u/i pause/resume button - bool Suspended { get; set; } + // eg u/i pause/resume button + bool Suspended { get; set; } - long DestSize { get; } - int Char { get; } - int PpmEscChar { get; set; } - } + long DestSize { get; } + int Char { get; } + int PpmEscChar { get; set; } } diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index 61201b62..1e4adfde 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -6,138 +6,144 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.Rar; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar; + +internal sealed class MultiVolumeReadOnlyStream : Stream { - internal sealed class MultiVolumeReadOnlyStream : Stream + private long currentPosition; + private long maxPosition; + + private IEnumerator filePartEnumerator; + private Stream currentStream; + + private readonly IExtractionListener streamListener; + + private long currentPartTotalReadBytes; + private long currentEntryTotalReadBytes; + + internal MultiVolumeReadOnlyStream( + IEnumerable parts, + IExtractionListener streamListener + ) { - private long currentPosition; - private long maxPosition; + this.streamListener = streamListener; - private IEnumerator filePartEnumerator; - private Stream currentStream; + filePartEnumerator = parts.GetEnumerator(); + filePartEnumerator.MoveNext(); + InitializeNextFilePart(); + } - private readonly IExtractionListener streamListener; - - private long currentPartTotalReadBytes; - private long currentEntryTotalReadBytes; - - internal MultiVolumeReadOnlyStream(IEnumerable parts, IExtractionListener streamListener) + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) { - this.streamListener = streamListener; - - filePartEnumerator = parts.GetEnumerator(); - filePartEnumerator.MoveNext(); - InitializeNextFilePart(); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) + if (filePartEnumerator != null) { - if (filePartEnumerator != null) - { - filePartEnumerator.Dispose(); - filePartEnumerator = null; - } - currentStream = null; + filePartEnumerator.Dispose(); + filePartEnumerator = null; } - } - - private void InitializeNextFilePart() - { - maxPosition = filePartEnumerator.Current.FileHeader.CompressedSize; - currentPosition = 0; - currentStream = filePartEnumerator.Current.GetCompressedStream(); - - currentPartTotalReadBytes = 0; - - CurrentCrc = filePartEnumerator.Current.FileHeader.FileCrc; - - streamListener.FireFilePartExtractionBegin(filePartEnumerator.Current.FilePartName, - filePartEnumerator.Current.FileHeader.CompressedSize, - filePartEnumerator.Current.FileHeader.UncompressedSize); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int totalRead = 0; - int currentOffset = offset; - int currentCount = count; - while (currentCount > 0) - { - int readSize = currentCount; - if (currentCount > maxPosition - currentPosition) - { - readSize = (int)(maxPosition - currentPosition); - } - - int read = currentStream.Read(buffer, currentOffset, readSize); - if (read < 0) - { - throw new EndOfStreamException(); - } - - currentPosition += read; - currentOffset += read; - currentCount -= read; - totalRead += read; - if (((maxPosition - currentPosition) == 0) - && filePartEnumerator.Current.FileHeader.IsSplitAfter) - { - if (filePartEnumerator.Current.FileHeader.R4Salt != null) - { - throw new InvalidFormatException("Sharpcompress currently does not support multi-volume decryption."); - } - string fileName = filePartEnumerator.Current.FileHeader.FileName; - if (!filePartEnumerator.MoveNext()) - { - throw new InvalidFormatException( - "Multi-part rar file is incomplete. Entry expects a new volume: " + fileName); - } - InitializeNextFilePart(); - } - else - { - break; - } - } - currentPartTotalReadBytes += totalRead; - currentEntryTotalReadBytes += totalRead; - streamListener.FireCompressedBytesRead(currentPartTotalReadBytes, currentEntryTotalReadBytes); - return totalRead; - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public uint CurrentCrc { get; private set; } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); + currentStream = null; } } -} \ No newline at end of file + + private void InitializeNextFilePart() + { + maxPosition = filePartEnumerator.Current.FileHeader.CompressedSize; + currentPosition = 0; + currentStream = filePartEnumerator.Current.GetCompressedStream(); + + currentPartTotalReadBytes = 0; + + CurrentCrc = filePartEnumerator.Current.FileHeader.FileCrc; + + streamListener.FireFilePartExtractionBegin( + filePartEnumerator.Current.FilePartName, + filePartEnumerator.Current.FileHeader.CompressedSize, + filePartEnumerator.Current.FileHeader.UncompressedSize + ); + } + + public override int Read(byte[] buffer, int offset, int count) + { + var totalRead = 0; + var currentOffset = offset; + var currentCount = count; + while (currentCount > 0) + { + var readSize = currentCount; + if (currentCount > maxPosition - currentPosition) + { + readSize = (int)(maxPosition - currentPosition); + } + + var read = currentStream.Read(buffer, currentOffset, readSize); + if (read < 0) + { + throw new EndOfStreamException(); + } + + currentPosition += read; + currentOffset += read; + currentCount -= read; + totalRead += read; + if ( + ((maxPosition - currentPosition) == 0) + && filePartEnumerator.Current.FileHeader.IsSplitAfter + ) + { + if (filePartEnumerator.Current.FileHeader.R4Salt != null) + { + throw new InvalidFormatException( + "Sharpcompress currently does not support multi-volume decryption." + ); + } + var fileName = filePartEnumerator.Current.FileHeader.FileName; + if (!filePartEnumerator.MoveNext()) + { + throw new InvalidFormatException( + "Multi-part rar file is incomplete. Entry expects a new volume: " + + fileName + ); + } + InitializeNextFilePart(); + } + else + { + break; + } + } + currentPartTotalReadBytes += totalRead; + currentEntryTotalReadBytes += totalRead; + streamListener.FireCompressedBytesRead( + currentPartTotalReadBytes, + currentEntryTotalReadBytes + ); + return totalRead; + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public uint CurrentCrc { get; private set; } + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Compressors/Rar/RarCRC.cs b/src/SharpCompress/Compressors/Rar/RarCRC.cs index c9e8159c..8f4fb47e 100644 --- a/src/SharpCompress/Compressors/Rar/RarCRC.cs +++ b/src/SharpCompress/Compressors/Rar/RarCRC.cs @@ -1,49 +1,46 @@ using System; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar; + +internal static class RarCRC { - internal static class RarCRC + private static readonly uint[] crcTab; + + public static uint CheckCrc(uint startCrc, byte b) => + (crcTab[((int)startCrc ^ b) & 0xff] ^ (startCrc >> 8)); + + public static uint CheckCrc(uint startCrc, byte[] data, int offset, int count) { - private static readonly uint[] crcTab; + var size = Math.Min(data.Length - offset, count); - public static uint CheckCrc(uint startCrc, byte b) + for (var i = 0; i < size; i++) { - return (crcTab[((int)((int)startCrc ^ (int)b)) & 0xff] ^ (startCrc >> 8)); + startCrc = (crcTab[((int)startCrc ^ data[offset + i]) & 0xff] ^ (startCrc >> 8)); } + return (startCrc); + } - public static uint CheckCrc(uint startCrc, byte[] data, int offset, int count) + static RarCRC() + { { - int size = Math.Min(data.Length - offset, count); - - for (int i = 0; i < size; i++) + crcTab = new uint[256]; + for (uint i = 0; i < 256; i++) { - startCrc = (crcTab[((int)startCrc ^ data[offset + i]) & 0xff] ^ (startCrc >> 8)); - } - return (startCrc); - } - - static RarCRC() - { - { - crcTab = new uint[256]; - for (uint i = 0; i < 256; i++) + var c = i; + for (var j = 0; j < 8; j++) { - uint c = i; - for (int j = 0; j < 8; j++) + if ((c & 1) != 0) { - if ((c & 1) != 0) - { - c = c >> 1; - c ^= 0xEDB88320; - } - else - { - c = c >> 1; - } + c >>= 1; + c ^= 0xEDB88320; + } + else + { + c >>= 1; } - crcTab[i] = c; } + crcTab[i] = c; } } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 127cef25..e4450c92 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -1,44 +1,40 @@ using SharpCompress.Common; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar; + +internal class RarCrcStream : RarStream { - internal class RarCrcStream : RarStream + private readonly MultiVolumeReadOnlyStream readStream; + private uint currentCrc; + + public RarCrcStream( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStream readStream + ) : base(unpack, fileHeader, readStream) { - private readonly MultiVolumeReadOnlyStream readStream; - private uint currentCrc; - - public RarCrcStream(IRarUnpack unpack, FileHeader fileHeader, MultiVolumeReadOnlyStream readStream) - : base(unpack, fileHeader, readStream) - { - this.readStream = readStream; - ResetCrc(); - } - - public uint GetCrc() - { - return ~currentCrc; - } - - public void ResetCrc() - { - currentCrc = 0xffffffff; - } - - public override int Read(byte[] buffer, int offset, int count) - { - var result = base.Read(buffer, offset, count); - if (result != 0) - { - currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result); - } - else if (GetCrc() != readStream.CurrentCrc && count != 0) - { - // NOTE: we use the last FileHeader in a multipart volume to check CRC - throw new InvalidFormatException("file crc mismatch"); - } - - return result; - } + this.readStream = readStream; + ResetCrc(); } -} \ No newline at end of file + + public uint GetCrc() => ~currentCrc; + + public void ResetCrc() => currentCrc = 0xffffffff; + + public override int Read(byte[] buffer, int offset, int count) + { + var result = base.Read(buffer, offset, count); + if (result != 0) + { + currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result); + } + else if (GetCrc() != readStream.CurrentCrc && count != 0) + { + // NOTE: we use the last FileHeader in a multipart volume to check CRC + throw new InvalidFormatException("file crc mismatch"); + } + + return result; + } +} diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index 2ad3c9b8..0bc2e054 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -4,133 +4,131 @@ using System; using System.IO; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar +namespace SharpCompress.Compressors.Rar; + +internal class RarStream : Stream { - internal class RarStream : Stream + private readonly IRarUnpack unpack; + private readonly FileHeader fileHeader; + private readonly Stream readStream; + + private bool fetch; + + private byte[] tmpBuffer = new byte[65536]; + private int tmpOffset; + private int tmpCount; + + private byte[] outBuffer; + private int outOffset; + private int outCount; + private int outTotal; + private bool isDisposed; + private long _position; + + public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) { - private readonly IRarUnpack unpack; - private readonly FileHeader fileHeader; - private readonly Stream readStream; + this.unpack = unpack; + this.fileHeader = fileHeader; + this.readStream = readStream; + fetch = true; + unpack.DoUnpack(fileHeader, readStream, this); + fetch = false; + _position = 0; + } - private bool fetch; - - private byte[] tmpBuffer = new byte[65536]; - private int tmpOffset; - private int tmpCount; - - private byte[] outBuffer; - private int outOffset; - private int outCount; - private int outTotal; - private bool isDisposed; - private long _position; - - public RarStream(IRarUnpack unpack, FileHeader fileHeader, Stream readStream) + protected override void Dispose(bool disposing) + { + if (!isDisposed) { - this.unpack = unpack; - this.fileHeader = fileHeader; - this.readStream = readStream; + isDisposed = true; + base.Dispose(disposing); + readStream.Dispose(); + } + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() { } + + public override long Length => fileHeader.UncompressedSize; + + //commented out code always returned the length of the file + public override long Position + { + get => _position; /* fileHeader.UncompressedSize - unpack.DestSize;*/ + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + outTotal = 0; + if (tmpCount > 0) + { + var toCopy = tmpCount < count ? tmpCount : count; + Buffer.BlockCopy(tmpBuffer, tmpOffset, buffer, offset, toCopy); + tmpOffset += toCopy; + tmpCount -= toCopy; + offset += toCopy; + count -= toCopy; + outTotal += toCopy; + } + if (count > 0 && unpack.DestSize > 0) + { + outBuffer = buffer; + outOffset = offset; + outCount = count; fetch = true; - unpack.DoUnpack(fileHeader, readStream, this); + unpack.DoUnpack(); fetch = false; - _position = 0; } + _position += outTotal; + return outTotal; + } - protected override void Dispose(bool disposing) - { - if (!isDisposed) - { - isDisposed = true; - base.Dispose(disposing); - readStream.Dispose(); - } - } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override bool CanRead => true; + public override void SetLength(long value) => throw new NotSupportedException(); - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - } - - public override long Length => fileHeader.UncompressedSize; - - //commented out code always returned the length of the file - public override long Position { get => _position; /* fileHeader.UncompressedSize - unpack.DestSize;*/ set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - outTotal = 0; - if (tmpCount > 0) - { - int toCopy = tmpCount < count ? tmpCount : count; - Buffer.BlockCopy(tmpBuffer, tmpOffset, buffer, offset, toCopy); - tmpOffset += toCopy; - tmpCount -= toCopy; - offset += toCopy; - count -= toCopy; - outTotal += toCopy; - } - if (count > 0 && unpack.DestSize > 0) - { - outBuffer = buffer; - outOffset = offset; - outCount = count; - fetch = true; - unpack.DoUnpack(); - fetch = false; - } - _position += (long)outTotal; - return outTotal; - } - - public override long Seek(long offset, SeekOrigin origin) + public override void Write(byte[] buffer, int offset, int count) + { + if (!fetch) { throw new NotSupportedException(); } - - public override void SetLength(long value) + if (outCount > 0) { - throw new NotSupportedException(); + var toCopy = outCount < count ? outCount : count; + Buffer.BlockCopy(buffer, offset, outBuffer, outOffset, toCopy); + outOffset += toCopy; + outCount -= toCopy; + offset += toCopy; + count -= toCopy; + outTotal += toCopy; } - - public override void Write(byte[] buffer, int offset, int count) + if (count > 0) { - if (!fetch) + if (tmpBuffer.Length < tmpCount + count) { - throw new NotSupportedException(); - } - if (outCount > 0) - { - int toCopy = outCount < count ? outCount : count; - Buffer.BlockCopy(buffer, offset, outBuffer, outOffset, toCopy); - outOffset += toCopy; - outCount -= toCopy; - offset += toCopy; - count -= toCopy; - outTotal += toCopy; - } - if (count > 0) - { - if (tmpBuffer.Length < tmpCount + count) - { - byte[] newBuffer = - new byte[tmpBuffer.Length * 2 > tmpCount + count ? tmpBuffer.Length * 2 : tmpCount + count]; - Buffer.BlockCopy(tmpBuffer, 0, newBuffer, 0, tmpCount); - tmpBuffer = newBuffer; - } - Buffer.BlockCopy(buffer, offset, tmpBuffer, tmpCount, count); - tmpCount += count; - tmpOffset = 0; - unpack.Suspended = true; - } - else - { - unpack.Suspended = false; + var newBuffer = new byte[ + tmpBuffer.Length * 2 > tmpCount + count + ? tmpBuffer.Length * 2 + : tmpCount + count + ]; + Buffer.BlockCopy(tmpBuffer, 0, newBuffer, 0, tmpCount); + tmpBuffer = newBuffer; } + Buffer.BlockCopy(buffer, offset, tmpBuffer, tmpCount, count); + tmpCount += count; + tmpOffset = 0; + unpack.Suspended = true; + } + else + { + unpack.Suspended = false; } } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs index c2510617..ade62f55 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/AudioVariables.cs @@ -1,26 +1,22 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class AudioVariables { - internal class AudioVariables - { - internal AudioVariables() - { - Dif = new int[11]; - } + internal AudioVariables() => Dif = new int[11]; - internal int[] Dif { get; } - internal int ByteCount { get; set; } - internal int D1 { get; set; } + internal int[] Dif { get; } + internal int ByteCount { get; set; } + internal int D1 { get; set; } - internal int D2 { get; set; } - internal int D3 { get; set; } - internal int D4 { get; set; } + internal int D2 { get; set; } + internal int D3 { get; set; } + internal int D4 { get; set; } - internal int K1 { get; set; } - internal int K2 { get; set; } - internal int K3 { get; set; } - internal int K4 { get; set; } - internal int K5 { get; set; } - internal int LastChar { get; set; } - internal int LastDelta { get; set; } - } -} \ No newline at end of file + internal int K1 { get; set; } + internal int K2 { get; set; } + internal int K3 { get; set; } + internal int K4 { get; set; } + internal int K5 { get; set; } + internal int LastChar { get; set; } + internal int LastDelta { get; set; } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs index a8d499b5..d61ce17a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/BitDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class BitDecode : Decode { - internal class BitDecode : Decode - { - internal BitDecode() - : base(new int[PackDef.BC]) - { - } - } -} \ No newline at end of file + internal BitDecode() : base(new int[PackDef.BC]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs index e9703ff1..0ec7943d 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/CodeType.cs @@ -1,15 +1,14 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal enum CodeType { - internal enum CodeType - { - CODE_HUFFMAN, - CODE_LZ, - CODE_LZ2, - CODE_REPEATLZ, - CODE_CACHELZ, - CODE_STARTFILE, - CODE_ENDFILE, - CODE_VM, - CODE_VMDATA - } -} \ No newline at end of file + CODE_HUFFMAN, + CODE_LZ, + CODE_LZ2, + CODE_REPEATLZ, + CODE_CACHELZ, + CODE_STARTFILE, + CODE_ENDFILE, + CODE_VM, + CODE_VMDATA +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs index ceed74de..44ce0333 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/Decode.cs @@ -1,34 +1,30 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class Decode { - internal class Decode + internal Decode() : this(new int[2]) { } + + protected Decode(int[] customDecodeNum) { - internal Decode() - : this(new int[2]) - { - } - - protected Decode(int[] customDecodeNum) - { - DecodeLen = new int[16]; - DecodePos = new int[16]; - DecodeNum = customDecodeNum; - } - - /// returns the decode Length array - /// decodeLength - /// - internal int[] DecodeLen { get; } - - /// returns the decode num array - /// decodeNum - /// - internal int[] DecodeNum { get; } - - /// returns the decodePos array - /// decodePos - /// - internal int[] DecodePos { get; } - - internal int MaxNum { get; set; } + DecodeLen = new int[16]; + DecodePos = new int[16]; + DecodeNum = customDecodeNum; } -} \ No newline at end of file + + /// returns the decode Length array + /// decodeLength + /// + internal int[] DecodeLen { get; } + + /// returns the decode num array + /// decodeNum + /// + internal int[] DecodeNum { get; } + + /// returns the decodePos array + /// decodePos + /// + internal int[] DecodePos { get; } + + internal int MaxNum { get; set; } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs index 1a0b666b..7b3c29b5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/DistDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class DistDecode : Decode { - internal class DistDecode : Decode - { - internal DistDecode() - : base(new int[PackDef.DC]) - { - } - } -} \ No newline at end of file + internal DistDecode() : base(new int[PackDef.DC]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs index 32166837..67e38c45 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/FilterType.cs @@ -1,10 +1,16 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal enum FilterType : byte { - internal enum FilterType : byte - { - // These values must not be changed, because we use them directly - // in RAR5 compression and decompression code. - FILTER_DELTA = 0, FILTER_E8, FILTER_E8E9, FILTER_ARM, - FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE - } -} \ No newline at end of file + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + FILTER_DELTA = 0, + FILTER_E8, + FILTER_E8E9, + FILTER_ARM, + FILTER_AUDIO, + FILTER_RGB, + FILTER_ITANIUM, + FILTER_PPM, + FILTER_NONE +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs index 8316c261..b45eaf8d 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LitDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class LitDecode : Decode { - internal class LitDecode : Decode - { - internal LitDecode() - : base(new int[PackDef.NC]) - { - } - } -} \ No newline at end of file + internal LitDecode() : base(new int[PackDef.NC]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs index a2095bd4..c438a3af 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/LowDistDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class LowDistDecode : Decode { - internal class LowDistDecode : Decode - { - internal LowDistDecode() - : base(new int[PackDef.LDC]) - { - } - } -} \ No newline at end of file + internal LowDistDecode() : base(new int[PackDef.LDC]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs index e6df223c..6ef48ceb 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/MultDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class MultDecode : Decode { - internal class MultDecode : Decode - { - internal MultDecode() - : base(new int[PackDef.MC20]) - { - } - } -} \ No newline at end of file + internal MultDecode() : base(new int[PackDef.MC20]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs index 163ba5f3..bed8d3d0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/PackDef.cs @@ -1,48 +1,48 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal static class PackDef { - internal static class PackDef - { - // 20171217 NOTE: these contants are gone from unrar src code - // seems to be more dynamic - public const int MAXWINSIZE = 0x400000; - public const int MAXWINMASK = MAXWINSIZE - 1; + // 20171217 NOTE: these contants are gone from unrar src code + // seems to be more dynamic + public const int MAXWINSIZE = 0x400000; + public const int MAXWINMASK = MAXWINSIZE - 1; - public const uint MAX_LZ_MATCH = 0x1001; - public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. - public const int LOW_DIST_REP_COUNT = 16; + public const uint MAX_LZ_MATCH = 0x1001; + public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + public const int LOW_DIST_REP_COUNT = 16; - public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC = 60; - public const int LDC = 17; - public const int RC = 28; - // 20171217: NOTE: these constants seem to have been updated in the unrar src code - // at some unknown point. updating causes decompression failure, not sure why. - // public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ - // public const int DC = 64; - // public const int LDC = 16; - // public const int RC = 44; - public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; - public const int BC = 20; + public const int NC = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC = 60; + public const int LDC = 17; + public const int RC = 28; - public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const uint DC30 = 60; - public const uint LDC30 = 17; - public const uint RC30 = 28; - public const uint BC30 = 20; - public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + // 20171217: NOTE: these constants seem to have been updated in the unrar src code + // at some unknown point. updating causes decompression failure, not sure why. + // public const int NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ + // public const int DC = 64; + // public const int LDC = 16; + // public const int RC = 44; + public const int HUFF_TABLE_SIZE = NC + DC + RC + LDC; + public const int BC = 20; - public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const int DC20 = 48; - public const int RC20 = 28; - public const int BC20 = 19; - public const int MC20 = 257; + public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC30 = 60; + public const uint LDC30 = 17; + public const uint RC30 = 28; + public const uint BC30 = 20; + public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; - // Largest alphabet size among all values listed above. - public const uint LARGEST_TABLE_SIZE = 306; + public const int NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const int DC20 = 48; + public const int RC20 = 28; + public const int BC20 = 19; + public const int MC20 = 257; - //public enum { - // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, - // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA - //} - } -} \ No newline at end of file + // Largest alphabet size among all values listed above. + public const uint LARGEST_TABLE_SIZE = 306; + + //public enum { + // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + //} +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs index 24d72d16..d13914b3 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Decode/RepDecode.cs @@ -1,10 +1,6 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.Decode +namespace SharpCompress.Compressors.Rar.UnpackV1.Decode; + +internal class RepDecode : Decode { - internal class RepDecode : Decode - { - internal RepDecode() - : base(new int[PackDef.RC]) - { - } - } -} \ No newline at end of file + internal RepDecode() : base(new int[PackDef.RC]) { } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs index b3f79fdc..3b4a9372 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/PPM/BlockTypes.cs @@ -1,8 +1,7 @@ -namespace SharpCompress.Compressors.Rar.UnpackV1.PPM +namespace SharpCompress.Compressors.Rar.UnpackV1.PPM; + +internal enum BlockTypes { - internal enum BlockTypes - { - BLOCK_LZ = 0, - BLOCK_PPM = 1 - } -} \ No newline at end of file + BLOCK_LZ = 0, + BLOCK_PPM = 1 +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index cc33b25f..2ca62d6b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -10,1267 +10,1312 @@ using SharpCompress.Compressors.Rar.UnpackV1.Decode; using SharpCompress.Compressors.Rar.UnpackV1.PPM; using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal sealed partial class Unpack : BitInput, IRarUnpack { - internal sealed partial class Unpack : BitInput, IRarUnpack + private readonly BitInput Inp; + + public Unpack() => + // to ease in porting Unpack50.cs + Inp = this; + + public bool FileExtracted { get; private set; } + + public long DestSize { - private readonly BitInput Inp; - - public Unpack() + get => destUnpSize; + set { - // to ease in porting Unpack50.cs - Inp = this; + destUnpSize = value; + FileExtracted = false; } + } + public bool Suspended + { + get => suspended; + set => suspended = value; + } - public bool FileExtracted { get; private set; } - - public long DestSize + public int Char + { + get { - get => destUnpSize; - set + if (inAddr > MAX_SIZE - 30) { - destUnpSize = value; - FileExtracted = false; + unpReadBuf(); } + return (InBuf[inAddr++] & 0xff); } + } - public bool Suspended + public int PpmEscChar { get; set; } + + private readonly ModelPpm ppm = new ModelPpm(); + + private readonly RarVM rarVM = new RarVM(); + + // Filters code, one entry per filter + private readonly List filters = new List(); + + // Filters stack, several entrances of same filter are possible + private readonly List prgStack = new List(); + + // lengths of preceding blocks, one length per filter. Used to reduce size + // required to write block length if lengths are repeating + private readonly List oldFilterLengths = new List(); + + private int lastFilter; + + private bool tablesRead; + + private readonly byte[] unpOldTable = new byte[PackDef.HUFF_TABLE_SIZE]; + + private BlockTypes unpBlockType; + + //private bool externalWindow; + + private long writtenFileSize; + + private bool ppmError; + + private int prevLowDist; + + private int lowDistRepCount; + + private static readonly int[] DBitLengthCounts = + { + 4, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 14, + 0, + 12 + }; + + private FileHeader fileHeader; + + private void Init(byte[] window) + { + if (window is null) { - get => suspended; - set => suspended = value; + this.window = new byte[PackDef.MAXWINSIZE]; } - - public int Char + else { - get + this.window = window; + + //externalWindow = true; + } + inAddr = 0; + UnpInitData(false); + } + + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + { + destUnpSize = fileHeader.UncompressedSize; + this.fileHeader = fileHeader; + this.readStream = readStream; + this.writeStream = writeStream; + if (!fileHeader.IsSolid) + { + Init(null); + } + suspended = false; + DoUnpack(); + } + + public void DoUnpack() + { + if (fileHeader.CompressionMethod == 0) + { + UnstoreFile(); + return; + } + switch (fileHeader.CompressionAlgorithm) + { + case 15: // rar 1.5 compression + unpack15(fileHeader.IsSolid); + break; + + case 20: // rar 2.x compression + case 26: // files larger than 2GB + unpack20(fileHeader.IsSolid); + break; + + case 29: // rar 3.x compression + case 36: // alternative hash + Unpack29(fileHeader.IsSolid); + break; + + case 50: // rar 5.x compression + Unpack5(fileHeader.IsSolid); + break; + + default: + throw new InvalidFormatException( + "unknown rar compression version " + fileHeader.CompressionAlgorithm + ); + } + } + + private void UnstoreFile() + { + var buffer = new byte[0x10000]; + while (true) + { + var code = readStream.Read(buffer, 0, (int)Math.Min(buffer.Length, destUnpSize)); + if (code == 0 || code == -1) { - if (inAddr > MAX_SIZE - 30) - { - unpReadBuf(); - } - return (InBuf[inAddr++] & 0xff); + break; } - } - - public int PpmEscChar { get; set; } - - private readonly ModelPpm ppm = new ModelPpm(); - - private readonly RarVM rarVM = new RarVM(); - - // Filters code, one entry per filter - private readonly List filters = new List(); - - // Filters stack, several entrances of same filter are possible - private readonly List prgStack = new List(); - - // lengths of preceding blocks, one length per filter. Used to reduce size - // required to write block length if lengths are repeating - private readonly List oldFilterLengths = new List(); - - private int lastFilter; - - private bool tablesRead; - - private readonly byte[] unpOldTable = new byte[PackDef.HUFF_TABLE_SIZE]; - - private BlockTypes unpBlockType; - - //private bool externalWindow; - - private long writtenFileSize; - - private bool ppmError; - - private int prevLowDist; - - private int lowDistRepCount; - - private static readonly int[] DBitLengthCounts = { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12 }; - - private FileHeader fileHeader; - - private void Init(byte[] window) - { - if (window is null) + code = code < destUnpSize ? code : (int)destUnpSize; + writeStream.Write(buffer, 0, code); + if (destUnpSize >= 0) { - this.window = new byte[PackDef.MAXWINSIZE]; + destUnpSize -= code; } - else + if (suspended) { - this.window = window; - - //externalWindow = true; - } - inAddr = 0; - UnpInitData(false); - } - - public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) - { - destUnpSize = fileHeader.UncompressedSize; - this.fileHeader = fileHeader; - this.readStream = readStream; - this.writeStream = writeStream; - if (!fileHeader.IsSolid) - { - Init(null); - } - suspended = false; - DoUnpack(); - } - - public void DoUnpack() - { - if (fileHeader.CompressionMethod == 0) - { - UnstoreFile(); return; } - switch (fileHeader.CompressionAlgorithm) + } + } + + private void Unpack29(bool solid) + { + var DDecode = new int[PackDef.DC]; + var DBits = new byte[PackDef.DC]; + + int Bits; + + if (DDecode[1] == 0) + { + int Dist = 0, + BitLength = 0, + Slot = 0; + for (var I = 0; I < DBitLengthCounts.Length; I++, BitLength++) { - case 15: // rar 1.5 compression - unpack15(fileHeader.IsSolid); - break; - - case 20: // rar 2.x compression - case 26: // files larger than 2GB - unpack20(fileHeader.IsSolid); - break; - - case 29: // rar 3.x compression - case 36: // alternative hash - Unpack29(fileHeader.IsSolid); - break; - - case 50: // rar 5.x compression - Unpack5(fileHeader.IsSolid); - break; - - default: - throw new InvalidFormatException("unknown rar compression version " + fileHeader.CompressionAlgorithm); + var count = DBitLengthCounts[I]; + for (var J = 0; J < count; J++, Slot++, Dist += (1 << BitLength)) + { + DDecode[Slot] = Dist; + DBits[Slot] = (byte)BitLength; + } } } - private void UnstoreFile() + FileExtracted = true; + + if (!suspended) { - byte[] buffer = new byte[0x10000]; - while (true) + UnpInitData(solid); + if (!unpReadBuf()) { - int code = readStream.Read(buffer, 0, (int)Math.Min(buffer.Length, destUnpSize)); - if (code == 0 || code == -1) + return; + } + if ((!solid || !tablesRead) && !ReadTables()) + { + return; + } + } + + if (ppmError) + { + return; + } + + while (true) + { + unpPtr &= PackDef.MAXWINMASK; + + if (inAddr > readBorder) + { + if (!unpReadBuf()) { break; } - code = code < destUnpSize ? code : (int)destUnpSize; - writeStream.Write(buffer, 0, code); - if (destUnpSize >= 0) + } + + // System.out.println(((wrPtr - unpPtr) & + // Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr); + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) + { + UnpWriteBuf(); + if (destUnpSize <= 0) { - destUnpSize -= code; + return; } if (suspended) { + FileExtracted = false; return; } } - } - - private void Unpack29(bool solid) - { - int[] DDecode = new int[PackDef.DC]; - byte[] DBits = new byte[PackDef.DC]; - - int Bits; - - if (DDecode[1] == 0) + if (unpBlockType == BlockTypes.BLOCK_PPM) { - int Dist = 0, BitLength = 0, Slot = 0; - for (int I = 0; I < DBitLengthCounts.Length; I++, BitLength++) + var Ch = ppm.DecodeChar(); + if (Ch == -1) { - int count = DBitLengthCounts[I]; - for (int J = 0; J < count; J++, Slot++, Dist += (1 << BitLength)) - { - DDecode[Slot] = Dist; - DBits[Slot] = (byte)BitLength; - } + ppmError = true; + break; } - } - - FileExtracted = true; - - if (!suspended) - { - UnpInitData(solid); - if (!unpReadBuf()) + if (Ch == PpmEscChar) { - return; - } - if ((!solid || !tablesRead) && !ReadTables()) - { - return; - } - } - - if (ppmError) - { - return; - } - - while (true) - { - unpPtr &= PackDef.MAXWINMASK; - - if (inAddr > readBorder) - { - if (!unpReadBuf()) + var NextCh = ppm.DecodeChar(); + if (NextCh == 0) { - break; - } - } - - // System.out.println(((wrPtr - unpPtr) & - // Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr); - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) - { - UnpWriteBuf(); - if (destUnpSize <= 0) - { - return; - } - if (suspended) - { - FileExtracted = false; - return; - } - } - if (unpBlockType == BlockTypes.BLOCK_PPM) - { - int Ch = ppm.DecodeChar(); - if (Ch == -1) - { - ppmError = true; - break; - } - if (Ch == PpmEscChar) - { - int NextCh = ppm.DecodeChar(); - if (NextCh == 0) - { - if (!ReadTables()) - { - break; - } - continue; - } - if (NextCh == 2 || NextCh == -1) + if (!ReadTables()) { break; } - if (NextCh == 3) + continue; + } + if (NextCh == 2 || NextCh == -1) + { + break; + } + if (NextCh == 3) + { + if (!ReadVMCodePPM()) { - if (!ReadVMCodePPM()) - { - break; - } - continue; + break; } - if (NextCh == 4) + continue; + } + if (NextCh == 4) + { + int Distance = 0, + Length = 0; + var failed = false; + for (var I = 0; I < 4 && !failed; I++) { - int Distance = 0, Length = 0; - bool failed = false; - for (int I = 0; I < 4 && !failed; I++) + var ch = ppm.DecodeChar(); + if (ch == -1) { - int ch = ppm.DecodeChar(); - if (ch == -1) + failed = true; + } + else + { + if (I == 3) { - failed = true; + // Bug fixed + Length = ch & 0xff; } else { - if (I == 3) - { - // Bug fixed - Length = ch & 0xff; - } - else - { - // Bug fixed - Distance = (Distance << 8) + (ch & 0xff); - } + // Bug fixed + Distance = (Distance << 8) + (ch & 0xff); } } - if (failed) - { - break; - } - CopyString(Length + 32, Distance + 2); - continue; } - if (NextCh == 5) + if (failed) { - int Length = ppm.DecodeChar(); - if (Length == -1) - { - break; - } - CopyString(Length + 4, 1); - continue; + break; } + CopyString(Length + 32, Distance + 2); + continue; } - window[unpPtr++] = (byte)Ch; - continue; + if (NextCh == 5) + { + var Length = ppm.DecodeChar(); + if (Length == -1) + { + break; + } + CopyString(Length + 4, 1); + continue; + } + } + window[unpPtr++] = (byte)Ch; + continue; + } + + var Number = this.decodeNumber(LD); + if (Number < 256) + { + window[unpPtr++] = (byte)Number; + continue; + } + if (Number >= 271) + { + var Length = LDecode[Number -= 271] + 3; + if ((Bits = LBits[Number]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); } - int Number = this.decodeNumber(LD); - if (Number < 256) + var DistNumber = this.decodeNumber(DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) { - window[unpPtr++] = (byte)Number; - continue; - } - if (Number >= 271) - { - int Length = LDecode[Number -= 271] + 3; - if ((Bits = LBits[Number]) > 0) + if (DistNumber > 9) { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - - int DistNumber = this.decodeNumber(DD); - int Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - if (DistNumber > 9) + if (Bits > 4) { - if (Bits > 4) + Distance += ((Utility.URShift(GetBits(), (20 - Bits))) << 4); + AddBits(Bits - 4); + } + if (lowDistRepCount > 0) + { + lowDistRepCount--; + Distance += prevLowDist; + } + else + { + var LowDist = this.decodeNumber(LDD); + if (LowDist == 16) { - Distance += ((Utility.URShift(GetBits(), (20 - Bits))) << 4); - AddBits(Bits - 4); - } - if (lowDistRepCount > 0) - { - lowDistRepCount--; + lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; Distance += prevLowDist; } else { - int LowDist = this.decodeNumber(LDD); - if (LowDist == 16) - { - lowDistRepCount = PackDef.LOW_DIST_REP_COUNT - 1; - Distance += prevLowDist; - } - else - { - Distance += LowDist; - prevLowDist = LowDist; - } + Distance += LowDist; + prevLowDist = LowDist; } } - else - { - Distance += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } } - - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000L) - { - Length++; - } - } - - InsertOldDist(Distance); - InsertLastMatch(Length, Distance); - - CopyString(Length, Distance); - continue; - } - if (Number == 256) - { - if (!ReadEndOfBlock()) - { - break; - } - continue; - } - if (Number == 257) - { - if (!ReadVMCode()) - { - break; - } - continue; - } - if (Number == 258) - { - if (lastLength != 0) - { - CopyString(lastLength, lastDist); - } - continue; - } - if (Number < 263) - { - int DistNum = Number - 259; - int Distance = oldDist[DistNum]; - for (int I = DistNum; I > 0; I--) - { - oldDist[I] = oldDist[I - 1]; - } - oldDist[0] = Distance; - - int LengthNumber = this.decodeNumber(RD); - int Length = LDecode[LengthNumber] + 2; - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - InsertLastMatch(Length, Distance); - CopyString(Length, Distance); - continue; - } - if (Number < 272) - { - int Distance = SDDecode[Number -= 263] + 1; - if ((Bits = SDBits[Number]) > 0) + else { Distance += Utility.URShift(GetBits(), (16 - Bits)); AddBits(Bits); } - InsertOldDist(Distance); - InsertLastMatch(2, Distance); - CopyString(2, Distance); } - } - UnpWriteBuf(); - } - private void UnpWriteBuf() - { - int WrittenBorder = wrPtr; - int WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; - for (int I = 0; I < prgStack.Count; I++) - { - UnpackFilter flt = prgStack[I]; - if (flt is null) + if (Distance >= 0x2000) { - continue; - } - if (flt.NextWindow) - { - flt.NextWindow = false; // ->NextWindow=false; - continue; - } - int BlockStart = flt.BlockStart; // ->BlockStart; - int BlockLength = flt.BlockLength; // ->BlockLength; - if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) - { - if (WrittenBorder != BlockStart) + Length++; + if (Distance >= 0x40000L) { - UnpWriteArea(WrittenBorder, BlockStart); - WrittenBorder = BlockStart; - WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + Length++; } - if (BlockLength <= WriteSize) + } + + InsertOldDist(Distance); + InsertLastMatch(Length, Distance); + + CopyString(Length, Distance); + continue; + } + if (Number == 256) + { + if (!ReadEndOfBlock()) + { + break; + } + continue; + } + if (Number == 257) + { + if (!ReadVMCode()) + { + break; + } + continue; + } + if (Number == 258) + { + if (lastLength != 0) + { + CopyString(lastLength, lastDist); + } + continue; + } + if (Number < 263) + { + var DistNum = Number - 259; + var Distance = oldDist[DistNum]; + for (var I = DistNum; I > 0; I--) + { + oldDist[I] = oldDist[I - 1]; + } + oldDist[0] = Distance; + + var LengthNumber = this.decodeNumber(RD); + var Length = LDecode[LengthNumber] + 2; + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + InsertLastMatch(Length, Distance); + CopyString(Length, Distance); + continue; + } + if (Number < 272) + { + var Distance = SDDecode[Number -= 263] + 1; + if ((Bits = SDBits[Number]) > 0) + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + InsertOldDist(Distance); + InsertLastMatch(2, Distance); + CopyString(2, Distance); + } + } + UnpWriteBuf(); + } + + private void UnpWriteBuf() + { + var WrittenBorder = wrPtr; + var WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + for (var I = 0; I < prgStack.Count; I++) + { + var flt = prgStack[I]; + if (flt is null) + { + continue; + } + if (flt.NextWindow) + { + flt.NextWindow = false; // ->NextWindow=false; + continue; + } + var BlockStart = flt.BlockStart; // ->BlockStart; + var BlockLength = flt.BlockLength; // ->BlockLength; + if (((BlockStart - WrittenBorder) & PackDef.MAXWINMASK) < WriteSize) + { + if (WrittenBorder != BlockStart) + { + UnpWriteArea(WrittenBorder, BlockStart); + WrittenBorder = BlockStart; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + } + if (BlockLength <= WriteSize) + { + var BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; + if (BlockStart < BlockEnd || BlockEnd == 0) { - int BlockEnd = (BlockStart + BlockLength) & PackDef.MAXWINMASK; - if (BlockStart < BlockEnd || BlockEnd == 0) - { - // VM.SetMemory(0,Window+BlockStart,BlockLength); - rarVM.setMemory(0, window, BlockStart, BlockLength); - } - else - { - int FirstPartLength = PackDef.MAXWINSIZE - BlockStart; - - // VM.SetMemory(0,Window+BlockStart,FirstPartLength); - rarVM.setMemory(0, window, BlockStart, FirstPartLength); - - // VM.SetMemory(FirstPartLength,Window,BlockEnd); - rarVM.setMemory(FirstPartLength, window, 0, BlockEnd); - } - - VMPreparedProgram ParentPrg = filters[flt.ParentFilter].Program; - VMPreparedProgram Prg = flt.Program; - - if (ParentPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - // copy global data from previous script execution if - // any - // Prg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); - // memcpy(&Prg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); - Prg.GlobalData.Clear(); - for (int i = 0; i < ParentPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) - { - Prg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - ParentPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - - ExecuteCode(Prg); - - if (Prg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - // save global data for next script execution - if (ParentPrg.GlobalData.Count < Prg.GlobalData.Count) - { - //ParentPrg.GlobalData.Clear(); // ->GlobalData.Alloc(Prg->GlobalData.Size()); - ParentPrg.GlobalData.SetSize(Prg.GlobalData.Count); - } - - // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&Prg->GlobalData[VM_FIXEDGLOBALSIZE],Prg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); - for (int i = 0; i < Prg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) - { - ParentPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - Prg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - else - { - ParentPrg.GlobalData.Clear(); - } - - int FilteredDataOffset = Prg.FilteredDataOffset; - int FilteredDataSize = Prg.FilteredDataSize; - byte[] FilteredData = new byte[FilteredDataSize]; - - for (int i = 0; i < FilteredDataSize; i++) - { - FilteredData[i] = rarVM.Mem[FilteredDataOffset + i]; - - // Prg.GlobalData.get(FilteredDataOffset - // + - // i); - } - - prgStack[I] = null; - while (I + 1 < prgStack.Count) - { - UnpackFilter NextFilter = prgStack[I + 1]; - if (NextFilter is null || NextFilter.BlockStart != BlockStart || - NextFilter.BlockLength != FilteredDataSize || NextFilter.NextWindow) - { - break; - } - - // apply several filters to same data block - - rarVM.setMemory(0, FilteredData, 0, FilteredDataSize); - - // .SetMemory(0,FilteredData,FilteredDataSize); - - VMPreparedProgram pPrg = filters[NextFilter.ParentFilter].Program; - VMPreparedProgram NextPrg = NextFilter.Program; - - if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - // copy global data from previous script execution - // if any - // NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); - NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count); - - // memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); - for (int i = 0; i < pPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) - { - NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - - ExecuteCode(NextPrg); - - if (NextPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) - { - // save global data for next script execution - if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count) - { - pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count); - } - - // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); - for (int i = 0; i < NextPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) - { - pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = - NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i]; - } - } - else - { - pPrg.GlobalData.Clear(); - } - FilteredDataOffset = NextPrg.FilteredDataOffset; - FilteredDataSize = NextPrg.FilteredDataSize; - - FilteredData = new byte[FilteredDataSize]; - for (int i = 0; i < FilteredDataSize; i++) - { - FilteredData[i] = NextPrg.GlobalData[FilteredDataOffset + i]; - } - - I++; - prgStack[I] = null; - } - writeStream.Write(FilteredData, 0, FilteredDataSize); - unpSomeRead = true; - writtenFileSize += FilteredDataSize; - destUnpSize -= FilteredDataSize; - WrittenBorder = BlockEnd; - WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + // VM.SetMemory(0,Window+BlockStart,BlockLength); + rarVM.setMemory(0, window, BlockStart, BlockLength); } else { - for (int J = I; J < prgStack.Count; J++) + var FirstPartLength = PackDef.MAXWINSIZE - BlockStart; + + // VM.SetMemory(0,Window+BlockStart,FirstPartLength); + rarVM.setMemory(0, window, BlockStart, FirstPartLength); + + // VM.SetMemory(FirstPartLength,Window,BlockEnd); + rarVM.setMemory(FirstPartLength, window, 0, BlockEnd); + } + + var ParentPrg = filters[flt.ParentFilter].Program; + var Prg = flt.Program; + + if (ParentPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + // copy global data from previous script execution if + // any + // Prg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); + // memcpy(&Prg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + Prg.GlobalData.Clear(); + for ( + var i = 0; + i < ParentPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) { - UnpackFilter filt = prgStack[J]; - if (filt != null && filt.NextWindow) + Prg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = ParentPrg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; + } + } + + ExecuteCode(Prg); + + if (Prg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + // save global data for next script execution + if (ParentPrg.GlobalData.Count < Prg.GlobalData.Count) + { + //ParentPrg.GlobalData.Clear(); // ->GlobalData.Alloc(Prg->GlobalData.Size()); + ParentPrg.GlobalData.SetSize(Prg.GlobalData.Count); + } + + // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&Prg->GlobalData[VM_FIXEDGLOBALSIZE],Prg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + for (var i = 0; i < Prg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; i++) + { + ParentPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = Prg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; + } + } + else + { + ParentPrg.GlobalData.Clear(); + } + + var FilteredDataOffset = Prg.FilteredDataOffset; + var FilteredDataSize = Prg.FilteredDataSize; + var FilteredData = new byte[FilteredDataSize]; + + for (var i = 0; i < FilteredDataSize; i++) + { + FilteredData[i] = rarVM.Mem[FilteredDataOffset + i]; + + // Prg.GlobalData.get(FilteredDataOffset + // + + // i); + } + + prgStack[I] = null; + while (I + 1 < prgStack.Count) + { + var NextFilter = prgStack[I + 1]; + if ( + NextFilter is null + || NextFilter.BlockStart != BlockStart + || NextFilter.BlockLength != FilteredDataSize + || NextFilter.NextWindow + ) + { + break; + } + + // apply several filters to same data block + + rarVM.setMemory(0, FilteredData, 0, FilteredDataSize); + + // .SetMemory(0,FilteredData,FilteredDataSize); + + var pPrg = filters[NextFilter.ParentFilter].Program; + var NextPrg = NextFilter.Program; + + if (pPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + // copy global data from previous script execution + // if any + // NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); + NextPrg.GlobalData.SetSize(pPrg.GlobalData.Count); + + // memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + for ( + var i = 0; + i < pPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) { - filt.NextWindow = false; + NextPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = pPrg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; } } - wrPtr = WrittenBorder; - return; + + ExecuteCode(NextPrg); + + if (NextPrg.GlobalData.Count > RarVM.VM_FIXEDGLOBALSIZE) + { + // save global data for next script execution + if (pPrg.GlobalData.Count < NextPrg.GlobalData.Count) + { + pPrg.GlobalData.SetSize(NextPrg.GlobalData.Count); + } + + // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + for ( + var i = 0; + i < NextPrg.GlobalData.Count - RarVM.VM_FIXEDGLOBALSIZE; + i++ + ) + { + pPrg.GlobalData[RarVM.VM_FIXEDGLOBALSIZE + i] = NextPrg.GlobalData[ + RarVM.VM_FIXEDGLOBALSIZE + i + ]; + } + } + else + { + pPrg.GlobalData.Clear(); + } + FilteredDataOffset = NextPrg.FilteredDataOffset; + FilteredDataSize = NextPrg.FilteredDataSize; + + FilteredData = new byte[FilteredDataSize]; + for (var i = 0; i < FilteredDataSize; i++) + { + FilteredData[i] = NextPrg.GlobalData[FilteredDataOffset + i]; + } + + I++; + prgStack[I] = null; } + writeStream.Write(FilteredData, 0, FilteredDataSize); + unpSomeRead = true; + writtenFileSize += FilteredDataSize; + destUnpSize -= FilteredDataSize; + WrittenBorder = BlockEnd; + WriteSize = (unpPtr - WrittenBorder) & PackDef.MAXWINMASK; + } + else + { + for (var J = I; J < prgStack.Count; J++) + { + var filt = prgStack[J]; + if (filt != null && filt.NextWindow) + { + filt.NextWindow = false; + } + } + wrPtr = WrittenBorder; + return; } } - - UnpWriteArea(WrittenBorder, unpPtr); - wrPtr = unpPtr; } - private void UnpWriteArea(int startPtr, int endPtr) + UnpWriteArea(WrittenBorder, unpPtr); + wrPtr = unpPtr; + } + + private void UnpWriteArea(int startPtr, int endPtr) + { + if (endPtr != startPtr) { - if (endPtr != startPtr) - { - unpSomeRead = true; - } - if (endPtr < startPtr) - { - UnpWriteData(window, startPtr, -startPtr & PackDef.MAXWINMASK); - UnpWriteData(window, 0, endPtr); - unpAllBuf = true; - } - else - { - UnpWriteData(window, startPtr, endPtr - startPtr); - } + unpSomeRead = true; } - - private void UnpWriteData(byte[] data, int offset, int size) + if (endPtr < startPtr) { - if (destUnpSize <= 0) - { - return; - } - int writeSize = size; - if (writeSize > destUnpSize) - { - writeSize = (int)destUnpSize; - } - writeStream.Write(data, offset, writeSize); - - writtenFileSize += size; - destUnpSize -= size; + UnpWriteData(window, startPtr, -startPtr & PackDef.MAXWINMASK); + UnpWriteData(window, 0, endPtr); + unpAllBuf = true; } - - private void InsertOldDist(uint distance) + else { - // TODO uint - InsertOldDist((int)distance); + UnpWriteData(window, startPtr, endPtr - startPtr); } + } - private void InsertOldDist(int distance) + private void UnpWriteData(byte[] data, int offset, int size) + { + if (destUnpSize <= 0) { - oldDist[3] = oldDist[2]; - oldDist[2] = oldDist[1]; - oldDist[1] = oldDist[0]; - oldDist[0] = distance; + return; } - - private void InsertLastMatch(int length, int distance) + var writeSize = size; + if (writeSize > destUnpSize) { - lastDist = distance; - lastLength = length; + writeSize = (int)destUnpSize; } + writeStream.Write(data, offset, writeSize); - private void CopyString(uint length, uint distance) + writtenFileSize += size; + destUnpSize -= size; + } + + private void InsertOldDist(uint distance) => + // TODO uint + InsertOldDist((int)distance); + + private void InsertOldDist(int distance) + { + oldDist[3] = oldDist[2]; + oldDist[2] = oldDist[1]; + oldDist[1] = oldDist[0]; + oldDist[0] = distance; + } + + private void InsertLastMatch(int length, int distance) + { + lastDist = distance; + lastLength = length; + } + + private void CopyString(uint length, uint distance) => + // TODO uint + CopyString((int)length, (int)distance); + + private void CopyString(int length, int distance) + { + // System.out.println("copyString(" + length + ", " + distance + ")"); + + var destPtr = unpPtr - distance; + + // System.out.println(unpPtr+":"+distance); + if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260) { - // TODO uint - CopyString((int)length, (int)distance); - } + window[unpPtr++] = window[destPtr++]; - private void CopyString(int length, int distance) - { - // System.out.println("copyString(" + length + ", " + distance + ")"); - - int destPtr = unpPtr - distance; - - // System.out.println(unpPtr+":"+distance); - if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260) + while (--length > 0) { window[unpPtr++] = window[destPtr++]; + } + } + else + { + while (length-- != 0) + { + window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; + } + } + } - while (--length > 0) + private void UnpInitData(bool solid) + { + if (!solid) + { + tablesRead = false; + new Span(oldDist).Clear(); // memset(oldDist,0,sizeof(OldDist)); + + oldDistPtr = 0; + lastDist = 0; + lastLength = 0; + + new Span(unpOldTable).Clear(); // memset(UnpOldTable,0,sizeof(UnpOldTable)); + + unpPtr = 0; + wrPtr = 0; + PpmEscChar = 2; + WriteBorder = Math.Min(MaxWinSize, UNPACK_MAX_WRITE) & MaxWinMask; + + InitFilters(); + } + InitBitInput(); + ppmError = false; + writtenFileSize = 0; + readTop = 0; + readBorder = 0; + unpInitData20(solid); + } + + //void Unpack::UnpInitData(bool Solid) + //{ + // if (!Solid) + // { + // memset(OldDist,0,sizeof(OldDist)); + // OldDistPtr=0; + // LastDist=LastLength=0; + //// memset(Window,0,MaxWinSize); + // memset(&BlockTables,0,sizeof(BlockTables)); + // UnpPtr=WrPtr=0; + // WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; + // } + // // Filters never share several solid files, so we can safely reset them + // // even in solid archive. + // InitFilters(); + // + // Inp.InitBitInput(); + // WrittenFileSize=0; + // ReadTop=0; + // ReadBorder=0; + // + // memset(&BlockHeader,0,sizeof(BlockHeader)); + // BlockHeader.BlockSize=-1; // '-1' means not defined yet. + //#ifndef SFX_MODULE + // UnpInitData20(Solid); + //#endif + // UnpInitData30(Solid); + // UnpInitData50(Solid); + //} + + private void InitFilters() + { + oldFilterLengths.Clear(); + lastFilter = 0; + + filters.Clear(); + + prgStack.Clear(); + } + + private bool ReadEndOfBlock() + { + var BitField = GetBits(); + bool NewTable, + NewFile = false; + if ((BitField & 0x8000) != 0) + { + NewTable = true; + AddBits(1); + } + else + { + NewFile = true; + NewTable = (BitField & 0x4000) != 0; + AddBits(2); + } + tablesRead = !NewTable; + return !(NewFile || NewTable && !ReadTables()); + } + + private bool ReadTables() + { + var bitLength = new byte[PackDef.BC]; + + var table = new byte[PackDef.HUFF_TABLE_SIZE]; + if (inAddr > readTop - 25) + { + if (!unpReadBuf()) + { + return (false); + } + } + AddBits((8 - inBit) & 7); + long bitField = GetBits() & unchecked((int)0xffFFffFF); + if ((bitField & 0x8000) != 0) + { + unpBlockType = BlockTypes.BLOCK_PPM; + return (ppm.DecodeInit(this, PpmEscChar)); + } + unpBlockType = BlockTypes.BLOCK_LZ; + + prevLowDist = 0; + lowDistRepCount = 0; + + if ((bitField & 0x4000) == 0) + { + new Span(unpOldTable).Clear(); // memset(UnpOldTable,0,sizeof(UnpOldTable)); + } + AddBits(2); + + for (var i = 0; i < PackDef.BC; i++) + { + var length = (Utility.URShift(GetBits(), 12)) & 0xFF; + AddBits(4); + if (length == 15) + { + var zeroCount = (Utility.URShift(GetBits(), 12)) & 0xFF; + AddBits(4); + if (zeroCount == 0) { - window[unpPtr++] = window[destPtr++]; + bitLength[i] = 15; + } + else + { + zeroCount += 2; + while (zeroCount-- > 0 && i < bitLength.Length) + { + bitLength[i++] = 0; + } + i--; } } else { - while (length-- != 0) - { - window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK]; - unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; - } + bitLength[i] = (byte)length; } } - private void UnpInitData(bool solid) + UnpackUtility.makeDecodeTables(bitLength, 0, BD, PackDef.BC); + + var TableSize = PackDef.HUFF_TABLE_SIZE; + + for (var i = 0; i < TableSize; ) { - if (!solid) - { - tablesRead = false; - new Span(oldDist).Clear(); // memset(oldDist,0,sizeof(OldDist)); - - oldDistPtr = 0; - lastDist = 0; - lastLength = 0; - - new Span(unpOldTable).Clear(); // memset(UnpOldTable,0,sizeof(UnpOldTable)); - - unpPtr = 0; - wrPtr = 0; - PpmEscChar = 2; - WriteBorder = Math.Min(MaxWinSize, UNPACK_MAX_WRITE) & MaxWinMask; - - InitFilters(); - } - InitBitInput(); - ppmError = false; - writtenFileSize = 0; - readTop = 0; - readBorder = 0; - unpInitData20(solid); - } - - //void Unpack::UnpInitData(bool Solid) - //{ - // if (!Solid) - // { - // memset(OldDist,0,sizeof(OldDist)); - // OldDistPtr=0; - // LastDist=LastLength=0; - //// memset(Window,0,MaxWinSize); - // memset(&BlockTables,0,sizeof(BlockTables)); - // UnpPtr=WrPtr=0; - // WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask; - // } - // // Filters never share several solid files, so we can safely reset them - // // even in solid archive. - // InitFilters(); - // - // Inp.InitBitInput(); - // WrittenFileSize=0; - // ReadTop=0; - // ReadBorder=0; - // - // memset(&BlockHeader,0,sizeof(BlockHeader)); - // BlockHeader.BlockSize=-1; // '-1' means not defined yet. - //#ifndef SFX_MODULE - // UnpInitData20(Solid); - //#endif - // UnpInitData30(Solid); - // UnpInitData50(Solid); - //} - - private void InitFilters() - { - oldFilterLengths.Clear(); - lastFilter = 0; - - filters.Clear(); - - prgStack.Clear(); - } - - private bool ReadEndOfBlock() - { - int BitField = GetBits(); - bool NewTable, NewFile = false; - if ((BitField & 0x8000) != 0) - { - NewTable = true; - AddBits(1); - } - else - { - NewFile = true; - NewTable = (BitField & 0x4000) != 0; - AddBits(2); - } - tablesRead = !NewTable; - return !(NewFile || NewTable && !ReadTables()); - } - - private bool ReadTables() - { - byte[] bitLength = new byte[PackDef.BC]; - - byte[] table = new byte[PackDef.HUFF_TABLE_SIZE]; - if (inAddr > readTop - 25) + if (inAddr > readTop - 5) { if (!unpReadBuf()) { return (false); } } - AddBits((8 - inBit) & 7); - long bitField = GetBits() & unchecked((int)0xffFFffFF); - if ((bitField & 0x8000) != 0) + var Number = this.decodeNumber(BD); + if (Number < 16) { - unpBlockType = BlockTypes.BLOCK_PPM; - return (ppm.DecodeInit(this, PpmEscChar)); + table[i] = (byte)((Number + unpOldTable[i]) & 0xf); + i++; } - unpBlockType = BlockTypes.BLOCK_LZ; - - prevLowDist = 0; - lowDistRepCount = 0; - - if ((bitField & 0x4000) == 0) + else if (Number < 18) { - new Span(unpOldTable).Clear(); // memset(UnpOldTable,0,sizeof(UnpOldTable)); - } - AddBits(2); - - for (int i = 0; i < PackDef.BC; i++) - { - int length = (Utility.URShift(GetBits(), 12)) & 0xFF; - AddBits(4); - if (length == 15) + int N; + if (Number == 16) { - int zeroCount = (Utility.URShift(GetBits(), 12)) & 0xFF; - AddBits(4); - if (zeroCount == 0) - { - bitLength[i] = 15; - } - else - { - zeroCount += 2; - while (zeroCount-- > 0 && i < bitLength.Length) - { - bitLength[i++] = 0; - } - i--; - } + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); } else { - bitLength[i] = (byte)length; + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); } - } - - UnpackUtility.makeDecodeTables(bitLength, 0, BD, PackDef.BC); - - int TableSize = PackDef.HUFF_TABLE_SIZE; - - for (int i = 0; i < TableSize;) - { - if (inAddr > readTop - 5) + while (N-- > 0 && i < TableSize) { - if (!unpReadBuf()) - { - return (false); - } - } - int Number = this.decodeNumber(BD); - if (Number < 16) - { - table[i] = (byte)((Number + unpOldTable[i]) & 0xf); + table[i] = table[i - 1]; i++; } - else if (Number < 18) + } + else + { + int N; + if (Number == 18) { - int N; - if (Number == 16) - { - N = (Utility.URShift(GetBits(), 13)) + 3; - AddBits(3); - } - else - { - N = (Utility.URShift(GetBits(), 9)) + 11; - AddBits(7); - } - while (N-- > 0 && i < TableSize) - { - table[i] = table[i - 1]; - i++; - } + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); } else { - int N; - if (Number == 18) - { - N = (Utility.URShift(GetBits(), 13)) + 3; - AddBits(3); - } - else - { - N = (Utility.URShift(GetBits(), 9)) + 11; - AddBits(7); - } - while (N-- > 0 && i < TableSize) - { - table[i++] = 0; - } + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); + } + while (N-- > 0 && i < TableSize) + { + table[i++] = 0; } } - tablesRead = true; - if (inAddr > readTop) - { - return (false); - } - UnpackUtility.makeDecodeTables(table, 0, LD, PackDef.NC); - UnpackUtility.makeDecodeTables(table, PackDef.NC, DD, PackDef.DC); - UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC, LDD, PackDef.LDC); - UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC + PackDef.LDC, RD, PackDef.RC); - - // memcpy(unpOldTable,table,sizeof(unpOldTable)); - - Buffer.BlockCopy(table, 0, unpOldTable, 0, unpOldTable.Length); - return (true); } - - private bool ReadVMCode() + tablesRead = true; + if (inAddr > readTop) { - int FirstByte = GetBits() >> 8; + return (false); + } + UnpackUtility.makeDecodeTables(table, 0, LD, PackDef.NC); + UnpackUtility.makeDecodeTables(table, PackDef.NC, DD, PackDef.DC); + UnpackUtility.makeDecodeTables(table, PackDef.NC + PackDef.DC, LDD, PackDef.LDC); + UnpackUtility.makeDecodeTables( + table, + PackDef.NC + PackDef.DC + PackDef.LDC, + RD, + PackDef.RC + ); + + // memcpy(unpOldTable,table,sizeof(unpOldTable)); + + Buffer.BlockCopy(table, 0, unpOldTable, 0, unpOldTable.Length); + return (true); + } + + private bool ReadVMCode() + { + var FirstByte = GetBits() >> 8; + AddBits(8); + var Length = (FirstByte & 7) + 1; + if (Length == 7) + { + Length = (GetBits() >> 8) + 7; AddBits(8); - int Length = (FirstByte & 7) + 1; - if (Length == 7) - { - Length = (GetBits() >> 8) + 7; - AddBits(8); - } - else if (Length == 8) - { - Length = GetBits(); - AddBits(16); - } - - List vmCode = new List(); - for (int I = 0; I < Length; I++) - { - if (inAddr >= readTop - 1 && !unpReadBuf() && I < Length - 1) - { - return (false); - } - vmCode.Add((byte)(GetBits() >> 8)); - AddBits(8); - } - return (AddVMCode(FirstByte, vmCode, Length)); + } + else if (Length == 8) + { + Length = GetBits(); + AddBits(16); } - private bool ReadVMCodePPM() + var vmCode = new List(); + for (var I = 0; I < Length; I++) { - int FirstByte = ppm.DecodeChar(); - if (FirstByte == -1) + if (inAddr >= readTop - 1 && !unpReadBuf() && I < Length - 1) { return (false); } - int Length = (FirstByte & 7) + 1; - if (Length == 7) - { - int B1 = ppm.DecodeChar(); - if (B1 == -1) - { - return (false); - } - Length = B1 + 7; - } - else if (Length == 8) - { - int B1 = ppm.DecodeChar(); - if (B1 == -1) - { - return (false); - } - int B2 = ppm.DecodeChar(); - if (B2 == -1) - { - return (false); - } - Length = B1 * 256 + B2; - } - - List vmCode = new List(); - for (int I = 0; I < Length; I++) - { - int Ch = ppm.DecodeChar(); - if (Ch == -1) - { - return (false); - } - vmCode.Add((byte)Ch); // VMCode[I]=Ch; - } - return (AddVMCode(FirstByte, vmCode, Length)); + vmCode.Add((byte)(GetBits() >> 8)); + AddBits(8); } + return (AddVMCode(FirstByte, vmCode, Length)); + } - private bool AddVMCode(int firstByte, List vmCode, int length) + private bool ReadVMCodePPM() + { + var FirstByte = ppm.DecodeChar(); + if (FirstByte == -1) { - BitInput Inp = new BitInput(); - Inp.InitBitInput(); - - // memcpy(Inp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize)); - for (int i = 0; i < Math.Min(MAX_SIZE, vmCode.Count); i++) - { - Inp.InBuf[i] = vmCode[i]; - } - rarVM.init(); - - int FiltPos; - if ((firstByte & 0x80) != 0) - { - FiltPos = RarVM.ReadData(Inp); - if (FiltPos == 0) - { - InitFilters(); - } - else - { - FiltPos--; - } - } - else - { - FiltPos = lastFilter; // use the same filter as last time - } - - if (FiltPos > filters.Count || FiltPos > oldFilterLengths.Count) + return (false); + } + var Length = (FirstByte & 7) + 1; + if (Length == 7) + { + var B1 = ppm.DecodeChar(); + if (B1 == -1) { return (false); } - lastFilter = FiltPos; - bool NewFilter = (FiltPos == filters.Count); - - UnpackFilter StackFilter = new UnpackFilter(); // new filter for - - // PrgStack - - UnpackFilter Filter; - if (NewFilter) - - // new filter code, never used before since VM reset + Length = B1 + 7; + } + else if (Length == 8) + { + var B1 = ppm.DecodeChar(); + if (B1 == -1) { - // too many different filters, corrupt archive - if (FiltPos > 1024) - { - return (false); - } - - // Filters[Filters.Size()-1]=Filter=new UnpackFilter; - Filter = new UnpackFilter(); - filters.Add(Filter); - StackFilter.ParentFilter = filters.Count - 1; - oldFilterLengths.Add(0); - Filter.ExecCount = 0; + return (false); } - - // filter was used in the past - else + var B2 = ppm.DecodeChar(); + if (B2 == -1) { - Filter = filters[FiltPos]; - StackFilter.ParentFilter = FiltPos; - Filter.ExecCount = Filter.ExecCount + 1; // ->ExecCount++; + return (false); } + Length = (B1 * 256) + B2; + } - prgStack.Add(StackFilter); - StackFilter.ExecCount = Filter.ExecCount; // ->ExecCount; - - int BlockStart = RarVM.ReadData(Inp); - if ((firstByte & 0x40) != 0) + var vmCode = new List(); + for (var I = 0; I < Length; I++) + { + var Ch = ppm.DecodeChar(); + if (Ch == -1) { - BlockStart += 258; + return (false); } - StackFilter.BlockStart = ((BlockStart + unpPtr) & PackDef.MAXWINMASK); - if ((firstByte & 0x20) != 0) + vmCode.Add((byte)Ch); // VMCode[I]=Ch; + } + return (AddVMCode(FirstByte, vmCode, Length)); + } + + private bool AddVMCode(int firstByte, List vmCode, int length) + { + var Inp = new BitInput(); + Inp.InitBitInput(); + + // memcpy(Inp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize)); + for (var i = 0; i < Math.Min(MAX_SIZE, vmCode.Count); i++) + { + Inp.InBuf[i] = vmCode[i]; + } + rarVM.init(); + + int FiltPos; + if ((firstByte & 0x80) != 0) + { + FiltPos = RarVM.ReadData(Inp); + if (FiltPos == 0) { - StackFilter.BlockLength = RarVM.ReadData(Inp); + InitFilters(); } else { - StackFilter.BlockLength = FiltPos < oldFilterLengths.Count ? oldFilterLengths[FiltPos] : 0; + FiltPos--; } - StackFilter.NextWindow = (wrPtr != unpPtr) && ((wrPtr - unpPtr) & PackDef.MAXWINMASK) <= BlockStart; + } + else + { + FiltPos = lastFilter; // use the same filter as last time + } - // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x - // BlockStart=%08x",UnpPtr,WrPtr,BlockStart); + if (FiltPos > filters.Count || FiltPos > oldFilterLengths.Count) + { + return (false); + } + lastFilter = FiltPos; + var NewFilter = (FiltPos == filters.Count); - oldFilterLengths[FiltPos] = StackFilter.BlockLength; + var StackFilter = new UnpackFilter(); // new filter for - // memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR)); - new Span(StackFilter.Program.InitR).Clear(); - StackFilter.Program.InitR[3] = RarVM.VM_GLOBALMEMADDR; // StackFilter->Prg.InitR[3]=VM_GLOBALMEMADDR; - StackFilter.Program.InitR[4] = StackFilter.BlockLength; + // PrgStack - // StackFilter->Prg.InitR[4]=StackFilter->BlockLength; - StackFilter.Program.InitR[5] = StackFilter.ExecCount; // StackFilter->Prg.InitR[5]=StackFilter->ExecCount; - - if ((firstByte & 0x10) != 0) - - // set registers to optional parameters - // if any + UnpackFilter Filter; + if (NewFilter) + // new filter code, never used before since VM reset + { + // too many different filters, corrupt archive + if (FiltPos > 1024) { - int InitMask = Utility.URShift(Inp.GetBits(), 9); - Inp.AddBits(7); - for (int I = 0; I < 7; I++) + return (false); + } + + // Filters[Filters.Size()-1]=Filter=new UnpackFilter; + Filter = new UnpackFilter(); + filters.Add(Filter); + StackFilter.ParentFilter = filters.Count - 1; + oldFilterLengths.Add(0); + Filter.ExecCount = 0; + } + // filter was used in the past + else + { + Filter = filters[FiltPos]; + StackFilter.ParentFilter = FiltPos; + Filter.ExecCount++; // ->ExecCount++; + } + + prgStack.Add(StackFilter); + StackFilter.ExecCount = Filter.ExecCount; // ->ExecCount; + + var BlockStart = RarVM.ReadData(Inp); + if ((firstByte & 0x40) != 0) + { + BlockStart += 258; + } + StackFilter.BlockStart = ((BlockStart + unpPtr) & PackDef.MAXWINMASK); + if ((firstByte & 0x20) != 0) + { + StackFilter.BlockLength = RarVM.ReadData(Inp); + } + else + { + StackFilter.BlockLength = + FiltPos < oldFilterLengths.Count ? oldFilterLengths[FiltPos] : 0; + } + StackFilter.NextWindow = + (wrPtr != unpPtr) && ((wrPtr - unpPtr) & PackDef.MAXWINMASK) <= BlockStart; + + // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x + // BlockStart=%08x",UnpPtr,WrPtr,BlockStart); + + oldFilterLengths[FiltPos] = StackFilter.BlockLength; + + // memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR)); + new Span(StackFilter.Program.InitR).Clear(); + StackFilter.Program.InitR[3] = RarVM.VM_GLOBALMEMADDR; // StackFilter->Prg.InitR[3]=VM_GLOBALMEMADDR; + StackFilter.Program.InitR[4] = StackFilter.BlockLength; + + // StackFilter->Prg.InitR[4]=StackFilter->BlockLength; + StackFilter.Program.InitR[5] = StackFilter.ExecCount; // StackFilter->Prg.InitR[5]=StackFilter->ExecCount; + + if ((firstByte & 0x10) != 0) + // set registers to optional parameters + // if any + { + var InitMask = Utility.URShift(Inp.GetBits(), 9); + Inp.AddBits(7); + for (var I = 0; I < 7; I++) + { + if ((InitMask & (1 << I)) != 0) { - if ((InitMask & (1 << I)) != 0) - { - // StackFilter->Prg.InitR[I]=RarVM::ReadData(Inp); - StackFilter.Program.InitR[I] = RarVM.ReadData(Inp); - } + // StackFilter->Prg.InitR[I]=RarVM::ReadData(Inp); + StackFilter.Program.InitR[I] = RarVM.ReadData(Inp); } } + } - if (NewFilter) + if (NewFilter) + { + var VMCodeSize = RarVM.ReadData(Inp); + if (VMCodeSize >= 0x10000 || VMCodeSize == 0) { - int VMCodeSize = RarVM.ReadData(Inp); - if (VMCodeSize >= 0x10000 || VMCodeSize == 0) - { - return (false); - } - byte[] VMCode = new byte[VMCodeSize]; - for (int I = 0; I < VMCodeSize; I++) - { - if (Inp.Overflow(3)) - { - return (false); - } - VMCode[I] = (byte)(Inp.GetBits() >> 8); - Inp.AddBits(8); - } - - // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); - rarVM.prepare(VMCode, VMCodeSize, Filter.Program); + return (false); } - StackFilter.Program.AltCommands = Filter.Program.Commands; // StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0]; - StackFilter.Program.CommandCount = Filter.Program.CommandCount; - - // StackFilter->Prg.CmdCount=Filter->Prg.CmdCount; - - int StaticDataSize = Filter.Program.StaticData.Count; - if (StaticDataSize > 0 && StaticDataSize < RarVM.VM_GLOBALMEMSIZE) - { - // read statically defined data contained in DB commands - // StackFilter->Prg.StaticData.Add(StaticDataSize); - StackFilter.Program.StaticData = Filter.Program.StaticData; - - // memcpy(&StackFilter->Prg.StaticData[0],&Filter->Prg.StaticData[0],StaticDataSize); - } - - if (StackFilter.Program.GlobalData.Count < RarVM.VM_FIXEDGLOBALSIZE) - { - // StackFilter->Prg.GlobalData.Reset(); - // StackFilter->Prg.GlobalData.Add(VM_FIXEDGLOBALSIZE); - StackFilter.Program.GlobalData.Clear(); - StackFilter.Program.GlobalData.SetSize(RarVM.VM_FIXEDGLOBALSIZE); - } - - // byte *GlobalData=&StackFilter->Prg.GlobalData[0]; - - List globalData = StackFilter.Program.GlobalData; - for (int I = 0; I < 7; I++) - { - rarVM.SetLowEndianValue(globalData, I * 4, StackFilter.Program.InitR[I]); - } - - // VM.SetLowEndianValue((uint - // *)&GlobalData[0x1c],StackFilter->BlockLength); - rarVM.SetLowEndianValue(globalData, 0x1c, StackFilter.BlockLength); - - // VM.SetLowEndianValue((uint *)&GlobalData[0x20],0); - rarVM.SetLowEndianValue(globalData, 0x20, 0); - rarVM.SetLowEndianValue(globalData, 0x24, 0); - rarVM.SetLowEndianValue(globalData, 0x28, 0); - - // VM.SetLowEndianValue((uint - // *)&GlobalData[0x2c],StackFilter->ExecCount); - rarVM.SetLowEndianValue(globalData, 0x2c, StackFilter.ExecCount); - - // memset(&GlobalData[0x30],0,16); - for (int i = 0; i < 16; i++) - { - globalData[0x30 + i] = 0x0; - } - if ((firstByte & 8) != 0) - - // put data block passed as parameter if any + var VMCode = new byte[VMCodeSize]; + for (var I = 0; I < VMCodeSize; I++) { if (Inp.Overflow(3)) { return (false); } - int DataSize = RarVM.ReadData(Inp); - if (DataSize > RarVM.VM_GLOBALMEMSIZE - RarVM.VM_FIXEDGLOBALSIZE) + VMCode[I] = (byte)(Inp.GetBits() >> 8); + Inp.AddBits(8); + } + + // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); + rarVM.prepare(VMCode, VMCodeSize, Filter.Program); + } + StackFilter.Program.AltCommands = Filter.Program.Commands; // StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0]; + StackFilter.Program.CommandCount = Filter.Program.CommandCount; + + // StackFilter->Prg.CmdCount=Filter->Prg.CmdCount; + + var StaticDataSize = Filter.Program.StaticData.Count; + if (StaticDataSize > 0 && StaticDataSize < RarVM.VM_GLOBALMEMSIZE) + { + // read statically defined data contained in DB commands + // StackFilter->Prg.StaticData.Add(StaticDataSize); + StackFilter.Program.StaticData = Filter.Program.StaticData; + + // memcpy(&StackFilter->Prg.StaticData[0],&Filter->Prg.StaticData[0],StaticDataSize); + } + + if (StackFilter.Program.GlobalData.Count < RarVM.VM_FIXEDGLOBALSIZE) + { + // StackFilter->Prg.GlobalData.Reset(); + // StackFilter->Prg.GlobalData.Add(VM_FIXEDGLOBALSIZE); + StackFilter.Program.GlobalData.Clear(); + StackFilter.Program.GlobalData.SetSize(RarVM.VM_FIXEDGLOBALSIZE); + } + + // byte *GlobalData=&StackFilter->Prg.GlobalData[0]; + + var globalData = StackFilter.Program.GlobalData; + for (var I = 0; I < 7; I++) + { + rarVM.SetLowEndianValue(globalData, I * 4, StackFilter.Program.InitR[I]); + } + + // VM.SetLowEndianValue((uint + // *)&GlobalData[0x1c],StackFilter->BlockLength); + rarVM.SetLowEndianValue(globalData, 0x1c, StackFilter.BlockLength); + + // VM.SetLowEndianValue((uint *)&GlobalData[0x20],0); + rarVM.SetLowEndianValue(globalData, 0x20, 0); + rarVM.SetLowEndianValue(globalData, 0x24, 0); + rarVM.SetLowEndianValue(globalData, 0x28, 0); + + // VM.SetLowEndianValue((uint + // *)&GlobalData[0x2c],StackFilter->ExecCount); + rarVM.SetLowEndianValue(globalData, 0x2c, StackFilter.ExecCount); + + // memset(&GlobalData[0x30],0,16); + for (var i = 0; i < 16; i++) + { + globalData[0x30 + i] = 0x0; + } + if ((firstByte & 8) != 0) + // put data block passed as parameter if any + { + if (Inp.Overflow(3)) + { + return (false); + } + var DataSize = RarVM.ReadData(Inp); + if (DataSize > RarVM.VM_GLOBALMEMSIZE - RarVM.VM_FIXEDGLOBALSIZE) + { + return (false); + } + var CurSize = StackFilter.Program.GlobalData.Count; + if (CurSize < DataSize + RarVM.VM_FIXEDGLOBALSIZE) + { + // StackFilter->Prg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize); + StackFilter.Program.GlobalData.SetSize( + DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize + ); + } + var offset = RarVM.VM_FIXEDGLOBALSIZE; + globalData = StackFilter.Program.GlobalData; + for (var I = 0; I < DataSize; I++) + { + if (Inp.Overflow(3)) { return (false); } - int CurSize = StackFilter.Program.GlobalData.Count; - if (CurSize < DataSize + RarVM.VM_FIXEDGLOBALSIZE) - { - // StackFilter->Prg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize); - StackFilter.Program.GlobalData.SetSize(DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize); - } - int offset = RarVM.VM_FIXEDGLOBALSIZE; - globalData = StackFilter.Program.GlobalData; - for (int I = 0; I < DataSize; I++) - { - if (Inp.Overflow(3)) - { - return (false); - } - globalData[offset + I] = (byte)(Utility.URShift(Inp.GetBits(), 8)); - Inp.AddBits(8); - } - } - return (true); - } - - private void ExecuteCode(VMPreparedProgram Prg) - { - if (Prg.GlobalData.Count > 0) - { - // Prg->InitR[6]=int64to32(WrittenFileSize); - Prg.InitR[6] = (int)(writtenFileSize); - - // rarVM.SetLowEndianValue((uint - // *)&Prg->GlobalData[0x24],int64to32(WrittenFileSize)); - rarVM.SetLowEndianValue(Prg.GlobalData, 0x24, (int)writtenFileSize); - - // rarVM.SetLowEndianValue((uint - // *)&Prg->GlobalData[0x28],int64to32(WrittenFileSize>>32)); - rarVM.SetLowEndianValue(Prg.GlobalData, 0x28, (int)(Utility.URShift(writtenFileSize, 32))); - rarVM.execute(Prg); + globalData[offset + I] = (byte)(Utility.URShift(Inp.GetBits(), 8)); + Inp.AddBits(8); } } + return (true); + } - private void CleanUp() + private void ExecuteCode(VMPreparedProgram Prg) + { + if (Prg.GlobalData.Count > 0) { - if (ppm != null) - { - SubAllocator allocator = ppm.SubAlloc; - if (allocator != null) - { - allocator.StopSubAllocator(); - } - } + // Prg->InitR[6]=int64to32(WrittenFileSize); + Prg.InitR[6] = (int)(writtenFileSize); + + // rarVM.SetLowEndianValue((uint + // *)&Prg->GlobalData[0x24],int64to32(WrittenFileSize)); + rarVM.SetLowEndianValue(Prg.GlobalData, 0x24, (int)writtenFileSize); + + // rarVM.SetLowEndianValue((uint + // *)&Prg->GlobalData[0x28],int64to32(WrittenFileSize>>32)); + rarVM.SetLowEndianValue( + Prg.GlobalData, + 0x28, + (int)(Utility.URShift(writtenFileSize, 32)) + ); + rarVM.execute(Prg); + } + } + + private void CleanUp() + { + if (ppm != null) + { + var allocator = ppm.SubAlloc; + allocator?.StopSubAllocator(); } } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs index 7a0ae2bd..8513c24b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs @@ -11,346 +11,419 @@ using System; using System.IO; using SharpCompress.Compressors.Rar.UnpackV1.Decode; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack { - internal partial class Unpack + private int readBorder; + + private bool suspended; + + internal bool unpAllBuf; + + //private ComprDataIO unpIO; + private Stream readStream; + private Stream writeStream; + + internal bool unpSomeRead; + + private int readTop; + + private long destUnpSize; + + private byte[] window; + + private readonly int[] oldDist = new int[4]; + + private int unpPtr, + wrPtr; + + private int oldDistPtr; + + private readonly int[] ChSet = new int[256], + ChSetA = new int[256], + ChSetB = new int[256], + ChSetC = new int[256]; + + private readonly int[] Place = new int[256], + PlaceA = new int[256], + PlaceB = new int[256], + PlaceC = new int[256]; + + private readonly int[] NToPl = new int[256], + NToPlB = new int[256], + NToPlC = new int[256]; + + private int FlagBuf, + AvrPlc, + AvrPlcB, + AvrLn1, + AvrLn2, + AvrLn3; + + private int Buf60, + NumHuf, + StMode, + LCount, + FlagsCnt; + + private int Nhfb, + Nlzb, + MaxDist3; + + private int lastDist, + lastLength; + + private const int STARTL1 = 2; + + private static readonly int[] DecL1 = { - private int readBorder; + 0x8000, + 0xa000, + 0xc000, + 0xd000, + 0xe000, + 0xea00, + 0xee00, + 0xf000, + 0xf200, + 0xf200, + 0xffff + }; - private bool suspended; + private static readonly int[] PosL1 = { 0, 0, 0, 2, 3, 5, 7, 11, 16, 20, 24, 32, 32 }; - internal bool unpAllBuf; + private const int STARTL2 = 3; - //private ComprDataIO unpIO; - private Stream readStream; - private Stream writeStream; + private static readonly int[] DecL2 = + { + 0xa000, + 0xc000, + 0xd000, + 0xe000, + 0xea00, + 0xee00, + 0xf000, + 0xf200, + 0xf240, + 0xffff + }; - internal bool unpSomeRead; + private static readonly int[] PosL2 = { 0, 0, 0, 0, 5, 7, 9, 13, 18, 22, 26, 34, 36 }; - private int readTop; + private const int STARTHF0 = 4; - private long destUnpSize; + private static readonly int[] DecHf0 = + { + 0x8000, + 0xc000, + 0xe000, + 0xf200, + 0xf200, + 0xf200, + 0xf200, + 0xf200, + 0xffff + }; - private byte[] window; + private static readonly int[] PosHf0 = { 0, 0, 0, 0, 0, 8, 16, 24, 33, 33, 33, 33, 33 }; - private readonly int[] oldDist = new int[4]; + private const int STARTHF1 = 5; - private int unpPtr, wrPtr; + private static readonly int[] DecHf1 = + { + 0x2000, + 0xc000, + 0xe000, + 0xf000, + 0xf200, + 0xf200, + 0xf7e0, + 0xffff + }; - private int oldDistPtr; + private static readonly int[] PosHf1 = { 0, 0, 0, 0, 0, 0, 4, 44, 60, 76, 80, 80, 127 }; - private readonly int[] ChSet = new int[256], - ChSetA = new int[256], - ChSetB = new int[256], - ChSetC = new int[256]; + private const int STARTHF2 = 5; - private readonly int[] Place = new int[256], - PlaceA = new int[256], - PlaceB = new int[256], - PlaceC = new int[256]; + private static readonly int[] DecHf2 = + { + 0x1000, + 0x2400, + 0x8000, + 0xc000, + 0xfa00, + 0xffff, + 0xffff, + 0xffff + }; - private readonly int[] NToPl = new int[256], NToPlB = new int[256], NToPlC = new int[256]; + private static readonly int[] PosHf2 = { 0, 0, 0, 0, 0, 0, 2, 7, 53, 117, 233, 0, 0 }; - private int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; + private const int STARTHF3 = 6; - private int Buf60, NumHuf, StMode, LCount, FlagsCnt; + private static readonly int[] DecHf3 = + { + 0x800, + 0x2400, + 0xee00, + 0xfe80, + 0xffff, + 0xffff, + 0xffff + }; - private int Nhfb, Nlzb, MaxDist3; + private static readonly int[] PosHf3 = { 0, 0, 0, 0, 0, 0, 0, 2, 16, 218, 251, 0, 0 }; - private int lastDist, lastLength; + private const int STARTHF4 = 8; - private const int STARTL1 = 2; + private static readonly int[] DecHf4 = { 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; - private static readonly int[] DecL1 = + private static readonly int[] PosHf4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0 }; + + private static readonly int[] ShortLen1 = { 1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0 }; + + private static readonly int[] ShortXor1 = + { + 0, + 0xa0, + 0xd0, + 0xe0, + 0xf0, + 0xf8, + 0xfc, + 0xfe, + 0xff, + 0xc0, + 0x80, + 0x90, + 0x98, + 0x9c, + 0xb0 + }; + + private static readonly int[] ShortLen2 = { 2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0 }; + + private static readonly int[] ShortXor2 = + { + 0, + 0x40, + 0x60, + 0xa0, + 0xd0, + 0xe0, + 0xf0, + 0xf8, + 0xfc, + 0xc0, + 0x80, + 0x90, + 0x98, + 0x9c, + 0xb0 + }; + + private void unpack15(bool solid) + { + if (suspended) { - 0x8000, 0xa000, 0xc000, 0xd000, 0xe000, 0xea00, 0xee00, 0xf000, 0xf200, 0xf200 - , 0xffff - }; - - private static readonly int[] PosL1 = { 0, 0, 0, 2, 3, 5, 7, 11, 16, 20, 24, 32, 32 }; - - private const int STARTL2 = 3; - - private static readonly int[] DecL2 = + unpPtr = wrPtr; + } + else { - 0xa000, 0xc000, 0xd000, 0xe000, 0xea00, 0xee00, 0xf000, 0xf200, 0xf240, 0xffff - }; - - private static readonly int[] PosL2 = { 0, 0, 0, 0, 5, 7, 9, 13, 18, 22, 26, 34, 36 }; - - private const int STARTHF0 = 4; - - private static readonly int[] DecHf0 = { 0x8000, 0xc000, 0xe000, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xffff }; - - private static readonly int[] PosHf0 = { 0, 0, 0, 0, 0, 8, 16, 24, 33, 33, 33, 33, 33 }; - - private const int STARTHF1 = 5; - - private static readonly int[] DecHf1 = { 0x2000, 0xc000, 0xe000, 0xf000, 0xf200, 0xf200, 0xf7e0, 0xffff }; - - private static readonly int[] PosHf1 = { 0, 0, 0, 0, 0, 0, 4, 44, 60, 76, 80, 80, 127 }; - - private const int STARTHF2 = 5; - - private static readonly int[] DecHf2 = { 0x1000, 0x2400, 0x8000, 0xc000, 0xfa00, 0xffff, 0xffff, 0xffff }; - - private static readonly int[] PosHf2 = { 0, 0, 0, 0, 0, 0, 2, 7, 53, 117, 233, 0, 0 }; - - private const int STARTHF3 = 6; - - private static readonly int[] DecHf3 = { 0x800, 0x2400, 0xee00, 0xfe80, 0xffff, 0xffff, 0xffff }; - - private static readonly int[] PosHf3 = { 0, 0, 0, 0, 0, 0, 0, 2, 16, 218, 251, 0, 0 }; - - private const int STARTHF4 = 8; - - private static readonly int[] DecHf4 = { 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; - - private static readonly int[] PosHf4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0 }; - - private static readonly int[] ShortLen1 = { 1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0 }; - - private static readonly int[] ShortXor1 = - { - 0, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff, 0xc0, 0x80, 0x90, 0x98 - , 0x9c, 0xb0 - }; - - private static readonly int[] ShortLen2 = { 2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0 }; - - private static readonly int[] ShortXor2 = - { - 0, 0x40, 0x60, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xc0, 0x80, 0x90, 0x98 - , 0x9c, 0xb0 - }; - - private void unpack15(bool solid) - { - if (suspended) + UnpInitData(solid); + oldUnpInitData(solid); + unpReadBuf(); + if (!solid) { - unpPtr = wrPtr; + initHuff(); + unpPtr = 0; } else { - UnpInitData(solid); - oldUnpInitData(solid); - unpReadBuf(); - if (!solid) + unpPtr = wrPtr; + } + --destUnpSize; + } + if (destUnpSize >= 0) + { + getFlagsBuf(); + FlagsCnt = 8; + } + + while (destUnpSize >= 0) + { + unpPtr &= PackDef.MAXWINMASK; + + if (inAddr > readTop - 30 && !unpReadBuf()) + { + break; + } + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) + { + oldUnpWriteBuf(); + if (suspended) { - initHuff(); - unpPtr = 0; + return; + } + } + if (StMode != 0) + { + huffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + getFlagsBuf(); + FlagsCnt = 7; + } + + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + longLZ(); } else - { - unpPtr = wrPtr; - } - --destUnpSize; - } - if (destUnpSize >= 0) - { - getFlagsBuf(); - FlagsCnt = 8; - } - - while (destUnpSize >= 0) - { - unpPtr &= PackDef.MAXWINMASK; - - if (inAddr > readTop - 30 && !unpReadBuf()) - { - break; - } - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) - { - oldUnpWriteBuf(); - if (suspended) - { - return; - } - } - if (StMode != 0) { huffDecode(); - continue; } - + } + else + { + FlagBuf <<= 1; if (--FlagsCnt < 0) { getFlagsBuf(); FlagsCnt = 7; } - if ((FlagBuf & 0x80) != 0) { FlagBuf <<= 1; if (Nlzb > Nhfb) { - longLZ(); + huffDecode(); } else { - huffDecode(); + longLZ(); } } else { FlagBuf <<= 1; - if (--FlagsCnt < 0) - { - getFlagsBuf(); - FlagsCnt = 7; - } - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - huffDecode(); - } - else - { - longLZ(); - } - } - else - { - FlagBuf <<= 1; - shortLZ(); - } + shortLZ(); } } - oldUnpWriteBuf(); + } + oldUnpWriteBuf(); + } + + private bool unpReadBuf() + { + var dataSize = readTop - inAddr; + if (dataSize < 0) + { + return (false); + } + if (inAddr > MAX_SIZE / 2) + { + if (dataSize > 0) + { + //memmove(InBuf,InBuf+InAddr,DataSize); + // for (int i = 0; i < dataSize; i++) { + // inBuf[i] = inBuf[inAddr + i]; + // } + Array.Copy(InBuf, inAddr, InBuf, 0, dataSize); + } + inAddr = 0; + readTop = dataSize; + } + else + { + dataSize = readTop; } - private bool unpReadBuf() + //int readCode=UnpIO->UnpRead(InBuf+DataSize,(BitInput::MAX_SIZE-DataSize)&~0xf); + var readCode = readStream.Read(InBuf, dataSize, (MAX_SIZE - dataSize) & ~0xf); + if (readCode > 0) { - int dataSize = readTop - inAddr; - if (dataSize < 0) + readTop += readCode; + } + readBorder = readTop - 30; + return (readCode != -1); + } + + private int getShortLen1(int pos) => pos == 1 ? Buf60 + 3 : ShortLen1[pos]; + + private int getShortLen2(int pos) => pos == 3 ? Buf60 + 3 : ShortLen2[pos]; + + private void shortLZ() + { + int Length, + SaveLength; + int LastDistance; + int Distance; + int DistancePlace; + NumHuf = 0; + + var BitField = GetBits(); + if (LCount == 2) + { + AddBits(1); + if (BitField >= 0x8000) { - return (false); + oldCopyString(lastDist, lastLength); + return; } - if (inAddr > MAX_SIZE / 2) + BitField <<= 1; + LCount = 0; + } + BitField = Utility.URShift(BitField, 8); + if (AvrLn1 < 37) + { + for (Length = 0; ; Length++) { - if (dataSize > 0) + if ( + ( + (BitField ^ ShortXor1[Length]) + & (~(Utility.URShift(0xff, getShortLen1(Length)))) + ) == 0 + ) { - //memmove(InBuf,InBuf+InAddr,DataSize); - // for (int i = 0; i < dataSize; i++) { - // inBuf[i] = inBuf[inAddr + i]; - // } - Array.Copy(InBuf, inAddr, InBuf, 0, dataSize); + break; } - inAddr = 0; - readTop = dataSize; } - else - { - dataSize = readTop; - } - - //int readCode=UnpIO->UnpRead(InBuf+DataSize,(BitInput::MAX_SIZE-DataSize)&~0xf); - int readCode = readStream.Read(InBuf, dataSize, (MAX_SIZE - dataSize) & ~0xf); - if (readCode > 0) - { - readTop += readCode; - } - readBorder = readTop - 30; - return (readCode != -1); + AddBits(getShortLen1(Length)); } - - private int getShortLen1(int pos) + else { - return pos == 1 ? Buf60 + 3 : ShortLen1[pos]; - } - - private int getShortLen2(int pos) - { - return pos == 3 ? Buf60 + 3 : ShortLen2[pos]; - } - - private void shortLZ() - { - int Length, SaveLength; - int LastDistance; - int Distance; - int DistancePlace; - NumHuf = 0; - - int BitField = GetBits(); - if (LCount == 2) + for (Length = 0; ; Length++) { - AddBits(1); - if (BitField >= 0x8000) + if (((BitField ^ ShortXor2[Length]) & (~(0xff >> getShortLen2(Length)))) == 0) { - oldCopyString(lastDist, lastLength); - return; + break; } - BitField <<= 1; + } + AddBits(getShortLen2(Length)); + } + + if (Length >= 9) + { + if (Length == 9) + { + LCount++; + oldCopyString(lastDist, lastLength); + return; + } + if (Length == 14) + { LCount = 0; - } - BitField = Utility.URShift(BitField, 8); - if (AvrLn1 < 37) - { - for (Length = 0; ; Length++) - { - if (((BitField ^ ShortXor1[Length]) & (~(Utility.URShift(0xff, getShortLen1(Length))))) == 0) - { - break; - } - } - AddBits(getShortLen1(Length)); - } - else - { - for (Length = 0; ; Length++) - { - if (((BitField ^ ShortXor2[Length]) & (~(0xff >> getShortLen2(Length)))) == 0) - { - break; - } - } - AddBits(getShortLen2(Length)); - } - - if (Length >= 9) - { - if (Length == 9) - { - LCount++; - oldCopyString(lastDist, lastLength); - return; - } - if (Length == 14) - { - LCount = 0; - Length = decodeNum(GetBits(), STARTL2, DecL2, PosL2) + 5; - Distance = (GetBits() >> 1) | 0x8000; - AddBits(15); - lastLength = Length; - lastDist = Distance; - oldCopyString(Distance, Length); - return; - } - - LCount = 0; - SaveLength = Length; - Distance = oldDist[(oldDistPtr - (Length - 9)) & 3]; - Length = decodeNum(GetBits(), STARTL1, DecL1, PosL1) + 2; - if (Length == 0x101 && SaveLength == 10) - { - Buf60 ^= 1; - return; - } - if (Distance > 256) - { - Length++; - } - if (Distance >= MaxDist3) - { - Length++; - } - - oldDist[oldDistPtr++] = Distance; - oldDistPtr = oldDistPtr & 3; + Length = decodeNum(GetBits(), STARTL2, DecL2, PosL2) + 5; + Distance = (GetBits() >> 1) | 0x8000; + AddBits(15); lastLength = Length; lastDist = Distance; oldCopyString(Distance, Length); @@ -358,363 +431,397 @@ namespace SharpCompress.Compressors.Rar.UnpackV1 } LCount = 0; - AvrLn1 += Length; - AvrLn1 -= (AvrLn1 >> 4); - - DistancePlace = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2) & 0xff; - Distance = ChSetA[DistancePlace]; - if (--DistancePlace != -1) + SaveLength = Length; + Distance = oldDist[(oldDistPtr - (Length - 9)) & 3]; + Length = decodeNum(GetBits(), STARTL1, DecL1, PosL1) + 2; + if (Length == 0x101 && SaveLength == 10) { - PlaceA[Distance]--; - LastDistance = ChSetA[DistancePlace]; - PlaceA[LastDistance]++; - ChSetA[DistancePlace + 1] = LastDistance; - ChSetA[DistancePlace] = Distance; + Buf60 ^= 1; + return; } - Length += 2; - oldDist[oldDistPtr++] = ++Distance; - oldDistPtr = oldDistPtr & 3; - lastLength = Length; - lastDist = Distance; - oldCopyString(Distance, Length); - } - - private void longLZ() - { - int Length; - int Distance; - int DistancePlace, NewDistancePlace; - int OldAvr2, OldAvr3; - - NumHuf = 0; - Nlzb += 16; - if (Nlzb > 0xff) + if (Distance > 256) { - Nlzb = 0x90; - Nhfb = Utility.URShift(Nhfb, 1); + Length++; } - OldAvr2 = AvrLn2; - - int BitField = GetBits(); - if (AvrLn2 >= 122) - { - Length = decodeNum(BitField, STARTL2, DecL2, PosL2); - } - else - { - if (AvrLn2 >= 64) - { - Length = decodeNum(BitField, STARTL1, DecL1, PosL1); - } - else - { - if (BitField < 0x100) - { - Length = BitField; - AddBits(16); - } - else - { - for (Length = 0; ((BitField << Length) & 0x8000) == 0; Length++) - { - ; - } - AddBits(Length + 1); - } - } - } - AvrLn2 += Length; - AvrLn2 -= Utility.URShift(AvrLn2, 5); - - BitField = GetBits(); - if (AvrPlcB > 0x28ff) - { - DistancePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2); - } - else - { - if (AvrPlcB > 0x6ff) - { - DistancePlace = decodeNum(BitField, STARTHF1, DecHf1, PosHf1); - } - else - { - DistancePlace = decodeNum(BitField, STARTHF0, DecHf0, PosHf0); - } - } - AvrPlcB += DistancePlace; - AvrPlcB -= (AvrPlcB >> 8); - while (true) - { - Distance = ChSetB[DistancePlace & 0xff]; - NewDistancePlace = NToPlB[Distance++ & 0xff]++; - if ((Distance & 0xff) == 0) - { - corrHuff(ChSetB, NToPlB); - } - else - { - break; - } - } - - ChSetB[DistancePlace] = ChSetB[NewDistancePlace]; - ChSetB[NewDistancePlace] = Distance; - - Distance = Utility.URShift(((Distance & 0xff00) | (Utility.URShift(GetBits(), 8))), 1); - AddBits(7); - - OldAvr3 = AvrLn3; - if (Length != 1 && Length != 4) - { - if (Length == 0 && Distance <= MaxDist3) - { - AvrLn3++; - AvrLn3 -= (AvrLn3 >> 8); - } - else - { - if (AvrLn3 > 0) - { - AvrLn3--; - } - } - } - Length += 3; if (Distance >= MaxDist3) { Length++; } - if (Distance <= 256) - { - Length += 8; - } - if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) - { - MaxDist3 = 0x7f00; - } - else - { - MaxDist3 = 0x2001; - } + oldDist[oldDistPtr++] = Distance; - oldDistPtr = oldDistPtr & 3; + oldDistPtr &= 3; lastLength = Length; lastDist = Distance; oldCopyString(Distance, Length); + return; } - private void huffDecode() + LCount = 0; + AvrLn1 += Length; + AvrLn1 -= (AvrLn1 >> 4); + + DistancePlace = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2) & 0xff; + Distance = ChSetA[DistancePlace]; + if (--DistancePlace != -1) { - int CurByte, NewBytePlace; - int Length; - int Distance; - int BytePlace; + PlaceA[Distance]--; + LastDistance = ChSetA[DistancePlace]; + PlaceA[LastDistance]++; + ChSetA[DistancePlace + 1] = LastDistance; + ChSetA[DistancePlace] = Distance; + } + Length += 2; + oldDist[oldDistPtr++] = ++Distance; + oldDistPtr &= 3; + lastLength = Length; + lastDist = Distance; + oldCopyString(Distance, Length); + } - int BitField = GetBits(); + private void longLZ() + { + int Length; + int Distance; + int DistancePlace, + NewDistancePlace; + int OldAvr2, + OldAvr3; - if (AvrPlc > 0x75ff) + NumHuf = 0; + Nlzb += 16; + if (Nlzb > 0xff) + { + Nlzb = 0x90; + Nhfb = Utility.URShift(Nhfb, 1); + } + OldAvr2 = AvrLn2; + + var BitField = GetBits(); + if (AvrLn2 >= 122) + { + Length = decodeNum(BitField, STARTL2, DecL2, PosL2); + } + else + { + if (AvrLn2 >= 64) { - BytePlace = decodeNum(BitField, STARTHF4, DecHf4, PosHf4); + Length = decodeNum(BitField, STARTL1, DecL1, PosL1); } else { - if (AvrPlc > 0x5dff) + if (BitField < 0x100) { - BytePlace = decodeNum(BitField, STARTHF3, DecHf3, PosHf3); + Length = BitField; + AddBits(16); } else { - if (AvrPlc > 0x35ff) + for (Length = 0; ((BitField << Length) & 0x8000) == 0; Length++) { - BytePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2); + ; + } + AddBits(Length + 1); + } + } + } + AvrLn2 += Length; + AvrLn2 -= Utility.URShift(AvrLn2, 5); + + BitField = GetBits(); + if (AvrPlcB > 0x28ff) + { + DistancePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2); + } + else + { + if (AvrPlcB > 0x6ff) + { + DistancePlace = decodeNum(BitField, STARTHF1, DecHf1, PosHf1); + } + else + { + DistancePlace = decodeNum(BitField, STARTHF0, DecHf0, PosHf0); + } + } + AvrPlcB += DistancePlace; + AvrPlcB -= (AvrPlcB >> 8); + while (true) + { + Distance = ChSetB[DistancePlace & 0xff]; + NewDistancePlace = NToPlB[Distance++ & 0xff]++; + if ((Distance & 0xff) == 0) + { + corrHuff(ChSetB, NToPlB); + } + else + { + break; + } + } + + ChSetB[DistancePlace] = ChSetB[NewDistancePlace]; + ChSetB[NewDistancePlace] = Distance; + + Distance = Utility.URShift(((Distance & 0xff00) | (Utility.URShift(GetBits(), 8))), 1); + AddBits(7); + + OldAvr3 = AvrLn3; + if (Length != 1 && Length != 4) + { + if (Length == 0 && Distance <= MaxDist3) + { + AvrLn3++; + AvrLn3 -= (AvrLn3 >> 8); + } + else + { + if (AvrLn3 > 0) + { + AvrLn3--; + } + } + } + Length += 3; + if (Distance >= MaxDist3) + { + Length++; + } + if (Distance <= 256) + { + Length += 8; + } + if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) + { + MaxDist3 = 0x7f00; + } + else + { + MaxDist3 = 0x2001; + } + oldDist[oldDistPtr++] = Distance; + oldDistPtr &= 3; + lastLength = Length; + lastDist = Distance; + oldCopyString(Distance, Length); + } + + private void huffDecode() + { + int CurByte, + NewBytePlace; + int Length; + int Distance; + int BytePlace; + + var BitField = GetBits(); + + if (AvrPlc > 0x75ff) + { + BytePlace = decodeNum(BitField, STARTHF4, DecHf4, PosHf4); + } + else + { + if (AvrPlc > 0x5dff) + { + BytePlace = decodeNum(BitField, STARTHF3, DecHf3, PosHf3); + } + else + { + if (AvrPlc > 0x35ff) + { + BytePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2); + } + else + { + if (AvrPlc > 0x0dff) + { + BytePlace = decodeNum(BitField, STARTHF1, DecHf1, PosHf1); } else { - if (AvrPlc > 0x0dff) - { - BytePlace = decodeNum(BitField, STARTHF1, DecHf1, PosHf1); - } - else - { - BytePlace = decodeNum(BitField, STARTHF0, DecHf0, PosHf0); - } + BytePlace = decodeNum(BitField, STARTHF0, DecHf0, PosHf0); } } } - BytePlace &= 0xff; - if (StMode != 0) + } + BytePlace &= 0xff; + if (StMode != 0) + { + if (BytePlace == 0 && BitField > 0xfff) { - if (BytePlace == 0 && BitField > 0xfff) + BytePlace = 0x100; + } + if (--BytePlace == -1) + { + BitField = GetBits(); + AddBits(1); + if ((BitField & 0x8000) != 0) { - BytePlace = 0x100; - } - if (--BytePlace == -1) - { - BitField = GetBits(); - AddBits(1); - if ((BitField & 0x8000) != 0) - { - NumHuf = StMode = 0; - return; - } - Length = (BitField & 0x4000) != 0 ? 4 : 3; - AddBits(1); - Distance = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2); - Distance = (Distance << 5) | (Utility.URShift(GetBits(), 11)); - AddBits(5); - oldCopyString(Distance, Length); + NumHuf = StMode = 0; return; } + Length = (BitField & 0x4000) != 0 ? 4 : 3; + AddBits(1); + Distance = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2); + Distance = (Distance << 5) | (Utility.URShift(GetBits(), 11)); + AddBits(5); + oldCopyString(Distance, Length); + return; + } + } + else + { + if (NumHuf++ >= 16 && FlagsCnt == 0) + { + StMode = 1; + } + } + AvrPlc += BytePlace; + AvrPlc -= Utility.URShift(AvrPlc, 8); + Nhfb += 16; + if (Nhfb > 0xff) + { + Nhfb = 0x90; + Nlzb = Utility.URShift(Nlzb, 1); + } + + window[unpPtr++] = (byte)(Utility.URShift(ChSet[BytePlace], 8)); + --destUnpSize; + + while (true) + { + CurByte = ChSet[BytePlace]; + NewBytePlace = NToPl[CurByte++ & 0xff]++; + if ((CurByte & 0xff) > 0xa1) + { + corrHuff(ChSet, NToPl); } else { - if (NumHuf++ >= 16 && FlagsCnt == 0) - { - StMode = 1; - } - } - AvrPlc += BytePlace; - AvrPlc -= Utility.URShift(AvrPlc, 8); - Nhfb += 16; - if (Nhfb > 0xff) - { - Nhfb = 0x90; - Nlzb = Utility.URShift(Nlzb, 1); - } - - window[unpPtr++] = (byte)(Utility.URShift(ChSet[BytePlace], 8)); - --destUnpSize; - - while (true) - { - CurByte = ChSet[BytePlace]; - NewBytePlace = NToPl[CurByte++ & 0xff]++; - if ((CurByte & 0xff) > 0xa1) - { - corrHuff(ChSet, NToPl); - } - else - { - break; - } - } - - ChSet[BytePlace] = ChSet[NewBytePlace]; - ChSet[NewBytePlace] = CurByte; - } - - private void getFlagsBuf() - { - int Flags, NewFlagsPlace; - int FlagsPlace = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2); - - while (true) - { - Flags = ChSetC[FlagsPlace]; - FlagBuf = Utility.URShift(Flags, 8); - NewFlagsPlace = NToPlC[Flags++ & 0xff]++; - if ((Flags & 0xff) != 0) - { - break; - } - corrHuff(ChSetC, NToPlC); - } - - ChSetC[FlagsPlace] = ChSetC[NewFlagsPlace]; - ChSetC[NewFlagsPlace] = Flags; - } - - private void oldUnpInitData(bool Solid) - { - if (!Solid) - { - AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0; - AvrPlc = 0x3500; - MaxDist3 = 0x2001; - Nhfb = Nlzb = 0x80; - } - FlagsCnt = 0; - FlagBuf = 0; - StMode = 0; - LCount = 0; - readTop = 0; - } - - private void initHuff() - { - for (int I = 0; I < 256; I++) - { - Place[I] = PlaceA[I] = PlaceB[I] = I; - PlaceC[I] = (~I + 1) & 0xff; - ChSet[I] = ChSetB[I] = I << 8; - ChSetA[I] = I; - ChSetC[I] = ((~I + 1) & 0xff) << 8; - } - - new Span(NToPl).Clear(); // memset(NToPl,0,sizeof(NToPl)); - new Span(NToPlB).Clear(); // memset(NToPlB,0,sizeof(NToPlB)); - new Span(NToPlC).Clear(); // memset(NToPlC,0,sizeof(NToPlC)); - corrHuff(ChSetB, NToPlB); - } - - private void corrHuff(int[] CharSet, int[] NumToPlace) - { - int I, J, pos = 0; - for (I = 7; I >= 0; I--) - { - for (J = 0; J < 32; J++, pos++) - { - CharSet[pos] = ((CharSet[pos] & ~0xff) | I); // *CharSet=(*CharSet - - // & ~0xff) | I; - } - } - new Span(NumToPlace).Clear(); // memset(NumToPlace,0,sizeof(NToPl)); - for (I = 6; I >= 0; I--) - { - NumToPlace[I] = (7 - I) * 32; + break; } } - private void oldCopyString(int Distance, int Length) + ChSet[BytePlace] = ChSet[NewBytePlace]; + ChSet[NewBytePlace] = CurByte; + } + + private void getFlagsBuf() + { + int Flags, + NewFlagsPlace; + var FlagsPlace = decodeNum(GetBits(), STARTHF2, DecHf2, PosHf2); + + while (true) { - destUnpSize -= Length; - while ((Length--) != 0) + Flags = ChSetC[FlagsPlace]; + FlagBuf = Utility.URShift(Flags, 8); + NewFlagsPlace = NToPlC[Flags++ & 0xff]++; + if ((Flags & 0xff) != 0) { - window[unpPtr] = window[(unpPtr - Distance) & PackDef.MAXWINMASK]; - unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; + break; } + corrHuff(ChSetC, NToPlC); } - private int decodeNum(int Num, int StartPos, int[] DecTab, int[] PosTab) + ChSetC[FlagsPlace] = ChSetC[NewFlagsPlace]; + ChSetC[NewFlagsPlace] = Flags; + } + + private void oldUnpInitData(bool Solid) + { + if (!Solid) { - int I; - for (Num &= 0xfff0, I = 0; DecTab[I] <= Num; I++) - { - StartPos++; - } - AddBits(StartPos); - return ((Utility.URShift((Num - (I != 0 ? DecTab[I - 1] : 0)), (16 - StartPos))) + PosTab[StartPos]); + AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0; + AvrPlc = 0x3500; + MaxDist3 = 0x2001; + Nhfb = Nlzb = 0x80; + } + FlagsCnt = 0; + FlagBuf = 0; + StMode = 0; + LCount = 0; + readTop = 0; + } + + private void initHuff() + { + for (var I = 0; I < 256; I++) + { + Place[I] = PlaceA[I] = PlaceB[I] = I; + PlaceC[I] = (~I + 1) & 0xff; + ChSet[I] = ChSetB[I] = I << 8; + ChSetA[I] = I; + ChSetC[I] = ((~I + 1) & 0xff) << 8; } - private void oldUnpWriteBuf() + new Span(NToPl).Clear(); // memset(NToPl,0,sizeof(NToPl)); + new Span(NToPlB).Clear(); // memset(NToPlB,0,sizeof(NToPlB)); + new Span(NToPlC).Clear(); // memset(NToPlC,0,sizeof(NToPlC)); + corrHuff(ChSetB, NToPlB); + } + + private void corrHuff(int[] CharSet, int[] NumToPlace) + { + int I, + J, + pos = 0; + for (I = 7; I >= 0; I--) { - if (unpPtr != wrPtr) + for (J = 0; J < 32; J++, pos++) { - unpSomeRead = true; + CharSet[pos] = ((CharSet[pos] & ~0xff) | I); // *CharSet=(*CharSet + + // & ~0xff) | I; } - if (unpPtr < wrPtr) - { - writeStream.Write(window, wrPtr, -wrPtr & PackDef.MAXWINMASK); - writeStream.Write(window, 0, unpPtr); - unpAllBuf = true; - } - else - { - writeStream.Write(window, wrPtr, unpPtr - wrPtr); - } - wrPtr = unpPtr; + } + new Span(NumToPlace).Clear(); // memset(NumToPlace,0,sizeof(NToPl)); + for (I = 6; I >= 0; I--) + { + NumToPlace[I] = (7 - I) * 32; } } + + private void oldCopyString(int Distance, int Length) + { + destUnpSize -= Length; + while ((Length--) != 0) + { + window[unpPtr] = window[(unpPtr - Distance) & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; + } + } + + private int decodeNum(int Num, int StartPos, int[] DecTab, int[] PosTab) + { + int I; + for (Num &= 0xfff0, I = 0; DecTab[I] <= Num; I++) + { + StartPos++; + } + AddBits(StartPos); + return ( + (Utility.URShift((Num - (I != 0 ? DecTab[I - 1] : 0)), (16 - StartPos))) + + PosTab[StartPos] + ); + } + + private void oldUnpWriteBuf() + { + if (unpPtr != wrPtr) + { + unpSomeRead = true; + } + if (unpPtr < wrPtr) + { + writeStream.Write(window, wrPtr, -wrPtr & PackDef.MAXWINMASK); + writeStream.Write(window, 0, unpPtr); + unpAllBuf = true; + } + else + { + writeStream.Write(window, wrPtr, unpPtr - wrPtr); + } + wrPtr = unpPtr; + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs index eb0ce540..15b980da 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs @@ -10,515 +10,661 @@ using System; using SharpCompress.Compressors.Rar.UnpackV1.Decode; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack { - internal partial class Unpack + private readonly MultDecode[] MD = new MultDecode[4]; + + private readonly byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; + + private int UnpAudioBlock, + UnpChannels, + UnpCurChannel, + UnpChannelDelta; + + private readonly AudioVariables[] AudV = new AudioVariables[4]; + + private readonly LitDecode LD = new LitDecode(); + + private readonly DistDecode DD = new DistDecode(); + + private readonly LowDistDecode LDD = new LowDistDecode(); + + private readonly RepDecode RD = new RepDecode(); + + private readonly BitDecode BD = new BitDecode(); + + private static readonly int[] LDecode = { - private readonly MultDecode[] MD = new MultDecode[4]; + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 12, + 14, + 16, + 20, + 24, + 28, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 160, + 192, + 224 + }; - private readonly byte[] UnpOldTable20 = new byte[PackDef.MC20 * 4]; - - private int UnpAudioBlock, UnpChannels, UnpCurChannel, UnpChannelDelta; - - private readonly AudioVariables[] AudV = new AudioVariables[4]; - - private readonly LitDecode LD = new LitDecode(); - - private readonly DistDecode DD = new DistDecode(); - - private readonly LowDistDecode LDD = new LowDistDecode(); - - private readonly RepDecode RD = new RepDecode(); - - private readonly BitDecode BD = new BitDecode(); - - private static readonly int[] LDecode = + private static ReadOnlySpan LBits => + new byte[] { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, - 56, 64, 80, 96, 112, 128, 160, 192, 224 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5 }; - private static ReadOnlySpan LBits => new byte[] + private static readonly int[] DDecode = + { + 0, + 1, + 2, + 3, + 4, + 6, + 8, + 12, + 16, + 24, + 32, + 48, + 64, + 96, + 128, + 192, + 256, + 384, + 512, + 768, + 1024, + 1536, + 2048, + 3072, + 4096, + 6144, + 8192, + 12288, + 16384, + 24576, + 32768, + 49152, + 65536, + 98304, + 131072, + 196608, + 262144, + 327680, + 393216, + 458752, + 524288, + 589824, + 655360, + 720896, + 786432, + 851968, + 917504, + 983040 + }; + + private static readonly int[] DBits = + { + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16 + }; + + private static readonly int[] SDDecode = { 0, 4, 8, 16, 32, 64, 128, 192 }; + + private static readonly int[] SDBits = { 2, 2, 3, 4, 5, 6, 6, 6 }; + + private void unpack20(bool solid) + { + int Bits; + + if (suspended) { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, - 4, 5, 5, 5, 5 - }; - - private static readonly int[] DDecode = + unpPtr = wrPtr; + } + else { - 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, - 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, - 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144, 327680, - 393216, 458752, 524288, 589824, 655360, 720896, 786432, 851968, - 917504, 983040 - }; - - private static readonly int[] DBits = - { - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, - 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16 - , 16, 16, 16, 16, 16, 16, 16 - }; - - private static readonly int[] SDDecode = { 0, 4, 8, 16, 32, 64, 128, 192 }; - - private static readonly int[] SDBits = { 2, 2, 3, 4, 5, 6, 6, 6 }; - - private void unpack20(bool solid) - { - int Bits; - - if (suspended) + UnpInitData(solid); + if (!unpReadBuf()) { - unpPtr = wrPtr; + return; } - else + if (!solid) { - UnpInitData(solid); - if (!unpReadBuf()) + if (!ReadTables20()) { return; } - if (!solid) + } + --destUnpSize; + } + + while (destUnpSize >= 0) + { + unpPtr &= PackDef.MAXWINMASK; + + if (inAddr > readTop - 30) + { + if (!unpReadBuf()) + { + break; + } + } + if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) + { + oldUnpWriteBuf(); + if (suspended) + { + return; + } + } + if (UnpAudioBlock != 0) + { + var AudioNumber = this.decodeNumber(MD[UnpCurChannel]); + + if (AudioNumber == 256) { if (!ReadTables20()) - { - return; - } - } - --destUnpSize; - } - - while (destUnpSize >= 0) - { - unpPtr &= PackDef.MAXWINMASK; - - if (inAddr > readTop - 30) - { - if (!unpReadBuf()) { break; } - } - if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) - { - oldUnpWriteBuf(); - if (suspended) - { - return; - } - } - if (UnpAudioBlock != 0) - { - int AudioNumber = this.decodeNumber(MD[UnpCurChannel]); - - if (AudioNumber == 256) - { - if (!ReadTables20()) - { - break; - } - continue; - } - window[unpPtr++] = DecodeAudio(AudioNumber); - if (++UnpCurChannel == UnpChannels) - { - UnpCurChannel = 0; - } - --destUnpSize; continue; } - - int Number = this.decodeNumber(LD); - if (Number < 256) + window[unpPtr++] = DecodeAudio(AudioNumber); + if (++UnpCurChannel == UnpChannels) { - window[unpPtr++] = (byte)Number; - --destUnpSize; - continue; + UnpCurChannel = 0; } - if (Number > 269) + --destUnpSize; + continue; + } + + var Number = this.decodeNumber(LD); + if (Number < 256) + { + window[unpPtr++] = (byte)Number; + --destUnpSize; + continue; + } + if (Number > 269) + { + var Length = LDecode[Number -= 270] + 3; + if ((Bits = LBits[Number]) > 0) { - int Length = LDecode[Number -= 270] + 3; - if ((Bits = LBits[Number]) > 0) - { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } - int DistNumber = this.decodeNumber(DD); - int Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - Distance += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } + var DistNumber = this.decodeNumber(DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) + { + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000L) + { + Length++; + } + } + + CopyString20(Length, Distance); + continue; + } + if (Number == 269) + { + if (!ReadTables20()) + { + break; + } + continue; + } + if (Number == 256) + { + CopyString20(lastLength, lastDist); + continue; + } + if (Number < 261) + { + var Distance = oldDist[(oldDistPtr - (Number - 256)) & 3]; + var LengthNumber = this.decodeNumber(RD); + var Length = LDecode[LengthNumber] + 2; + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); + } + if (Distance >= 0x101) + { + Length++; if (Distance >= 0x2000) { Length++; - if (Distance >= 0x40000L) + if (Distance >= 0x40000) { Length++; } } - - CopyString20(Length, Distance); - continue; - } - if (Number == 269) - { - if (!ReadTables20()) - { - break; - } - continue; - } - if (Number == 256) - { - CopyString20(lastLength, lastDist); - continue; - } - if (Number < 261) - { - int Distance = oldDist[(oldDistPtr - (Number - 256)) & 3]; - int LengthNumber = this.decodeNumber(RD); - int Length = LDecode[LengthNumber] + 2; - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - if (Distance >= 0x101) - { - Length++; - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000) - { - Length++; - } - } - } - CopyString20(Length, Distance); - continue; - } - if (Number < 270) - { - int Distance = SDDecode[Number -= 261] + 1; - if ((Bits = SDBits[Number]) > 0) - { - Distance += Utility.URShift(GetBits(), (16 - Bits)); - AddBits(Bits); - } - CopyString20(2, Distance); } + CopyString20(Length, Distance); + continue; } - ReadLastTables(); - oldUnpWriteBuf(); - } - - private void CopyString20(int Length, int Distance) - { - lastDist = oldDist[oldDistPtr++ & 3] = Distance; - lastLength = Length; - destUnpSize -= Length; - - int DestPtr = unpPtr - Distance; - if (DestPtr < PackDef.MAXWINSIZE - 300 && unpPtr < PackDef.MAXWINSIZE - 300) + if (Number < 270) { - window[unpPtr++] = window[DestPtr++]; - window[unpPtr++] = window[DestPtr++]; - while (Length > 2) + var Distance = SDDecode[Number -= 261] + 1; + if ((Bits = SDBits[Number]) > 0) { - Length--; - window[unpPtr++] = window[DestPtr++]; - } - } - else - { - while ((Length--) != 0) - { - window[unpPtr] = window[DestPtr++ & PackDef.MAXWINMASK]; - unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; + Distance += Utility.URShift(GetBits(), (16 - Bits)); + AddBits(Bits); } + CopyString20(2, Distance); } } + ReadLastTables(); + oldUnpWriteBuf(); + } - private bool ReadTables20() + private void CopyString20(int Length, int Distance) + { + lastDist = oldDist[oldDistPtr++ & 3] = Distance; + lastLength = Length; + destUnpSize -= Length; + + var DestPtr = unpPtr - Distance; + if (DestPtr < PackDef.MAXWINSIZE - 300 && unpPtr < PackDef.MAXWINSIZE - 300) { - byte[] BitLength = new byte[PackDef.BC20]; - byte[] Table = new byte[PackDef.MC20 * 4]; - int TableSize, N, I; - if (inAddr > readTop - 25) + window[unpPtr++] = window[DestPtr++]; + window[unpPtr++] = window[DestPtr++]; + while (Length > 2) + { + Length--; + window[unpPtr++] = window[DestPtr++]; + } + } + else + { + while ((Length--) != 0) + { + window[unpPtr] = window[DestPtr++ & PackDef.MAXWINMASK]; + unpPtr = (unpPtr + 1) & PackDef.MAXWINMASK; + } + } + } + + private bool ReadTables20() + { + var BitLength = new byte[PackDef.BC20]; + var Table = new byte[PackDef.MC20 * 4]; + int TableSize, + N, + I; + if (inAddr > readTop - 25) + { + if (!unpReadBuf()) + { + return (false); + } + } + var BitField = GetBits(); + UnpAudioBlock = (BitField & 0x8000); + + if (0 == (BitField & 0x4000)) + { + // memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + new Span(UnpOldTable20).Clear(); + } + AddBits(2); + + if (UnpAudioBlock != 0) + { + UnpChannels = ((Utility.URShift(BitField, 12)) & 3) + 1; + if (UnpCurChannel >= UnpChannels) + { + UnpCurChannel = 0; + } + AddBits(2); + TableSize = PackDef.MC20 * UnpChannels; + } + else + { + TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; + } + for (I = 0; I < PackDef.BC20; I++) + { + BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); + AddBits(4); + } + UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); + I = 0; + while (I < TableSize) + { + if (inAddr > readTop - 5) { if (!unpReadBuf()) { return (false); } } - int BitField = GetBits(); - UnpAudioBlock = (BitField & 0x8000); - - if (0 == (BitField & 0x4000)) + var Number = this.decodeNumber(BD); + if (Number < 16) { - // memset(UnpOldTable20,0,sizeof(UnpOldTable20)); - new Span(UnpOldTable20).Clear(); + Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); + I++; } - AddBits(2); - - if (UnpAudioBlock != 0) + else if (Number == 16) { - UnpChannels = ((Utility.URShift(BitField, 12)) & 3) + 1; - if (UnpCurChannel >= UnpChannels) - { - UnpCurChannel = 0; - } + N = (Utility.URShift(GetBits(), 14)) + 3; AddBits(2); - TableSize = PackDef.MC20 * UnpChannels; - } - else - { - TableSize = PackDef.NC20 + PackDef.DC20 + PackDef.RC20; - } - for (I = 0; I < PackDef.BC20; I++) - { - BitLength[I] = (byte)(Utility.URShift(GetBits(), 12)); - AddBits(4); - } - UnpackUtility.makeDecodeTables(BitLength, 0, BD, PackDef.BC20); - I = 0; - while (I < TableSize) - { - if (inAddr > readTop - 5) + while (N-- > 0 && I < TableSize) { - if (!unpReadBuf()) - { - return (false); - } - } - int Number = this.decodeNumber(BD); - if (Number < 16) - { - Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); + Table[I] = Table[I - 1]; I++; } - else if (Number == 16) + } + else + { + if (Number == 17) { - N = (Utility.URShift(GetBits(), 14)) + 3; - AddBits(2); - while (N-- > 0 && I < TableSize) - { - Table[I] = Table[I - 1]; - I++; - } + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); } else { - if (Number == 17) - { - N = (Utility.URShift(GetBits(), 13)) + 3; - AddBits(3); - } - else - { - N = (Utility.URShift(GetBits(), 9)) + 11; - AddBits(7); - } - while (N-- > 0 && I < TableSize) - { - Table[I++] = 0; - } + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; } } - if (inAddr > readTop) + } + if (inAddr > readTop) + { + return (true); + } + if (UnpAudioBlock != 0) + { + for (I = 0; I < UnpChannels; I++) { - return (true); + UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); } + } + else + { + UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); + UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); + } + + // memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); + for (var i = 0; i < UnpOldTable20.Length; i++) + { + UnpOldTable20[i] = Table[i]; + } + return (true); + } + + private void unpInitData20(bool Solid) + { + if (!Solid) + { + UnpChannelDelta = UnpCurChannel = 0; + UnpChannels = 1; + + // memset(AudV,0,sizeof(AudV)); + AudV[0] = new AudioVariables(); + AudV[1] = new AudioVariables(); + AudV[2] = new AudioVariables(); + AudV[3] = new AudioVariables(); + + // memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + new Span(UnpOldTable20).Clear(); + } + } + + private void ReadLastTables() + { + if (readTop >= inAddr + 5) + { if (UnpAudioBlock != 0) { - for (I = 0; I < UnpChannels; I++) + if (this.decodeNumber(MD[UnpCurChannel]) == 256) { - UnpackUtility.makeDecodeTables(Table, I * PackDef.MC20, MD[I], PackDef.MC20); + ReadTables20(); } } else { - UnpackUtility.makeDecodeTables(Table, 0, LD, PackDef.NC20); - UnpackUtility.makeDecodeTables(Table, PackDef.NC20, DD, PackDef.DC20); - UnpackUtility.makeDecodeTables(Table, PackDef.NC20 + PackDef.DC20, RD, PackDef.RC20); - } - - // memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); - for (int i = 0; i < UnpOldTable20.Length; i++) - { - UnpOldTable20[i] = Table[i]; - } - return (true); - } - - private void unpInitData20(bool Solid) - { - if (!Solid) - { - UnpChannelDelta = UnpCurChannel = 0; - UnpChannels = 1; - - // memset(AudV,0,sizeof(AudV)); - AudV[0] = new AudioVariables(); - AudV[1] = new AudioVariables(); - AudV[2] = new AudioVariables(); - AudV[3] = new AudioVariables(); - - // memset(UnpOldTable20,0,sizeof(UnpOldTable20)); - new Span(UnpOldTable20).Clear(); - } - } - - private void ReadLastTables() - { - if (readTop >= inAddr + 5) - { - if (UnpAudioBlock != 0) + if (this.decodeNumber(LD) == 269) { - if (this.decodeNumber(MD[UnpCurChannel]) == 256) - { - ReadTables20(); - } - } - else - { - if (this.decodeNumber(LD) == 269) - { - ReadTables20(); - } + ReadTables20(); } } } - - private byte DecodeAudio(int Delta) - { - AudioVariables v = AudV[UnpCurChannel]; - v.ByteCount = v.ByteCount + 1; - v.D4 = v.D3; - v.D3 = v.D2; // ->D3=V->D2; - v.D2 = v.LastDelta - v.D1; // ->D2=V->LastDelta-V->D1; - v.D1 = v.LastDelta; // V->D1=V->LastDelta; - - // int PCh=8*V->LastChar+V->K1*V->D1 +V->K2*V->D2 +V->K3*V->D3 - // +V->K4*V->D4+ V->K5*UnpChannelDelta; - int PCh = 8 * v.LastChar + v.K1 * v.D1; - PCh += v.K2 * v.D2 + v.K3 * v.D3; - PCh += v.K4 * v.D4 + v.K5 * UnpChannelDelta; - PCh = (Utility.URShift(PCh, 3)) & 0xFF; - - int Ch = PCh - Delta; - - int D = ((byte)Delta) << 3; - - v.Dif[0] += Math.Abs(D); // V->Dif[0]+=abs(D); - v.Dif[1] += Math.Abs(D - v.D1); // V->Dif[1]+=abs(D-V->D1); - v.Dif[2] += Math.Abs(D + v.D1); // V->Dif[2]+=abs(D+V->D1); - v.Dif[3] += Math.Abs(D - v.D2); // V->Dif[3]+=abs(D-V->D2); - v.Dif[4] += Math.Abs(D + v.D2); // V->Dif[4]+=abs(D+V->D2); - v.Dif[5] += Math.Abs(D - v.D3); // V->Dif[5]+=abs(D-V->D3); - v.Dif[6] += Math.Abs(D + v.D3); // V->Dif[6]+=abs(D+V->D3); - v.Dif[7] += Math.Abs(D - v.D4); // V->Dif[7]+=abs(D-V->D4); - v.Dif[8] += Math.Abs(D + v.D4); // V->Dif[8]+=abs(D+V->D4); - v.Dif[9] += Math.Abs(D - UnpChannelDelta); // V->Dif[9]+=abs(D-UnpChannelDelta); - v.Dif[10] += Math.Abs(D + UnpChannelDelta); // V->Dif[10]+=abs(D+UnpChannelDelta); - - v.LastDelta = (byte)(Ch - v.LastChar); - UnpChannelDelta = v.LastDelta; - v.LastChar = Ch; // V->LastChar=Ch; - - if ((v.ByteCount & 0x1F) == 0) - { - int MinDif = v.Dif[0], NumMinDif = 0; - v.Dif[0] = 0; // ->Dif[0]=0; - for (int I = 1; I < v.Dif.Length; I++) - { - if (v.Dif[I] < MinDif) - { - MinDif = v.Dif[I]; - NumMinDif = I; - } - v.Dif[I] = 0; - } - switch (NumMinDif) - { - case 1: - if (v.K1 >= -16) - { - v.K1 = v.K1 - 1; // V->K1--; - } - break; - - case 2: - if (v.K1 < 16) - { - v.K1 = v.K1 + 1; // V->K1++; - } - break; - - case 3: - if (v.K2 >= -16) - { - v.K2 = v.K2 - 1; // V->K2--; - } - break; - - case 4: - if (v.K2 < 16) - { - v.K2 = v.K2 + 1; // V->K2++; - } - break; - - case 5: - if (v.K3 >= -16) - { - v.K3 = v.K3 - 1; - } - break; - - case 6: - if (v.K3 < 16) - { - v.K3 = v.K3 + 1; - } - break; - - case 7: - if (v.K4 >= -16) - { - v.K4 = v.K4 - 1; - } - break; - - case 8: - if (v.K4 < 16) - { - v.K4 = v.K4 + 1; - } - break; - - case 9: - if (v.K5 >= -16) - { - v.K5 = v.K5 - 1; - } - break; - - case 10: - if (v.K5 < 16) - { - v.K5 = v.K5 + 1; - } - break; - } - } - return ((byte)Ch); - } + } + + private byte DecodeAudio(int Delta) + { + var v = AudV[UnpCurChannel]; + v.ByteCount++; + v.D4 = v.D3; + v.D3 = v.D2; // ->D3=V->D2; + v.D2 = v.LastDelta - v.D1; // ->D2=V->LastDelta-V->D1; + v.D1 = v.LastDelta; // V->D1=V->LastDelta; + + // int PCh=8*V->LastChar+V->K1*V->D1 +V->K2*V->D2 +V->K3*V->D3 + // +V->K4*V->D4+ V->K5*UnpChannelDelta; + var PCh = (8 * v.LastChar) + (v.K1 * v.D1); + PCh += (v.K2 * v.D2) + (v.K3 * v.D3); + PCh += (v.K4 * v.D4) + (v.K5 * UnpChannelDelta); + PCh = (Utility.URShift(PCh, 3)) & 0xFF; + + var Ch = PCh - Delta; + + var D = ((byte)Delta) << 3; + + v.Dif[0] += Math.Abs(D); // V->Dif[0]+=abs(D); + v.Dif[1] += Math.Abs(D - v.D1); // V->Dif[1]+=abs(D-V->D1); + v.Dif[2] += Math.Abs(D + v.D1); // V->Dif[2]+=abs(D+V->D1); + v.Dif[3] += Math.Abs(D - v.D2); // V->Dif[3]+=abs(D-V->D2); + v.Dif[4] += Math.Abs(D + v.D2); // V->Dif[4]+=abs(D+V->D2); + v.Dif[5] += Math.Abs(D - v.D3); // V->Dif[5]+=abs(D-V->D3); + v.Dif[6] += Math.Abs(D + v.D3); // V->Dif[6]+=abs(D+V->D3); + v.Dif[7] += Math.Abs(D - v.D4); // V->Dif[7]+=abs(D-V->D4); + v.Dif[8] += Math.Abs(D + v.D4); // V->Dif[8]+=abs(D+V->D4); + v.Dif[9] += Math.Abs(D - UnpChannelDelta); // V->Dif[9]+=abs(D-UnpChannelDelta); + v.Dif[10] += Math.Abs(D + UnpChannelDelta); // V->Dif[10]+=abs(D+UnpChannelDelta); + + v.LastDelta = (byte)(Ch - v.LastChar); + UnpChannelDelta = v.LastDelta; + v.LastChar = Ch; // V->LastChar=Ch; + + if ((v.ByteCount & 0x1F) == 0) + { + int MinDif = v.Dif[0], + NumMinDif = 0; + v.Dif[0] = 0; // ->Dif[0]=0; + for (var I = 1; I < v.Dif.Length; I++) + { + if (v.Dif[I] < MinDif) + { + MinDif = v.Dif[I]; + NumMinDif = I; + } + v.Dif[I] = 0; + } + switch (NumMinDif) + { + case 1: + if (v.K1 >= -16) + { + v.K1--; // V->K1--; + } + break; + + case 2: + if (v.K1 < 16) + { + v.K1++; // V->K1++; + } + break; + + case 3: + if (v.K2 >= -16) + { + v.K2--; // V->K2--; + } + break; + + case 4: + if (v.K2 < 16) + { + v.K2++; // V->K2++; + } + break; + + case 5: + if (v.K3 >= -16) + { + v.K3--; + } + break; + + case 6: + if (v.K3 < 16) + { + v.K3++; + } + break; + + case 7: + if (v.K4 >= -16) + { + v.K4--; + } + break; + + case 8: + if (v.K4 < 16) + { + v.K4++; + } + break; + + case 9: + if (v.K5 >= -16) + { + v.K5--; + } + break; + + case 10: + if (v.K5 < 16) + { + v.K5++; + } + break; + } + } + return ((byte)Ch); } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs index d9e68205..bebaebee 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs @@ -2,895 +2,929 @@ using System; using System.Collections.Generic; using SharpCompress.Compressors.Rar.UnpackV1.Decode; -using size_t = System.UInt32; -using UnpackBlockHeader = SharpCompress.Compressors.Rar.UnpackV1; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal partial class Unpack { - internal partial class Unpack + // Maximum allowed number of compressed bits processed in quick mode. + private const int MAX_QUICK_DECODE_BITS = 10; + + // Maximum number of filters per entire data block. Must be at least + // twice more than MAX_PACK_FILTERS to store filters from two data blocks. + private const int MAX_UNPACK_FILTERS = 8192; + + // Maximum number of filters per entire data block for RAR3 unpack. + // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters + // from two data blocks. + private const int MAX3_UNPACK_FILTERS = 8192; + + // Limit maximum number of channels in RAR3 delta filter to some reasonable + // value to prevent too slow processing of corrupt archives with invalid + // channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. + // No need to provide it for RAR5, which uses only 5 bits to store channels. + private const int MAX3_UNPACK_CHANNELS = 1024; + + // Maximum size of single filter block. We restrict it to limit memory + // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. + private const int MAX_FILTER_BLOCK_SIZE = 0x400000; + + // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, + // so we keep number of buffered filter in unpacker reasonable. + private const int UNPACK_MAX_WRITE = 0x400000; + + // Decode compressed bit fields to alphabet numbers. + // struct DecodeTable + // { + // // Real size of DecodeNum table. + // public uint MaxNum; + // + // // Left aligned start and upper limit codes defining code space + // // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of + // // range for bit length and DecodeLen[BitLength] defines next code + // // after the end of range or in other words the upper limit code + // // for specified bit length. + // //uint DecodeLen[16]; + // public uint [] DecodeLen = new uint[16]; + // + // // Every item of this array contains the sum of all preceding items. + // // So it contains the start position in code list for every bit length. + // public uint DecodePos[16]; + // + // // Number of compressed bits processed in quick mode. + // // Must not exceed MAX_QUICK_DECODE_BITS. + // public uint QuickBits; + // + // // Translates compressed bits (up to QuickBits length) + // // to bit length in quick mode. + // public byte QuickLen[1< unpPtr; + set => unpPtr = value; + } + private int ReadBorder + { + get => readBorder; + set => readBorder = value; + } + private long DestUnpSize + { + get => destUnpSize; + set => destUnpSize = value; + } + private long WrittenFileSize + { + get => writtenFileSize; + set => writtenFileSize = value; + } + private byte[] Window => window; - // Maximum allowed number of compressed bits processed in quick mode. - private const int MAX_QUICK_DECODE_BITS = 10; + private uint LastLength + { + get => (uint)lastLength; + set => lastLength = (int)value; + } - // Maximum number of filters per entire data block. Must be at least - // twice more than MAX_PACK_FILTERS to store filters from two data blocks. - private const int MAX_UNPACK_FILTERS = 8192; + private uint OldDistN(int i) => (uint)oldDist[i]; - // Maximum number of filters per entire data block for RAR3 unpack. - // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters - // from two data blocks. - private const int MAX3_UNPACK_FILTERS = 8192; + private void SetOldDistN(int i, uint value) => oldDist[i] = (int)value; - // Limit maximum number of channels in RAR3 delta filter to some reasonable - // value to prevent too slow processing of corrupt archives with invalid - // channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. - // No need to provide it for RAR5, which uses only 5 bits to store channels. - private const int MAX3_UNPACK_CHANNELS = 1024; + private int WrPtr + { + get => wrPtr; + set => wrPtr = value; + } + private Unpack BlockHeader => this; - // Maximum size of single filter block. We restrict it to limit memory - // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. - private const int MAX_FILTER_BLOCK_SIZE = 0x400000; + private Unpack Header => this; - // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, - // so we keep number of buffered filter in unpacker reasonable. - private const int UNPACK_MAX_WRITE = 0x400000; + private int ReadTop + { + get => readTop; + set => readTop = value; + } + private List Filters => filters; - // Decode compressed bit fields to alphabet numbers. - // struct DecodeTable - // { - // // Real size of DecodeNum table. - // public uint MaxNum; - // - // // Left aligned start and upper limit codes defining code space - // // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of - // // range for bit length and DecodeLen[BitLength] defines next code - // // after the end of range or in other words the upper limit code - // // for specified bit length. - // //uint DecodeLen[16]; - // public uint [] DecodeLen = new uint[16]; - // - // // Every item of this array contains the sum of all preceding items. - // // So it contains the start position in code list for every bit length. - // public uint DecodePos[16]; - // - // // Number of compressed bits processed in quick mode. - // // Must not exceed MAX_QUICK_DECODE_BITS. - // public uint QuickBits; - // - // // Translates compressed bits (up to QuickBits length) - // // to bit length in quick mode. - // public byte QuickLen[1< Filters { get { return filters; } } - - // TODO: make sure these aren't already somewhere else - public int BlockSize; - public int BlockBitSize; - public int BlockStart; - public int HeaderSize; - public bool LastBlockInFile; - public bool TablePresent; - - public void Unpack5(bool Solid) + if (!Suspended) { - FileExtracted = true; - - if (!Suspended) + UnpInitData(Solid); + if (!UnpReadBuf()) { - UnpInitData(Solid); - if (!UnpReadBuf()) + return; + } + + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if (!ReadBlockHeader() || !ReadTables() || !TablesRead5) + { + return; + } + } + + while (true) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr >= ReadBorder) + { + var FileDone = false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while ( + Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + && Inp.InBit >= BlockHeader.BlockBitSize + ) + { + if (BlockHeader.LastBlockInFile) + { + FileDone = true; + break; + } + if (!ReadBlockHeader() || !ReadTables()) + { + return; + } + } + if (FileDone || !UnpReadBuf()) + { + break; + } + } + + if ( + ((WriteBorder - UnpPtr) & MaxWinMask) < PackDef.MAX_LZ_MATCH + 3 + && WriteBorder != UnpPtr + ) + { + UnpWriteBuf(); + if (WrittenFileSize > DestUnpSize) { return; } - // Check TablesRead5 to be sure that we read tables at least once - // regardless of current block header TablePresent flag. - // So we can safefly use these tables below. - if (!ReadBlockHeader() || - !ReadTables() || !TablesRead5) + if (Suspended) { + FileExtracted = false; return; } } - while (true) + //uint MainSlot=DecodeNumber(Inp,LD); + var MainSlot = this.DecodeNumber(LD); + if (MainSlot < 256) { - UnpPtr &= MaxWinMask; + // if (Fragmented) + // FragWindow[UnpPtr++]=(byte)MainSlot; + // else + Window[UnpPtr++] = (byte)MainSlot; + continue; + } + if (MainSlot >= 262) + { + var Length = SlotToLength(MainSlot - 262); - if (Inp.InAddr >= ReadBorder) + //uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); + int DBits; + uint Distance = 1, + DistSlot = this.DecodeNumber(DD); + if (DistSlot < 4) { - bool FileDone = false; + DBits = 0; + Distance += DistSlot; + } + else + { + //DBits=DistSlot/2 - 1; + DBits = (int)((DistSlot / 2) - 1); + Distance += (2 | (DistSlot & 1)) << DBits; + } - // We use 'while', because for empty block containing only Huffman table, - // we'll be on the block border once again just after reading the table. - while (Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 || - Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 && - Inp.InBit >= BlockHeader.BlockBitSize) + if (DBits > 0) + { + if (DBits >= 4) { - if (BlockHeader.LastBlockInFile) + if (DBits > 4) { - FileDone = true; - break; + Distance += ((Inp.getbits() >> (36 - DBits)) << 4); + Inp.AddBits(DBits - 4); } - if (!ReadBlockHeader() || !ReadTables()) - { - return; - } - } - if (FileDone || !UnpReadBuf()) - { - break; - } - } - - if (((WriteBorder - UnpPtr) & MaxWinMask) < PackDef.MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) - { - UnpWriteBuf(); - if (WrittenFileSize > DestUnpSize) - { - return; - } - - if (Suspended) - { - FileExtracted = false; - return; - } - } - - //uint MainSlot=DecodeNumber(Inp,LD); - uint MainSlot = this.DecodeNumber(LD); - if (MainSlot < 256) - { - // if (Fragmented) - // FragWindow[UnpPtr++]=(byte)MainSlot; - // else - Window[UnpPtr++] = (byte)MainSlot; - continue; - } - if (MainSlot >= 262) - { - uint Length = SlotToLength(MainSlot - 262); - - //uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD); - int DBits; - uint Distance = 1, DistSlot = this.DecodeNumber(DD); - if (DistSlot < 4) - { - DBits = 0; - Distance += DistSlot; + //uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); + var LowDist = this.DecodeNumber(LDD); + Distance += LowDist; } else { - //DBits=DistSlot/2 - 1; - DBits = (int)(DistSlot / 2 - 1); - Distance += (2 | (DistSlot & 1)) << DBits; + Distance += Inp.getbits() >> (32 - DBits); + Inp.AddBits(DBits); } + } - if (DBits > 0) - { - if (DBits >= 4) - { - if (DBits > 4) - { - Distance += ((Inp.getbits() >> (36 - DBits)) << 4); - Inp.AddBits(DBits - 4); - } - //uint LowDist=DecodeNumber(Inp,&BlockTables.LDD); - uint LowDist = this.DecodeNumber(LDD); - Distance += LowDist; - } - else - { - Distance += Inp.getbits() >> (32 - DBits); - Inp.AddBits(DBits); - } - } - - if (Distance > 0x100) + if (Distance > 0x100) + { + Length++; + if (Distance > 0x2000) { Length++; - if (Distance > 0x2000) + if (Distance > 0x40000) { Length++; - if (Distance > 0x40000) - { - Length++; - } } } - - InsertOldDist(Distance); - LastLength = Length; - // if (Fragmented) - // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); - // else - CopyString(Length, Distance); - continue; } - if (MainSlot == 256) - { - UnpackFilter Filter = new UnpackFilter(); - if (!ReadFilter(Filter) || !AddFilter(Filter)) - { - break; - } - continue; - } - if (MainSlot == 257) - { - if (LastLength != 0) - // if (Fragmented) - // FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); - // else - //CopyString(LastLength,OldDist[0]); - { - CopyString(LastLength, OldDistN(0)); - } - - continue; - } - if (MainSlot < 262) - { - //uint DistNum=MainSlot-258; - int DistNum = (int)(MainSlot - 258); - //uint Distance=OldDist[DistNum]; - uint Distance = OldDistN(DistNum); - //for (uint I=DistNum;I>0;I--) - for (int I = DistNum; I > 0; I--) - //OldDistN[I]=OldDistN(I-1); - { - SetOldDistN(I, OldDistN(I - 1)); - } - - //OldDistN[0]=Distance; - SetOldDistN(0, Distance); - - uint LengthSlot = this.DecodeNumber(RD); - uint Length = SlotToLength(LengthSlot); - LastLength = Length; - // if (Fragmented) - // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); - // else - CopyString(Length, Distance); - continue; - } + InsertOldDist(Distance); + LastLength = Length; + // if (Fragmented) + // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); + // else + CopyString(Length, Distance); + continue; + } + if (MainSlot == 256) + { + var Filter = new UnpackFilter(); + if (!ReadFilter(Filter) || !AddFilter(Filter)) + { + break; + } + + continue; + } + if (MainSlot == 257) + { + if (LastLength != 0) + // if (Fragmented) + // FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask); + // else + //CopyString(LastLength,OldDist[0]); + { + CopyString(LastLength, OldDistN(0)); + } + + continue; + } + if (MainSlot < 262) + { + //uint DistNum=MainSlot-258; + var DistNum = (int)(MainSlot - 258); + //uint Distance=OldDist[DistNum]; + var Distance = OldDistN(DistNum); + //for (uint I=DistNum;I>0;I--) + for (var I = DistNum; I > 0; I--) + //OldDistN[I]=OldDistN(I-1); + { + SetOldDistN(I, OldDistN(I - 1)); + } + + //OldDistN[0]=Distance; + SetOldDistN(0, Distance); + + var LengthSlot = this.DecodeNumber(RD); + var Length = SlotToLength(LengthSlot); + LastLength = Length; + // if (Fragmented) + // FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask); + // else + CopyString(Length, Distance); + continue; + } + } + UnpWriteBuf(); + } + + private uint ReadFilterData() + { + var ByteCount = (Inp.fgetbits() >> 14) + 1; + Inp.AddBits(2); + + uint Data = 0; + //for (uint I=0;I> 8) << (I * 8); + Inp.AddBits(8); + } + return Data; + } + + private bool ReadFilter(UnpackFilter Filter) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + { + if (!UnpReadBuf()) + { + return false; } - UnpWriteBuf(); } - private uint ReadFilterData() + Filter.uBlockStart = ReadFilterData(); + Filter.uBlockLength = ReadFilterData(); + if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) { - uint ByteCount = (Inp.fgetbits() >> 14) + 1; - Inp.AddBits(2); - - uint Data = 0; - //for (uint I=0;I> 8) << (I * 8); - Inp.AddBits(8); - } - return Data; + Filter.BlockLength = 0; } - private bool ReadFilter(UnpackFilter Filter) + //Filter.Type=Inp.fgetbits()>>13; + Filter.Type = (byte)(Inp.fgetbits() >> 13); + Inp.faddbits(3); + + if (Filter.Type == (byte)FilterType.FILTER_DELTA) { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) - { - if (!UnpReadBuf()) - { - return false; - } - } - - Filter.uBlockStart = ReadFilterData(); - Filter.uBlockLength = ReadFilterData(); - if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) - { - Filter.BlockLength = 0; - } - - //Filter.Type=Inp.fgetbits()>>13; - Filter.Type = (byte)(Inp.fgetbits() >> 13); - Inp.faddbits(3); - - if (Filter.Type == (byte)FilterType.FILTER_DELTA) - { - //Filter.Channels=(Inp.fgetbits()>>11)+1; - Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); - Inp.faddbits(5); - } - - return true; + //Filter.Channels=(Inp.fgetbits()>>11)+1; + Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); + Inp.faddbits(5); } - private bool AddFilter(UnpackFilter Filter) + return true; + } + + private bool AddFilter(UnpackFilter Filter) + { + if (Filters.Count >= MAX_UNPACK_FILTERS) { + UnpWriteBuf(); // Write data, apply and flush filters. if (Filters.Count >= MAX_UNPACK_FILTERS) { - UnpWriteBuf(); // Write data, apply and flush filters. - if (Filters.Count >= MAX_UNPACK_FILTERS) - { - InitFilters(); // Still too many filters, prevent excessive memory use. - } - } - - // If distance to filter start is that large that due to circular dictionary - // mode now it points to old not written yet data, then we set 'NextWindow' - // flag and process this filter only after processing that older data. - Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; - - Filter.uBlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask); - Filters.Add(Filter); - return true; - } - - private bool UnpReadBuf() - { - int DataSize = ReadTop - Inp.InAddr; // Data left to process. - if (DataSize < 0) - { - return false; - } - - BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; - if (Inp.InAddr > MAX_SIZE / 2) - { - // If we already processed more than half of buffer, let's move - // remaining data into beginning to free more space for new data - // and ensure that calling function does not cross the buffer border - // even if we did not read anything here. Also it ensures that read size - // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk - // to make it zero. - if (DataSize > 0) - //memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); - { - Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); - } - - // TODO: perf - //Buffer.BlockCopy(InBuf, inAddr, InBuf, 0, DataSize); - - Inp.InAddr = 0; - ReadTop = DataSize; - } - else - { - DataSize = ReadTop; - } - - int ReadCode = 0; - if (MAX_SIZE != DataSize) - //ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); - { - ReadCode = readStream.Read(InBuf, DataSize, MAX_SIZE - DataSize); - } - - if (ReadCode > 0) // Can be also -1. - { - ReadTop += ReadCode; - } - - ReadBorder = ReadTop - 30; - BlockHeader.BlockStart = Inp.InAddr; - if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. - { - // We may need to quit from main extraction loop and read new block header - // and trees earlier than data in input buffer ends. - ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - } - return ReadCode != -1; - } - - //? - // void UnpWriteBuf() - // { - // size_t WrittenBorder=WrPtr; - // size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; - // size_t WriteSizeLeft=FullWriteSize; - // bool NotAllFiltersProcessed=false; - // for (size_t I=0;IType==FilterType.FILTER_NONE) - // continue; - // if (flt->NextWindow) - // { - // // Here we skip filters which have block start in current data range - // // due to address wrap around in circular dictionary, but actually - // // belong to next dictionary block. If such filter start position - // // is included to current write range, then we reset 'NextWindow' flag. - // // In fact we can reset it even without such check, because current - // // implementation seems to guarantee 'NextWindow' flag reset after - // // buffer writing for all existing filters. But let's keep this check - // // just in case. Compressor guarantees that distance between - // // filter block start and filter storing position cannot exceed - // // the dictionary size. So if we covered the filter block start with - // // our write here, we can safely assume that filter is applicable - // // to next block on no further wrap arounds is possible. - // if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) - // flt->NextWindow=false; - // continue; - // } - // uint BlockStart=flt->BlockStart; - // uint BlockLength=flt->BlockLength; - // if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. - // { - // uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; - // - // FilterSrcMemory.Alloc(BlockLength); - // byte *Mem=&FilterSrcMemory[0]; - // if (BlockStartUnpWrite(OutMem,BlockLength); - // - // UnpSomeRead=true; - // WrittenFileSize+=BlockLength; - // WrittenBorder=BlockEnd; - // WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; - // } - // } - // else - // { - // // Current filter intersects the window write border, so we adjust - // // the window border to process this filter next time, not now. - // WrPtr=WrittenBorder; - // - // // Since Filter start position can only increase, we quit processing - // // all following filters for this data block and reset 'NextWindow' - // // flag for them. - // for (size_t J=I;JType!=FilterType.FILTER_NONE) - // flt->NextWindow=false; - // } - // - // // Do not write data left after current filter now. - // NotAllFiltersProcessed=true; - // break; - // } - // } - // } - // - // // Remove processed filters from queue. - // size_t EmptyCount=0; - // for (size_t I=0;I0) - // Filters[I-EmptyCount]=Filters[I]; - // if (Filters[I].Type==FilterType.FILTER_NONE) - // EmptyCount++; - // } - // if (EmptyCount>0) - // Filters.Alloc(Filters.Size()-EmptyCount); - // - // if (!NotAllFiltersProcessed) // Only if all filters are processed. - // { - // // Write data left after last filter. - // UnpWriteArea(WrittenBorder,UnpPtr); - // WrPtr=UnpPtr; - // } - // - // // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE - // // instead of potentially huge MaxWinSize blocks. It also allows us - // // to keep the size of Filters array reasonable. - // WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; - // - // // Choose the nearest among WriteBorder and WrPtr actual written border. - // // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. - // if (WriteBorder==UnpPtr || - // WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) - // WriteBorder=WrPtr; - // } - - - // unused - //x byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) - // byte[] ApplyFilter(byte []Data, uint DataSize, UnpackFilter Flt) - // { - // //x byte *SrcData=Data; - // byte []SrcData=Data; - // switch(Flt.Type) - // { - // case (byte)FilterType.FILTER_E8: - // case (byte)FilterType.FILTER_E8E9: - // { - // uint FileOffset=(uint)WrittenFileSize; - // - // const uint FileSize=0x1000000; - // byte CmpByte2=Flt.Type==(byte)FilterType.FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; - // // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" - // // to avoid overflow for DataSize<4. - // for (uint CurPos=0;CurPos+4=0 - // RawPut4(Addr+FileSize,Data); - // } - // else - // if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); - // D[2]=(byte)(Offset>>16); - // } - // } - // } - // return SrcData; - // case (byte)FilterType.FILTER_DELTA: - // { - // // Unlike RAR3, we do not need to reject excessive channel - // // values here, since RAR5 uses only 5 bits to store channel. - // uint Channels=Flt->Channels,SrcPos=0; - // - // FilterDstMemory.Alloc(DataSize); - // byte *DstData=&FilterDstMemory[0]; - // - // // Bytes from same channels are grouped to continual data blocks, - // // so we need to place them back to their interleaving positions. - // for (uint CurChannel=0;CurChannel0) - //// { - //// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); - //// UnpWriteData(&FragWindow[StartPtr],BlockSize); - //// SizeToWrite-=BlockSize; - //// StartPtr=(StartPtr+BlockSize) & MaxWinMask; - //// } - //// } - //// else - // if (EndPtr=DestUnpSize) - // return; - // size_t WriteSize=Size; - // long LeftToWrite=DestUnpSize-WrittenFileSize; - // if ((long)WriteSize>LeftToWrite) - // WriteSize=(size_t)LeftToWrite; - // UnpIO->UnpWrite(Data,WriteSize); - // WrittenFileSize+=Size; - // } - - private void UnpInitData50(bool Solid) - { - if (!Solid) - { - TablesRead5 = false; + InitFilters(); // Still too many filters, prevent excessive memory use. } } - private bool ReadBlockHeader() - { - Header.HeaderSize = 0; - - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) - { - if (!UnpReadBuf()) - { - return false; - } - } - - //Inp.faddbits((8-Inp.InBit)&7); - Inp.faddbits((uint)((8 - Inp.InBit) & 7)); - - byte BlockFlags = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - //uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. - uint ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. - - if (ByteCount == 4) - { - return false; - } - - //Header.HeaderSize=2+ByteCount; - Header.HeaderSize = (int)(2 + ByteCount); - - Header.BlockBitSize = (BlockFlags & 7) + 1; - - byte SavedCheckSum = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - - int BlockSize = 0; - //for (uint I=0;I>8)<<(I*8); - BlockSize += (int)(Inp.fgetbits() >> 8) << (I * 8); - Inp.AddBits(8); - } - - Header.BlockSize = BlockSize; - byte CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); - if (CheckSum != SavedCheckSum) - { - return false; - } - - Header.BlockStart = Inp.InAddr; - ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); - - Header.LastBlockInFile = (BlockFlags & 0x40) != 0; - Header.TablePresent = (BlockFlags & 0x80) != 0; - return true; - } - - //? - // bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) - // { - // if (!Header.TablePresent) - // return true; - // - // if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) - // if (!UnpReadBuf()) - // return false; - // - // byte BitLength[BC]; - // for (uint I=0;I> 12); - // Inp.faddbits(4); - // if (Length==15) - // { - // uint ZeroCount=(byte)(Inp.fgetbits() >> 12); - // Inp.faddbits(4); - // if (ZeroCount==0) - // BitLength[I]=15; - // else - // { - // ZeroCount+=2; - // while (ZeroCount-- > 0 && IReadTop-5) - // if (!UnpReadBuf()) - // return false; - // uint Number=DecodeNumber(Inp,&Tables.BD); - // if (Number<16) - // { - // Table[I]=Number; - // I++; - // } - // else - // if (Number<18) - // { - // uint N; - // if (Number==16) - // { - // N=(Inp.fgetbits() >> 13)+3; - // Inp.faddbits(3); - // } - // else - // { - // N=(Inp.fgetbits() >> 9)+11; - // Inp.faddbits(7); - // } - // if (I==0) - // { - // // We cannot have "repeat previous" code at the first position. - // // Multiple such codes would shift Inp position without changing I, - // // which can lead to reading beyond of Inp boundary in mutithreading - // // mode, where Inp.ExternalBuffer disables bounds check and we just - // // reserve a lot of buffer space to not need such check normally. - // return false; - // } - // else - // while (N-- > 0 && I> 13)+3; - // Inp.faddbits(3); - // } - // else - // { - // N=(Inp.fgetbits() >> 9)+11; - // Inp.faddbits(7); - // } - // while (N-- > 0 && IReadTop) - // return false; - // MakeDecodeTables(&Table[0],&Tables.LD,NC); - // MakeDecodeTables(&Table[NC],&Tables.DD,DC); - // MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); - // MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); - // return true; - // } - - //? - // void InitFilters() - // { - // Filters.SoftReset(); - // } + // If distance to filter start is that large that due to circular dictionary + // mode now it points to old not written yet data, then we set 'NextWindow' + // flag and process this filter only after processing that older data. + Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; + Filter.uBlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask); + Filters.Add(Filter); + return true; } + + private bool UnpReadBuf() + { + var DataSize = ReadTop - Inp.InAddr; // Data left to process. + if (DataSize < 0) + { + return false; + } + + BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; + if (Inp.InAddr > MAX_SIZE / 2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize > 0) + //memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + { + Array.Copy(InBuf, inAddr, InBuf, 0, DataSize); + } + + // TODO: perf + //Buffer.BlockCopy(InBuf, inAddr, InBuf, 0, DataSize); + + Inp.InAddr = 0; + ReadTop = DataSize; + } + else + { + DataSize = ReadTop; + } + + var ReadCode = 0; + if (MAX_SIZE != DataSize) + //ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput.MAX_SIZE-DataSize); + { + ReadCode = readStream.Read(InBuf, DataSize, MAX_SIZE - DataSize); + } + + if (ReadCode > 0) // Can be also -1. + { + ReadTop += ReadCode; + } + + ReadBorder = ReadTop - 30; + BlockHeader.BlockStart = Inp.InAddr; + if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. + { + // We may need to quit from main extraction loop and read new block header + // and trees earlier than data in input buffer ends. + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + } + return ReadCode != -1; + } + + //? + // void UnpWriteBuf() + // { + // size_t WrittenBorder=WrPtr; + // size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask; + // size_t WriteSizeLeft=FullWriteSize; + // bool NotAllFiltersProcessed=false; + // for (size_t I=0;IType==FilterType.FILTER_NONE) + // continue; + // if (flt->NextWindow) + // { + // // Here we skip filters which have block start in current data range + // // due to address wrap around in circular dictionary, but actually + // // belong to next dictionary block. If such filter start position + // // is included to current write range, then we reset 'NextWindow' flag. + // // In fact we can reset it even without such check, because current + // // implementation seems to guarantee 'NextWindow' flag reset after + // // buffer writing for all existing filters. But let's keep this check + // // just in case. Compressor guarantees that distance between + // // filter block start and filter storing position cannot exceed + // // the dictionary size. So if we covered the filter block start with + // // our write here, we can safely assume that filter is applicable + // // to next block on no further wrap arounds is possible. + // if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize) + // flt->NextWindow=false; + // continue; + // } + // uint BlockStart=flt->BlockStart; + // uint BlockLength=flt->BlockLength; + // if (((BlockStart-WrittenBorder)&MaxWinMask)0) // We set it to 0 also for invalid filters. + // { + // uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask; + // + // FilterSrcMemory.Alloc(BlockLength); + // byte *Mem=&FilterSrcMemory[0]; + // if (BlockStartUnpWrite(OutMem,BlockLength); + // + // UnpSomeRead=true; + // WrittenFileSize+=BlockLength; + // WrittenBorder=BlockEnd; + // WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask; + // } + // } + // else + // { + // // Current filter intersects the window write border, so we adjust + // // the window border to process this filter next time, not now. + // WrPtr=WrittenBorder; + // + // // Since Filter start position can only increase, we quit processing + // // all following filters for this data block and reset 'NextWindow' + // // flag for them. + // for (size_t J=I;JType!=FilterType.FILTER_NONE) + // flt->NextWindow=false; + // } + // + // // Do not write data left after current filter now. + // NotAllFiltersProcessed=true; + // break; + // } + // } + // } + // + // // Remove processed filters from queue. + // size_t EmptyCount=0; + // for (size_t I=0;I0) + // Filters[I-EmptyCount]=Filters[I]; + // if (Filters[I].Type==FilterType.FILTER_NONE) + // EmptyCount++; + // } + // if (EmptyCount>0) + // Filters.Alloc(Filters.Size()-EmptyCount); + // + // if (!NotAllFiltersProcessed) // Only if all filters are processed. + // { + // // Write data left after last filter. + // UnpWriteArea(WrittenBorder,UnpPtr); + // WrPtr=UnpPtr; + // } + // + // // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE + // // instead of potentially huge MaxWinSize blocks. It also allows us + // // to keep the size of Filters array reasonable. + // WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask; + // + // // Choose the nearest among WriteBorder and WrPtr actual written border. + // // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. + // if (WriteBorder==UnpPtr || + // WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask)) + // WriteBorder=WrPtr; + // } + + + // unused + //x byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt) + // byte[] ApplyFilter(byte []Data, uint DataSize, UnpackFilter Flt) + // { + // //x byte *SrcData=Data; + // byte []SrcData=Data; + // switch(Flt.Type) + // { + // case (byte)FilterType.FILTER_E8: + // case (byte)FilterType.FILTER_E8E9: + // { + // uint FileOffset=(uint)WrittenFileSize; + // + // const uint FileSize=0x1000000; + // byte CmpByte2=Flt.Type==(byte)FilterType.FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; + // // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" + // // to avoid overflow for DataSize<4. + // for (uint CurPos=0;CurPos+4=0 + // RawPut4(Addr+FileSize,Data); + // } + // else + // if (((Addr-FileSize) & 0x80000000)!=0) // Addr>8); + // D[2]=(byte)(Offset>>16); + // } + // } + // } + // return SrcData; + // case (byte)FilterType.FILTER_DELTA: + // { + // // Unlike RAR3, we do not need to reject excessive channel + // // values here, since RAR5 uses only 5 bits to store channel. + // uint Channels=Flt->Channels,SrcPos=0; + // + // FilterDstMemory.Alloc(DataSize); + // byte *DstData=&FilterDstMemory[0]; + // + // // Bytes from same channels are grouped to continual data blocks, + // // so we need to place them back to their interleaving positions. + // for (uint CurChannel=0;CurChannel0) + //// { + //// size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite); + //// UnpWriteData(&FragWindow[StartPtr],BlockSize); + //// SizeToWrite-=BlockSize; + //// StartPtr=(StartPtr+BlockSize) & MaxWinMask; + //// } + //// } + //// else + // if (EndPtr=DestUnpSize) + // return; + // size_t WriteSize=Size; + // long LeftToWrite=DestUnpSize-WrittenFileSize; + // if ((long)WriteSize>LeftToWrite) + // WriteSize=(size_t)LeftToWrite; + // UnpIO->UnpWrite(Data,WriteSize); + // WrittenFileSize+=Size; + // } + + private void UnpInitData50(bool Solid) + { + if (!Solid) + { + TablesRead5 = false; + } + } + + private bool ReadBlockHeader() + { + Header.HeaderSize = 0; + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + { + if (!UnpReadBuf()) + { + return false; + } + } + + //Inp.faddbits((8-Inp.InBit)&7); + Inp.faddbits((uint)((8 - Inp.InBit) & 7)); + + var BlockFlags = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + //uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count. + var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. + + if (ByteCount == 4) + { + return false; + } + + //Header.HeaderSize=2+ByteCount; + Header.HeaderSize = (int)(2 + ByteCount); + + Header.BlockBitSize = (BlockFlags & 7) + 1; + + var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + + var BlockSize = 0; + //for (uint I=0;I>8)<<(I*8); + BlockSize += (int)(Inp.fgetbits() >> 8) << (I * 8); + Inp.AddBits(8); + } + + Header.BlockSize = BlockSize; + var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); + if (CheckSum != SavedCheckSum) + { + return false; + } + + Header.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); + + Header.LastBlockInFile = (BlockFlags & 0x40) != 0; + Header.TablePresent = (BlockFlags & 0x80) != 0; + return true; + } + + //? + // bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) + // { + // if (!Header.TablePresent) + // return true; + // + // if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25) + // if (!UnpReadBuf()) + // return false; + // + // byte BitLength[BC]; + // for (uint I=0;I> 12); + // Inp.faddbits(4); + // if (Length==15) + // { + // uint ZeroCount=(byte)(Inp.fgetbits() >> 12); + // Inp.faddbits(4); + // if (ZeroCount==0) + // BitLength[I]=15; + // else + // { + // ZeroCount+=2; + // while (ZeroCount-- > 0 && IReadTop-5) + // if (!UnpReadBuf()) + // return false; + // uint Number=DecodeNumber(Inp,&Tables.BD); + // if (Number<16) + // { + // Table[I]=Number; + // I++; + // } + // else + // if (Number<18) + // { + // uint N; + // if (Number==16) + // { + // N=(Inp.fgetbits() >> 13)+3; + // Inp.faddbits(3); + // } + // else + // { + // N=(Inp.fgetbits() >> 9)+11; + // Inp.faddbits(7); + // } + // if (I==0) + // { + // // We cannot have "repeat previous" code at the first position. + // // Multiple such codes would shift Inp position without changing I, + // // which can lead to reading beyond of Inp boundary in mutithreading + // // mode, where Inp.ExternalBuffer disables bounds check and we just + // // reserve a lot of buffer space to not need such check normally. + // return false; + // } + // else + // while (N-- > 0 && I> 13)+3; + // Inp.faddbits(3); + // } + // else + // { + // N=(Inp.fgetbits() >> 9)+11; + // Inp.faddbits(7); + // } + // while (N-- > 0 && IReadTop) + // return false; + // MakeDecodeTables(&Table[0],&Tables.LD,NC); + // MakeDecodeTables(&Table[NC],&Tables.DD,DC); + // MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC); + // MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC); + // return true; + // } + + //? + // void InitFilters() + // { + // Filters.SoftReset(); + // } } -#endif \ No newline at end of file +#endif diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs index 66d5d887..5359ab24 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackFilter.cs @@ -1,33 +1,37 @@ using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal class UnpackFilter { - internal class UnpackFilter + public byte Type; + public byte Channels; + + internal UnpackFilter() => Program = new VMPreparedProgram(); + + // TODO uint + internal uint uBlockStart { - public byte Type; - public byte Channels; - - internal UnpackFilter() - { - Program = new VMPreparedProgram(); - } - - // TODO uint - internal uint uBlockStart { get { return (uint)BlockStart; } set { BlockStart = (int)value; } } - internal uint uBlockLength { get { return (uint)BlockLength; } set { BlockLength = (int)value; } } - - internal int BlockStart { get; set; } - - internal int BlockLength { get; set; } - - internal int ExecCount { get; set; } - - internal bool NextWindow { get; set; } - - // position of parent filter in Filters array used as prototype for filter - // in PrgStack array. Not defined for filters in Filters array. - internal int ParentFilter { get; set; } - - internal VMPreparedProgram Program { get; set; } + get => (uint)BlockStart; + set => BlockStart = (int)value; } -} \ No newline at end of file + internal uint uBlockLength + { + get => (uint)BlockLength; + set => BlockLength = (int)value; + } + + internal int BlockStart { get; set; } + + internal int BlockLength { get; set; } + + internal int ExecCount { get; set; } + + internal bool NextWindow { get; set; } + + // position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + internal int ParentFilter { get; set; } + + internal VMPreparedProgram Program { get; set; } +} diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs index e509afa2..8148b250 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackInline.cs @@ -1,31 +1,29 @@ - -namespace SharpCompress.Compressors.Rar.UnpackV1 -{ - internal partial class Unpack - { - private uint SlotToLength(uint Slot) - { - //uint LBits,Length=2; - int LBits; - uint Length = 2; - if (Slot < 8) - { - LBits = 0; - Length += Slot; - } - else - { - //LBits=Slot/4-1; - LBits = (int)(Slot / 4 - 1); - Length += (4 | (Slot & 3)) << LBits; - } +namespace SharpCompress.Compressors.Rar.UnpackV1; - if (LBits > 0) - { - Length += getbits() >> (16 - LBits); - AddBits(LBits); - } - return Length; +internal partial class Unpack +{ + private uint SlotToLength(uint Slot) + { + //uint LBits,Length=2; + int LBits; + uint Length = 2; + if (Slot < 8) + { + LBits = 0; + Length += Slot; } + else + { + //LBits=Slot/4-1; + LBits = (int)((Slot / 4) - 1); + Length += (4 | (Slot & 3)) << LBits; + } + + if (LBits > 0) + { + Length += getbits() >> (16 - LBits); + AddBits(LBits); + } + return Length; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs index ec04db1c..09f5ee8f 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs @@ -2,220 +2,220 @@ using SharpCompress.Compressors.Rar.VM; -namespace SharpCompress.Compressors.Rar.UnpackV1 +namespace SharpCompress.Compressors.Rar.UnpackV1; + +internal static class UnpackUtility { - internal static class UnpackUtility + //!!! TODO rename methods + internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) => + (uint)input.decodeNumber(dec); + + internal static int decodeNumber(this BitInput input, Decode.Decode dec) { - //!!! TODO rename methods - internal static uint DecodeNumber(this BitInput input, Decode.Decode dec) - { - return (uint)input.decodeNumber(dec); - } + int bits; + long bitField = input.GetBits() & 0xfffe; - internal static int decodeNumber(this BitInput input, Decode.Decode dec) + // if (bitField < dec.getDecodeLen()[8]) { + // if (bitField < dec.getDecodeLen()[4]) { + // if (bitField < dec.getDecodeLen()[2]) { + // if (bitField < dec.getDecodeLen()[1]) { + // bits = 1; + // } else { + // bits = 2; + // } + // } else { + // if (bitField < dec.getDecodeLen()[3]) { + // bits = 3; + // } else { + // bits = 4; + // } + // } + // } else { + // if (bitField < dec.getDecodeLen()[6]) { + // if (bitField < dec.getDecodeLen()[5]) + // bits = 5; + // else + // bits = 6; + // } else { + // if (bitField < dec.getDecodeLen()[7]) { + // bits = 7; + // } else { + // bits = 8; + // } + // } + // } + // } else { + // if (bitField < dec.getDecodeLen()[12]) { + // if (bitField < dec.getDecodeLen()[10]) + // if (bitField < dec.getDecodeLen()[9]) + // bits = 9; + // else + // bits = 10; + // else if (bitField < dec.getDecodeLen()[11]) + // bits = 11; + // else + // bits = 12; + // } else { + // if (bitField < dec.getDecodeLen()[14]) { + // if (bitField < dec.getDecodeLen()[13]) { + // bits = 13; + // } else { + // bits = 14; + // } + // } else { + // bits = 15; + // } + // } + // } + // addbits(bits); + // int N = dec.getDecodePos()[bits] + // + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits)); + // if (N >= dec.getMaxNum()) { + // N = 0; + // } + // return (dec.getDecodeNum()[N]); + var decodeLen = dec.DecodeLen; + if (bitField < decodeLen[8]) { - int bits; - long bitField = input.GetBits() & 0xfffe; - - // if (bitField < dec.getDecodeLen()[8]) { - // if (bitField < dec.getDecodeLen()[4]) { - // if (bitField < dec.getDecodeLen()[2]) { - // if (bitField < dec.getDecodeLen()[1]) { - // bits = 1; - // } else { - // bits = 2; - // } - // } else { - // if (bitField < dec.getDecodeLen()[3]) { - // bits = 3; - // } else { - // bits = 4; - // } - // } - // } else { - // if (bitField < dec.getDecodeLen()[6]) { - // if (bitField < dec.getDecodeLen()[5]) - // bits = 5; - // else - // bits = 6; - // } else { - // if (bitField < dec.getDecodeLen()[7]) { - // bits = 7; - // } else { - // bits = 8; - // } - // } - // } - // } else { - // if (bitField < dec.getDecodeLen()[12]) { - // if (bitField < dec.getDecodeLen()[10]) - // if (bitField < dec.getDecodeLen()[9]) - // bits = 9; - // else - // bits = 10; - // else if (bitField < dec.getDecodeLen()[11]) - // bits = 11; - // else - // bits = 12; - // } else { - // if (bitField < dec.getDecodeLen()[14]) { - // if (bitField < dec.getDecodeLen()[13]) { - // bits = 13; - // } else { - // bits = 14; - // } - // } else { - // bits = 15; - // } - // } - // } - // addbits(bits); - // int N = dec.getDecodePos()[bits] - // + (((int) bitField - dec.getDecodeLen()[bits - 1]) >>> (16 - bits)); - // if (N >= dec.getMaxNum()) { - // N = 0; - // } - // return (dec.getDecodeNum()[N]); - int[] decodeLen = dec.DecodeLen; - if (bitField < decodeLen[8]) + if (bitField < decodeLen[4]) { - if (bitField < decodeLen[4]) + if (bitField < decodeLen[2]) { - if (bitField < decodeLen[2]) + if (bitField < decodeLen[1]) { - if (bitField < decodeLen[1]) - { - bits = 1; - } - else - { - bits = 2; - } + bits = 1; } else { - if (bitField < decodeLen[3]) - { - bits = 3; - } - else - { - bits = 4; - } + bits = 2; } } else { - if (bitField < decodeLen[6]) + if (bitField < decodeLen[3]) { - if (bitField < decodeLen[5]) - { - bits = 5; - } - else - { - bits = 6; - } + bits = 3; } else { - if (bitField < decodeLen[7]) - { - bits = 7; - } - else - { - bits = 8; - } + bits = 4; } } } else { - if (bitField < decodeLen[12]) + if (bitField < decodeLen[6]) { - if (bitField < decodeLen[10]) + if (bitField < decodeLen[5]) { - if (bitField < decodeLen[9]) - { - bits = 9; - } - else - { - bits = 10; - } - } - else if (bitField < decodeLen[11]) - { - bits = 11; + bits = 5; } else { - bits = 12; + bits = 6; } } else { - if (bitField < decodeLen[14]) + if (bitField < decodeLen[7]) { - if (bitField < decodeLen[13]) - { - bits = 13; - } - else - { - bits = 14; - } + bits = 7; } else { - bits = 15; + bits = 8; } } } - input.AddBits(bits); - int N = dec.DecodePos[bits] + (Utility.URShift(((int)bitField - decodeLen[bits - 1]), (16 - bits))); - if (N >= dec.MaxNum) - { - N = 0; - } - return (dec.DecodeNum[N]); } - - internal static void makeDecodeTables(byte[] lenTab, int offset, Decode.Decode dec, int size) + else { - Span lenCount = stackalloc int[16]; - Span tmpPos = stackalloc int[16]; - int i; - long M, N; - - new Span(dec.DecodeNum).Clear(); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); - - for (i = 0; i < size; i++) + if (bitField < decodeLen[12]) { - lenCount[lenTab[offset + i] & 0xF]++; - } - lenCount[0] = 0; - for (tmpPos[0] = 0, dec.DecodePos[0] = 0, dec.DecodeLen[0] = 0, N = 0, i = 1; i < 16; i++) - { - N = 2 * (N + lenCount[i]); - M = N << (15 - i); - if (M > 0xFFFF) + if (bitField < decodeLen[10]) { - M = 0xFFFF; + if (bitField < decodeLen[9]) + { + bits = 9; + } + else + { + bits = 10; + } } - dec.DecodeLen[i] = (int)M; - tmpPos[i] = dec.DecodePos[i] = dec.DecodePos[i - 1] + lenCount[i - 1]; - } - - for (i = 0; i < size; i++) - { - if (lenTab[offset + i] != 0) + else if (bitField < decodeLen[11]) { - dec.DecodeNum[tmpPos[lenTab[offset + i] & 0xF]++] = i; + bits = 11; + } + else + { + bits = 12; + } + } + else + { + if (bitField < decodeLen[14]) + { + if (bitField < decodeLen[13]) + { + bits = 13; + } + else + { + bits = 14; + } + } + else + { + bits = 15; } } - dec.MaxNum = size; } + input.AddBits(bits); + var N = + dec.DecodePos[bits] + + (Utility.URShift(((int)bitField - decodeLen[bits - 1]), (16 - bits))); + if (N >= dec.MaxNum) + { + N = 0; + } + return (dec.DecodeNum[N]); + } + + internal static void makeDecodeTables(byte[] lenTab, int offset, Decode.Decode dec, int size) + { + Span lenCount = stackalloc int[16]; + Span tmpPos = stackalloc int[16]; + int i; + long M, + N; + + new Span(dec.DecodeNum).Clear(); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + + for (i = 0; i < size; i++) + { + lenCount[lenTab[offset + i] & 0xF]++; + } + lenCount[0] = 0; + for (tmpPos[0] = 0, dec.DecodePos[0] = 0, dec.DecodeLen[0] = 0, N = 0, i = 1; i < 16; i++) + { + N = 2 * (N + lenCount[i]); + M = N << (15 - i); + if (M > 0xFFFF) + { + M = 0xFFFF; + } + dec.DecodeLen[i] = (int)M; + tmpPos[i] = dec.DecodePos[i] = dec.DecodePos[i - 1] + lenCount[i - 1]; + } + + for (i = 0; i < size; i++) + { + if (lenTab[offset + i] != 0) + { + dec.DecodeNum[tmpPos[lenTab[offset + i] & 0xF]++] = i; + } + } + dec.MaxNum = size; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs index c1de6517..17368531 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_cpp.cs @@ -1,6 +1,4 @@ #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; using size_t = System.UInt32; #else using nint = System.Int64; @@ -10,62 +8,44 @@ using size_t = System.UInt64; #nullable disable -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class BitInput { - internal partial class BitInput + public BitInput(bool AllocBuffer) { - - public BitInput(bool AllocBuffer) + ExternalBuffer = false; + if (AllocBuffer) { - ExternalBuffer = false; - if (AllocBuffer) - { - // getbits32 attempts to read data from InAddr, ... InAddr+3 positions. - // So let's allocate 3 additional bytes for situation, when we need to - // read only 1 byte from the last position of buffer and avoid a crash - // from access to next 3 bytes, which contents we do not need. - size_t BufSize = MAX_SIZE + 3; - InBuf = new byte[BufSize]; + // getbits32 attempts to read data from InAddr, ... InAddr+3 positions. + // So let's allocate 3 additional bytes for situation, when we need to + // read only 1 byte from the last position of buffer and avoid a crash + // from access to next 3 bytes, which contents we do not need. + size_t BufSize = MAX_SIZE + 3; + InBuf = new byte[BufSize]; - // Ensure that we get predictable results when accessing bytes in area - // not filled with read data. - //memset(InBuf,0,BufSize); - } - else - { - InBuf = null; - } + // Ensure that we get predictable results when accessing bytes in area + // not filled with read data. + //memset(InBuf,0,BufSize); } - - - //BitInput::~BitInput() - //{ - // if (!ExternalBuffer) - // delete[] InBuf; - //} - // - - public - void faddbits(uint Bits) + else { - // Function wrapped version of inline addbits to save code size. - addbits(Bits); + InBuf = null; } - - public - uint fgetbits() - { - // Function wrapped version of inline getbits to save code size. - return getbits(); - } - - private void SetExternalBuffer(byte[] Buf) - { - //if (InBuf!=NULL && !ExternalBuffer) - // delete[] InBuf; - InBuf = Buf; - ExternalBuffer = true; - } - } + + //BitInput::~BitInput() + //{ + // if (!ExternalBuffer) + // delete[] InBuf; + //} + // + + public void faddbits(uint Bits) => + // Function wrapped version of inline addbits to save code size. + addbits(Bits); + + public uint fgetbits() => + // Function wrapped version of inline getbits to save code size. + getbits(); } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs index 14867ed8..f245ea19 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/BitInput.getbits_hpp.cs @@ -1,70 +1,53 @@ -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class BitInput { + public const int MAX_SIZE = 0x8000; // Size of input buffer. - internal partial class BitInput + public int InAddr; // Curent byte position in the buffer. + public int InBit; // Current bit position in the current byte. + + public bool ExternalBuffer; + + //BitInput(bool AllocBuffer); + //~BitInput(); + + public byte[] InBuf; // Dynamically allocated input buffer. + + public void InitBitInput() => InAddr = InBit = 0; + + // Move forward by 'Bits' bits. + public void addbits(uint _Bits) { - public const int MAX_SIZE = 0x8000; // Size of input buffer. - - public int InAddr; // Curent byte position in the buffer. - public int InBit; // Current bit position in the current byte. - - public bool ExternalBuffer; - - //BitInput(bool AllocBuffer); - //~BitInput(); - - public byte[] InBuf; // Dynamically allocated input buffer. - - public - void InitBitInput() - { - InAddr = InBit = 0; - } - - // Move forward by 'Bits' bits. - public void addbits(uint _Bits) - { - var Bits = checked((int)_Bits); - Bits += InBit; - InAddr += Bits >> 3; - InBit = Bits & 7; - } - - // Return 16 bits from current position in the buffer. - // Bit at (InAddr,InBit) has the highest position in returning data. - public uint getbits() - { - uint BitField = (uint)InBuf[InAddr] << 16; - BitField |= (uint)InBuf[InAddr + 1] << 8; - BitField |= (uint)InBuf[InAddr + 2]; - BitField >>= (8 - InBit); - return BitField & 0xffff; - } - - // Return 32 bits from current position in the buffer. - // Bit at (InAddr,InBit) has the highest position in returning data. - public uint getbits32() - { - uint BitField = (uint)InBuf[InAddr] << 24; - BitField |= (uint)InBuf[InAddr + 1] << 16; - BitField |= (uint)InBuf[InAddr + 2] << 8; - BitField |= (uint)InBuf[InAddr + 3]; - BitField <<= InBit; - BitField |= (uint)InBuf[InAddr + 4] >> (8 - InBit); - return BitField & 0xffffffff; - } - - //void faddbits(uint Bits); - //uint fgetbits(); - - // Check if buffer has enough space for IncPtr bytes. Returns 'true' - // if buffer will be overflown. - private bool Overflow(uint IncPtr) - { - return InAddr + IncPtr >= MAX_SIZE; - } - - //void SetExternalBuffer(byte *Buf); + var Bits = checked((int)_Bits); + Bits += InBit; + InAddr += Bits >> 3; + InBit = Bits & 7; } + // Return 16 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + public uint getbits() + { + var BitField = (uint)InBuf[InAddr] << 16; + BitField |= (uint)InBuf[InAddr + 1] << 8; + BitField |= InBuf[InAddr + 2]; + BitField >>= (8 - InBit); + return BitField & 0xffff; + } + + // Return 32 bits from current position in the buffer. + // Bit at (InAddr,InBit) has the highest position in returning data. + public uint getbits32() + { + var BitField = (uint)InBuf[InAddr] << 24; + BitField |= (uint)InBuf[InAddr + 1] << 16; + BitField |= (uint)InBuf[InAddr + 2] << 8; + BitField |= InBuf[InAddr + 3]; + BitField <<= InBit; + BitField |= (uint)InBuf[InAddr + 4] >> (8 - InBit); + return BitField & 0xffffffff; + } + + //void SetExternalBuffer(byte *Buf); } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs index 66f8d06b..bfa69829 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/FragmentedWindow.unpack50frag_cpp.cs @@ -1,192 +1,181 @@ -#nullable disable +#nullable disable #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; using size_t = System.UInt32; #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; - using System; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class FragmentedWindow { - internal partial class FragmentedWindow + public FragmentedWindow() { + //memset(Mem,0,sizeof(Mem)); + //memset(MemSize,0,sizeof(MemSize)); + } - public FragmentedWindow() + //FragmentedWindow::~FragmentedWindow() + //{ + // Reset(); + //} + + private void Reset() + { + for (uint I = 0; I < Mem.Length; I++) { - //memset(Mem,0,sizeof(Mem)); - //memset(MemSize,0,sizeof(MemSize)); - } - - - //FragmentedWindow::~FragmentedWindow() - //{ - // Reset(); - //} - - private void Reset() - { - for (uint I = 0; I < Mem.Length; I++) + if (Mem[I] != null) { - if (Mem[I] != null) - { - //free(Mem[I]); - Mem[I] = null; - } + //free(Mem[I]); + Mem[I] = null; } } + } + public void Init(size_t WinSize) + { + Reset(); - public void Init(size_t WinSize) + uint BlockNum = 0; + size_t TotalSize = 0; // Already allocated. + while (TotalSize < WinSize && BlockNum < Mem.Length) { - Reset(); + var Size = WinSize - TotalSize; // Size needed to allocate. - uint BlockNum = 0; - size_t TotalSize = 0; // Already allocated. - while (TotalSize < WinSize && BlockNum < Mem.Length) + // Minimum still acceptable block size. Next allocations cannot be larger + // than current, so we do not need blocks if they are smaller than + // "size left / attempts left". Also we do not waste time to blocks + // smaller than some arbitrary constant. + var MinSize = Math.Max(Size / (size_t)(Mem.Length - BlockNum), 0x400000); + + byte[] NewMem = null; + while (Size >= MinSize) { - size_t Size = WinSize - TotalSize; // Size needed to allocate. - - // Minimum still acceptable block size. Next allocations cannot be larger - // than current, so we do not need blocks if they are smaller than - // "size left / attempts left". Also we do not waste time to blocks - // smaller than some arbitrary constant. - size_t MinSize = Math.Max(Size / (size_t)(Mem.Length - BlockNum), 0x400000); - - byte[] NewMem = null; - while (Size >= MinSize) + NewMem = new byte[Size]; + if (NewMem != null) { - NewMem = new byte[Size]; - if (NewMem != null) - { - break; - } - - Size -= Size / 32; - } - if (NewMem == null) - //throw std::bad_alloc(); - { - throw new InvalidOperationException(); + break; } - // Clean the window to generate the same output when unpacking corrupt - // RAR files, which may access to unused areas of sliding dictionary. - // sharpcompress: don't need this, freshly allocated above - //Utility.Memset(NewMem,0,Size); + Size -= Size / 32; + } - Mem[BlockNum] = NewMem; - TotalSize += Size; - MemSize[BlockNum] = TotalSize; - BlockNum++; - } - if (TotalSize < WinSize) // Not found enough free blocks. - //throw std::bad_alloc(); - { - throw new InvalidOperationException(); - } + // Clean the window to generate the same output when unpacking corrupt + // RAR files, which may access to unused areas of sliding dictionary. + // sharpcompress: don't need this, freshly allocated above + //Utility.Memset(NewMem,0,Size); + + Mem[BlockNum] = NewMem ?? throw new InvalidOperationException(); + TotalSize += Size; + MemSize[BlockNum] = TotalSize; + BlockNum++; } - - - public byte this[size_t Item] + if (TotalSize < WinSize) // Not found enough free blocks. + //throw std::bad_alloc(); { - get - { - if (Item < MemSize[0]) - { - return Mem[0][Item]; - } - - for (uint I = 1; I < MemSize.Length; I++) - { - if (Item < MemSize[I]) - { - return Mem[I][Item - MemSize[I - 1]]; - } - } - - return Mem[0][0]; // Must never happen; - } - set - { - if (Item < MemSize[0]) - { - Mem[0][Item] = value; - return; - } - for (uint I = 1; I < MemSize.Length; I++) - { - if (Item < MemSize[I]) - { - Mem[I][Item - MemSize[I - 1]] = value; - return; - } - } - - Mem[0][0] = value; // Must never happen; - } + throw new InvalidOperationException(); } + } - // sharpcompress: added the following code - public void GetBuffer(size_t Item, out byte[] buf, out uint offset) + public byte this[size_t Item] + { + get { if (Item < MemSize[0]) { - //return Mem[0][Item]; - buf = Mem[0]; offset = Item; return; + return Mem[0][Item]; + } + + for (uint I = 1; I < MemSize.Length; I++) + { + if (Item < MemSize[I]) + { + return Mem[I][Item - MemSize[I - 1]]; + } + } + + return Mem[0][0]; // Must never happen; + } + set + { + if (Item < MemSize[0]) + { + Mem[0][Item] = value; + return; } for (uint I = 1; I < MemSize.Length; I++) { if (Item < MemSize[I]) { - //return Mem[I][Item-MemSize[I-1]]; - buf = Mem[I]; offset = Item - MemSize[I - 1]; return; - } - } - //return Mem[0][0]; // Must never happen; - buf = Mem[0]; offset = 0; return; // Must never happen; - } - - public void CopyString(uint Length, uint Distance, ref size_t UnpPtr, size_t MaxWinMask) - { - size_t SrcPtr = UnpPtr - Distance; - while (Length-- > 0) - { - this[UnpPtr] = this[SrcPtr++ & MaxWinMask]; - // We need to have masked UnpPtr after quit from loop, so it must not - // be replaced with '(*this)[UnpPtr++ & MaxWinMask]' - UnpPtr = (UnpPtr + 1) & MaxWinMask; - } - } - - - public void CopyData(byte[] Dest, size_t destOffset, size_t WinPos, size_t Size) - { - for (size_t I = 0; I < Size; I++) - { - Dest[destOffset + I] = this[WinPos + I]; - } - } - - - public size_t GetBlockSize(size_t StartPos, size_t RequiredSize) - { - for (uint I = 0; I < MemSize.Length; I++) - { - if (StartPos < MemSize[I]) - { - return Math.Min(MemSize[I] - StartPos, RequiredSize); + Mem[I][Item - MemSize[I - 1]] = value; + return; } } - return 0; // Must never be here. + Mem[0][0] = value; // Must never happen; + } + } + + // sharpcompress: added the following code + public void GetBuffer(size_t Item, out byte[] buf, out uint offset) + { + if (Item < MemSize[0]) + { + //return Mem[0][Item]; + buf = Mem[0]; + offset = Item; + return; + } + for (uint I = 1; I < MemSize.Length; I++) + { + if (Item < MemSize[I]) + { + //return Mem[I][Item-MemSize[I-1]]; + buf = Mem[I]; + offset = Item - MemSize[I - 1]; + return; + } + } + //return Mem[0][0]; // Must never happen; + buf = Mem[0]; + offset = 0; + return; // Must never happen; + } + + public void CopyString(uint Length, uint Distance, ref size_t UnpPtr, size_t MaxWinMask) + { + var SrcPtr = UnpPtr - Distance; + while (Length-- > 0) + { + this[UnpPtr] = this[SrcPtr++ & MaxWinMask]; + // We need to have masked UnpPtr after quit from loop, so it must not + // be replaced with '(*this)[UnpPtr++ & MaxWinMask]' + UnpPtr = (UnpPtr + 1) & MaxWinMask; + } + } + + public void CopyData(byte[] Dest, size_t destOffset, size_t WinPos, size_t Size) + { + for (size_t I = 0; I < Size; I++) + { + Dest[destOffset + I] = this[WinPos + I]; + } + } + + public size_t GetBlockSize(size_t StartPos, size_t RequiredSize) + { + for (uint I = 0; I < MemSize.Length; I++) + { + if (StartPos < MemSize[I]) + { + return Math.Min(MemSize[I] - StartPos, RequiredSize); + } } + return 0; // Must never be here. } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/PackDef.compress_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/PackDef.compress_hpp.cs index 34021193..ed4e74ea 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/PackDef.compress_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/PackDef.compress_hpp.cs @@ -1,56 +1,53 @@ -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal static class PackDef { - internal static class PackDef - { + // Combine pack and unpack constants to class to avoid polluting global + // namespace with numerous short names. + public const uint MAX_LZ_MATCH = 0x1001; + public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. + public const uint LOW_DIST_REP_COUNT = 16; - // Combine pack and unpack constants to class to avoid polluting global - // namespace with numerous short names. - public const uint MAX_LZ_MATCH = 0x1001; - public const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3. - public const uint LOW_DIST_REP_COUNT = 16; + public const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC = 64; + public const uint LDC = 16; + public const uint RC = 44; + public const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC; + public const uint BC = 20; - public const uint NC = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const uint DC = 64; - public const uint LDC = 16; - public const uint RC = 44; - public const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC; - public const uint BC = 20; + public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC30 = 60; + public const uint LDC30 = 17; + public const uint RC30 = 28; + public const uint BC30 = 20; + public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; - public const uint NC30 = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const uint DC30 = 60; - public const uint LDC30 = 17; - public const uint RC30 = 28; - public const uint BC30 = 20; - public const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30; + public const uint NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ + public const uint DC20 = 48; + public const uint RC20 = 28; + public const uint BC20 = 19; + public const uint MC20 = 257; - public const uint NC20 = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */ - public const uint DC20 = 48; - public const uint RC20 = 28; - public const uint BC20 = 19; - public const uint MC20 = 257; + // Largest alphabet size among all values listed above. + public const uint LARGEST_TABLE_SIZE = 306; - // Largest alphabet size among all values listed above. - public const uint LARGEST_TABLE_SIZE = 306; - - // enum { - // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, - // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA - // }; + // enum { + // CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE, + // CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA + // }; - //enum FilterType { - // These values must not be changed, because we use them directly - // in RAR5 compression and decompression code. - public const int FILTER_DELTA = 0; - public const int FILTER_E8 = 1; - public const int FILTER_E8E9 = 2; - public const int FILTER_ARM = 3; - public const int FILTER_AUDIO = 4; - public const int FILTER_RGB = 5; - public const int FILTER_ITANIUM = 6; - public const int FILTER_PPM = 7; - public const int FILTER_NONE = 8; - //} - - } + //enum FilterType { + // These values must not be changed, because we use them directly + // in RAR5 compression and decompression code. + public const int FILTER_DELTA = 0; + public const int FILTER_E8 = 1; + public const int FILTER_E8E9 = 2; + public const int FILTER_ARM = 3; + public const int FILTER_AUDIO = 4; + public const int FILTER_RGB = 5; + public const int FILTER_ITANIUM = 6; + public const int FILTER_PPM = 7; + public const int FILTER_NONE = 8; + //} } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 7953fb89..53db46d2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -1,116 +1,108 @@ #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; using size_t = System.UInt32; #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; - using System; using System.IO; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack : IRarUnpack { - internal partial class Unpack : IRarUnpack + private FileHeader fileHeader; + private Stream readStream; + private Stream writeStream; + + private void _UnpackCtor() { - private FileHeader fileHeader; - private Stream readStream; - private Stream writeStream; - - private void _UnpackCtor() + for (var i = 0; i < AudV.Length; i++) { - for (int i = 0; i < AudV.Length; i++) - { - AudV[i] = new AudioVariables(); - } + AudV[i] = new AudioVariables(); } - - private int UnpIO_UnpRead(byte[] buf, int offset, int count) - { - // NOTE: caller has logic to check for -1 for error we throw instead. - return readStream.Read(buf, offset, count); - } - - private void UnpIO_UnpWrite(byte[] buf, size_t offset, uint count) - { - writeStream.Write(buf, checked((int)offset), checked((int)count)); - } - - public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) - { - // as of 12/2017 .NET limits array indexing to using a signed integer - // MaxWinSize causes unpack to use a fragmented window when the file - // window size exceeds MaxWinSize - // uggh, that's not how this variable is used, it's the size of the currently allocated window buffer - //x MaxWinSize = ((uint)int.MaxValue) + 1; - - // may be long.MaxValue which could indicate unknown size (not present in header) - DestUnpSize = fileHeader.UncompressedSize; - this.fileHeader = fileHeader; - this.readStream = readStream; - this.writeStream = writeStream; - if (!fileHeader.IsStored) - { - Init(fileHeader.WindowSize, fileHeader.IsSolid); - } - Suspended = false; - DoUnpack(); - } - - public void DoUnpack() - { - if (fileHeader.IsStored) - { - UnstoreFile(); - } - else - { - DoUnpack(fileHeader.CompressionAlgorithm, fileHeader.IsSolid); - } - } - - private void UnstoreFile() - { - var b = new byte[0x10000]; - do - { - int n = readStream.Read(b, 0, (int)Math.Min(b.Length, DestUnpSize)); - if (n == 0) - { - break; - } - writeStream.Write(b, 0, n); - DestUnpSize -= n; - } while (!Suspended); - } - - public bool Suspended { get; set; } - - public long DestSize { get => DestUnpSize; } - - public int Char - { - get - { - // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code - if (InAddr > MAX_SIZE - 30) - { - UnpReadBuf(); - } - return InBuf[InAddr++]; - } - } - - public int PpmEscChar { get => PPMEscChar; set => PPMEscChar = value; } - - public static byte[] EnsureCapacity(byte[] array, int length) - { - return array.Length < length ? new byte[length] : array; - } - } + + private int UnpIO_UnpRead(byte[] buf, int offset, int count) => + // NOTE: caller has logic to check for -1 for error we throw instead. + readStream.Read(buf, offset, count); + + private void UnpIO_UnpWrite(byte[] buf, size_t offset, uint count) => + writeStream.Write(buf, checked((int)offset), checked((int)count)); + + public void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream) + { + // as of 12/2017 .NET limits array indexing to using a signed integer + // MaxWinSize causes unpack to use a fragmented window when the file + // window size exceeds MaxWinSize + // uggh, that's not how this variable is used, it's the size of the currently allocated window buffer + //x MaxWinSize = ((uint)int.MaxValue) + 1; + + // may be long.MaxValue which could indicate unknown size (not present in header) + DestUnpSize = fileHeader.UncompressedSize; + this.fileHeader = fileHeader; + this.readStream = readStream; + this.writeStream = writeStream; + if (!fileHeader.IsStored) + { + Init(fileHeader.WindowSize, fileHeader.IsSolid); + } + Suspended = false; + DoUnpack(); + } + + public void DoUnpack() + { + if (fileHeader.IsStored) + { + UnstoreFile(); + } + else + { + DoUnpack(fileHeader.CompressionAlgorithm, fileHeader.IsSolid); + } + } + + private void UnstoreFile() + { + var b = new byte[0x10000]; + do + { + var n = readStream.Read(b, 0, (int)Math.Min(b.Length, DestUnpSize)); + if (n == 0) + { + break; + } + writeStream.Write(b, 0, n); + DestUnpSize -= n; + } while (!Suspended); + } + + public bool Suspended { get; set; } + + public long DestSize => DestUnpSize; + + public int Char + { + get + { + // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code + if (InAddr > MAX_SIZE - 30) + { + UnpReadBuf(); + } + return InBuf[InAddr++]; + } + } + + public int PpmEscChar + { + get => PPMEscChar; + set => PPMEscChar = value; + } + + public static byte[] EnsureCapacity(byte[] array, int length) => + array.Length < length ? new byte[length] : array; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs index 818d4295..03614dde 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.rawint_hpp.cs @@ -1,122 +1,114 @@ #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; -using size_t = System.UInt32; #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; using uint32 = System.UInt32; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack { - internal partial class Unpack + //#define rotls(x,n,xsize) (((x)<<(n)) | ((x)>>(xsize-(n)))) + //#define rotrs(x,n,xsize) (((x)>>(n)) | ((x)<<(xsize-(n)))) + //#define rotl32(x,n) rotls(x,n,32) + //#define rotr32(x,n) rotrs(x,n,32) + // + //inline uint RawGet2(const void *Data) + //{ + // byte *D=(byte *)Data; + // return D[0]+(D[1]<<8); + //} + + private uint32 RawGet4(byte[] D, int offset) => + (uint)(D[offset] + (D[offset + 1] << 8) + (D[offset + 2] << 16) + (D[offset + 3] << 24)); + + //inline uint64 RawGet8(const void *Data) + //{ + //#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) + // byte *D=(byte *)Data; + // return INT32TO64(RawGet4(D+4),RawGet4(D)); + //#else + // return *(uint64 *)Data; + //#endif + //} + // + // + //inline void RawPut2(uint Field,void *Data) + //{ + // byte *D=(byte *)Data; + // D[0]=(byte)(Field); + // D[1]=(byte)(Field>>8); + //} + + private void RawPut4(uint32 Field, byte[] D, int offset) { - //#define rotls(x,n,xsize) (((x)<<(n)) | ((x)>>(xsize-(n)))) - //#define rotrs(x,n,xsize) (((x)>>(n)) | ((x)<<(xsize-(n)))) - //#define rotl32(x,n) rotls(x,n,32) - //#define rotr32(x,n) rotrs(x,n,32) - // - //inline uint RawGet2(const void *Data) - //{ - // byte *D=(byte *)Data; - // return D[0]+(D[1]<<8); - //} - - private uint32 RawGet4(byte[] D, int offset) - { - return (uint)(D[offset] + (D[offset + 1] << 8) + (D[offset + 2] << 16) + (D[offset + 3] << 24)); - } - - //inline uint64 RawGet8(const void *Data) - //{ - //#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) - // byte *D=(byte *)Data; - // return INT32TO64(RawGet4(D+4),RawGet4(D)); - //#else - // return *(uint64 *)Data; - //#endif - //} - // - // - //inline void RawPut2(uint Field,void *Data) - //{ - // byte *D=(byte *)Data; - // D[0]=(byte)(Field); - // D[1]=(byte)(Field>>8); - //} - - private void RawPut4(uint32 Field, byte[] D, int offset) - { - D[offset] = (byte)(Field); - D[offset + 1] = (byte)(Field >> 8); - D[offset + 2] = (byte)(Field >> 16); - D[offset + 3] = (byte)(Field >> 24); - } - - //inline void RawPut8(uint64 Field,void *Data) - //{ - //#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) - // byte *D=(byte *)Data; - // D[0]=(byte)(Field); - // D[1]=(byte)(Field>>8); - // D[2]=(byte)(Field>>16); - // D[3]=(byte)(Field>>24); - // D[4]=(byte)(Field>>32); - // D[5]=(byte)(Field>>40); - // D[6]=(byte)(Field>>48); - // D[7]=(byte)(Field>>56); - //#else - // *(uint64 *)Data=Field; - //#endif - //} - - - //#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) - //#define USE_MEM_BYTESWAP - //#endif - - // Load 4 big endian bytes from memory and return uint32. - //inline uint32 RawGetBE4(const byte *m) - //{ - //#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) - // return _byteswap_ulong(*(uint32 *)m); - //#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) - // return __builtin_bswap32(*(uint32 *)m); - //#else - // return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; - //#endif - //} - - - // Save integer to memory as big endian. - //inline void RawPutBE4(uint32 i,byte *mem) - //{ - //#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) - // *(uint32*)mem = _byteswap_ulong(i); - //#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) - // *(uint32*)mem = __builtin_bswap32(i); - //#else - // mem[0]=byte(i>>24); - // mem[1]=byte(i>>16); - // mem[2]=byte(i>>8); - // mem[3]=byte(i); - //#endif - //} - - - //inline uint32 ByteSwap32(uint32 i) - //{ - //#ifdef _MSC_VER - // return _byteswap_ulong(i); - //#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) - // return __builtin_bswap32(i); - //#else - // return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); - //#endif - //} - + D[offset] = (byte)(Field); + D[offset + 1] = (byte)(Field >> 8); + D[offset + 2] = (byte)(Field >> 16); + D[offset + 3] = (byte)(Field >> 24); } + + //inline void RawPut8(uint64 Field,void *Data) + //{ + //#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED) + // byte *D=(byte *)Data; + // D[0]=(byte)(Field); + // D[1]=(byte)(Field>>8); + // D[2]=(byte)(Field>>16); + // D[3]=(byte)(Field>>24); + // D[4]=(byte)(Field>>32); + // D[5]=(byte)(Field>>40); + // D[6]=(byte)(Field>>48); + // D[7]=(byte)(Field>>56); + //#else + // *(uint64 *)Data=Field; + //#endif + //} + + + //#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED) + //#define USE_MEM_BYTESWAP + //#endif + + // Load 4 big endian bytes from memory and return uint32. + //inline uint32 RawGetBE4(const byte *m) + //{ + //#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) + // return _byteswap_ulong(*(uint32 *)m); + //#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) + // return __builtin_bswap32(*(uint32 *)m); + //#else + // return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3]; + //#endif + //} + + + // Save integer to memory as big endian. + //inline void RawPutBE4(uint32 i,byte *mem) + //{ + //#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER) + // *(uint32*)mem = _byteswap_ulong(i); + //#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) + // *(uint32*)mem = __builtin_bswap32(i); + //#else + // mem[0]=byte(i>>24); + // mem[1]=byte(i>>16); + // mem[2]=byte(i>>8); + // mem[3]=byte(i); + //#endif + //} + + + //inline uint32 ByteSwap32(uint32 i) + //{ + //#ifdef _MSC_VER + // return _byteswap_ulong(i); + //#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2) + // return __builtin_bswap32(i); + //#else + // return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF); + //#endif + //} } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs index d3f2bd3a..dabb0e08 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs @@ -1,254 +1,355 @@ using System; using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack15Local; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack { - internal partial class Unpack + private const int STARTL1 = 2; + + private static readonly uint[] DecL1 = { - private const int STARTL1 = 2; + 0x8000, + 0xa000, + 0xc000, + 0xd000, + 0xe000, + 0xea00, + 0xee00, + 0xf000, + 0xf200, + 0xf200, + 0xffff + }; - private static readonly uint[] DecL1 ={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, - 0xee00,0xf000,0xf200,0xf200,0xffff}; + private static readonly uint[] PosL1 = { 0, 0, 0, 2, 3, 5, 7, 11, 16, 20, 24, 32, 32 }; - private static readonly uint[] PosL1 = { 0, 0, 0, 2, 3, 5, 7, 11, 16, 20, 24, 32, 32 }; + private const int STARTL2 = 3; - private const int STARTL2 = 3; + private static readonly uint[] DecL2 = + { + 0xa000, + 0xc000, + 0xd000, + 0xe000, + 0xea00, + 0xee00, + 0xf000, + 0xf200, + 0xf240, + 0xffff + }; - private static readonly uint[] DecL2 ={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00, - 0xf000,0xf200,0xf240,0xffff}; + private static readonly uint[] PosL2 = { 0, 0, 0, 0, 5, 7, 9, 13, 18, 22, 26, 34, 36 }; - private static readonly uint[] PosL2 = { 0, 0, 0, 0, 5, 7, 9, 13, 18, 22, 26, 34, 36 }; + private const int STARTHF0 = 4; - private const int STARTHF0 = 4; + private static readonly uint[] DecHf0 = + { + 0x8000, + 0xc000, + 0xe000, + 0xf200, + 0xf200, + 0xf200, + 0xf200, + 0xf200, + 0xffff + }; - private static readonly uint[] DecHf0 ={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200, - 0xf200,0xf200,0xffff}; + private static readonly uint[] PosHf0 = { 0, 0, 0, 0, 0, 8, 16, 24, 33, 33, 33, 33, 33 }; - private static readonly uint[] PosHf0 = { 0, 0, 0, 0, 0, 8, 16, 24, 33, 33, 33, 33, 33 }; + private const int STARTHF1 = 5; - private const int STARTHF1 = 5; + private static readonly uint[] DecHf1 = + { + 0x2000, + 0xc000, + 0xe000, + 0xf000, + 0xf200, + 0xf200, + 0xf7e0, + 0xffff + }; - private static readonly uint[] DecHf1 ={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200, - 0xf7e0,0xffff}; + private static readonly uint[] PosHf1 = { 0, 0, 0, 0, 0, 0, 4, 44, 60, 76, 80, 80, 127 }; - private static readonly uint[] PosHf1 = { 0, 0, 0, 0, 0, 0, 4, 44, 60, 76, 80, 80, 127 }; + private const int STARTHF2 = 5; - private const int STARTHF2 = 5; + private static readonly uint[] DecHf2 = + { + 0x1000, + 0x2400, + 0x8000, + 0xc000, + 0xfa00, + 0xffff, + 0xffff, + 0xffff + }; - private static readonly uint[] DecHf2 ={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff, - 0xffff,0xffff}; + private static readonly uint[] PosHf2 = { 0, 0, 0, 0, 0, 0, 2, 7, 53, 117, 233, 0, 0 }; - private static readonly uint[] PosHf2 = { 0, 0, 0, 0, 0, 0, 2, 7, 53, 117, 233, 0, 0 }; + private const int STARTHF3 = 6; - private const int STARTHF3 = 6; + private static readonly uint[] DecHf3 = + { + 0x800, + 0x2400, + 0xee00, + 0xfe80, + 0xffff, + 0xffff, + 0xffff + }; - private static readonly uint[] DecHf3 ={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff, - 0xffff}; + private static readonly uint[] PosHf3 = { 0, 0, 0, 0, 0, 0, 0, 2, 16, 218, 251, 0, 0 }; - private static readonly uint[] PosHf3 = { 0, 0, 0, 0, 0, 0, 0, 2, 16, 218, 251, 0, 0 }; + private const int STARTHF4 = 8; + private static readonly uint[] DecHf4 = { 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; + private static readonly uint[] PosHf4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0 }; - private const int STARTHF4 = 8; - private static readonly uint[] DecHf4 = { 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; - private static readonly uint[] PosHf4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0 }; - - private void Unpack15(bool Solid) + private void Unpack15(bool Solid) + { + UnpInitData(Solid); + UnpInitData15(Solid); + UnpReadBuf(); + if (!Solid) { - UnpInitData(Solid); - UnpInitData15(Solid); - UnpReadBuf(); - if (!Solid) + InitHuff(); + UnpPtr = 0; + } + else + { + UnpPtr = WrPtr; + } + + --DestUnpSize; + if (DestUnpSize >= 0) + { + GetFlagsBuf(); + FlagsCnt = 8; + } + + while (DestUnpSize >= 0) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr > ReadTop - 30 && !UnpReadBuf()) { - InitHuff(); - UnpPtr = 0; + break; + } + + if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) + { + UnpWriteBuf20(); + } + + if (StMode != 0) + { + HuffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt = 7; + } + + if ((FlagBuf & 0x80) != 0) + { + FlagBuf <<= 1; + if (Nlzb > Nhfb) + { + LongLZ(); + } + else + { + HuffDecode(); + } } else { - UnpPtr = WrPtr; - } - - --DestUnpSize; - if (DestUnpSize >= 0) - { - GetFlagsBuf(); - FlagsCnt = 8; - } - - while (DestUnpSize >= 0) - { - UnpPtr &= MaxWinMask; - - if (Inp.InAddr > ReadTop - 30 && !UnpReadBuf()) - { - break; - } - - if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) - { - UnpWriteBuf20(); - } - - if (StMode != 0) - { - HuffDecode(); - continue; - } - + FlagBuf <<= 1; if (--FlagsCnt < 0) { GetFlagsBuf(); FlagsCnt = 7; } - if ((FlagBuf & 0x80) != 0) { FlagBuf <<= 1; if (Nlzb > Nhfb) { - LongLZ(); + HuffDecode(); } else { - HuffDecode(); + LongLZ(); } } else { FlagBuf <<= 1; - if (--FlagsCnt < 0) - { - GetFlagsBuf(); - FlagsCnt = 7; - } - if ((FlagBuf & 0x80) != 0) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - HuffDecode(); - } - else - { - LongLZ(); - } - } - else - { - FlagBuf <<= 1; - ShortLZ(); - } + ShortLZ(); } } - UnpWriteBuf20(); + } + UnpWriteBuf20(); + } + + //#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos]) + private uint GetShortLen1(uint pos) => ((pos) == 1 ? (uint)(Buf60 + 3) : ShortLen1[pos]); + + //#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos]) + private uint GetShortLen2(uint pos) => ((pos) == 3 ? (uint)(Buf60 + 3) : ShortLen2[pos]); + + internal static class Unpack15Local + { + public static readonly uint[] ShortLen1 = + { + 1, + 3, + 4, + 4, + 5, + 6, + 7, + 8, + 8, + 4, + 4, + 5, + 6, + 6, + 4, + 0 + }; + public static readonly uint[] ShortXor1 = + { + 0, + 0xa0, + 0xd0, + 0xe0, + 0xf0, + 0xf8, + 0xfc, + 0xfe, + 0xff, + 0xc0, + 0x80, + 0x90, + 0x98, + 0x9c, + 0xb0 + }; + public static readonly uint[] ShortLen2 = + { + 2, + 3, + 3, + 3, + 4, + 4, + 5, + 6, + 6, + 4, + 4, + 5, + 6, + 6, + 4, + 0 + }; + public static readonly uint[] ShortXor2 = + { + 0, + 0x40, + 0x60, + 0xa0, + 0xd0, + 0xe0, + 0xf0, + 0xf8, + 0xfc, + 0xc0, + 0x80, + 0x90, + 0x98, + 0x9c, + 0xb0 + }; + } + + private void ShortLZ() + { + uint Length, + SaveLength; + uint LastDistance; + uint Distance; + int DistancePlace; + NumHuf = 0; + + var BitField = Inp.fgetbits(); + if (LCount == 2) + { + Inp.faddbits(1); + if (BitField >= 0x8000) + { + CopyString15(LastDist, LastLength); + return; + } + BitField <<= 1; + LCount = 0; } + BitField >>= 8; - //#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos]) - private uint GetShortLen1(uint pos) { return ((pos) == 1 ? (uint)(Buf60 + 3) : ShortLen1[pos]); } - //#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos]) - private uint GetShortLen2(uint pos) { return ((pos) == 3 ? (uint)(Buf60 + 3) : ShortLen2[pos]); } + // not thread safe, replaced by GetShortLen1 and GetShortLen2 macro + // ShortLen1[1]=ShortLen2[3]=Buf60+3; - internal static class Unpack15Local + if (AvrLn1 < 37) { - public static readonly uint[] ShortLen1 = { 1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0 }; - public static readonly uint[] ShortXor1 ={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, - 0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; - public static readonly uint[] ShortLen2 = { 2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0 }; - public static readonly uint[] ShortXor2 ={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, - 0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; + for (Length = 0; ; Length++) + { + if (((BitField ^ ShortXor1[Length]) & (~(0xff >> (int)GetShortLen1(Length)))) == 0) + { + break; + } + } + + Inp.faddbits(GetShortLen1(Length)); + } + else + { + for (Length = 0; ; Length++) + { + if (((BitField ^ ShortXor2[Length]) & (~(0xff >> (int)GetShortLen2(Length)))) == 0) + { + break; + } + } + + Inp.faddbits(GetShortLen2(Length)); } - private void ShortLZ() + if (Length >= 9) { - uint Length, SaveLength; - uint LastDistance; - uint Distance; - int DistancePlace; - NumHuf = 0; - - uint BitField = Inp.fgetbits(); - if (LCount == 2) + if (Length == 9) + { + LCount++; + CopyString15(LastDist, LastLength); + return; + } + if (Length == 14) { - Inp.faddbits(1); - if (BitField >= 0x8000) - { - CopyString15((uint)LastDist, LastLength); - return; - } - BitField <<= 1; LCount = 0; - } - - BitField >>= 8; - - // not thread safe, replaced by GetShortLen1 and GetShortLen2 macro - // ShortLen1[1]=ShortLen2[3]=Buf60+3; - - if (AvrLn1 < 37) - { - for (Length = 0; ; Length++) - { - if (((BitField ^ ShortXor1[Length]) & (~(0xff >> (int)GetShortLen1(Length)))) == 0) - { - break; - } - } - - Inp.faddbits(GetShortLen1(Length)); - } - else - { - for (Length = 0; ; Length++) - { - if (((BitField ^ ShortXor2[Length]) & (~(0xff >> (int)GetShortLen2(Length)))) == 0) - { - break; - } - } - - Inp.faddbits(GetShortLen2(Length)); - } - - if (Length >= 9) - { - if (Length == 9) - { - LCount++; - CopyString15((uint)LastDist, LastLength); - return; - } - if (Length == 14) - { - LCount = 0; - Length = DecodeNum(Inp.fgetbits(), STARTL2, DecL2, PosL2) + 5; - Distance = (Inp.fgetbits() >> 1) | 0x8000; - Inp.faddbits(15); - LastLength = Length; - LastDist = Distance; - CopyString15(Distance, Length); - return; - } - - LCount = 0; - SaveLength = Length; - Distance = OldDist[(OldDistPtr - (Length - 9)) & 3]; - Length = DecodeNum(Inp.fgetbits(), STARTL1, DecL1, PosL1) + 2; - if (Length == 0x101 && SaveLength == 10) - { - Buf60 ^= 1; - return; - } - if (Distance > 256) - { - Length++; - } - - if (Distance >= MaxDist3) - { - Length++; - } - - OldDist[OldDistPtr++] = Distance; - OldDistPtr = OldDistPtr & 3; + Length = DecodeNum(Inp.fgetbits(), STARTL2, DecL2, PosL2) + 5; + Distance = (Inp.fgetbits() >> 1) | 0x8000; + Inp.faddbits(15); LastLength = Length; LastDist = Distance; CopyString15(Distance, Length); @@ -256,347 +357,371 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 } LCount = 0; - AvrLn1 += Length; - AvrLn1 -= AvrLn1 >> 4; - - DistancePlace = (int)(DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2) & 0xff); - Distance = ChSetA[DistancePlace]; - if (--DistancePlace != -1) + SaveLength = Length; + Distance = OldDist[(OldDistPtr - (Length - 9)) & 3]; + Length = DecodeNum(Inp.fgetbits(), STARTL1, DecL1, PosL1) + 2; + if (Length == 0x101 && SaveLength == 10) { - LastDistance = ChSetA[DistancePlace]; - ChSetA[DistancePlace + 1] = (ushort)LastDistance; - ChSetA[DistancePlace] = (ushort)Distance; + Buf60 ^= 1; + return; } - Length += 2; - OldDist[OldDistPtr++] = ++Distance; - OldDistPtr = OldDistPtr & 3; - LastLength = Length; - LastDist = Distance; - CopyString15(Distance, Length); - } - - private void LongLZ() - { - uint Length; - uint Distance; - uint DistancePlace, NewDistancePlace; - uint OldAvr2, OldAvr3; - - NumHuf = 0; - Nlzb += 16; - if (Nlzb > 0xff) + if (Distance > 256) { - Nlzb = 0x90; - Nhfb >>= 1; - } - OldAvr2 = AvrLn2; - - uint BitField = Inp.fgetbits(); - if (AvrLn2 >= 122) - { - Length = DecodeNum(BitField, STARTL2, DecL2, PosL2); - } - else - if (AvrLn2 >= 64) - { - Length = DecodeNum(BitField, STARTL1, DecL1, PosL1); - } - else - if (BitField < 0x100) - { - Length = BitField; - Inp.faddbits(16); - } - else - { - for (Length = 0; ((BitField << (int)Length) & 0x8000) == 0; Length++) - { - ; - } - - Inp.faddbits(Length + 1); + Length++; } - AvrLn2 += Length; - AvrLn2 -= AvrLn2 >> 5; - - BitField = Inp.fgetbits(); - if (AvrPlcB > 0x28ff) - { - DistancePlace = DecodeNum(BitField, STARTHF2, DecHf2, PosHf2); - } - else - if (AvrPlcB > 0x6ff) - { - DistancePlace = DecodeNum(BitField, STARTHF1, DecHf1, PosHf1); - } - else - { - DistancePlace = DecodeNum(BitField, STARTHF0, DecHf0, PosHf0); - } - - AvrPlcB += DistancePlace; - AvrPlcB -= AvrPlcB >> 8; - while (true) - { - Distance = ChSetB[DistancePlace & 0xff]; - NewDistancePlace = NToPlB[Distance++ & 0xff]++; - if ((Distance & 0xff) != 0) - { - CorrHuff(ChSetB, NToPlB); - } - else - { - break; - } - } - - ChSetB[DistancePlace & 0xff] = ChSetB[NewDistancePlace]; - ChSetB[NewDistancePlace] = (ushort)Distance; - - Distance = ((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1; - Inp.faddbits(7); - - OldAvr3 = AvrLn3; - if (Length != 1 && Length != 4) - { - if (Length == 0 && Distance <= MaxDist3) - { - AvrLn3++; - AvrLn3 -= AvrLn3 >> 8; - } - else - if (AvrLn3 > 0) - { - AvrLn3--; - } - } - - Length += 3; if (Distance >= MaxDist3) { Length++; } - if (Distance <= 256) - { - Length += 8; - } - - if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) - { - MaxDist3 = 0x7f00; - } - else - { - MaxDist3 = 0x2001; - } - OldDist[OldDistPtr++] = Distance; - OldDistPtr = OldDistPtr & 3; + OldDistPtr &= 3; LastLength = Length; LastDist = Distance; CopyString15(Distance, Length); + return; } - private void HuffDecode() + LCount = 0; + AvrLn1 += Length; + AvrLn1 -= AvrLn1 >> 4; + + DistancePlace = (int)(DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2) & 0xff); + Distance = ChSetA[DistancePlace]; + if (--DistancePlace != -1) { - uint CurByte, NewBytePlace; - uint Length; - uint Distance; - int BytePlace; + LastDistance = ChSetA[DistancePlace]; + ChSetA[DistancePlace + 1] = (ushort)LastDistance; + ChSetA[DistancePlace] = (ushort)Distance; + } + Length += 2; + OldDist[OldDistPtr++] = ++Distance; + OldDistPtr &= 3; + LastLength = Length; + LastDist = Distance; + CopyString15(Distance, Length); + } - uint BitField = Inp.fgetbits(); + private void LongLZ() + { + uint Length; + uint Distance; + uint DistancePlace, + NewDistancePlace; + uint OldAvr2, + OldAvr3; - if (AvrPlc > 0x75ff) + NumHuf = 0; + Nlzb += 16; + if (Nlzb > 0xff) + { + Nlzb = 0x90; + Nhfb >>= 1; + } + OldAvr2 = AvrLn2; + + var BitField = Inp.fgetbits(); + if (AvrLn2 >= 122) + { + Length = DecodeNum(BitField, STARTL2, DecL2, PosL2); + } + else if (AvrLn2 >= 64) + { + Length = DecodeNum(BitField, STARTL1, DecL1, PosL1); + } + else if (BitField < 0x100) + { + Length = BitField; + Inp.faddbits(16); + } + else + { + for (Length = 0; ((BitField << (int)Length) & 0x8000) == 0; Length++) { - BytePlace = (int)DecodeNum(BitField, STARTHF4, DecHf4, PosHf4); - } - else - if (AvrPlc > 0x5dff) - { - BytePlace = (int)DecodeNum(BitField, STARTHF3, DecHf3, PosHf3); - } - else - if (AvrPlc > 0x35ff) - { - BytePlace = (int)DecodeNum(BitField, STARTHF2, DecHf2, PosHf2); - } - else - if (AvrPlc > 0x0dff) - { - BytePlace = (int)DecodeNum(BitField, STARTHF1, DecHf1, PosHf1); - } - else - { - BytePlace = (int)DecodeNum(BitField, STARTHF0, DecHf0, PosHf0); + ; } - BytePlace &= 0xff; - if (StMode != 0) + Inp.faddbits(Length + 1); + } + + AvrLn2 += Length; + AvrLn2 -= AvrLn2 >> 5; + + BitField = Inp.fgetbits(); + if (AvrPlcB > 0x28ff) + { + DistancePlace = DecodeNum(BitField, STARTHF2, DecHf2, PosHf2); + } + else if (AvrPlcB > 0x6ff) + { + DistancePlace = DecodeNum(BitField, STARTHF1, DecHf1, PosHf1); + } + else + { + DistancePlace = DecodeNum(BitField, STARTHF0, DecHf0, PosHf0); + } + + AvrPlcB += DistancePlace; + AvrPlcB -= AvrPlcB >> 8; + while (true) + { + Distance = ChSetB[DistancePlace & 0xff]; + NewDistancePlace = NToPlB[Distance++ & 0xff]++; + if ((Distance & 0xff) != 0) { - if (BytePlace == 0 && BitField > 0xfff) + CorrHuff(ChSetB, NToPlB); + } + else + { + break; + } + } + + ChSetB[DistancePlace & 0xff] = ChSetB[NewDistancePlace]; + ChSetB[NewDistancePlace] = (ushort)Distance; + + Distance = ((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1; + Inp.faddbits(7); + + OldAvr3 = AvrLn3; + if (Length != 1 && Length != 4) + { + if (Length == 0 && Distance <= MaxDist3) + { + AvrLn3++; + AvrLn3 -= AvrLn3 >> 8; + } + else if (AvrLn3 > 0) + { + AvrLn3--; + } + } + + Length += 3; + if (Distance >= MaxDist3) + { + Length++; + } + + if (Distance <= 256) + { + Length += 8; + } + + if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) + { + MaxDist3 = 0x7f00; + } + else + { + MaxDist3 = 0x2001; + } + + OldDist[OldDistPtr++] = Distance; + OldDistPtr &= 3; + LastLength = Length; + LastDist = Distance; + CopyString15(Distance, Length); + } + + private void HuffDecode() + { + uint CurByte, + NewBytePlace; + uint Length; + uint Distance; + int BytePlace; + + var BitField = Inp.fgetbits(); + + if (AvrPlc > 0x75ff) + { + BytePlace = (int)DecodeNum(BitField, STARTHF4, DecHf4, PosHf4); + } + else if (AvrPlc > 0x5dff) + { + BytePlace = (int)DecodeNum(BitField, STARTHF3, DecHf3, PosHf3); + } + else if (AvrPlc > 0x35ff) + { + BytePlace = (int)DecodeNum(BitField, STARTHF2, DecHf2, PosHf2); + } + else if (AvrPlc > 0x0dff) + { + BytePlace = (int)DecodeNum(BitField, STARTHF1, DecHf1, PosHf1); + } + else + { + BytePlace = (int)DecodeNum(BitField, STARTHF0, DecHf0, PosHf0); + } + + BytePlace &= 0xff; + if (StMode != 0) + { + if (BytePlace == 0 && BitField > 0xfff) + { + BytePlace = 0x100; + } + + if (--BytePlace == -1) + { + BitField = Inp.fgetbits(); + Inp.faddbits(1); + if ((BitField & 0x8000) != 0) { - BytePlace = 0x100; - } - - if (--BytePlace == -1) - { - BitField = Inp.fgetbits(); - Inp.faddbits(1); - if ((BitField & 0x8000) != 0) - { - NumHuf = StMode = 0; - return; - } - else - { - Length = (BitField & 0x4000) != 0 ? 4U : 3; - Inp.faddbits(1); - Distance = DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2); - Distance = (Distance << 5) | (Inp.fgetbits() >> 11); - Inp.faddbits(5); - CopyString15(Distance, Length); - return; - } - } - } - else - if (NumHuf++ >= 16 && FlagsCnt == 0) - { - StMode = 1; - } - - AvrPlc += (uint)BytePlace; - AvrPlc -= AvrPlc >> 8; - Nhfb += 16; - if (Nhfb > 0xff) - { - Nhfb = 0x90; - Nlzb >>= 1; - } - - Window[UnpPtr++] = (byte)(ChSet[BytePlace] >> 8); - --DestUnpSize; - - while (true) - { - CurByte = ChSet[BytePlace]; - NewBytePlace = NToPl[CurByte++ & 0xff]++; - if ((CurByte & 0xff) > 0xa1) - { - CorrHuff(ChSet, NToPl); + NumHuf = StMode = 0; + return; } else { - break; + Length = (BitField & 0x4000) != 0 ? 4U : 3; + Inp.faddbits(1); + Distance = DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2); + Distance = (Distance << 5) | (Inp.fgetbits() >> 11); + Inp.faddbits(5); + CopyString15(Distance, Length); + return; } } - - ChSet[BytePlace] = ChSet[NewBytePlace]; - ChSet[NewBytePlace] = (ushort)CurByte; } - - private void GetFlagsBuf() + else if (NumHuf++ >= 16 && FlagsCnt == 0) { - uint Flags, NewFlagsPlace; - uint FlagsPlace = DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2); - - // Our Huffman table stores 257 items and needs all them in other parts - // of code such as when StMode is on, so the first item is control item. - // While normally we do not use the last item to code the flags byte here, - // we need to check for value 256 when unpacking in case we unpack - // a corrupt archive. - if (FlagsPlace >= ChSetC.Length) - { - return; - } - - while (true) - { - Flags = ChSetC[FlagsPlace]; - FlagBuf = Flags >> 8; - NewFlagsPlace = NToPlC[Flags++ & 0xff]++; - if ((Flags & 0xff) != 0) - { - break; - } - - CorrHuff(ChSetC, NToPlC); - } - - ChSetC[FlagsPlace] = ChSetC[NewFlagsPlace]; - ChSetC[NewFlagsPlace] = (ushort)Flags; + StMode = 1; } - private void UnpInitData15(bool Solid) + AvrPlc += (uint)BytePlace; + AvrPlc -= AvrPlc >> 8; + Nhfb += 16; + if (Nhfb > 0xff) { - if (!Solid) - { - AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = 0; - NumHuf = Buf60 = 0; - AvrPlc = 0x3500; - MaxDist3 = 0x2001; - Nhfb = Nlzb = 0x80; - } - FlagsCnt = 0; - FlagBuf = 0; - StMode = 0; - LCount = 0; - ReadTop = 0; + Nhfb = 0x90; + Nlzb >>= 1; } - private void InitHuff() + Window[UnpPtr++] = (byte)(ChSet[BytePlace] >> 8); + --DestUnpSize; + + while (true) { - for (uint I = 0; I < 256; I++) + CurByte = ChSet[BytePlace]; + NewBytePlace = NToPl[CurByte++ & 0xff]++; + if ((CurByte & 0xff) > 0xa1) { - ChSet[I] = ChSetB[I] = (ushort)(I << 8); - ChSetA[I] = (ushort)I; - ChSetC[I] = (ushort)(((~I + 1) & 0xff) << 8); + CorrHuff(ChSet, NToPl); + } + else + { + break; } - new Span(NToPl).Clear(); - new Span(NToPlB).Clear(); - new Span(NToPlC).Clear(); - CorrHuff(ChSetB, NToPlB); } - private void CorrHuff(ushort[] CharSet, byte[] NumToPlace) + ChSet[BytePlace] = ChSet[NewBytePlace]; + ChSet[NewBytePlace] = (ushort)CurByte; + } + + private void GetFlagsBuf() + { + uint Flags, + NewFlagsPlace; + var FlagsPlace = DecodeNum(Inp.fgetbits(), STARTHF2, DecHf2, PosHf2); + + // Our Huffman table stores 257 items and needs all them in other parts + // of code such as when StMode is on, so the first item is control item. + // While normally we do not use the last item to code the flags byte here, + // we need to check for value 256 when unpacking in case we unpack + // a corrupt archive. + if (FlagsPlace >= ChSetC.Length) { - int I, J; - for (I = 7; I >= 0; I--) - for (J = 0; J < 32; J++) - { - CharSet[J] = (ushort)((CharSet[J] & ~0xff) | I); - } - - new Span(NumToPlace, 0, NToPl.Length).Clear(); - for (I = 6; I >= 0; I--) - { - NumToPlace[I] = (byte)((7 - I) * 32); - } + return; } - private void CopyString15(uint Distance, uint Length) + while (true) { - DestUnpSize -= Length; - while (Length-- != 0) + Flags = ChSetC[FlagsPlace]; + FlagBuf = Flags >> 8; + NewFlagsPlace = NToPlC[Flags++ & 0xff]++; + if ((Flags & 0xff) != 0) { - Window[UnpPtr] = Window[(UnpPtr - Distance) & MaxWinMask]; - UnpPtr = (UnpPtr + 1) & MaxWinMask; + break; } + + CorrHuff(ChSetC, NToPlC); } - private uint DecodeNum(uint Num, uint StartPos, uint[] DecTab, uint[] PosTab) + ChSetC[FlagsPlace] = ChSetC[NewFlagsPlace]; + ChSetC[NewFlagsPlace] = (ushort)Flags; + } + + private void UnpInitData15(bool Solid) + { + if (!Solid) { - int I; - for (Num &= 0xfff0, I = 0; DecTab[I] <= Num; I++) - { - StartPos++; - } + AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = 0; + NumHuf = Buf60 = 0; + AvrPlc = 0x3500; + MaxDist3 = 0x2001; + Nhfb = Nlzb = 0x80; + } + FlagsCnt = 0; + FlagBuf = 0; + StMode = 0; + LCount = 0; + ReadTop = 0; + } - Inp.faddbits(StartPos); - return (((Num - (I != 0 ? DecTab[I - 1] : 0)) >> (int)(16 - StartPos)) + PosTab[StartPos]); + private void InitHuff() + { + for (uint I = 0; I < 256; I++) + { + ChSet[I] = ChSetB[I] = (ushort)(I << 8); + ChSetA[I] = (ushort)I; + ChSetC[I] = (ushort)(((~I + 1) & 0xff) << 8); + } + new Span(NToPl).Clear(); + new Span(NToPlB).Clear(); + new Span(NToPlC).Clear(); + CorrHuff(ChSetB, NToPlB); + } + + private void CorrHuff(ushort[] CharSet, byte[] NumToPlace) + { + int I, + J; + for (I = 7; I >= 0; I--) + { + for (J = 0; J < 32; J++) + { + CharSet[J] = (ushort)((CharSet[J] & ~0xff) | I); + } } + new Span(NumToPlace, 0, NToPl.Length).Clear(); + for (I = 6; I >= 0; I--) + { + NumToPlace[I] = (byte)((7 - I) * 32); + } + } + + private void CopyString15(uint Distance, uint Length) + { + DestUnpSize -= Length; + while (Length-- != 0) + { + Window[UnpPtr] = Window[(UnpPtr - Distance) & MaxWinMask]; + UnpPtr = (UnpPtr + 1) & MaxWinMask; + } + } + + private uint DecodeNum(uint Num, uint StartPos, uint[] DecTab, uint[] PosTab) + { + int I; + for (Num &= 0xfff0, I = 0; DecTab[I] <= Num; I++) + { + StartPos++; + } + + Inp.faddbits(StartPos); + return (((Num - (I != 0 ? DecTab[I - 1] : 0)) >> (int)(16 - StartPos)) + PosTab[StartPos]); } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs index 7f59f6dd..b92af290 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs @@ -1,145 +1,244 @@ -#if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; -using size_t = System.UInt32; +#if !Rar2017_64bit #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; - using System; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack20Local; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack { - internal partial class Unpack + private void CopyString20(uint Length, uint Distance) { - private void CopyString20(uint Length, uint Distance) + LastDist = OldDist[OldDistPtr++ & 3] = Distance; + LastLength = Length; + DestUnpSize -= Length; + CopyString(Length, Distance); + } + + internal static class Unpack20Local + { + public static readonly byte[] LDecode = { - LastDist = OldDist[OldDistPtr++ & 3] = Distance; - LastLength = Length; - DestUnpSize -= Length; - CopyString(Length, Distance); + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 12, + 14, + 16, + 20, + 24, + 28, + 32, + 40, + 48, + 56, + 64, + 80, + 96, + 112, + 128, + 160, + 192, + 224 + }; + public static readonly byte[] LBits = + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5 + }; + public static readonly uint[] DDecode = + { + 0, + 1, + 2, + 3, + 4, + 6, + 8, + 12, + 16, + 24, + 32, + 48, + 64, + 96, + 128, + 192, + 256, + 384, + 512, + 768, + 1024, + 1536, + 2048, + 3072, + 4096, + 6144, + 8192, + 12288, + 16384, + 24576, + 32768U, + 49152U, + 65536, + 98304, + 131072, + 196608, + 262144, + 327680, + 393216, + 458752, + 524288, + 589824, + 655360, + 720896, + 786432, + 851968, + 917504, + 983040 + }; + public static readonly byte[] DBits = + { + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16 + }; + public static readonly byte[] SDDecode = { 0, 4, 8, 16, 32, 64, 128, 192 }; + public static readonly byte[] SDBits = { 2, 2, 3, 4, 5, 6, 6, 6 }; + } + + private void Unpack20(bool Solid) + { + uint Bits; + + if (Suspended) + { + UnpPtr = WrPtr; } - - - internal static class Unpack20Local + else { - public static readonly byte[] LDecode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224 }; - public static readonly byte[] LBits = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; - public static readonly uint[] DDecode = { 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768U, 49152U, 65536, 98304, 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, 655360, 720896, 786432, 851968, 917504, 983040 }; - public static readonly byte[] DBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; - public static readonly byte[] SDDecode = { 0, 4, 8, 16, 32, 64, 128, 192 }; - public static readonly byte[] SDBits = { 2, 2, 3, 4, 5, 6, 6, 6 }; - } - - private void Unpack20(bool Solid) - { - uint Bits; - - if (Suspended) + UnpInitData(Solid); + if (!UnpReadBuf()) { - UnpPtr = WrPtr; + return; } - else + + if ((!Solid || !TablesRead2) && !ReadTables20()) + { + return; + } + + --DestUnpSize; + } + + while (DestUnpSize >= 0) + { + UnpPtr &= MaxWinMask; + + if (Inp.InAddr > ReadTop - 30) { - UnpInitData(Solid); if (!UnpReadBuf()) { - return; + break; } - - if ((!Solid || !TablesRead2) && !ReadTables20()) - { - return; - } - - --DestUnpSize; } - while (DestUnpSize >= 0) + if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) { - UnpPtr &= MaxWinMask; - - if (Inp.InAddr > ReadTop - 30) + UnpWriteBuf20(); + if (Suspended) { - if (!UnpReadBuf()) - { - break; - } + return; } + } + if (UnpAudioBlock) + { + var AudioNumber = DecodeNumber(Inp, MD[UnpCurChannel]); - if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) - { - UnpWriteBuf20(); - if (Suspended) - { - return; - } - } - if (UnpAudioBlock) - { - uint AudioNumber = DecodeNumber(Inp, MD[UnpCurChannel]); - - if (AudioNumber == 256) - { - if (!ReadTables20()) - { - break; - } - - continue; - } - Window[UnpPtr++] = DecodeAudio((int)AudioNumber); - if (++UnpCurChannel == UnpChannels) - { - UnpCurChannel = 0; - } - - --DestUnpSize; - continue; - } - - uint Number = DecodeNumber(Inp, BlockTables.LD); - if (Number < 256) - { - Window[UnpPtr++] = (byte)Number; - --DestUnpSize; - continue; - } - if (Number > 269) - { - uint Length = (uint)(LDecode[Number -= 270] + 3); - if ((Bits = LBits[Number]) > 0) - { - Length += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - - uint DistNumber = DecodeNumber(Inp, BlockTables.DD); - uint Distance = DDecode[DistNumber] + 1; - if ((Bits = DBits[DistNumber]) > 0) - { - Distance += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - - if (Distance >= 0x2000) - { - Length++; - if (Distance >= 0x40000L) - { - Length++; - } - } - - CopyString20(Length, Distance); - continue; - } - if (Number == 269) + if (AudioNumber == 256) { if (!ReadTables20()) { @@ -148,78 +247,176 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 continue; } - if (Number == 256) + Window[UnpPtr++] = DecodeAudio((int)AudioNumber); + if (++UnpCurChannel == UnpChannels) { - CopyString20(LastLength, LastDist); - continue; + UnpCurChannel = 0; } - if (Number < 261) + + --DestUnpSize; + continue; + } + + var Number = DecodeNumber(Inp, BlockTables.LD); + if (Number < 256) + { + Window[UnpPtr++] = (byte)Number; + --DestUnpSize; + continue; + } + if (Number > 269) + { + var Length = (uint)(LDecode[Number -= 270] + 3); + if ((Bits = LBits[Number]) > 0) { - uint Distance = OldDist[(OldDistPtr - (Number - 256)) & 3]; - uint LengthNumber = DecodeNumber(Inp, BlockTables.RD); - uint Length = (uint)(LDecode[LengthNumber] + 2); - if ((Bits = LBits[LengthNumber]) > 0) - { - Length += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - if (Distance >= 0x101) + Length += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + + var DistNumber = DecodeNumber(Inp, BlockTables.DD); + var Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) + { + Distance += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000L) { Length++; - if (Distance >= 0x2000) + } + } + + CopyString20(Length, Distance); + continue; + } + if (Number == 269) + { + if (!ReadTables20()) + { + break; + } + + continue; + } + if (Number == 256) + { + CopyString20(LastLength, LastDist); + continue; + } + if (Number < 261) + { + var Distance = OldDist[(OldDistPtr - (Number - 256)) & 3]; + var LengthNumber = DecodeNumber(Inp, BlockTables.RD); + var Length = (uint)(LDecode[LengthNumber] + 2); + if ((Bits = LBits[LengthNumber]) > 0) + { + Length += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); + } + if (Distance >= 0x101) + { + Length++; + if (Distance >= 0x2000) + { + Length++; + if (Distance >= 0x40000) { Length++; - if (Distance >= 0x40000) - { - Length++; - } } } - CopyString20(Length, Distance); - continue; } - if (Number < 270) + CopyString20(Length, Distance); + continue; + } + if (Number < 270) + { + var Distance = (uint)(SDDecode[Number -= 261] + 1); + if ((Bits = SDBits[Number]) > 0) { - uint Distance = (uint)(SDDecode[Number -= 261] + 1); - if ((Bits = SDBits[Number]) > 0) - { - Distance += Inp.getbits() >> (int)(16 - Bits); - Inp.addbits(Bits); - } - CopyString20(2, Distance); - continue; + Distance += Inp.getbits() >> (int)(16 - Bits); + Inp.addbits(Bits); } + CopyString20(2, Distance); + continue; } - ReadLastTables(); - UnpWriteBuf20(); + } + ReadLastTables(); + UnpWriteBuf20(); + } + + private void UnpWriteBuf20() + { + if (UnpPtr != WrPtr) + { + UnpSomeRead = true; } - private void UnpWriteBuf20() + if (UnpPtr < WrPtr) { - if (UnpPtr != WrPtr) - { - UnpSomeRead = true; - } - - if (UnpPtr < WrPtr) - { - UnpIO_UnpWrite(Window, WrPtr, (uint)(-(int)WrPtr & MaxWinMask)); - UnpIO_UnpWrite(Window, 0, UnpPtr); - UnpAllBuf = true; - } - else - { - UnpIO_UnpWrite(Window, WrPtr, UnpPtr - WrPtr); - } - - WrPtr = UnpPtr; + UnpIO_UnpWrite(Window, WrPtr, (uint)(-(int)WrPtr & MaxWinMask)); + UnpIO_UnpWrite(Window, 0, UnpPtr); + UnpAllBuf = true; + } + else + { + UnpIO_UnpWrite(Window, WrPtr, UnpPtr - WrPtr); } - private bool ReadTables20() + WrPtr = UnpPtr; + } + + private bool ReadTables20() + { + var BitLength = new byte[BC20]; + var Table = new byte[MC20 * 4]; + if (Inp.InAddr > ReadTop - 25) { - byte[] BitLength = new byte[BC20]; - byte[] Table = new byte[MC20 * 4]; - if (Inp.InAddr > ReadTop - 25) + if (!UnpReadBuf()) + { + return false; + } + } + + var BitField = Inp.getbits(); + UnpAudioBlock = (BitField & 0x8000) != 0; + + if ((BitField & 0x4000) != 0) + { + new Span(UnpOldTable20).Clear(); + } + + Inp.addbits(2); + + uint TableSize; + if (UnpAudioBlock) + { + UnpChannels = ((BitField >> 12) & 3) + 1; + if (UnpCurChannel >= UnpChannels) + { + UnpCurChannel = 0; + } + + Inp.addbits(2); + TableSize = MC20 * UnpChannels; + } + else + { + TableSize = NC20 + DC20 + RC20; + } + + for (uint I = 0; I < BC20; I++) + { + BitLength[I] = (byte)(Inp.getbits() >> 12); + Inp.addbits(4); + } + MakeDecodeTables(BitLength, 0, BlockTables.BD, BC20); + for (uint I = 0; I < TableSize; ) + { + if (Inp.InAddr > ReadTop - 5) { if (!UnpReadBuf()) { @@ -227,275 +424,235 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 } } - uint BitField = Inp.getbits(); - UnpAudioBlock = (BitField & 0x8000) != 0; - - if ((BitField & 0x4000) != 0) + var Number = DecodeNumber(Inp, BlockTables.BD); + if (Number < 16) { - new Span(UnpOldTable20).Clear(); + Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); + I++; } - - Inp.addbits(2); - - uint TableSize; - if (UnpAudioBlock) + else if (Number == 16) { - UnpChannels = ((BitField >> 12) & 3) + 1; - if (UnpCurChannel >= UnpChannels) - { - UnpCurChannel = 0; - } - + var N = (Inp.getbits() >> 14) + 3; Inp.addbits(2); - TableSize = MC20 * UnpChannels; - } - else - { - TableSize = NC20 + DC20 + RC20; - } - - for (uint I = 0; I < BC20; I++) - { - BitLength[I] = (byte)(Inp.getbits() >> 12); - Inp.addbits(4); - } - MakeDecodeTables(BitLength, 0, BlockTables.BD, BC20); - for (uint I = 0; I < TableSize;) - { - if (Inp.InAddr > ReadTop - 5) + if (I == 0) { - if (!UnpReadBuf()) - { - return false; - } - } - - uint Number = DecodeNumber(Inp, BlockTables.BD); - if (Number < 16) - { - Table[I] = (byte)((Number + UnpOldTable20[I]) & 0xf); - I++; - } - else - if (Number == 16) - { - uint N = (Inp.getbits() >> 14) + 3; - Inp.addbits(2); - if (I == 0) - { - return false; // We cannot have "repeat previous" code at the first position. - } - else - { - while (N-- > 0 && I < TableSize) - { - Table[I] = Table[I - 1]; - I++; - } - } + return false; // We cannot have "repeat previous" code at the first position. } else { - uint N; - if (Number == 17) - { - N = (Inp.getbits() >> 13) + 3; - Inp.addbits(3); - } - else - { - N = (Inp.getbits() >> 9) + 11; - Inp.addbits(7); - } while (N-- > 0 && I < TableSize) { - Table[I++] = 0; + Table[I] = Table[I - 1]; + I++; } } } - TablesRead2 = true; - if (Inp.InAddr > ReadTop) - { - return true; - } - - if (UnpAudioBlock) - { - for (uint I = 0; I < UnpChannels; I++) - { - MakeDecodeTables(Table, (int)(I * MC20), MD[I], MC20); - } - } else { - MakeDecodeTables(Table, 0, BlockTables.LD, NC20); - MakeDecodeTables(Table, (int)NC20, BlockTables.DD, DC20); - MakeDecodeTables(Table, (int)(NC20 + DC20), BlockTables.RD, RC20); + uint N; + if (Number == 17) + { + N = (Inp.getbits() >> 13) + 3; + Inp.addbits(3); + } + else + { + N = (Inp.getbits() >> 9) + 11; + Inp.addbits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } } - //x memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); - Array.Copy(Table, UnpOldTable20, UnpOldTable20.Length); + } + TablesRead2 = true; + if (Inp.InAddr > ReadTop) + { return true; } - private void ReadLastTables() + if (UnpAudioBlock) { - if (ReadTop >= Inp.InAddr + 5) + for (uint I = 0; I < UnpChannels; I++) { - if (UnpAudioBlock) - { - if (DecodeNumber(Inp, MD[UnpCurChannel]) == 256) - { - ReadTables20(); - } - } - else - if (DecodeNumber(Inp, BlockTables.LD) == 269) + MakeDecodeTables(Table, (int)(I * MC20), MD[I], MC20); + } + } + else + { + MakeDecodeTables(Table, 0, BlockTables.LD, NC20); + MakeDecodeTables(Table, (int)NC20, BlockTables.DD, DC20); + MakeDecodeTables(Table, (int)(NC20 + DC20), BlockTables.RD, RC20); + } + //x memcpy(UnpOldTable20,Table,sizeof(UnpOldTable20)); + Array.Copy(Table, UnpOldTable20, UnpOldTable20.Length); + return true; + } + + private void ReadLastTables() + { + if (ReadTop >= Inp.InAddr + 5) + { + if (UnpAudioBlock) + { + if (DecodeNumber(Inp, MD[UnpCurChannel]) == 256) { ReadTables20(); } } - } - - private void UnpInitData20(bool Solid) - { - if (!Solid) + else if (DecodeNumber(Inp, BlockTables.LD) == 269) { - TablesRead2 = false; - UnpAudioBlock = false; - UnpChannelDelta = 0; - UnpCurChannel = 0; - UnpChannels = 1; - - //memset(AudV,0,sizeof(AudV)); - AudV = new AudioVariables[4]; - new Span(UnpOldTable20).Clear(); - //memset(MD,0,sizeof(MD)); - MD = new DecodeTable[4]; + ReadTables20(); } } + } - private byte DecodeAudio(int Delta) + private void UnpInitData20(bool Solid) + { + if (!Solid) { - AudioVariables V = AudV[UnpCurChannel]; - V.ByteCount++; - V.D4 = V.D3; - V.D3 = V.D2; - V.D2 = V.LastDelta - V.D1; - V.D1 = V.LastDelta; - int PCh = 8 * V.LastChar + V.K1 * V.D1 + V.K2 * V.D2 + V.K3 * V.D3 + V.K4 * V.D4 + V.K5 * UnpChannelDelta; - PCh = (PCh >> 3) & 0xFF; + TablesRead2 = false; + UnpAudioBlock = false; + UnpChannelDelta = 0; + UnpCurChannel = 0; + UnpChannels = 1; - uint Ch = (uint)(PCh - Delta); + //memset(AudV,0,sizeof(AudV)); + AudV = new AudioVariables[4]; + new Span(UnpOldTable20).Clear(); + //memset(MD,0,sizeof(MD)); + MD = new DecodeTable[4]; + } + } - int D = (sbyte)Delta; - // Left shift of negative value is undefined behavior in C++, - // so we cast it to unsigned to follow the standard. - D = (int)((uint)D << 3); + private byte DecodeAudio(int Delta) + { + var V = AudV[UnpCurChannel]; + V.ByteCount++; + V.D4 = V.D3; + V.D3 = V.D2; + V.D2 = V.LastDelta - V.D1; + V.D1 = V.LastDelta; + var PCh = + (8 * V.LastChar) + + (V.K1 * V.D1) + + (V.K2 * V.D2) + + (V.K3 * V.D3) + + (V.K4 * V.D4) + + (V.K5 * UnpChannelDelta); + PCh = (PCh >> 3) & 0xFF; - V.Dif[0] += (uint)Math.Abs(D); - V.Dif[1] += (uint)Math.Abs(D - V.D1); - V.Dif[2] += (uint)Math.Abs(D + V.D1); - V.Dif[3] += (uint)Math.Abs(D - V.D2); - V.Dif[4] += (uint)Math.Abs(D + V.D2); - V.Dif[5] += (uint)Math.Abs(D - V.D3); - V.Dif[6] += (uint)Math.Abs(D + V.D3); - V.Dif[7] += (uint)Math.Abs(D - V.D4); - V.Dif[8] += (uint)Math.Abs(D + V.D4); - V.Dif[9] += (uint)Math.Abs(D - UnpChannelDelta); - V.Dif[10] += (uint)Math.Abs(D + UnpChannelDelta); + var Ch = (uint)(PCh - Delta); - UnpChannelDelta = V.LastDelta = (sbyte)(Ch - V.LastChar); - V.LastChar = (int)Ch; + int D = (sbyte)Delta; + // Left shift of negative value is undefined behavior in C++, + // so we cast it to unsigned to follow the standard. + D = (int)((uint)D << 3); - if ((V.ByteCount & 0x1F) == 0) + V.Dif[0] += (uint)Math.Abs(D); + V.Dif[1] += (uint)Math.Abs(D - V.D1); + V.Dif[2] += (uint)Math.Abs(D + V.D1); + V.Dif[3] += (uint)Math.Abs(D - V.D2); + V.Dif[4] += (uint)Math.Abs(D + V.D2); + V.Dif[5] += (uint)Math.Abs(D - V.D3); + V.Dif[6] += (uint)Math.Abs(D + V.D3); + V.Dif[7] += (uint)Math.Abs(D - V.D4); + V.Dif[8] += (uint)Math.Abs(D + V.D4); + V.Dif[9] += (uint)Math.Abs(D - UnpChannelDelta); + V.Dif[10] += (uint)Math.Abs(D + UnpChannelDelta); + + UnpChannelDelta = V.LastDelta = (sbyte)(Ch - V.LastChar); + V.LastChar = (int)Ch; + + if ((V.ByteCount & 0x1F) == 0) + { + uint MinDif = V.Dif[0], + NumMinDif = 0; + V.Dif[0] = 0; + for (uint I = 1; I < V.Dif.Length; I++) { - uint MinDif = V.Dif[0], NumMinDif = 0; - V.Dif[0] = 0; - for (uint I = 1; I < V.Dif.Length; I++) + if (V.Dif[I] < MinDif) { - if (V.Dif[I] < MinDif) + MinDif = V.Dif[I]; + NumMinDif = I; + } + V.Dif[I] = 0; + } + switch (NumMinDif) + { + case 1: + if (V.K1 >= -16) { - MinDif = V.Dif[I]; - NumMinDif = I; + V.K1--; } - V.Dif[I] = 0; - } - switch (NumMinDif) - { - case 1: - if (V.K1 >= -16) - { - V.K1--; - } - break; - case 2: - if (V.K1 < 16) - { - V.K1++; - } + break; + case 2: + if (V.K1 < 16) + { + V.K1++; + } - break; - case 3: - if (V.K2 >= -16) - { - V.K2--; - } + break; + case 3: + if (V.K2 >= -16) + { + V.K2--; + } - break; - case 4: - if (V.K2 < 16) - { - V.K2++; - } + break; + case 4: + if (V.K2 < 16) + { + V.K2++; + } - break; - case 5: - if (V.K3 >= -16) - { - V.K3--; - } + break; + case 5: + if (V.K3 >= -16) + { + V.K3--; + } - break; - case 6: - if (V.K3 < 16) - { - V.K3++; - } + break; + case 6: + if (V.K3 < 16) + { + V.K3++; + } - break; - case 7: - if (V.K4 >= -16) - { - V.K4--; - } + break; + case 7: + if (V.K4 >= -16) + { + V.K4--; + } - break; - case 8: - if (V.K4 < 16) - { - V.K4++; - } + break; + case 8: + if (V.K4 < 16) + { + V.K4++; + } - break; - case 9: - if (V.K5 >= -16) - { - V.K5--; - } + break; + case 9: + if (V.K5 >= -16) + { + V.K5--; + } - break; - case 10: - if (V.K5 < 16) - { - V.K5++; - } + break; + case 10: + if (V.K5 < 16) + { + V.K5++; + } - break; - } + break; } - return (byte)Ch; } - + return (byte)Ch; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs index 1647b902..7a5caee0 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack30_cpp.cs @@ -1,13 +1,9 @@ -#if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; -using size_t = System.UInt32; +#if !Rar2017_64bit #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; //using static SharpCompress.Compressors.Rar.UnpackV2017.Unpack.Unpack30Local; /* @@ -307,7 +303,7 @@ bool ReadEndOfBlock() bool ReadVMCode() { - // Entire VM code is guaranteed to fully present in block defined + // Entire VM code is guaranteed to fully present in block defined // by current Huffman table. Compressor checks that VM code does not cross // Huffman block boundaries. uint FirstByte=Inp.getbits()>>8; @@ -418,7 +414,7 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) Filters30[Filters30.Count-1]=Filter=new UnpackFilter30(); StackFilter.ParentFilter=(uint)(Filters30.Count-1); - // Reserve one item to store the data block length of our new filter + // Reserve one item to store the data block length of our new filter // entry. We'll set it to real block length below, after reading it. // But we need to initialize it now, because when processing corrupt // data, we can access this item even before we set it to real value. @@ -466,7 +462,7 @@ bool AddVMCode(uint FirstByte,byte[] Code,int CodeSize) else { // Set the data block size to same value as the previous block size - // for same filter. It is possible for corrupt data to access a new + // for same filter. It is possible for corrupt data to access a new // and not filled yet item of OldFilterLengths array here. This is why // we set new OldFilterLengths items to zero above. StackFilter.BlockLength=FiltPos= ReadBorder) - { - bool FileDone = false; - - // We use 'while', because for empty block containing only Huffman table, - // we'll be on the block border once again just after reading the table. - while (Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 || - Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 && - Inp.InBit >= BlockHeader.BlockBitSize) - { - if (BlockHeader.LastBlockInFile) - { - FileDone = true; - break; - } - if (!ReadBlockHeader(Inp, ref BlockHeader) || !ReadTables(Inp, ref BlockHeader, ref BlockTables)) - { - return; - } - } - if (FileDone || !UnpReadBuf()) - { - break; - } - } - - if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) - { - UnpWriteBuf(); - if (WrittenFileSize > DestUnpSize) - { - return; - } - - if (Suspended) - { - FileExtracted = false; - return; - } - } - - uint MainSlot = DecodeNumber(Inp, BlockTables.LD); - if (MainSlot < 256) - { - if (Fragmented) - { - FragWindow[UnpPtr++] = (byte)MainSlot; - } - else - { - Window[UnpPtr++] = (byte)MainSlot; - } - - continue; - } - if (MainSlot >= 262) - { - uint Length = SlotToLength(Inp, MainSlot - 262); - - uint DBits, Distance = 1, DistSlot = DecodeNumber(Inp, BlockTables.DD); - if (DistSlot < 4) - { - DBits = 0; - Distance += DistSlot; - } - else - { - DBits = DistSlot / 2 - 1; - Distance += (2 | (DistSlot & 1)) << (int)DBits; - } - - if (DBits > 0) - { - if (DBits >= 4) - { - if (DBits > 4) - { - Distance += ((Inp.getbits32() >> (int)(36 - DBits)) << 4); - Inp.addbits(DBits - 4); - } - uint LowDist = DecodeNumber(Inp, BlockTables.LDD); - Distance += LowDist; - } - else - { - Distance += Inp.getbits32() >> (int)(32 - DBits); - Inp.addbits(DBits); - } - } - - if (Distance > 0x100) - { - Length++; - if (Distance > 0x2000) - { - Length++; - if (Distance > 0x40000) - { - Length++; - } - } - } - - InsertOldDist(Distance); - LastLength = Length; - if (Fragmented) - { - FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); - } - else - { - CopyString(Length, Distance); - } - - continue; - } - if (MainSlot == 256) - { - UnpackFilter Filter = new UnpackFilter(); - if (!ReadFilter(Inp, Filter) || !AddFilter(Filter)) - { - break; - } - - continue; - } - if (MainSlot == 257) - { - if (LastLength != 0) - { - if (Fragmented) - { - FragWindow.CopyString(LastLength, OldDist[0], ref UnpPtr, MaxWinMask); - } - else - { - CopyString(LastLength, OldDist[0]); - } - } - - continue; - } - if (MainSlot < 262) - { - uint DistNum = MainSlot - 258; - uint Distance = OldDist[DistNum]; - for (uint I = DistNum; I > 0; I--) - { - OldDist[I] = OldDist[I - 1]; - } - - OldDist[0] = Distance; - - uint LengthSlot = DecodeNumber(Inp, BlockTables.RD); - uint Length = SlotToLength(Inp, LengthSlot); - LastLength = Length; - if (Fragmented) - { - FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); - } - else - { - CopyString(Length, Distance); - } - - continue; - } - } - UnpWriteBuf(); - } - - private uint ReadFilterData(BitInput Inp) - { - uint ByteCount = (Inp.fgetbits() >> 14) + 1; - Inp.addbits(2); - - uint Data = 0; - for (uint I = 0; I < ByteCount; I++) - { - Data += (Inp.fgetbits() >> 8) << (int)(I * 8); - Inp.addbits(8); - } - return Data; - } - - private bool ReadFilter(BitInput Inp, UnpackFilter Filter) - { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) - { - if (!UnpReadBuf()) - { - return false; - } - } - - Filter.BlockStart = ReadFilterData(Inp); - Filter.BlockLength = ReadFilterData(Inp); - if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) - { - Filter.BlockLength = 0; - } - - Filter.Type = (byte)(Inp.fgetbits() >> 13); - Inp.faddbits(3); - - if (Filter.Type == FILTER_DELTA) - { - Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); - Inp.faddbits(5); - } - - return true; - } - - private bool AddFilter(UnpackFilter Filter) - { - if (Filters.Count >= MAX_UNPACK_FILTERS) - { - UnpWriteBuf(); // Write data, apply and flush filters. - if (Filters.Count >= MAX_UNPACK_FILTERS) - { - InitFilters(); // Still too many filters, prevent excessive memory use. - } - } - - // If distance to filter start is that large that due to circular dictionary - // mode now it points to old not written yet data, then we set 'NextWindow' - // flag and process this filter only after processing that older data. - Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; - - Filter.BlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask); - Filters.Add(Filter); - return true; - } - - private bool UnpReadBuf() - { - int DataSize = ReadTop - Inp.InAddr; // Data left to process. - if (DataSize < 0) - { - return false; - } - - BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; - if (Inp.InAddr > MAX_SIZE / 2) - { - // If we already processed more than half of buffer, let's move - // remaining data into beginning to free more space for new data - // and ensure that calling function does not cross the buffer border - // even if we did not read anything here. Also it ensures that read size - // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk - // to make it zero. - if (DataSize > 0) - //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); - { - Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); - } - - Inp.InAddr = 0; - ReadTop = DataSize; - } - else - { - DataSize = ReadTop; - } - - int ReadCode = 0; - if (MAX_SIZE != DataSize) - { - ReadCode = UnpIO_UnpRead(Inp.InBuf, DataSize, MAX_SIZE - DataSize); - } - - if (ReadCode > 0) // Can be also -1. - { - ReadTop += ReadCode; - } - - ReadBorder = ReadTop - 30; - BlockHeader.BlockStart = Inp.InAddr; - if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. - { - // We may need to quit from main extraction loop and read new block header - // and trees earlier than data in input buffer ends. - ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); - } - return ReadCode != -1; - } - - private void UnpWriteBuf() - { - size_t WrittenBorder = WrPtr; - size_t FullWriteSize = (UnpPtr - WrittenBorder) & MaxWinMask; - size_t WriteSizeLeft = FullWriteSize; - bool NotAllFiltersProcessed = false; - //for (size_t I=0;I int - for (int I = 0; I < Filters.Count; I++) - { - // Here we apply filters to data which we need to write. - // We always copy data to another memory block before processing. - // We cannot process them just in place in Window buffer, because - // these data can be used for future string matches, so we must - // preserve them in original form. - - UnpackFilter flt = Filters[I]; - if (flt.Type == FILTER_NONE) - { - continue; - } - - if (flt.NextWindow) - { - // Here we skip filters which have block start in current data range - // due to address wrap around in circular dictionary, but actually - // belong to next dictionary block. If such filter start position - // is included to current write range, then we reset 'NextWindow' flag. - // In fact we can reset it even without such check, because current - // implementation seems to guarantee 'NextWindow' flag reset after - // buffer writing for all existing filters. But let's keep this check - // just in case. Compressor guarantees that distance between - // filter block start and filter storing position cannot exceed - // the dictionary size. So if we covered the filter block start with - // our write here, we can safely assume that filter is applicable - // to next block on no further wrap arounds is possible. - if (((flt.BlockStart - WrPtr) & MaxWinMask) <= FullWriteSize) - { - flt.NextWindow = false; - } - - continue; - } - uint BlockStart = flt.BlockStart; - uint BlockLength = flt.BlockLength; - if (((BlockStart - WrittenBorder) & MaxWinMask) < WriteSizeLeft) - { - if (WrittenBorder != BlockStart) - { - UnpWriteArea(WrittenBorder, BlockStart); - WrittenBorder = BlockStart; - WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; - } - if (BlockLength <= WriteSizeLeft) - { - if (BlockLength > 0) // We set it to 0 also for invalid filters. - { - uint BlockEnd = (BlockStart + BlockLength) & MaxWinMask; - - //x FilterSrcMemory.Alloc(BlockLength); - FilterSrcMemory = EnsureCapacity(FilterSrcMemory, checked((int)BlockLength)); - byte[] Mem = FilterSrcMemory; - if (BlockStart < BlockEnd || BlockEnd == 0) - { - if (Fragmented) - { - FragWindow.CopyData(Mem, 0, BlockStart, BlockLength); - } - else - //x memcpy(Mem,Window+BlockStart,BlockLength); - { - Utility.Copy(Window, BlockStart, Mem, 0, BlockLength); - } - } - else - { - size_t FirstPartLength = (size_t)(MaxWinSize - BlockStart); - if (Fragmented) - { - FragWindow.CopyData(Mem, 0, BlockStart, FirstPartLength); - FragWindow.CopyData(Mem, FirstPartLength, 0, BlockEnd); - } - else - { - //x memcpy(Mem,Window+BlockStart,FirstPartLength); - Utility.Copy(Window, BlockStart, Mem, 0, FirstPartLength); - //x memcpy(Mem+FirstPartLength,Window,BlockEnd); - Utility.Copy(Window, 0, Mem, FirstPartLength, BlockEnd); - } - } - - byte[] OutMem = ApplyFilter(Mem, BlockLength, flt); - - Filters[I].Type = FILTER_NONE; - - if (OutMem != null) - { - UnpIO_UnpWrite(OutMem, 0, BlockLength); - } - - UnpSomeRead = true; - WrittenFileSize += BlockLength; - WrittenBorder = BlockEnd; - WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; - } - } - else - { - // Current filter intersects the window write border, so we adjust - // the window border to process this filter next time, not now. - WrPtr = WrittenBorder; - - // Since Filter start position can only increase, we quit processing - // all following filters for this data block and reset 'NextWindow' - // flag for them. - //for (size_t J=I;J int - for (int J = I; J < Filters.Count; J++) - { - UnpackFilter _flt = Filters[J]; - if (_flt.Type != FILTER_NONE) - { - _flt.NextWindow = false; - } - } - - // Do not write data left after current filter now. - NotAllFiltersProcessed = true; - break; - } - } - } - - // Remove processed filters from queue. - // sharpcompress: size_t -> int - int EmptyCount = 0; - // sharpcompress: size_t -> int - for (int I = 0; I < Filters.Count; I++) - { - if (EmptyCount > 0) - { - Filters[I - EmptyCount] = Filters[I]; - } - - if (Filters[I].Type == FILTER_NONE) - { - EmptyCount++; - } - } - if (EmptyCount > 0) - //Filters.Alloc(Filters.Count-EmptyCount); - { - Filters.RemoveRange(Filters.Count - EmptyCount, EmptyCount); - } - - if (!NotAllFiltersProcessed) // Only if all filters are processed. - { - // Write data left after last filter. - UnpWriteArea(WrittenBorder, UnpPtr); - WrPtr = UnpPtr; - } - - // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE - // instead of potentially huge MaxWinSize blocks. It also allows us - // to keep the size of Filters array reasonable. - WriteBorder = (UnpPtr + Math.Min(MaxWinSize, UNPACK_MAX_WRITE)) & MaxWinMask; - - // Choose the nearest among WriteBorder and WrPtr actual written border. - // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. - if (WriteBorder == UnpPtr || - WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) < ((WriteBorder - UnpPtr) & MaxWinMask)) - { - WriteBorder = WrPtr; - } - } - - private byte[] ApplyFilter(byte[] __d, uint DataSize, UnpackFilter Flt) - { - int Data = 0; - byte[] SrcData = __d; - switch (Flt.Type) - { - case FILTER_E8: - case FILTER_E8E9: - { - uint FileOffset = (uint)WrittenFileSize; - - const uint FileSize = 0x1000000; - byte CmpByte2 = Flt.Type == FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; - // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" - // to avoid overflow for DataSize<4. - for (uint CurPos = 0; CurPos + 4 < DataSize;) - { - //x byte CurByte=*(Data++); - byte CurByte = __d[Data++]; - CurPos++; - if (CurByte == 0xe8 || CurByte == CmpByte2) - { - uint Offset = (CurPos + FileOffset) % FileSize; - uint Addr = RawGet4(__d, Data); - - // We check 0x80000000 bit instead of '< 0' comparison - // not assuming int32 presence or uint size and endianness. - if ((Addr & 0x80000000) != 0) // Addr<0 - { - if (((Addr + Offset) & 0x80000000) == 0) // Addr+Offset>=0 - { - RawPut4(Addr + FileSize, __d, Data); - } - } - else - if (((Addr - FileSize) & 0x80000000) != 0) // Addr> 8); - __d[D + 2] = (byte)(Offset >> 16); - } - } - } - return SrcData; - case FILTER_DELTA: - { - // Unlike RAR3, we do not need to reject excessive channel - // values here, since RAR5 uses only 5 bits to store channel. - uint Channels = Flt.Channels, SrcPos = 0; - - //x FilterDstMemory.Alloc(DataSize); - FilterDstMemory = EnsureCapacity(FilterDstMemory, checked((int)DataSize)); - - byte[] DstData = FilterDstMemory; - - // Bytes from same channels are grouped to continual data blocks, - // so we need to place them back to their interleaving positions. - for (uint CurChannel = 0; CurChannel < Channels; CurChannel++) - { - byte PrevByte = 0; - for (uint DestPos = CurChannel; DestPos < DataSize; DestPos += Channels) - { - DstData[DestPos] = (PrevByte -= __d[Data + SrcPos++]); - } - } - return DstData; - } - - } - return null; - } - - private void UnpWriteArea(size_t StartPtr, size_t EndPtr) - { - if (EndPtr != StartPtr) - { - UnpSomeRead = true; - } - - if (EndPtr < StartPtr) - { - UnpAllBuf = true; - } - - if (Fragmented) - { - size_t SizeToWrite = (EndPtr - StartPtr) & MaxWinMask; - while (SizeToWrite > 0) - { - size_t BlockSize = FragWindow.GetBlockSize(StartPtr, SizeToWrite); - //UnpWriteData(&FragWindow[StartPtr],BlockSize); - FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); - UnpWriteData(__buffer, __offset, BlockSize); - SizeToWrite -= BlockSize; - StartPtr = (StartPtr + BlockSize) & MaxWinMask; - } - } - else - if (EndPtr < StartPtr) - { - UnpWriteData(Window, StartPtr, MaxWinSize - StartPtr); - UnpWriteData(Window, 0, EndPtr); - } - else - { - UnpWriteData(Window, StartPtr, EndPtr - StartPtr); - } - } - - private void UnpWriteData(byte[] Data, size_t offset, size_t Size) - { - if (WrittenFileSize >= DestUnpSize) + UnpInitData(Solid); + if (!UnpReadBuf()) { return; } - size_t WriteSize = Size; - int64 LeftToWrite = DestUnpSize - WrittenFileSize; - if ((int64)WriteSize > LeftToWrite) + // Check TablesRead5 to be sure that we read tables at least once + // regardless of current block header TablePresent flag. + // So we can safefly use these tables below. + if ( + !ReadBlockHeader(Inp, ref BlockHeader) + || !ReadTables(Inp, ref BlockHeader, ref BlockTables) + || !TablesRead5 + ) { - WriteSize = (size_t)LeftToWrite; - } - - UnpIO_UnpWrite(Data, offset, WriteSize); - WrittenFileSize += Size; - } - - private void UnpInitData50(bool Solid) - { - if (!Solid) - { - TablesRead5 = false; + return; } } - private bool ReadBlockHeader(BitInput Inp, ref UnpackBlockHeader Header) + while (true) { - Header.HeaderSize = 0; + UnpPtr &= MaxWinMask; - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + if (Inp.InAddr >= ReadBorder) { - if (!UnpReadBuf()) + var FileDone = false; + + // We use 'while', because for empty block containing only Huffman table, + // we'll be on the block border once again just after reading the table. + while ( + Inp.InAddr > BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + || Inp.InAddr == BlockHeader.BlockStart + BlockHeader.BlockSize - 1 + && Inp.InBit >= BlockHeader.BlockBitSize + ) { - return false; + if (BlockHeader.LastBlockInFile) + { + FileDone = true; + break; + } + if ( + !ReadBlockHeader(Inp, ref BlockHeader) + || !ReadTables(Inp, ref BlockHeader, ref BlockTables) + ) + { + return; + } + } + if (FileDone || !UnpReadBuf()) + { + break; } } - Inp.faddbits((uint)((8 - Inp.InBit) & 7)); + if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) + { + UnpWriteBuf(); + if (WrittenFileSize > DestUnpSize) + { + return; + } - byte BlockFlags = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); - uint ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. + if (Suspended) + { + FileExtracted = false; + return; + } + } - if (ByteCount == 4) + var MainSlot = DecodeNumber(Inp, BlockTables.LD); + if (MainSlot < 256) + { + if (Fragmented) + { + FragWindow[UnpPtr++] = (byte)MainSlot; + } + else + { + Window[UnpPtr++] = (byte)MainSlot; + } + + continue; + } + if (MainSlot >= 262) + { + var Length = SlotToLength(Inp, MainSlot - 262); + + uint DBits, + Distance = 1, + DistSlot = DecodeNumber(Inp, BlockTables.DD); + if (DistSlot < 4) + { + DBits = 0; + Distance += DistSlot; + } + else + { + DBits = (DistSlot / 2) - 1; + Distance += (2 | (DistSlot & 1)) << (int)DBits; + } + + if (DBits > 0) + { + if (DBits >= 4) + { + if (DBits > 4) + { + Distance += ((Inp.getbits32() >> (int)(36 - DBits)) << 4); + Inp.addbits(DBits - 4); + } + var LowDist = DecodeNumber(Inp, BlockTables.LDD); + Distance += LowDist; + } + else + { + Distance += Inp.getbits32() >> (int)(32 - DBits); + Inp.addbits(DBits); + } + } + + if (Distance > 0x100) + { + Length++; + if (Distance > 0x2000) + { + Length++; + if (Distance > 0x40000) + { + Length++; + } + } + } + + InsertOldDist(Distance); + LastLength = Length; + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } + + continue; + } + if (MainSlot == 256) + { + var Filter = new UnpackFilter(); + if (!ReadFilter(Inp, Filter) || !AddFilter(Filter)) + { + break; + } + + continue; + } + if (MainSlot == 257) + { + if (LastLength != 0) + { + if (Fragmented) + { + FragWindow.CopyString(LastLength, OldDist[0], ref UnpPtr, MaxWinMask); + } + else + { + CopyString(LastLength, OldDist[0]); + } + } + + continue; + } + if (MainSlot < 262) + { + var DistNum = MainSlot - 258; + var Distance = OldDist[DistNum]; + for (var I = DistNum; I > 0; I--) + { + OldDist[I] = OldDist[I - 1]; + } + + OldDist[0] = Distance; + + var LengthSlot = DecodeNumber(Inp, BlockTables.RD); + var Length = SlotToLength(Inp, LengthSlot); + LastLength = Length; + if (Fragmented) + { + FragWindow.CopyString(Length, Distance, ref UnpPtr, MaxWinMask); + } + else + { + CopyString(Length, Distance); + } + + continue; + } + } + UnpWriteBuf(); + } + + private uint ReadFilterData(BitInput Inp) + { + var ByteCount = (Inp.fgetbits() >> 14) + 1; + Inp.addbits(2); + + uint Data = 0; + for (uint I = 0; I < ByteCount; I++) + { + Data += (Inp.fgetbits() >> 8) << (int)(I * 8); + Inp.addbits(8); + } + return Data; + } + + private bool ReadFilter(BitInput Inp, UnpackFilter Filter) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + { + if (!UnpReadBuf()) { return false; } + } - Header.HeaderSize = (int)(2 + ByteCount); + Filter.BlockStart = ReadFilterData(Inp); + Filter.BlockLength = ReadFilterData(Inp); + if (Filter.BlockLength > MAX_FILTER_BLOCK_SIZE) + { + Filter.BlockLength = 0; + } - Header.BlockBitSize = (BlockFlags & 7) + 1; + Filter.Type = (byte)(Inp.fgetbits() >> 13); + Inp.faddbits(3); - byte SavedCheckSum = (byte)(Inp.fgetbits() >> 8); - Inp.faddbits(8); + if (Filter.Type == FILTER_DELTA) + { + Filter.Channels = (byte)((Inp.fgetbits() >> 11) + 1); + Inp.faddbits(5); + } - int BlockSize = 0; - for (uint I = 0; I < ByteCount; I++) + return true; + } + + private bool AddFilter(UnpackFilter Filter) + { + if (Filters.Count >= MAX_UNPACK_FILTERS) + { + UnpWriteBuf(); // Write data, apply and flush filters. + if (Filters.Count >= MAX_UNPACK_FILTERS) { - BlockSize += (int)((Inp.fgetbits() >> 8) << (int)(I * 8)); - Inp.addbits(8); + InitFilters(); // Still too many filters, prevent excessive memory use. + } + } + + // If distance to filter start is that large that due to circular dictionary + // mode now it points to old not written yet data, then we set 'NextWindow' + // flag and process this filter only after processing that older data. + Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; + + Filter.BlockStart = (Filter.BlockStart + UnpPtr) & MaxWinMask; + Filters.Add(Filter); + return true; + } + + private bool UnpReadBuf() + { + var DataSize = ReadTop - Inp.InAddr; // Data left to process. + if (DataSize < 0) + { + return false; + } + + BlockHeader.BlockSize -= Inp.InAddr - BlockHeader.BlockStart; + if (Inp.InAddr > MAX_SIZE / 2) + { + // If we already processed more than half of buffer, let's move + // remaining data into beginning to free more space for new data + // and ensure that calling function does not cross the buffer border + // even if we did not read anything here. Also it ensures that read size + // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk + // to make it zero. + if (DataSize > 0) + //x memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize); + { + Buffer.BlockCopy(Inp.InBuf, Inp.InAddr, Inp.InBuf, 0, DataSize); } - Header.BlockSize = BlockSize; - byte CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); - if (CheckSum != SavedCheckSum) + Inp.InAddr = 0; + ReadTop = DataSize; + } + else + { + DataSize = ReadTop; + } + + var ReadCode = 0; + if (MAX_SIZE != DataSize) + { + ReadCode = UnpIO_UnpRead(Inp.InBuf, DataSize, MAX_SIZE - DataSize); + } + + if (ReadCode > 0) // Can be also -1. + { + ReadTop += ReadCode; + } + + ReadBorder = ReadTop - 30; + BlockHeader.BlockStart = Inp.InAddr; + if (BlockHeader.BlockSize != -1) // '-1' means not defined yet. + { + // We may need to quit from main extraction loop and read new block header + // and trees earlier than data in input buffer ends. + ReadBorder = Math.Min(ReadBorder, BlockHeader.BlockStart + BlockHeader.BlockSize - 1); + } + return ReadCode != -1; + } + + private void UnpWriteBuf() + { + var WrittenBorder = WrPtr; + var FullWriteSize = (UnpPtr - WrittenBorder) & MaxWinMask; + var WriteSizeLeft = FullWriteSize; + var NotAllFiltersProcessed = false; + //for (size_t I=0;I int + for (var I = 0; I < Filters.Count; I++) + { + // Here we apply filters to data which we need to write. + // We always copy data to another memory block before processing. + // We cannot process them just in place in Window buffer, because + // these data can be used for future string matches, so we must + // preserve them in original form. + + var flt = Filters[I]; + if (flt.Type == FILTER_NONE) + { + continue; + } + + if (flt.NextWindow) + { + // Here we skip filters which have block start in current data range + // due to address wrap around in circular dictionary, but actually + // belong to next dictionary block. If such filter start position + // is included to current write range, then we reset 'NextWindow' flag. + // In fact we can reset it even without such check, because current + // implementation seems to guarantee 'NextWindow' flag reset after + // buffer writing for all existing filters. But let's keep this check + // just in case. Compressor guarantees that distance between + // filter block start and filter storing position cannot exceed + // the dictionary size. So if we covered the filter block start with + // our write here, we can safely assume that filter is applicable + // to next block on no further wrap arounds is possible. + if (((flt.BlockStart - WrPtr) & MaxWinMask) <= FullWriteSize) + { + flt.NextWindow = false; + } + + continue; + } + var BlockStart = flt.BlockStart; + var BlockLength = flt.BlockLength; + if (((BlockStart - WrittenBorder) & MaxWinMask) < WriteSizeLeft) + { + if (WrittenBorder != BlockStart) + { + UnpWriteArea(WrittenBorder, BlockStart); + WrittenBorder = BlockStart; + WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; + } + if (BlockLength <= WriteSizeLeft) + { + if (BlockLength > 0) // We set it to 0 also for invalid filters. + { + var BlockEnd = (BlockStart + BlockLength) & MaxWinMask; + + //x FilterSrcMemory.Alloc(BlockLength); + FilterSrcMemory = EnsureCapacity( + FilterSrcMemory, + checked((int)BlockLength) + ); + var Mem = FilterSrcMemory; + if (BlockStart < BlockEnd || BlockEnd == 0) + { + if (Fragmented) + { + FragWindow.CopyData(Mem, 0, BlockStart, BlockLength); + } + else + //x memcpy(Mem,Window+BlockStart,BlockLength); + { + Utility.Copy(Window, BlockStart, Mem, 0, BlockLength); + } + } + else + { + var FirstPartLength = MaxWinSize - BlockStart; + if (Fragmented) + { + FragWindow.CopyData(Mem, 0, BlockStart, FirstPartLength); + FragWindow.CopyData(Mem, FirstPartLength, 0, BlockEnd); + } + else + { + //x memcpy(Mem,Window+BlockStart,FirstPartLength); + Utility.Copy(Window, BlockStart, Mem, 0, FirstPartLength); + //x memcpy(Mem+FirstPartLength,Window,BlockEnd); + Utility.Copy(Window, 0, Mem, FirstPartLength, BlockEnd); + } + } + + var OutMem = ApplyFilter(Mem, BlockLength, flt); + + Filters[I].Type = FILTER_NONE; + + if (OutMem != null) + { + UnpIO_UnpWrite(OutMem, 0, BlockLength); + } + + UnpSomeRead = true; + WrittenFileSize += BlockLength; + WrittenBorder = BlockEnd; + WriteSizeLeft = (UnpPtr - WrittenBorder) & MaxWinMask; + } + } + else + { + // Current filter intersects the window write border, so we adjust + // the window border to process this filter next time, not now. + WrPtr = WrittenBorder; + + // Since Filter start position can only increase, we quit processing + // all following filters for this data block and reset 'NextWindow' + // flag for them. + //for (size_t J=I;J int + for (var J = I; J < Filters.Count; J++) + { + var _flt = Filters[J]; + if (_flt.Type != FILTER_NONE) + { + _flt.NextWindow = false; + } + } + + // Do not write data left after current filter now. + NotAllFiltersProcessed = true; + break; + } + } + } + + // Remove processed filters from queue. + // sharpcompress: size_t -> int + var EmptyCount = 0; + // sharpcompress: size_t -> int + for (var I = 0; I < Filters.Count; I++) + { + if (EmptyCount > 0) + { + Filters[I - EmptyCount] = Filters[I]; + } + + if (Filters[I].Type == FILTER_NONE) + { + EmptyCount++; + } + } + if (EmptyCount > 0) + //Filters.Alloc(Filters.Count-EmptyCount); + { + Filters.RemoveRange(Filters.Count - EmptyCount, EmptyCount); + } + + if (!NotAllFiltersProcessed) // Only if all filters are processed. + { + // Write data left after last filter. + UnpWriteArea(WrittenBorder, UnpPtr); + WrPtr = UnpPtr; + } + + // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE + // instead of potentially huge MaxWinSize blocks. It also allows us + // to keep the size of Filters array reasonable. + WriteBorder = (UnpPtr + Math.Min(MaxWinSize, UNPACK_MAX_WRITE)) & MaxWinMask; + + // Choose the nearest among WriteBorder and WrPtr actual written border. + // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead. + if ( + WriteBorder == UnpPtr + || WrPtr != UnpPtr + && ((WrPtr - UnpPtr) & MaxWinMask) < ((WriteBorder - UnpPtr) & MaxWinMask) + ) + { + WriteBorder = WrPtr; + } + } + + private byte[] ApplyFilter(byte[] __d, uint DataSize, UnpackFilter Flt) + { + var Data = 0; + var SrcData = __d; + switch (Flt.Type) + { + case FILTER_E8: + case FILTER_E8E9: + + { + var FileOffset = (uint)WrittenFileSize; + + const uint FileSize = 0x1000000; + var CmpByte2 = Flt.Type == FILTER_E8E9 ? (byte)0xe9 : (byte)0xe8; + // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4" + // to avoid overflow for DataSize<4. + for (uint CurPos = 0; CurPos + 4 < DataSize; ) + { + //x byte CurByte=*(Data++); + var CurByte = __d[Data++]; + CurPos++; + if (CurByte == 0xe8 || CurByte == CmpByte2) + { + var Offset = (CurPos + FileOffset) % FileSize; + var Addr = RawGet4(__d, Data); + + // We check 0x80000000 bit instead of '< 0' comparison + // not assuming int32 presence or uint size and endianness. + if ((Addr & 0x80000000) != 0) // Addr<0 + { + if (((Addr + Offset) & 0x80000000) == 0) // Addr+Offset>=0 + { + RawPut4(Addr + FileSize, __d, Data); + } + } + else if (((Addr - FileSize) & 0x80000000) != 0) // Addr> 8); + __d[D + 2] = (byte)(Offset >> 16); + } + } + } + return SrcData; + case FILTER_DELTA: + { + // Unlike RAR3, we do not need to reject excessive channel + // values here, since RAR5 uses only 5 bits to store channel. + uint Channels = Flt.Channels, + SrcPos = 0; + + //x FilterDstMemory.Alloc(DataSize); + FilterDstMemory = EnsureCapacity(FilterDstMemory, checked((int)DataSize)); + + var DstData = FilterDstMemory; + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (uint CurChannel = 0; CurChannel < Channels; CurChannel++) + { + byte PrevByte = 0; + for (var DestPos = CurChannel; DestPos < DataSize; DestPos += Channels) + { + DstData[DestPos] = (PrevByte -= __d[Data + SrcPos++]); + } + } + return DstData; + } + } + return null; + } + + private void UnpWriteArea(size_t StartPtr, size_t EndPtr) + { + if (EndPtr != StartPtr) + { + UnpSomeRead = true; + } + + if (EndPtr < StartPtr) + { + UnpAllBuf = true; + } + + if (Fragmented) + { + var SizeToWrite = (EndPtr - StartPtr) & MaxWinMask; + while (SizeToWrite > 0) + { + var BlockSize = FragWindow.GetBlockSize(StartPtr, SizeToWrite); + //UnpWriteData(&FragWindow[StartPtr],BlockSize); + FragWindow.GetBuffer(StartPtr, out var __buffer, out var __offset); + UnpWriteData(__buffer, __offset, BlockSize); + SizeToWrite -= BlockSize; + StartPtr = (StartPtr + BlockSize) & MaxWinMask; + } + } + else if (EndPtr < StartPtr) + { + UnpWriteData(Window, StartPtr, MaxWinSize - StartPtr); + UnpWriteData(Window, 0, EndPtr); + } + else + { + UnpWriteData(Window, StartPtr, EndPtr - StartPtr); + } + } + + private void UnpWriteData(byte[] Data, size_t offset, size_t Size) + { + if (WrittenFileSize >= DestUnpSize) + { + return; + } + + var WriteSize = Size; + var LeftToWrite = DestUnpSize - WrittenFileSize; + if (WriteSize > LeftToWrite) + { + WriteSize = (size_t)LeftToWrite; + } + + UnpIO_UnpWrite(Data, offset, WriteSize); + WrittenFileSize += Size; + } + + private void UnpInitData50(bool Solid) + { + if (!Solid) + { + TablesRead5 = false; + } + } + + private bool ReadBlockHeader(BitInput Inp, ref UnpackBlockHeader Header) + { + Header.HeaderSize = 0; + + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + { + if (!UnpReadBuf()) { return false; } + } - Header.BlockStart = Inp.InAddr; - ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); + Inp.faddbits((uint)((8 - Inp.InBit) & 7)); - Header.LastBlockInFile = (BlockFlags & 0x40) != 0; - Header.TablePresent = (BlockFlags & 0x80) != 0; + var BlockFlags = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + var ByteCount = (uint)(((BlockFlags >> 3) & 3) + 1); // Block size byte count. + + if (ByteCount == 4) + { + return false; + } + + Header.HeaderSize = (int)(2 + ByteCount); + + Header.BlockBitSize = (BlockFlags & 7) + 1; + + var SavedCheckSum = (byte)(Inp.fgetbits() >> 8); + Inp.faddbits(8); + + var BlockSize = 0; + for (uint I = 0; I < ByteCount; I++) + { + BlockSize += (int)((Inp.fgetbits() >> 8) << (int)(I * 8)); + Inp.addbits(8); + } + + Header.BlockSize = BlockSize; + var CheckSum = (byte)(0x5a ^ BlockFlags ^ BlockSize ^ (BlockSize >> 8) ^ (BlockSize >> 16)); + if (CheckSum != SavedCheckSum) + { + return false; + } + + Header.BlockStart = Inp.InAddr; + ReadBorder = Math.Min(ReadBorder, Header.BlockStart + Header.BlockSize - 1); + + Header.LastBlockInFile = (BlockFlags & 0x40) != 0; + Header.TablePresent = (BlockFlags & 0x80) != 0; + return true; + } + + private bool ReadTables( + BitInput Inp, + ref UnpackBlockHeader Header, + ref UnpackBlockTables Tables + ) + { + if (!Header.TablePresent) + { return true; } - private bool ReadTables(BitInput Inp, ref UnpackBlockHeader Header, ref UnpackBlockTables Tables) + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) { - if (!Header.TablePresent) + if (!UnpReadBuf()) { - return true; + return false; } + } - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) + var BitLength = new byte[BC]; + for (uint I = 0; I < BC; I++) + { + uint Length = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (Length == 15) + { + uint ZeroCount = (byte)(Inp.fgetbits() >> 12); + Inp.faddbits(4); + if (ZeroCount == 0) + { + BitLength[I] = 15; + } + else + { + ZeroCount += 2; + while (ZeroCount-- > 0 && I < BitLength.Length) + { + BitLength[I++] = 0; + } + + I--; + } + } + else + { + BitLength[I] = (byte)Length; + } + } + + MakeDecodeTables(BitLength, 0, Tables.BD, BC); + + var Table = new byte[HUFF_TABLE_SIZE]; + const uint TableSize = HUFF_TABLE_SIZE; + for (uint I = 0; I < TableSize; ) + { + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) { if (!UnpReadBuf()) { @@ -732,125 +797,76 @@ namespace SharpCompress.Compressors.Rar.UnpackV2017 } } - byte[] BitLength = new byte[BC]; - for (uint I = 0; I < BC; I++) + var Number = DecodeNumber(Inp, Tables.BD); + if (Number < 16) { - uint Length = (byte)(Inp.fgetbits() >> 12); - Inp.faddbits(4); - if (Length == 15) - { - uint ZeroCount = (byte)(Inp.fgetbits() >> 12); - Inp.faddbits(4); - if (ZeroCount == 0) - { - BitLength[I] = 15; - } - else - { - ZeroCount += 2; - while (ZeroCount-- > 0 && I < BitLength.Length) - { - BitLength[I++] = 0; - } - - I--; - } - } - else - { - BitLength[I] = (byte)Length; - } + Table[I] = (byte)Number; + I++; } - - MakeDecodeTables(BitLength, 0, Tables.BD, BC); - - byte[] Table = new byte[HUFF_TABLE_SIZE]; - const uint TableSize = HUFF_TABLE_SIZE; - for (uint I = 0; I < TableSize;) + else if (Number < 18) { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) + uint N; + if (Number == 16) { - if (!UnpReadBuf()) - { - return false; - } - } - - uint Number = DecodeNumber(Inp, Tables.BD); - if (Number < 16) - { - Table[I] = (byte)Number; - I++; - } - else - if (Number < 18) - { - uint N; - if (Number == 16) - { - N = (Inp.fgetbits() >> 13) + 3; - Inp.faddbits(3); - } - else - { - N = (Inp.fgetbits() >> 9) + 11; - Inp.faddbits(7); - } - if (I == 0) - { - // We cannot have "repeat previous" code at the first position. - // Multiple such codes would shift Inp position without changing I, - // which can lead to reading beyond of Inp boundary in mutithreading - // mode, where Inp.ExternalBuffer disables bounds check and we just - // reserve a lot of buffer space to not need such check normally. - return false; - } - else - { - while (N-- > 0 && I < TableSize) - { - Table[I] = Table[I - 1]; - I++; - } - } + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + if (I == 0) + { + // We cannot have "repeat previous" code at the first position. + // Multiple such codes would shift Inp position without changing I, + // which can lead to reading beyond of Inp boundary in mutithreading + // mode, where Inp.ExternalBuffer disables bounds check and we just + // reserve a lot of buffer space to not need such check normally. + return false; } else { - uint N; - if (Number == 18) - { - N = (Inp.fgetbits() >> 13) + 3; - Inp.faddbits(3); - } - else - { - N = (Inp.fgetbits() >> 9) + 11; - Inp.faddbits(7); - } while (N-- > 0 && I < TableSize) { - Table[I++] = 0; + Table[I] = Table[I - 1]; + I++; } } } - TablesRead5 = true; - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop) + else { - return false; + uint N; + if (Number == 18) + { + N = (Inp.fgetbits() >> 13) + 3; + Inp.faddbits(3); + } + else + { + N = (Inp.fgetbits() >> 9) + 11; + Inp.faddbits(7); + } + while (N-- > 0 && I < TableSize) + { + Table[I++] = 0; + } } - - MakeDecodeTables(Table, 0, Tables.LD, NC); - MakeDecodeTables(Table, (int)NC, Tables.DD, DC); - MakeDecodeTables(Table, (int)(NC + DC), Tables.LDD, LDC); - MakeDecodeTables(Table, (int)(NC + DC + LDC), Tables.RD, RC); - return true; } - - private void InitFilters() + TablesRead5 = true; + if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop) { - //Filters.SoftReset(); - Filters.Clear(); + return false; } + MakeDecodeTables(Table, 0, Tables.LD, NC); + MakeDecodeTables(Table, (int)NC, Tables.DD, DC); + MakeDecodeTables(Table, (int)(NC + DC), Tables.LDD, LDC); + MakeDecodeTables(Table, (int)(NC + DC + LDC), Tables.RD, RC); + return true; } + + private void InitFilters() => + //Filters.SoftReset(); + Filters.Clear(); } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs index 48df0608..f9a74440 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs @@ -1,8 +1,6 @@ -#nullable disable +#nullable disable #if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; using size_t = System.UInt32; #else using nint = System.Int64; @@ -15,395 +13,399 @@ using SharpCompress.Common; using static SharpCompress.Compressors.Rar.UnpackV2017.UnpackGlobal; using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; -namespace SharpCompress.Compressors.Rar.UnpackV2017 -{ - internal sealed partial class Unpack : BitInput - { +namespace SharpCompress.Compressors.Rar.UnpackV2017; - public Unpack(/* ComprDataIO *DataIO */) +internal sealed partial class Unpack : BitInput +{ + public Unpack( /* ComprDataIO *DataIO */ + ) //:Inp(true),VMCodeInp(true) : base(true) - { - _UnpackCtor(); + { + _UnpackCtor(); - //UnpIO=DataIO; - Window = null; - Fragmented = false; - Suspended = false; - UnpAllBuf = false; - UnpSomeRead = false; + //UnpIO=DataIO; + Window = null; + Fragmented = false; + Suspended = false; + UnpAllBuf = false; + UnpSomeRead = false; #if RarV2017_RAR_SMP - MaxUserThreads=1; - UnpThreadPool=CreateThreadPool(); - ReadBufMT=null; - UnpThreadData=null; + MaxUserThreads = 1; + UnpThreadPool = CreateThreadPool(); + ReadBufMT = null; + UnpThreadData = null; #endif - MaxWinSize = 0; - MaxWinMask = 0; + MaxWinSize = 0; + MaxWinMask = 0; - // Perform initialization, which should be done only once for all files. - // It prevents crash if first DoUnpack call is later made with wrong - // (true) 'Solid' value. - UnpInitData(false); + // Perform initialization, which should be done only once for all files. + // It prevents crash if first DoUnpack call is later made with wrong + // (true) 'Solid' value. + UnpInitData(false); #if !RarV2017_SFX_MODULE - // RAR 1.5 decompression initialization - UnpInitData15(false); - InitHuff(); + // RAR 1.5 decompression initialization + UnpInitData15(false); + InitHuff(); #endif + } + + // later: may need Dispose() if we support thread pool + //Unpack::~Unpack() + //{ + // InitFilters30(false); + // + // if (Window!=null) + // free(Window); + //#if RarV2017_RAR_SMP + // DestroyThreadPool(UnpThreadPool); + // delete[] ReadBufMT; + // delete[] UnpThreadData; + //#endif + //} + + private void Init(size_t WinSize, bool Solid) + { + // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size + // will be 0 because of size_t overflow. Let's issue the memory error. + if (WinSize == 0) + //ErrHandler.MemoryError(); + { + throw new InvalidFormatException( + "invalid window size (possibly due to a rar file with a 4GB being unpacked on a 32-bit platform)" + ); } - // later: may need Dispose() if we support thread pool - //Unpack::~Unpack() - //{ - // InitFilters30(false); - // - // if (Window!=null) - // free(Window); - //#if RarV2017_RAR_SMP - // DestroyThreadPool(UnpThreadPool); - // delete[] ReadBufMT; - // delete[] UnpThreadData; - //#endif - //} - - private void Init(size_t WinSize, bool Solid) + // Minimum window size must be at least twice more than maximum possible + // size of filter block, which is 0x10000 in RAR now. If window size is + // smaller, we can have a block with never cleared flt->NextWindow flag + // in UnpWriteBuf(). Minimum window size 0x20000 would be enough, but let's + // use 0x40000 for extra safety and possible filter area size expansion. + const size_t MinAllocSize = 0x40000; + if (WinSize < MinAllocSize) { - // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size - // will be 0 because of size_t overflow. Let's issue the memory error. - if (WinSize == 0) - //ErrHandler.MemoryError(); - { - throw new InvalidFormatException("invalid window size (possibly due to a rar file with a 4GB being unpacked on a 32-bit platform)"); - } - - // Minimum window size must be at least twice more than maximum possible - // size of filter block, which is 0x10000 in RAR now. If window size is - // smaller, we can have a block with never cleared flt->NextWindow flag - // in UnpWriteBuf(). Minimum window size 0x20000 would be enough, but let's - // use 0x40000 for extra safety and possible filter area size expansion. - const size_t MinAllocSize = 0x40000; - if (WinSize < MinAllocSize) - { - WinSize = MinAllocSize; - } - - if (WinSize <= MaxWinSize) // Use the already allocated window. - { - return; - } - - if ((WinSize >> 16) > 0x10000) // Window size must not exceed 4 GB. - { - return; - } - - // Archiving code guarantees that window size does not grow in the same - // solid stream. So if we are here, we are either creating a new window - // or increasing the size of non-solid window. So we could safely reject - // current window data without copying them to a new window, though being - // extra cautious, we still handle the solid window grow case below. - bool Grow = Solid && (Window != null || Fragmented); - - // We do not handle growth for existing fragmented window. - if (Grow && Fragmented) - //throw std::bad_alloc(); - { - throw new InvalidFormatException("Grow && Fragmented"); - } - - byte[] NewWindow = Fragmented ? null : new byte[WinSize]; - - if (NewWindow == null) - { - if (Grow || WinSize < 0x1000000) - { - // We do not support growth for new fragmented window. - // Also exclude RAR4 and small dictionaries. - //throw std::bad_alloc(); - throw new InvalidFormatException("Grow || WinSize<0x1000000"); - } - else - { - if (Window != null) // If allocated by preceding files. - { - //free(Window); - Window = null; - } - FragWindow.Init(WinSize); - Fragmented = true; - } - } - - if (!Fragmented) - { - // Clean the window to generate the same output when unpacking corrupt - // RAR files, which may access unused areas of sliding dictionary. - // sharpcompress: don't need this, freshly allocated above - //memset(NewWindow,0,WinSize); - - - // If Window is not NULL, it means that window size has grown. - // In solid streams we need to copy data to a new window in such case. - // RAR archiving code does not allow it in solid streams now, - // but let's implement it anyway just in case we'll change it sometimes. - if (Grow) - { - for (size_t I = 1; I <= MaxWinSize; I++) - { - NewWindow[(UnpPtr - I) & (WinSize - 1)] = Window[(UnpPtr - I) & (MaxWinSize - 1)]; - } - } - - //if (Window!=null) - // free(Window); - Window = NewWindow; - } - - MaxWinSize = WinSize; - MaxWinMask = MaxWinSize - 1; + WinSize = MinAllocSize; } - private void DoUnpack(uint Method, bool Solid) + if (WinSize <= MaxWinSize) // Use the already allocated window. { - // Methods <50 will crash in Fragmented mode when accessing NULL Window. - // They cannot be called in such mode now, but we check it below anyway - // just for extra safety. - switch (Method) + return; + } + + if ((WinSize >> 16) > 0x10000) // Window size must not exceed 4 GB. + { + return; + } + + // Archiving code guarantees that window size does not grow in the same + // solid stream. So if we are here, we are either creating a new window + // or increasing the size of non-solid window. So we could safely reject + // current window data without copying them to a new window, though being + // extra cautious, we still handle the solid window grow case below. + var Grow = Solid && (Window != null || Fragmented); + + // We do not handle growth for existing fragmented window. + if (Grow && Fragmented) + //throw std::bad_alloc(); + { + throw new InvalidFormatException("Grow && Fragmented"); + } + + var NewWindow = Fragmented ? null : new byte[WinSize]; + + if (NewWindow == null) + { + if (Grow || WinSize < 0x1000000) { + // We do not support growth for new fragmented window. + // Also exclude RAR4 and small dictionaries. + //throw std::bad_alloc(); + throw new InvalidFormatException("Grow || WinSize<0x1000000"); + } + else + { + if (Window != null) // If allocated by preceding files. + { + //free(Window); + Window = null; + } + FragWindow.Init(WinSize); + Fragmented = true; + } + } + + if (!Fragmented) + { + // Clean the window to generate the same output when unpacking corrupt + // RAR files, which may access unused areas of sliding dictionary. + // sharpcompress: don't need this, freshly allocated above + //memset(NewWindow,0,WinSize); + + + // If Window is not NULL, it means that window size has grown. + // In solid streams we need to copy data to a new window in such case. + // RAR archiving code does not allow it in solid streams now, + // but let's implement it anyway just in case we'll change it sometimes. + if (Grow) + { + for (size_t I = 1; I <= MaxWinSize; I++) + { + NewWindow[(UnpPtr - I) & (WinSize - 1)] = Window[ + (UnpPtr - I) & (MaxWinSize - 1) + ]; + } + } + + //if (Window!=null) + // free(Window); + Window = NewWindow; + } + + MaxWinSize = WinSize; + MaxWinMask = MaxWinSize - 1; + } + + private void DoUnpack(uint Method, bool Solid) + { + // Methods <50 will crash in Fragmented mode when accessing NULL Window. + // They cannot be called in such mode now, but we check it below anyway + // just for extra safety. + switch (Method) + { #if !RarV2017_SFX_MODULE - case 15: // rar 1.5 compression - if (!Fragmented) - { - Unpack15(Solid); - } + case 15: // rar 1.5 compression + if (!Fragmented) + { + Unpack15(Solid); + } - break; - case 20: // rar 2.x compression - case 26: // files larger than 2GB - if (!Fragmented) - { - Unpack20(Solid); - } + break; + case 20: // rar 2.x compression + case 26: // files larger than 2GB + if (!Fragmented) + { + Unpack20(Solid); + } - break; + break; #endif #if !RarV2017_RAR5ONLY - case 29: // rar 3.x compression + case 29: // rar 3.x compression + if (!Fragmented) + { + throw new NotImplementedException(); + } + + break; +#endif + case 50: // RAR 5.0 compression algorithm. +#if RarV2017_RAR_SMP + if (MaxUserThreads > 1) + { + // We do not use the multithreaded unpack routine to repack RAR archives + // in 'suspended' mode, because unlike the single threaded code it can + // write more than one dictionary for same loop pass. So we would need + // larger buffers of unknown size. Also we do not support multithreading + // in fragmented window mode. if (!Fragmented) { - throw new NotImplementedException(); + Unpack5MT(Solid); + break; } - - break; + } #endif - case 50: // RAR 5.0 compression algorithm. -#if RarV2017_RAR_SMP - if (MaxUserThreads>1) - { -// We do not use the multithreaded unpack routine to repack RAR archives -// in 'suspended' mode, because unlike the single threaded code it can -// write more than one dictionary for same loop pass. So we would need -// larger buffers of unknown size. Also we do not support multithreading -// in fragmented window mode. - if (!Fragmented) - { - Unpack5MT(Solid); - break; - } - } -#endif - Unpack5(Solid); - break; + Unpack5(Solid); + break; #if !Rar2017_NOSTRICT - default: throw new InvalidFormatException("unknown compression method " + Method); + default: + throw new InvalidFormatException("unknown compression method " + Method); #endif - } } + } - private void UnpInitData(bool Solid) + private void UnpInitData(bool Solid) + { + if (!Solid) { - if (!Solid) - { - new Span(OldDist).Clear(); - OldDistPtr = 0; - LastDist = LastLength = 0; - // memset(Window,0,MaxWinSize); - //memset(&BlockTables,0,sizeof(BlockTables)); - BlockTables = new UnpackBlockTables(); - // sharpcompress: no default ctor for struct - BlockTables.Init(); - UnpPtr = WrPtr = 0; - WriteBorder = Math.Min(MaxWinSize, UNPACK_MAX_WRITE) & MaxWinMask; - } - // Filters never share several solid files, so we can safely reset them - // even in solid archive. - InitFilters(); + new Span(OldDist).Clear(); + OldDistPtr = 0; + LastDist = LastLength = 0; + // memset(Window,0,MaxWinSize); + //memset(&BlockTables,0,sizeof(BlockTables)); + BlockTables = new UnpackBlockTables(); + // sharpcompress: no default ctor for struct + BlockTables.Init(); + UnpPtr = WrPtr = 0; + WriteBorder = Math.Min(MaxWinSize, UNPACK_MAX_WRITE) & MaxWinMask; + } + // Filters never share several solid files, so we can safely reset them + // even in solid archive. + InitFilters(); - Inp.InitBitInput(); - WrittenFileSize = 0; - ReadTop = 0; - ReadBorder = 0; + Inp.InitBitInput(); + WrittenFileSize = 0; + ReadTop = 0; + ReadBorder = 0; - //memset(&BlockHeader,0,sizeof(BlockHeader)); - BlockHeader = new UnpackBlockHeader(); - BlockHeader.BlockSize = -1; // '-1' means not defined yet. + //memset(&BlockHeader,0,sizeof(BlockHeader)); + BlockHeader = new UnpackBlockHeader(); + BlockHeader.BlockSize = -1; // '-1' means not defined yet. #if !RarV2017_SFX_MODULE - UnpInitData20(Solid); + UnpInitData20(Solid); #endif - //UnpInitData30(Solid); - UnpInitData50(Solid); - } + //UnpInitData30(Solid); + UnpInitData50(Solid); + } + // LengthTable contains the length in bits for every element of alphabet. + // Dec is the structure to decode Huffman code/ + // Size is size of length table and DecodeNum field in Dec structure, + private void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec, uint Size) + { + // Size of alphabet and DecodePos array. + Dec.MaxNum = Size; - // LengthTable contains the length in bits for every element of alphabet. - // Dec is the structure to decode Huffman code/ - // Size is size of length table and DecodeNum field in Dec structure, - private void MakeDecodeTables(byte[] LengthTable, int offset, DecodeTable Dec, uint Size) + // Calculate how many entries for every bit length in LengthTable we have. + var LengthCount = new uint[16]; + //memset(LengthCount,0,sizeof(LengthCount)); + for (size_t I = 0; I < Size; I++) { - // Size of alphabet and DecodePos array. - Dec.MaxNum = Size; + LengthCount[LengthTable[offset + I] & 0xf]++; + } - // Calculate how many entries for every bit length in LengthTable we have. - uint[] LengthCount = new uint[16]; - //memset(LengthCount,0,sizeof(LengthCount)); - for (size_t I = 0; I < Size; I++) + // We must not calculate the number of zero length codes. + LengthCount[0] = 0; + + // Set the entire DecodeNum to zero. + //memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + new Span(Dec.DecodeNum).Clear(); + + // Initialize not really used entry for zero length code. + Dec.DecodePos[0] = 0; + + // Start code for bit length 1 is 0. + Dec.DecodeLen[0] = 0; + + // Right aligned upper limit code for current bit length. + uint UpperLimit = 0; + + for (var I = 1; I < 16; I++) + { + // Adjust the upper limit code. + UpperLimit += LengthCount[I]; + + // Left aligned upper limit code. + var LeftAligned = UpperLimit << (16 - I); + + // Prepare the upper limit code for next bit length. + UpperLimit *= 2; + + // Store the left aligned upper limit code. + Dec.DecodeLen[I] = LeftAligned; + + // Every item of this array contains the sum of all preceding items. + // So it contains the start position in code list for every bit length. + Dec.DecodePos[I] = Dec.DecodePos[I - 1] + LengthCount[I - 1]; + } + + // Prepare the copy of DecodePos. We'll modify this copy below, + // so we cannot use the original DecodePos. + var CopyDecodePos = new uint[Dec.DecodePos.Length]; + //memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); + Array.Copy(Dec.DecodePos, CopyDecodePos, CopyDecodePos.Length); + + // For every bit length in the bit length table and so for every item + // of alphabet. + for (uint I = 0; I < Size; I++) + { + // Get the current bit length. + var _CurBitLength = (byte)(LengthTable[offset + I] & 0xf); + + if (_CurBitLength != 0) { - LengthCount[LengthTable[offset + I] & 0xf]++; - } + // Last position in code list for current bit length. + var LastPos = CopyDecodePos[_CurBitLength]; - // We must not calculate the number of zero length codes. - LengthCount[0] = 0; + // Prepare the decode table, so this position in code list will be + // decoded to current alphabet item number. + Dec.DecodeNum[LastPos] = (ushort)I; - // Set the entire DecodeNum to zero. - //memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); - new Span(Dec.DecodeNum).Clear(); - - // Initialize not really used entry for zero length code. - Dec.DecodePos[0] = 0; - - // Start code for bit length 1 is 0. - Dec.DecodeLen[0] = 0; - - // Right aligned upper limit code for current bit length. - uint UpperLimit = 0; - - for (int I = 1; I < 16; I++) - { - // Adjust the upper limit code. - UpperLimit += LengthCount[I]; - - // Left aligned upper limit code. - uint LeftAligned = UpperLimit << (16 - I); - - // Prepare the upper limit code for next bit length. - UpperLimit *= 2; - - // Store the left aligned upper limit code. - Dec.DecodeLen[I] = (uint)LeftAligned; - - // Every item of this array contains the sum of all preceding items. - // So it contains the start position in code list for every bit length. - Dec.DecodePos[I] = Dec.DecodePos[I - 1] + LengthCount[I - 1]; - } - - // Prepare the copy of DecodePos. We'll modify this copy below, - // so we cannot use the original DecodePos. - uint[] CopyDecodePos = new uint[Dec.DecodePos.Length]; - //memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos)); - Array.Copy(Dec.DecodePos, CopyDecodePos, CopyDecodePos.Length); - - // For every bit length in the bit length table and so for every item - // of alphabet. - for (uint I = 0; I < Size; I++) - { - // Get the current bit length. - byte _CurBitLength = (byte)(LengthTable[offset + I] & 0xf); - - if (_CurBitLength != 0) - { - // Last position in code list for current bit length. - uint LastPos = CopyDecodePos[_CurBitLength]; - - // Prepare the decode table, so this position in code list will be - // decoded to current alphabet item number. - Dec.DecodeNum[LastPos] = (ushort)I; - - // We'll use next position number for this bit length next time. - // So we pass through the entire range of positions available - // for every bit length. - CopyDecodePos[_CurBitLength]++; - } - } - - // Define the number of bits to process in quick mode. We use more bits - // for larger alphabets. More bits means that more codes will be processed - // in quick mode, but also that more time will be spent to preparation - // of tables for quick decode. - switch (Size) - { - case NC: - case NC20: - case NC30: - Dec.QuickBits = MAX_QUICK_DECODE_BITS; - break; - default: - Dec.QuickBits = MAX_QUICK_DECODE_BITS - 3; - break; - } - - // Size of tables for quick mode. - uint QuickDataSize = 1U << (int)Dec.QuickBits; - - // Bit length for current code, start from 1 bit codes. It is important - // to use 1 bit instead of 0 for minimum code length, so we are moving - // forward even when processing a corrupt archive. - //uint CurBitLength=1; - byte CurBitLength = 1; - - // For every right aligned bit string which supports the quick decoding. - for (uint Code = 0; Code < QuickDataSize; Code++) - { - // Left align the current code, so it will be in usual bit field format. - uint BitField = Code << (int)(16 - Dec.QuickBits); - - // Prepare the table for quick decoding of bit lengths. - - // Find the upper limit for current bit field and adjust the bit length - // accordingly if necessary. - while (CurBitLength < Dec.DecodeLen.Length && BitField >= Dec.DecodeLen[CurBitLength]) - { - CurBitLength++; - } - - // Translation of right aligned bit string to bit length. - Dec.QuickLen[Code] = CurBitLength; - - // Prepare the table for quick translation of position in code list - // to position in alphabet. - - // Calculate the distance from the start code for current bit length. - uint Dist = BitField - Dec.DecodeLen[CurBitLength - 1]; - - // Right align the distance. - Dist >>= (16 - CurBitLength); - - // Now we can calculate the position in the code list. It is the sum - // of first position for current bit length and right aligned distance - // between our bit field and start code for current bit length. - uint Pos; - if (CurBitLength < Dec.DecodePos.Length && - (Pos = Dec.DecodePos[CurBitLength] + Dist) < Size) - { - // Define the code to alphabet number translation. - Dec.QuickNum[Code] = Dec.DecodeNum[Pos]; - } - else - { - // Can be here for length table filled with zeroes only (empty). - Dec.QuickNum[Code] = 0; - } + // We'll use next position number for this bit length next time. + // So we pass through the entire range of positions available + // for every bit length. + CopyDecodePos[_CurBitLength]++; } } + // Define the number of bits to process in quick mode. We use more bits + // for larger alphabets. More bits means that more codes will be processed + // in quick mode, but also that more time will be spent to preparation + // of tables for quick decode. + switch (Size) + { + case NC: + case NC20: + case NC30: + Dec.QuickBits = MAX_QUICK_DECODE_BITS; + break; + default: + Dec.QuickBits = MAX_QUICK_DECODE_BITS - 3; + break; + } + + // Size of tables for quick mode. + var QuickDataSize = 1U << (int)Dec.QuickBits; + + // Bit length for current code, start from 1 bit codes. It is important + // to use 1 bit instead of 0 for minimum code length, so we are moving + // forward even when processing a corrupt archive. + //uint CurBitLength=1; + byte CurBitLength = 1; + + // For every right aligned bit string which supports the quick decoding. + for (uint Code = 0; Code < QuickDataSize; Code++) + { + // Left align the current code, so it will be in usual bit field format. + var BitField = Code << (int)(16 - Dec.QuickBits); + + // Prepare the table for quick decoding of bit lengths. + + // Find the upper limit for current bit field and adjust the bit length + // accordingly if necessary. + while (CurBitLength < Dec.DecodeLen.Length && BitField >= Dec.DecodeLen[CurBitLength]) + { + CurBitLength++; + } + + // Translation of right aligned bit string to bit length. + Dec.QuickLen[Code] = CurBitLength; + + // Prepare the table for quick translation of position in code list + // to position in alphabet. + + // Calculate the distance from the start code for current bit length. + var Dist = BitField - Dec.DecodeLen[CurBitLength - 1]; + + // Right align the distance. + Dist >>= (16 - CurBitLength); + + // Now we can calculate the position in the code list. It is the sum + // of first position for current bit length and right aligned distance + // between our bit field and start code for current bit length. + uint Pos; + if ( + CurBitLength < Dec.DecodePos.Length + && (Pos = Dec.DecodePos[CurBitLength] + Dist) < Size + ) + { + // Define the code to alphabet number translation. + Dec.QuickNum[Code] = Dec.DecodeNum[Pos]; + } + else + { + // Can be here for length table filled with zeroes only (empty). + Dec.QuickNum[Code] = 0; + } + } } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs index 228be40b..584d0b51 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpackinline_cpp.cs @@ -1,179 +1,173 @@ -#if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; -using size_t = System.UInt32; +#if !Rar2017_64bit #else using nint = System.Int64; using nuint = System.UInt64; using size_t = System.UInt64; #endif -using int64 = System.Int64; - using static SharpCompress.Compressors.Rar.UnpackV2017.PackDef; -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal partial class Unpack { - internal partial class Unpack + private void InsertOldDist(uint Distance) { - private void InsertOldDist(uint Distance) + OldDist[3] = OldDist[2]; + OldDist[2] = OldDist[1]; + OldDist[1] = OldDist[0]; + OldDist[0] = Distance; + } + + //#ifdef _MSC_VER + //#define FAST_MEMCPY + //#endif + + private void CopyString(uint Length, uint Distance) + { + var SrcPtr = UnpPtr - Distance; + + if (SrcPtr < MaxWinSize - MAX_LZ_MATCH && UnpPtr < MaxWinSize - MAX_LZ_MATCH) { - OldDist[3] = OldDist[2]; - OldDist[2] = OldDist[1]; - OldDist[1] = OldDist[0]; - OldDist[0] = Distance; + // If we are not close to end of window, we do not need to waste time + // to "& MaxWinMask" pointer protection. + + // TODO: sharpcompress: non-optimized loop, we may be able to unroll and speed up + var Window = this.Window; + while (Length-- > 0) + { + Window[UnpPtr++] = Window[SrcPtr++]; + } + + // byte *Src=Window+SrcPtr; + // byte *Dest=Window+UnpPtr; + // UnpPtr+=Length; + // + //#if FAST_MEMCPY + // if (Distance=8) + // { + // Dest[0]=Src[0]; + // Dest[1]=Src[1]; + // Dest[2]=Src[2]; + // Dest[3]=Src[3]; + // Dest[4]=Src[4]; + // Dest[5]=Src[5]; + // Dest[6]=Src[6]; + // Dest[7]=Src[7]; + // + // Src+=8; + // Dest+=8; + // Length-=8; + // } + //#if FAST_MEMCPY + // else + // while (Length>=8) + // { + // // In theory we still could overlap here. + // // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8). + // // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH + // // always, so overlap here is impossible. + // + // // This memcpy expanded inline by MSVC. We could also use uint64 + // // assignment, which seems to provide about the same speed. + // memcpy(Dest,Src,8); + // + // Src+=8; + // Dest+=8; + // Length-=8; + // } + //#endif + // + // // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s. + // if (Length>0) { Dest[0]=Src[0]; + // if (Length>1) { Dest[1]=Src[1]; + // if (Length>2) { Dest[2]=Src[2]; + // if (Length>3) { Dest[3]=Src[3]; + // if (Length>4) { Dest[4]=Src[4]; + // if (Length>5) { Dest[5]=Src[5]; + // if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s. + } + else + { + while (Length-- > 0) // Slow copying with all possible precautions. + { + Window[UnpPtr] = Window[SrcPtr++ & MaxWinMask]; + // We need to have masked UnpPtr after quit from loop, so it must not + // be replaced with 'Window[UnpPtr++ & MaxWinMask]' + UnpPtr = (UnpPtr + 1) & MaxWinMask; + } + } + } + + private uint DecodeNumber(BitInput Inp, DecodeTable Dec) + { + // Left aligned 15 bit length raw bit field. + var BitField = Inp.getbits() & 0xfffe; + + if (BitField < Dec.DecodeLen[Dec.QuickBits]) + { + var Code = BitField >> (int)(16 - Dec.QuickBits); + Inp.addbits(Dec.QuickLen[Code]); + return Dec.QuickNum[Code]; } - //#ifdef _MSC_VER - //#define FAST_MEMCPY - //#endif - - private void CopyString(uint Length, uint Distance) + // Detect the real bit length for current code. + uint Bits = 15; + for (var I = Dec.QuickBits + 1; I < 15; I++) { - size_t SrcPtr = UnpPtr - Distance; - - if (SrcPtr < MaxWinSize - MAX_LZ_MATCH && UnpPtr < MaxWinSize - MAX_LZ_MATCH) + if (BitField < Dec.DecodeLen[I]) { - // If we are not close to end of window, we do not need to waste time - // to "& MaxWinMask" pointer protection. - - // TODO: sharpcompress: non-optimized loop, we may be able to unroll and speed up - var Window = this.Window; - while (Length-- > 0) - { - Window[UnpPtr++] = Window[SrcPtr++]; - } - - // byte *Src=Window+SrcPtr; - // byte *Dest=Window+UnpPtr; - // UnpPtr+=Length; - // - //#if FAST_MEMCPY - // if (Distance=8) - // { - // Dest[0]=Src[0]; - // Dest[1]=Src[1]; - // Dest[2]=Src[2]; - // Dest[3]=Src[3]; - // Dest[4]=Src[4]; - // Dest[5]=Src[5]; - // Dest[6]=Src[6]; - // Dest[7]=Src[7]; - // - // Src+=8; - // Dest+=8; - // Length-=8; - // } - //#if FAST_MEMCPY - // else - // while (Length>=8) - // { - // // In theory we still could overlap here. - // // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8). - // // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH - // // always, so overlap here is impossible. - // - // // This memcpy expanded inline by MSVC. We could also use uint64 - // // assignment, which seems to provide about the same speed. - // memcpy(Dest,Src,8); - // - // Src+=8; - // Dest+=8; - // Length-=8; - // } - //#endif - // - // // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s. - // if (Length>0) { Dest[0]=Src[0]; - // if (Length>1) { Dest[1]=Src[1]; - // if (Length>2) { Dest[2]=Src[2]; - // if (Length>3) { Dest[3]=Src[3]; - // if (Length>4) { Dest[4]=Src[4]; - // if (Length>5) { Dest[5]=Src[5]; - // if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s. - } - else - { - while (Length-- > 0) // Slow copying with all possible precautions. - { - Window[UnpPtr] = Window[SrcPtr++ & MaxWinMask]; - // We need to have masked UnpPtr after quit from loop, so it must not - // be replaced with 'Window[UnpPtr++ & MaxWinMask]' - UnpPtr = (UnpPtr + 1) & MaxWinMask; - } + Bits = I; + break; } } - private uint DecodeNumber(BitInput Inp, DecodeTable Dec) + Inp.addbits(Bits); + + // Calculate the distance from the start code for current bit length. + var Dist = BitField - Dec.DecodeLen[Bits - 1]; + + // Start codes are left aligned, but we need the normal right aligned + // number. So we shift the distance to the right. + Dist >>= (int)(16 - Bits); + + // Now we can calculate the position in the code list. It is the sum + // of first position for current bit length and right aligned distance + // between our bit field and start code for current bit length. + var Pos = Dec.DecodePos[Bits] + Dist; + + // Out of bounds safety check required for damaged archives. + if (Pos >= Dec.MaxNum) { - // Left aligned 15 bit length raw bit field. - uint BitField = Inp.getbits() & 0xfffe; - - if (BitField < Dec.DecodeLen[Dec.QuickBits]) - { - uint Code = BitField >> (int)(16 - Dec.QuickBits); - Inp.addbits(Dec.QuickLen[Code]); - return Dec.QuickNum[Code]; - } - - // Detect the real bit length for current code. - uint Bits = 15; - for (uint I = Dec.QuickBits + 1; I < 15; I++) - { - if (BitField < Dec.DecodeLen[I]) - { - Bits = I; - break; - } - } - - Inp.addbits(Bits); - - // Calculate the distance from the start code for current bit length. - uint Dist = BitField - Dec.DecodeLen[Bits - 1]; - - // Start codes are left aligned, but we need the normal right aligned - // number. So we shift the distance to the right. - Dist >>= (int)(16 - Bits); - - // Now we can calculate the position in the code list. It is the sum - // of first position for current bit length and right aligned distance - // between our bit field and start code for current bit length. - uint Pos = Dec.DecodePos[Bits] + Dist; - - // Out of bounds safety check required for damaged archives. - if (Pos >= Dec.MaxNum) - { - Pos = 0; - } - - // Convert the position in the code list to position in alphabet - // and return it. - return Dec.DecodeNum[Pos]; + Pos = 0; } - private uint SlotToLength(BitInput Inp, uint Slot) - { - uint LBits, Length = 2; - if (Slot < 8) - { - LBits = 0; - Length += Slot; - } - else - { - LBits = Slot / 4 - 1; - Length += (4 | (Slot & 3)) << (int)LBits; - } + // Convert the position in the code list to position in alphabet + // and return it. + return Dec.DecodeNum[Pos]; + } - if (LBits > 0) - { - Length += Inp.getbits() >> (int)(16 - LBits); - Inp.addbits(LBits); - } - return Length; + private uint SlotToLength(BitInput Inp, uint Slot) + { + uint LBits, + Length = 2; + if (Slot < 8) + { + LBits = 0; + Length += Slot; + } + else + { + LBits = (Slot / 4) - 1; + Length += (4 | (Slot & 3)) << (int)LBits; } + if (LBits > 0) + { + Length += Inp.getbits() >> (int)(16 - LBits); + Inp.addbits(LBits); + } + return Length; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index e28720d6..18cd9697 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -1,6 +1,4 @@ -#if !Rar2017_64bit -using nint = System.Int32; -using nuint = System.UInt32; +#if !Rar2017_64bit using size_t = System.UInt32; #else using nint = System.Int64; @@ -18,429 +16,383 @@ using System; #pragma warning disable 169 #pragma warning disable 414 -namespace SharpCompress.Compressors.Rar.UnpackV2017 +namespace SharpCompress.Compressors.Rar.UnpackV2017; + +internal static class UnpackGlobal { - internal static class UnpackGlobal + + + // Maximum allowed number of compressed bits processed in quick mode. + public const int MAX_QUICK_DECODE_BITS = 10; + + // Maximum number of filters per entire data block. Must be at least + // twice more than MAX_PACK_FILTERS to store filters from two data blocks. + public const int MAX_UNPACK_FILTERS = 8192; + + // Maximum number of filters per entire data block for RAR3 unpack. + // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters + // from two data blocks. + public const int MAX3_UNPACK_FILTERS = 8192; + + // Maximum size of single filter block. We restrict it to limit memory + // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. + public const int MAX_FILTER_BLOCK_SIZE = 0x400000; + + // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, + // so we keep number of buffered filter in unpacker reasonable. + public const int UNPACK_MAX_WRITE = 0x400000; +} + +// Decode compressed bit fields to alphabet numbers. +internal sealed class DecodeTable +{ + // Real size of DecodeNum table. + public uint MaxNum; + + // Left aligned start and upper limit codes defining code space + // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of + // range for bit length and DecodeLen[BitLength] defines next code + // after the end of range or in other words the upper limit code + // for specified bit length. + public readonly uint[] DecodeLen = new uint[16]; + + // Every item of this array contains the sum of all preceding items. + // So it contains the start position in code list for every bit length. + public readonly uint[] DecodePos = new uint[16]; + + // Number of compressed bits processed in quick mode. + // Must not exceed MAX_QUICK_DECODE_BITS. + public uint QuickBits; + + // Translates compressed bits (up to QuickBits length) + // to bit length in quick mode. + public readonly byte[] QuickLen = new byte[1 << MAX_QUICK_DECODE_BITS]; + + // Translates compressed bits (up to QuickBits length) + // to position in alphabet in quick mode. + // 'ushort' saves some memory and even provides a little speed gain + // comparting to 'uint' here. + public readonly ushort[] QuickNum = new ushort[1 << MAX_QUICK_DECODE_BITS]; + + // Translate the position in code list to position in alphabet. + // We do not allocate it dynamically to avoid performance overhead + // introduced by pointer, so we use the largest possible table size + // as array dimension. Real size of this array is defined in MaxNum. + // We use this array if compressed bit field is too lengthy + // for QuickLen based translation. + // 'ushort' saves some memory and even provides a little speed gain + // comparting to 'uint' here. + public readonly ushort[] DecodeNum = new ushort[LARGEST_TABLE_SIZE]; +}; + +internal struct UnpackBlockHeader +{ + public int BlockSize; + public int BlockBitSize; + public int BlockStart; + public int HeaderSize; + public bool LastBlockInFile; + public bool TablePresent; +}; + +internal struct UnpackBlockTables +{ + public DecodeTable LD; // Decode literals. + public DecodeTable DD; // Decode distances. + public DecodeTable LDD; // Decode lower bits of distances. + public DecodeTable RD; // Decode repeating distances. + public DecodeTable BD; // Decode bit lengths in Huffman table. + + public void Init() { - - - // Maximum allowed number of compressed bits processed in quick mode. - public const int MAX_QUICK_DECODE_BITS = 10; - - // Maximum number of filters per entire data block. Must be at least - // twice more than MAX_PACK_FILTERS to store filters from two data blocks. - public const int MAX_UNPACK_FILTERS = 8192; - - // Maximum number of filters per entire data block for RAR3 unpack. - // Must be at least twice more than v3_MAX_PACK_FILTERS to store filters - // from two data blocks. - public const int MAX3_UNPACK_FILTERS = 8192; - - // Limit maximum number of channels in RAR3 delta filter to some reasonable - // value to prevent too slow processing of corrupt archives with invalid - // channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS. - // No need to provide it for RAR5, which uses only 5 bits to store channels. - private const int MAX3_UNPACK_CHANNELS = 1024; - - // Maximum size of single filter block. We restrict it to limit memory - // allocation. Must be equal or larger than MAX_ANALYZE_SIZE. - public const int MAX_FILTER_BLOCK_SIZE = 0x400000; - - // Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE, - // so we keep number of buffered filter in unpacker reasonable. - public const int UNPACK_MAX_WRITE = 0x400000; + LD = new DecodeTable(); + DD = new DecodeTable(); + LDD = new DecodeTable(); + RD = new DecodeTable(); + BD = new DecodeTable(); } - - // Decode compressed bit fields to alphabet numbers. - internal sealed class DecodeTable - { - // Real size of DecodeNum table. - public uint MaxNum; - - // Left aligned start and upper limit codes defining code space - // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of - // range for bit length and DecodeLen[BitLength] defines next code - // after the end of range or in other words the upper limit code - // for specified bit length. - public readonly uint[] DecodeLen = new uint[16]; - - // Every item of this array contains the sum of all preceding items. - // So it contains the start position in code list for every bit length. - public readonly uint[] DecodePos = new uint[16]; - - // Number of compressed bits processed in quick mode. - // Must not exceed MAX_QUICK_DECODE_BITS. - public uint QuickBits; - - // Translates compressed bits (up to QuickBits length) - // to bit length in quick mode. - public readonly byte[] QuickLen = new byte[1 << MAX_QUICK_DECODE_BITS]; - - // Translates compressed bits (up to QuickBits length) - // to position in alphabet in quick mode. - // 'ushort' saves some memory and even provides a little speed gain - // comparting to 'uint' here. - public readonly ushort[] QuickNum = new ushort[1 << MAX_QUICK_DECODE_BITS]; - - // Translate the position in code list to position in alphabet. - // We do not allocate it dynamically to avoid performance overhead - // introduced by pointer, so we use the largest possible table size - // as array dimension. Real size of this array is defined in MaxNum. - // We use this array if compressed bit field is too lengthy - // for QuickLen based translation. - // 'ushort' saves some memory and even provides a little speed gain - // comparting to 'uint' here. - public readonly ushort[] DecodeNum = new ushort[LARGEST_TABLE_SIZE]; - }; - - internal struct UnpackBlockHeader - { - public int BlockSize; - public int BlockBitSize; - public int BlockStart; - public int HeaderSize; - public bool LastBlockInFile; - public bool TablePresent; - }; - - internal struct UnpackBlockTables - { - public DecodeTable LD; // Decode literals. - public DecodeTable DD; // Decode distances. - public DecodeTable LDD; // Decode lower bits of distances. - public DecodeTable RD; // Decode repeating distances. - public DecodeTable BD; // Decode bit lengths in Huffman table. - - public void Init() - { - LD = new DecodeTable(); - DD = new DecodeTable(); - LDD = new DecodeTable(); - RD = new DecodeTable(); - BD = new DecodeTable(); - } - }; +}; #if RarV2017_RAR_SMP enum UNP_DEC_TYPE { - UNPDT_LITERAL,UNPDT_MATCH,UNPDT_FULLREP,UNPDT_REP,UNPDT_FILTER +UNPDT_LITERAL,UNPDT_MATCH,UNPDT_FULLREP,UNPDT_REP,UNPDT_FILTER }; struct UnpackDecodedItem { - UNP_DEC_TYPE Type; - ushort Length; - union - { - uint Distance; - byte Literal[4]; - }; +UNP_DEC_TYPE Type; +ushort Length; +union +{ +uint Distance; +byte Literal[4]; +}; }; struct UnpackThreadData { - Unpack *UnpackPtr; - BitInput Inp; - bool HeaderRead; - UnpackBlockHeader BlockHeader; - bool TableRead; - UnpackBlockTables BlockTables; - int DataSize; // Data left in buffer. Can be less than block size. - bool DamagedData; - bool LargeBlock; - bool NoDataLeft; // 'true' if file is read completely. - bool Incomplete; // Not entire block was processed, need to read more data. +Unpack *UnpackPtr; +BitInput Inp; +bool HeaderRead; +UnpackBlockHeader BlockHeader; +bool TableRead; +UnpackBlockTables BlockTables; +int DataSize; // Data left in buffer. Can be less than block size. +bool DamagedData; +bool LargeBlock; +bool NoDataLeft; // 'true' if file is read completely. +bool Incomplete; // Not entire block was processed, need to read more data. - UnpackDecodedItem *Decoded; - uint DecodedSize; - uint DecodedAllocated; - uint ThreadNumber; // For debugging. +UnpackDecodedItem *Decoded; +uint DecodedSize; +uint DecodedAllocated; +uint ThreadNumber; // For debugging. - UnpackThreadData() - :Inp(false) - { - Decoded=NULL; - } - ~UnpackThreadData() - { - if (Decoded!=NULL) - free(Decoded); - } +UnpackThreadData() +:Inp(false) +{ +Decoded=NULL; +} +~UnpackThreadData() +{ +if (Decoded!=NULL) + free(Decoded); +} }; #endif - //struct UnpackFilter - internal class UnpackFilter - { - public byte Type; - public uint BlockStart; - public uint BlockLength; - public byte Channels; - // uint Width; - // byte PosR; - public bool NextWindow; - }; +//struct UnpackFilter +internal class UnpackFilter +{ + public byte Type; + public uint BlockStart; + public uint BlockLength; + public byte Channels; + // uint Width; + // byte PosR; + public bool NextWindow; +}; - //struct UnpackFilter30 - internal class UnpackFilter30 - { - public uint BlockStart; - public uint BlockLength; - public bool NextWindow; +//struct UnpackFilter30 +internal class UnpackFilter30 +{ + public uint BlockStart; + public uint BlockLength; + public bool NextWindow; - // Position of parent filter in Filters array used as prototype for filter - // in PrgStack array. Not defined for filters in Filters array. - public uint ParentFilter; + // Position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + public uint ParentFilter; - /*#if !RarV2017_RAR5ONLY - public VM_PreparedProgram Prg; - #endif*/ - }; + /*#if !RarV2017_RAR5ONLY + public VM_PreparedProgram Prg; + #endif*/ +}; - internal class AudioVariables // For RAR 2.0 archives only. - { - public int K1, K2, K3, K4, K5; - public int D1, D2, D3, D4; - public int LastDelta; - public readonly uint[] Dif = new uint[11]; - public uint ByteCount; - public int LastChar; - }; +internal class AudioVariables // For RAR 2.0 archives only. +{ + public int K1, K2, K3, K4, K5; + public int D1, D2, D3, D4; + public int LastDelta; + public readonly uint[] Dif = new uint[11]; + public uint ByteCount; + public int LastChar; +}; - // We can use the fragmented dictionary in case heap does not have the single - // large enough memory block. It is slower than normal dictionary. - internal partial class FragmentedWindow - { - private const int MAX_MEM_BLOCKS = 32; +// We can use the fragmented dictionary in case heap does not have the single +// large enough memory block. It is slower than normal dictionary. +internal partial class FragmentedWindow +{ + private const int MAX_MEM_BLOCKS = 32; - //void Reset(); - private readonly byte[][] Mem = new byte[MAX_MEM_BLOCKS][]; - private readonly size_t[] MemSize = new size_t[MAX_MEM_BLOCKS]; + //void Reset(); + private readonly byte[][] Mem = new byte[MAX_MEM_BLOCKS][]; + private readonly size_t[] MemSize = new size_t[MAX_MEM_BLOCKS]; - //FragmentedWindow(); - //~FragmentedWindow(); - //void Init(size_t WinSize); - //byte& operator [](size_t Item); - //void CopyString(uint Length,uint Distance,size_t &UnpPtr,size_t MaxWinMask); - //void CopyData(byte *Dest,size_t WinPos,size_t Size); - //size_t GetBlockSize(size_t StartPos,size_t RequiredSize); - }; + //FragmentedWindow(); + //~FragmentedWindow(); + //void Init(size_t WinSize); + //byte& operator [](size_t Item); + //void CopyString(uint Length,uint Distance,size_t &UnpPtr,size_t MaxWinMask); + //void CopyData(byte *Dest,size_t WinPos,size_t Size); + //size_t GetBlockSize(size_t StartPos,size_t RequiredSize); +}; - internal partial class Unpack - { +internal partial class Unpack +{ - //void Unpack5(bool Solid); - //void Unpack5MT(bool Solid); - //bool UnpReadBuf(); - //void UnpWriteBuf(); - //byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt); - //void UnpWriteArea(size_t StartPtr,size_t EndPtr); - //void UnpWriteData(byte *Data,size_t Size); - //_forceinline uint SlotToLength(BitInput &Inp,uint Slot); - //void UnpInitData50(bool Solid); - //bool ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header); - //bool ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables); - //void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size); - //_forceinline uint DecodeNumber(BitInput &Inp,DecodeTable *Dec); - //void CopyString(); - //inline void InsertOldDist(uint Distance); - //void UnpInitData(bool Solid); - //_forceinline void CopyString(uint Length,uint Distance); - //uint ReadFilterData(BitInput &Inp); - //bool ReadFilter(BitInput &Inp,UnpackFilter &Filter); - //bool AddFilter(UnpackFilter &Filter); - //bool AddFilter(); - //void InitFilters(); + //void Unpack5(bool Solid); + //void Unpack5MT(bool Solid); + //bool UnpReadBuf(); + //void UnpWriteBuf(); + //byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt); + //void UnpWriteArea(size_t StartPtr,size_t EndPtr); + //void UnpWriteData(byte *Data,size_t Size); + //_forceinline uint SlotToLength(BitInput &Inp,uint Slot); + //void UnpInitData50(bool Solid); + //bool ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header); + //bool ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables); + //void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size); + //_forceinline uint DecodeNumber(BitInput &Inp,DecodeTable *Dec); + //void CopyString(); + //inline void InsertOldDist(uint Distance); + //void UnpInitData(bool Solid); + //_forceinline void CopyString(uint Length,uint Distance); + //uint ReadFilterData(BitInput &Inp); + //bool ReadFilter(BitInput &Inp,UnpackFilter &Filter); + //bool AddFilter(UnpackFilter &Filter); + //bool AddFilter(); + //void InitFilters(); - //ComprDataIO *UnpIO; - //BitInput Inp; - private BitInput Inp { get { return this; } } // hopefully this gets inlined + //ComprDataIO *UnpIO; + //BitInput Inp; + private BitInput Inp => this; // hopefully this gets inlined #if RarV2017_RAR_SMP - void InitMT(); - bool UnpackLargeBlock(UnpackThreadData &D); - bool ProcessDecoded(UnpackThreadData &D); +void InitMT(); +bool UnpackLargeBlock(UnpackThreadData &D); +bool ProcessDecoded(UnpackThreadData &D); - ThreadPool *UnpThreadPool; - UnpackThreadData *UnpThreadData; - uint MaxUserThreads; - byte *ReadBufMT; +ThreadPool *UnpThreadPool; +UnpackThreadData *UnpThreadData; +uint MaxUserThreads; +byte *ReadBufMT; #endif - private byte[] FilterSrcMemory = Array.Empty(); - private byte[] FilterDstMemory = Array.Empty(); + private byte[] FilterSrcMemory = Array.Empty(); + private byte[] FilterDstMemory = Array.Empty(); - // Filters code, one entry per filter. - private readonly List Filters = new List(); + // Filters code, one entry per filter. + private readonly List Filters = new List(); - private readonly uint[] OldDist = new uint[4]; - private uint OldDistPtr; - private uint LastLength; + private readonly uint[] OldDist = new uint[4]; + private uint OldDistPtr; + private uint LastLength; - // LastDist is necessary only for RAR2 and older with circular OldDist - // array. In RAR3 last distance is always stored in OldDist[0]. - private uint LastDist; + // LastDist is necessary only for RAR2 and older with circular OldDist + // array. In RAR3 last distance is always stored in OldDist[0]. + private uint LastDist; - private size_t UnpPtr, WrPtr; + private size_t UnpPtr, WrPtr; - // Top border of read packed data. - private int ReadTop; + // Top border of read packed data. + private int ReadTop; - // Border to call UnpReadBuf. We use it instead of (ReadTop-C) - // for optimization reasons. Ensures that we have C bytes in buffer - // unless we are at the end of file. - private int ReadBorder; + // Border to call UnpReadBuf. We use it instead of (ReadTop-C) + // for optimization reasons. Ensures that we have C bytes in buffer + // unless we are at the end of file. + private int ReadBorder; - private UnpackBlockHeader BlockHeader; - private UnpackBlockTables BlockTables; + private UnpackBlockHeader BlockHeader; + private UnpackBlockTables BlockTables; - private size_t WriteBorder; + private size_t WriteBorder; - private byte[] Window; + private byte[] Window; - private readonly FragmentedWindow FragWindow = new FragmentedWindow(); - private bool Fragmented; + private readonly FragmentedWindow FragWindow = new FragmentedWindow(); + private bool Fragmented; - private int64 DestUnpSize; + private int64 DestUnpSize; - //bool Suspended; - private bool UnpAllBuf; - private bool UnpSomeRead; - private int64 WrittenFileSize; - private bool FileExtracted; + //bool Suspended; + private bool UnpAllBuf; + private bool UnpSomeRead; + private int64 WrittenFileSize; + private bool FileExtracted; - /***************************** Unpack v 1.5 *********************************/ - //void Unpack15(bool Solid); - //void ShortLZ(); - //void LongLZ(); - //void HuffDecode(); - //void GetFlagsBuf(); - //void UnpInitData15(int Solid); - //void InitHuff(); - //void CorrHuff(ushort *CharSet,byte *NumToPlace); - //void CopyString15(uint Distance,uint Length); - //uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab); + /***************************** Unpack v 1.5 *********************************/ + //void Unpack15(bool Solid); + //void ShortLZ(); + //void LongLZ(); + //void HuffDecode(); + //void GetFlagsBuf(); + //void UnpInitData15(int Solid); + //void InitHuff(); + //void CorrHuff(ushort *CharSet,byte *NumToPlace); + //void CopyString15(uint Distance,uint Length); + //uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab); - private readonly ushort[] ChSet = new ushort[256], ChSetA = new ushort[256], ChSetB = new ushort[256], ChSetC = new ushort[256]; - private readonly byte[] NToPl = new byte[256], NToPlB = new byte[256], NToPlC = new byte[256]; - private uint FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; - private int Buf60, NumHuf, StMode, LCount, FlagsCnt; + private readonly ushort[] ChSet = new ushort[256], ChSetA = new ushort[256], ChSetB = new ushort[256], ChSetC = new ushort[256]; + private readonly byte[] NToPl = new byte[256], NToPlB = new byte[256], NToPlC = new byte[256]; + private uint FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; + private int Buf60, NumHuf, StMode, LCount, FlagsCnt; - private uint Nhfb, Nlzb, MaxDist3; - /***************************** Unpack v 1.5 *********************************/ + private uint Nhfb, Nlzb, MaxDist3; + /***************************** Unpack v 1.5 *********************************/ - /***************************** Unpack v 2.0 *********************************/ - //void Unpack20(bool Solid); + /***************************** Unpack v 2.0 *********************************/ + //void Unpack20(bool Solid); - private DecodeTable[] MD = new DecodeTable[4]; // Decode multimedia data, up to 4 channels. + private DecodeTable[] MD = new DecodeTable[4]; // Decode multimedia data, up to 4 channels. - private readonly byte[] UnpOldTable20 = new byte[MC20 * 4]; - private bool UnpAudioBlock; - private uint UnpChannels, UnpCurChannel; + private readonly byte[] UnpOldTable20 = new byte[MC20 * 4]; + private bool UnpAudioBlock; + private uint UnpChannels, UnpCurChannel; - private int UnpChannelDelta; - //void CopyString20(uint Length,uint Distance); - //bool ReadTables20(); - //void UnpWriteBuf20(); - //void UnpInitData20(int Solid); - //void ReadLastTables(); - //byte DecodeAudio(int Delta); - private AudioVariables[] AudV = new AudioVariables[4]; - /***************************** Unpack v 2.0 *********************************/ + private int UnpChannelDelta; + //void CopyString20(uint Length,uint Distance); + //bool ReadTables20(); + //void UnpWriteBuf20(); + //void UnpInitData20(int Solid); + //void ReadLastTables(); + //byte DecodeAudio(int Delta); + private AudioVariables[] AudV = new AudioVariables[4]; + /***************************** Unpack v 2.0 *********************************/ - /***************************** Unpack v 3.0 *********************************/ - public const int BLOCK_LZ = 0; - public const int BLOCK_PPM = 1; + /***************************** Unpack v 3.0 *********************************/ + public const int BLOCK_LZ = 0; + public const int BLOCK_PPM = 1; - //void UnpInitData30(bool Solid); - //void Unpack29(bool Solid); - //void InitFilters30(bool Solid); - //bool ReadEndOfBlock(); - //bool ReadVMCode(); - //bool ReadVMCodePPM(); - //bool AddVMCode(uint FirstByte,byte *Code,int CodeSize); - //int SafePPMDecodeChar(); - //bool ReadTables30(); - //bool UnpReadBuf30(); - //void UnpWriteBuf30(); - //void ExecuteCode(VM_PreparedProgram *Prg); + /*#if !RarV2017_RAR5ONLY + ModelPPM PPM; + #endif*/ + private int PPMEscChar; - private int PrevLowDist, LowDistRepCount; + private readonly byte[] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; - /*#if !RarV2017_RAR5ONLY - ModelPPM PPM; - #endif*/ - private int PPMEscChar; + // If we already read decoding tables for Unpack v2,v3,v5. + // We should not use a single variable for all algorithm versions, + // because we can have a corrupt archive with one algorithm file + // followed by another algorithm file with "solid" flag and we do not + // want to reuse tables from one algorithm in another. + private bool TablesRead2, TablesRead5; - private readonly byte[] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; - private int UnpBlockType; + // Virtual machine to execute filters code. + /*#if !RarV2017_RAR5ONLY + RarVM VM; + #endif*/ - // If we already read decoding tables for Unpack v2,v3,v5. - // We should not use a single variable for all algorithm versions, - // because we can have a corrupt archive with one algorithm file - // followed by another algorithm file with "solid" flag and we do not - // want to reuse tables from one algorithm in another. - private bool TablesRead2, TablesRead3, TablesRead5; + // Buffer to read VM filters code. We moved it here from AddVMCode + // function to reduce time spent in BitInput constructor. + private readonly BitInput VMCodeInp = new BitInput(true); - // Virtual machine to execute filters code. - /*#if !RarV2017_RAR5ONLY - RarVM VM; - #endif*/ + // Filters code, one entry per filter. + private readonly List Filters30 = new List(); - // Buffer to read VM filters code. We moved it here from AddVMCode - // function to reduce time spent in BitInput constructor. - private readonly BitInput VMCodeInp = new BitInput(true); + // Filters stack, several entrances of same filter are possible. + private readonly List PrgStack = new List(); - // Filters code, one entry per filter. - private readonly List Filters30 = new List(); - - // Filters stack, several entrances of same filter are possible. - private readonly List PrgStack = new List(); - - // Lengths of preceding data blocks, one length of one last block - // for every filter. Used to reduce the size required to write - // the data block length if lengths are repeating. - private readonly List OldFilterLengths = new List(); - - private int LastFilter; - /***************************** Unpack v 3.0 *********************************/ - - //Unpack(ComprDataIO *DataIO); - //~Unpack(); - //void Init(size_t WinSize,bool Solid); - //void DoUnpack(uint Method,bool Solid); - private bool IsFileExtracted() { return (FileExtracted); } - private void SetDestSize(int64 DestSize) { DestUnpSize = DestSize; FileExtracted = false; } - private void SetSuspended(bool Suspended) { this.Suspended = Suspended; } + // Lengths of preceding data blocks, one length of one last block + // for every filter. Used to reduce the size required to write + // the data block length if lengths are repeating. + private readonly List OldFilterLengths = new List(); #if RarV2017_RAR_SMP - // More than 8 threads are unlikely to provide a noticeable gain - // for unpacking, but would use the additional memory. - void SetThreads(uint Threads) {MaxUserThreads=Min(Threads,8);} +// More than 8 threads are unlikely to provide a noticeable gain +// for unpacking, but would use the additional memory. +void SetThreads(uint Threads) {MaxUserThreads=Min(Threads,8);} - void UnpackDecode(UnpackThreadData &D); +void UnpackDecode(UnpackThreadData &D); #endif - private size_t MaxWinSize; - private size_t MaxWinMask; - - private uint GetChar() - { - if (Inp.InAddr > MAX_SIZE - 30) - { - UnpReadBuf(); - } - - return (Inp.InBuf[Inp.InAddr++]); - } - - - } + private size_t MaxWinSize; + private size_t MaxWinMask; } diff --git a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs index c0656086..fe349fe7 100644 --- a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs +++ b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs @@ -1,89 +1,90 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal class BitInput { - internal class BitInput + /// the max size of the input + internal const int MAX_SIZE = 0x8000; + + public int inAddr; + public int inBit; + + // TODO: rename var + public int InAddr { - /// the max size of the input - internal const int MAX_SIZE = 0x8000; - - public int inAddr; - public int inBit; - - // TODO: rename var - public int InAddr { get { return inAddr; } set { inAddr = value; } } - public int InBit { get { return inBit; } set { inBit = value; } } - public bool ExternalBuffer; - - - /// - internal BitInput() - { - InBuf = new byte[MAX_SIZE]; - } - - internal byte[] InBuf { get; } - - internal void InitBitInput() - { - inAddr = 0; - inBit = 0; - } - - internal void faddbits(uint bits) - { - // TODO uint - AddBits((int)bits); - } - - /// - /// also named faddbits - /// - /// - internal void AddBits(int bits) - { - bits += inBit; - inAddr += (bits >> 3); - inBit = bits & 7; - } - - internal uint fgetbits() - { - // TODO uint - return (uint)GetBits(); - } - - internal uint getbits() - { - // TODO uint - return (uint)GetBits(); - } - - /// - /// (also named fgetbits) - /// - /// - /// the bits (unsigned short) - /// - internal int GetBits() - { - // int BitField=0; - // BitField|=(int)(inBuf[inAddr] << 16)&0xFF0000; - // BitField|=(int)(inBuf[inAddr+1] << 8)&0xff00; - // BitField|=(int)(inBuf[inAddr+2])&0xFF; - // BitField >>>= (8-inBit); - // return (BitField & 0xffff); - return ((Utility.URShift((((InBuf[inAddr] & 0xff) << 16) - + ((InBuf[inAddr + 1] & 0xff) << 8) - + ((InBuf[inAddr + 2] & 0xff))), (8 - inBit))) & 0xffff); - } - - /// Indicates an Overfow - /// how many bytes to inc - /// - /// true if an Oververflow would occur - /// - internal bool Overflow(int IncPtr) - { - return (inAddr + IncPtr >= MAX_SIZE); - } + get => inAddr; + set => inAddr = value; } -} \ No newline at end of file + public int InBit + { + get => inBit; + set => inBit = value; + } + public bool ExternalBuffer; + + /// + internal BitInput() => InBuf = new byte[MAX_SIZE]; + + internal byte[] InBuf { get; } + + internal void InitBitInput() + { + inAddr = 0; + inBit = 0; + } + + internal void faddbits(uint bits) => + // TODO uint + AddBits((int)bits); + + /// + /// also named faddbits + /// + /// + internal void AddBits(int bits) + { + bits += inBit; + inAddr += (bits >> 3); + inBit = bits & 7; + } + + internal uint fgetbits() => + // TODO uint + (uint)GetBits(); + + internal uint getbits() => + // TODO uint + (uint)GetBits(); + + /// + /// (also named fgetbits) + /// + /// + /// the bits (unsigned short) + /// + internal int GetBits() => + // int BitField=0; + // BitField|=(int)(inBuf[inAddr] << 16)&0xFF0000; + // BitField|=(int)(inBuf[inAddr+1] << 8)&0xff00; + // BitField|=(int)(inBuf[inAddr+2])&0xFF; + // BitField >>>= (8-inBit); + // return (BitField & 0xffff); + ( + ( + Utility.URShift( + ( + ((InBuf[inAddr] & 0xff) << 16) + + ((InBuf[inAddr + 1] & 0xff) << 8) + + ((InBuf[inAddr + 2] & 0xff)) + ), + (8 - inBit) + ) + ) & 0xffff + ); + + /// Indicates an Overfow + /// how many bytes to inc + /// + /// true if an Oververflow would occur + /// + internal bool Overflow(int IncPtr) => (inAddr + IncPtr >= MAX_SIZE); +} diff --git a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs index d7180efb..48b25c5c 100644 --- a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs +++ b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs @@ -4,1448 +4,1570 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal sealed class RarVM : BitInput { - internal sealed class RarVM : BitInput + //private void InitBlock() + //{ + // Mem.set_Renamed(offset + 0, Byte.valueOf((sbyte) (value_Renamed & 0xff))); + // Mem.set_Renamed(offset + 1, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 8)) & 0xff))); + // Mem.set_Renamed(offset + 2, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 16)) & 0xff))); + // Mem.set_Renamed(offset + 3, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 24)) & 0xff))); + + //} + internal byte[] Mem { get; private set; } + + public const int VM_MEMSIZE = 0x40000; + + //UPGRADE_NOTE: Final was removed from the declaration of 'VM_MEMMASK '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + public static readonly int VM_MEMMASK = (VM_MEMSIZE - 1); + + public const int VM_GLOBALMEMADDR = 0x3C000; + + public const int VM_GLOBALMEMSIZE = 0x2000; + + public const int VM_FIXEDGLOBALSIZE = 64; + + private const int regCount = 8; + private readonly int[] R = new int[regCount]; + + private VMFlags flags; + + private int maxOpCount = 25000000; + + private int codeSize; + + private int IP; + + internal RarVM() => + //InitBlock(); + Mem = null; + + internal void init() => Mem ??= new byte[VM_MEMSIZE + 4]; + + private bool IsVMMem(byte[] mem) => Mem == mem; + + private int GetValue(bool byteMode, byte[] mem, int offset) { - //private void InitBlock() - //{ - // Mem.set_Renamed(offset + 0, Byte.valueOf((sbyte) (value_Renamed & 0xff))); - // Mem.set_Renamed(offset + 1, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 8)) & 0xff))); - // Mem.set_Renamed(offset + 2, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 16)) & 0xff))); - // Mem.set_Renamed(offset + 3, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 24)) & 0xff))); - - //} - internal byte[] Mem { get; private set; } - - public const int VM_MEMSIZE = 0x40000; - - //UPGRADE_NOTE: Final was removed from the declaration of 'VM_MEMMASK '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - public static readonly int VM_MEMMASK = (VM_MEMSIZE - 1); - - public const int VM_GLOBALMEMADDR = 0x3C000; - - public const int VM_GLOBALMEMSIZE = 0x2000; - - public const int VM_FIXEDGLOBALSIZE = 64; - - private const int regCount = 8; - - private const long UINT_MASK = 0xffffFFFF; //((long)2*(long)Integer.MAX_VALUE); - - private readonly int[] R = new int[regCount]; - - private VMFlags flags; - - private int maxOpCount = 25000000; - - private int codeSize; - - private int IP; - - internal RarVM() + if (byteMode) { - //InitBlock(); - Mem = null; - } - - internal void init() - { - Mem ??= new byte[VM_MEMSIZE + 4]; - } - - private bool IsVMMem(byte[] mem) - { - return Mem == mem; - } - - private int GetValue(bool byteMode, byte[] mem, int offset) - { - if (byteMode) - { - if (IsVMMem(mem)) - { - return (mem[offset]); - } - return (mem[offset] & 0xff); - } if (IsVMMem(mem)) { - return BinaryPrimitives.ReadInt32LittleEndian(mem.AsSpan(offset)); + return (mem[offset]); } - return BinaryPrimitives.ReadInt32BigEndian(mem.AsSpan(offset)); + return (mem[offset] & 0xff); } - - private void SetValue(bool byteMode, byte[] mem, int offset, int value) + if (IsVMMem(mem)) { - if (byteMode) + return BinaryPrimitives.ReadInt32LittleEndian(mem.AsSpan(offset)); + } + return BinaryPrimitives.ReadInt32BigEndian(mem.AsSpan(offset)); + } + + private void SetValue(bool byteMode, byte[] mem, int offset, int value) + { + if (byteMode) + { + if (IsVMMem(mem)) { - if (IsVMMem(mem)) - { - mem[offset] = (byte)value; - } - else - { - mem[offset] = (byte)((mem[offset] & 0x00) | (byte)(value & 0xff)); - } + mem[offset] = (byte)value; } else { - if (IsVMMem(mem)) - { - BinaryPrimitives.WriteInt32LittleEndian(mem.AsSpan(offset), value); - } - else - { - BinaryPrimitives.WriteInt32BigEndian(mem.AsSpan(offset), value); - } + mem[offset] = (byte)((mem[offset] & 0x00) | (byte)(value & 0xff)); } - - // #define SET_VALUE(ByteMode,Addr,Value) SetValue(ByteMode,(uint - // *)Addr,Value) } - - internal void SetLowEndianValue(List mem, int offset, int value) + else { - mem[offset + 0] = (byte)(value & 0xff); - mem[offset + 1] = (byte)(Utility.URShift(value, 8) & 0xff); - mem[offset + 2] = (byte)(Utility.URShift(value, 16) & 0xff); - mem[offset + 3] = (byte)(Utility.URShift(value, 24) & 0xff); - } - - private int GetOperand(VMPreparedOperand cmdOp) - { - int ret = 0; - if (cmdOp.Type == VMOpType.VM_OPREGMEM) + if (IsVMMem(mem)) { - int pos = (cmdOp.Offset + cmdOp.Base) & VM_MEMMASK; - ret = BinaryPrimitives.ReadInt32LittleEndian(Mem.AsSpan(pos)); + BinaryPrimitives.WriteInt32LittleEndian(mem.AsSpan(offset), value); } else { - int pos = cmdOp.Offset; - ret = BinaryPrimitives.ReadInt32LittleEndian(Mem.AsSpan(pos)); - } - return ret; - } - - public void execute(VMPreparedProgram prg) - { - for (int i = 0; i < prg.InitR.Length; i++) - - // memcpy(R,Prg->InitR,sizeof(Prg->InitR)); - { - R[i] = prg.InitR[i]; - } - - long globalSize = Math.Min(prg.GlobalData.Count, VM_GLOBALMEMSIZE) & 0xffFFffFF; - if (globalSize != 0) - { - for (int i = 0; i < globalSize; i++) - - // memcpy(Mem+VM_GLOBALMEMADDR,&Prg->GlobalData[0],GlobalSize); - { - Mem[VM_GLOBALMEMADDR + i] = prg.GlobalData[i]; - } - } - long staticSize = Math.Min(prg.StaticData.Count, VM_GLOBALMEMSIZE - globalSize) & 0xffFFffFF; - if (staticSize != 0) - { - for (int i = 0; i < staticSize; i++) - - // memcpy(Mem+VM_GLOBALMEMADDR+GlobalSize,&Prg->StaticData[0],StaticSize); - { - Mem[VM_GLOBALMEMADDR + (int)globalSize + i] = prg.StaticData[i]; - } - } - R[7] = VM_MEMSIZE; - flags = 0; - - //UPGRADE_NOTE: There is an untranslated Statement. Please refer to original code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1153'" - List preparedCode = prg.AltCommands.Count != 0 - ? prg - .AltCommands - : prg.Commands; - - if (!ExecuteCode(preparedCode, prg.CommandCount)) - { - preparedCode[0].OpCode = VMCommands.VM_RET; - } - int newBlockPos = GetValue(false, Mem, VM_GLOBALMEMADDR + 0x20) & VM_MEMMASK; - int newBlockSize = GetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c) & VM_MEMMASK; - if ((newBlockPos + newBlockSize) >= VM_MEMSIZE) - { - newBlockPos = 0; - newBlockSize = 0; - } - - prg.FilteredDataOffset = newBlockPos; - prg.FilteredDataSize = newBlockSize; - - prg.GlobalData.Clear(); - - int dataSize = Math.Min(GetValue(false, Mem, VM_GLOBALMEMADDR + 0x30), VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE); - if (dataSize != 0) - { - //prg.GlobalData.Clear(); - // ->GlobalData.Add(dataSize+VM_FIXEDGLOBALSIZE); - prg.GlobalData.Capacity = dataSize + VM_FIXEDGLOBALSIZE; - for (int i = 0; i < dataSize + VM_FIXEDGLOBALSIZE; i++) - - // memcpy(&Prg->GlobalData[0],&Mem[VM_GLOBALMEMADDR],DataSize+VM_FIXEDGLOBALSIZE); - { - prg.GlobalData.Add(Mem[VM_GLOBALMEMADDR + i]); - } + BinaryPrimitives.WriteInt32BigEndian(mem.AsSpan(offset), value); } } - private bool setIP(int ip) + // #define SET_VALUE(ByteMode,Addr,Value) SetValue(ByteMode,(uint + // *)Addr,Value) + } + + internal void SetLowEndianValue(List mem, int offset, int value) + { + mem[offset + 0] = (byte)(value & 0xff); + mem[offset + 1] = (byte)(Utility.URShift(value, 8) & 0xff); + mem[offset + 2] = (byte)(Utility.URShift(value, 16) & 0xff); + mem[offset + 3] = (byte)(Utility.URShift(value, 24) & 0xff); + } + + private int GetOperand(VMPreparedOperand cmdOp) + { + var ret = 0; + if (cmdOp.Type == VMOpType.VM_OPREGMEM) { - if ((ip) >= codeSize) - { - return (true); - } + var pos = (cmdOp.Offset + cmdOp.Base) & VM_MEMMASK; + ret = BinaryPrimitives.ReadInt32LittleEndian(Mem.AsSpan(pos)); + } + else + { + var pos = cmdOp.Offset; + ret = BinaryPrimitives.ReadInt32LittleEndian(Mem.AsSpan(pos)); + } + return ret; + } - if (--maxOpCount <= 0) - { - return (false); - } - - IP = ip; - return true; + public void execute(VMPreparedProgram prg) + { + for (var i = 0; i < prg.InitR.Length; i++) + // memcpy(R,Prg->InitR,sizeof(Prg->InitR)); + { + R[i] = prg.InitR[i]; } - private bool ExecuteCode(List preparedCode, - int cmdCount) + var globalSize = Math.Min(prg.GlobalData.Count, VM_GLOBALMEMSIZE) & 0xffFFffFF; + if (globalSize != 0) { - maxOpCount = 25000000; - codeSize = cmdCount; - IP = 0; - - while (true) + for (var i = 0; i < globalSize; i++) + // memcpy(Mem+VM_GLOBALMEMADDR,&Prg->GlobalData[0],GlobalSize); { - VMPreparedCommand cmd = preparedCode[IP]; - int op1 = GetOperand(cmd.Op1); - int op2 = GetOperand(cmd.Op2); - switch (cmd.OpCode) - { - case VMCommands.VM_MOV: - SetValue(cmd.IsByteMode, Mem, op1, GetValue(cmd.IsByteMode, Mem, op2)); - - // SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2)); - break; - - case VMCommands.VM_MOVB: - SetValue(true, Mem, op1, GetValue(true, Mem, op2)); - break; - - case VMCommands.VM_MOVD: - SetValue(false, Mem, op1, GetValue(false, Mem, op2)); - break; - - case VMCommands.VM_CMP: - { - VMFlags value1 = (VMFlags)GetValue(cmd.IsByteMode, Mem, op1); - VMFlags result = value1 - GetValue(cmd.IsByteMode, Mem, op2); - - if (result == 0) - { - flags = VMFlags.VM_FZ; - } - else - { - flags = (VMFlags)((result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS)); - } - } - break; - - case VMCommands.VM_CMPB: - { - VMFlags value1 = (VMFlags)GetValue(true, Mem, op1); - VMFlags result = value1 - GetValue(true, Mem, op2); - if (result == 0) - { - flags = VMFlags.VM_FZ; - } - else - { - flags = (VMFlags)((result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS)); - } - } - break; - - case VMCommands.VM_CMPD: - { - VMFlags value1 = (VMFlags)GetValue(false, Mem, op1); - VMFlags result = value1 - GetValue(false, Mem, op2); - if (result == 0) - { - flags = VMFlags.VM_FZ; - } - else - { - flags = (VMFlags)((result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS)); - } - } - break; - - case VMCommands.VM_ADD: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int result = - (int) - (((value1 + (long)GetValue(cmd.IsByteMode, Mem, op2))) & - unchecked((int)0xffffffff)); - if (cmd.IsByteMode) - { - result &= 0xff; - flags = - (VMFlags) - ((result < value1) - ? 1 - : 0 | - (result == 0 - ? (int)VMFlags.VM_FZ - : (((result & 0x80) != 0) ? (int)VMFlags.VM_FS : 0))); - - // Flags=(Result value1) ? 1 : 0 | (result & (int)VMFlags.VM_FS))); - SetValue(cmd.IsByteMode, Mem, op1, result); // (Cmd->ByteMode,Op1,Result); - } - break; - - case VMCommands.VM_SUBB: - SetValue(true, Mem, op1, - (int) - (GetValue(true, Mem, op1) & 0xFFffFFff - GetValue(true, Mem, op2) & - unchecked((int)0xFFffFFff))); - break; - - case VMCommands.VM_SUBD: - SetValue(false, Mem, op1, - (int) - (GetValue(false, Mem, op1) & 0xFFffFFff - GetValue(false, Mem, op2) & - unchecked((int)0xFFffFFff))); - break; - - case VMCommands.VM_JZ: - if ((flags & VMFlags.VM_FZ) != 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JNZ: - if ((flags & VMFlags.VM_FZ) == 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_INC: - { - int result = (int)(GetValue(cmd.IsByteMode, Mem, op1) & 0xFFffFFffL + 1L); - if (cmd.IsByteMode) - { - result &= 0xff; - } - - SetValue(cmd.IsByteMode, Mem, op1, result); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - } - break; - - case VMCommands.VM_INCB: - SetValue(true, Mem, op1, (int)(GetValue(true, Mem, op1) & 0xFFffFFffL + 1L)); - break; - - case VMCommands.VM_INCD: - SetValue(false, Mem, op1, (int)(GetValue(false, Mem, op1) & 0xFFffFFffL + 1L)); - break; - - case VMCommands.VM_DEC: - { - int result = (int)(GetValue(cmd.IsByteMode, Mem, op1) & 0xFFffFFff - 1); - SetValue(cmd.IsByteMode, Mem, op1, result); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - } - break; - - case VMCommands.VM_DECB: - SetValue(true, Mem, op1, (int)(GetValue(true, Mem, op1) & 0xFFffFFff - 1)); - break; - - case VMCommands.VM_DECD: - SetValue(false, Mem, op1, (int)(GetValue(false, Mem, op1) & 0xFFffFFff - 1)); - break; - - case VMCommands.VM_JMP: - setIP(GetValue(false, Mem, op1)); - continue; - - case VMCommands.VM_XOR: - { - int result = GetValue(cmd.IsByteMode, Mem, op1) ^ GetValue(cmd.IsByteMode, Mem, op2); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_AND: - { - int result = GetValue(cmd.IsByteMode, Mem, op1) & GetValue(cmd.IsByteMode, Mem, op2); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_OR: - { - int result = GetValue(cmd.IsByteMode, Mem, op1) | GetValue(cmd.IsByteMode, Mem, op2); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_TEST: - { - int result = GetValue(cmd.IsByteMode, Mem, op1) & GetValue(cmd.IsByteMode, Mem, op2); - flags = (VMFlags)(result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS); - } - break; - - case VMCommands.VM_JS: - if ((flags & VMFlags.VM_FS) != 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JNS: - if ((flags & VMFlags.VM_FS) == 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JB: - if ((flags & VMFlags.VM_FC) != 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JBE: - if ((flags & (VMFlags.VM_FC | VMFlags.VM_FZ)) != 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JA: - if ((flags & (VMFlags.VM_FC | VMFlags.VM_FZ)) == 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_JAE: - if ((flags & VMFlags.VM_FC) == 0) - { - setIP(GetValue(false, Mem, op1)); - continue; - } - break; - - case VMCommands.VM_PUSH: - R[7] -= 4; - SetValue(false, Mem, R[7] & VM_MEMMASK, GetValue(false, Mem, op1)); - break; - - case VMCommands.VM_POP: - SetValue(false, Mem, op1, GetValue(false, Mem, R[7] & VM_MEMMASK)); - R[7] += 4; - break; - - case VMCommands.VM_CALL: - R[7] -= 4; - SetValue(false, Mem, R[7] & VM_MEMMASK, IP + 1); - setIP(GetValue(false, Mem, op1)); - continue; - - case VMCommands.VM_NOT: - SetValue(cmd.IsByteMode, Mem, op1, ~GetValue(cmd.IsByteMode, Mem, op1)); - break; - - case VMCommands.VM_SHL: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int value2 = GetValue(cmd.IsByteMode, Mem, op2); - int result = value1 << value2; - flags = - (VMFlags) - ((result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) | - (((value1 << (value2 - 1)) & unchecked((int)0x80000000)) != 0 - ? (int)VMFlags.VM_FC - : 0)); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_SHR: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int value2 = GetValue(cmd.IsByteMode, Mem, op2); - int result = Utility.URShift(value1, value2); - flags = - (VMFlags) - ((result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) | - ((Utility.URShift(value1, (value2 - 1))) & (int)VMFlags.VM_FC)); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_SAR: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int value2 = GetValue(cmd.IsByteMode, Mem, op2); - int result = value1 >> value2; - flags = - (VMFlags) - ((result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) | - ((value1 >> (value2 - 1)) & (int)VMFlags.VM_FC)); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_NEG: - { - int result = -GetValue(cmd.IsByteMode, Mem, op1); - flags = - (VMFlags) - (result == 0 - ? (int)VMFlags.VM_FZ - : (int)VMFlags.VM_FC | (result & (int)VMFlags.VM_FS)); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_NEGB: - SetValue(true, Mem, op1, -GetValue(true, Mem, op1)); - break; - - case VMCommands.VM_NEGD: - SetValue(false, Mem, op1, -GetValue(false, Mem, op1)); - break; - - case VMCommands.VM_PUSHA: - { - for (int i = 0, SP = R[7] - 4; i < regCount; i++, SP -= 4) - { - SetValue(false, Mem, SP & VM_MEMMASK, R[i]); - } - R[7] -= regCount * 4; - } - break; - - case VMCommands.VM_POPA: - { - for (int i = 0, SP = R[7]; i < regCount; i++, SP += 4) - { - R[7 - i] = GetValue(false, Mem, SP & VM_MEMMASK); - } - } - break; - - case VMCommands.VM_PUSHF: - R[7] -= 4; - SetValue(false, Mem, R[7] & VM_MEMMASK, (int)flags); - break; - - case VMCommands.VM_POPF: - flags = (VMFlags)GetValue(false, Mem, R[7] & VM_MEMMASK); - R[7] += 4; - break; - - case VMCommands.VM_MOVZX: - SetValue(false, Mem, op1, GetValue(true, Mem, op2)); - break; - - case VMCommands.VM_MOVSX: - SetValue(false, Mem, op1, (byte)GetValue(true, Mem, op2)); - break; - - case VMCommands.VM_XCHG: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - SetValue(cmd.IsByteMode, Mem, op1, GetValue(cmd.IsByteMode, Mem, op2)); - SetValue(cmd.IsByteMode, Mem, op2, value1); - } - break; - - case VMCommands.VM_MUL: - { - int result = - (int) - ((GetValue(cmd.IsByteMode, Mem, op1) & - 0xFFffFFff * GetValue(cmd.IsByteMode, Mem, op2) & unchecked((int)0xFFffFFff)) & - unchecked((int)0xFFffFFff)); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_DIV: - { - int divider = GetValue(cmd.IsByteMode, Mem, op2); - if (divider != 0) - { - int result = GetValue(cmd.IsByteMode, Mem, op1) / divider; - SetValue(cmd.IsByteMode, Mem, op1, result); - } - } - break; - - case VMCommands.VM_ADC: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int FC = (int)(flags & VMFlags.VM_FC); - int result = - (int) - (value1 & 0xFFffFFff + GetValue(cmd.IsByteMode, Mem, op2) & - 0xFFffFFff + FC & unchecked((int)0xFFffFFff)); - if (cmd.IsByteMode) - { - result &= 0xff; - } - - flags = - (VMFlags) - ((result < value1 || result == value1 && FC != 0) - ? 1 - : 0 | (result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS))); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_SBB: - { - int value1 = GetValue(cmd.IsByteMode, Mem, op1); - int FC = (int)(flags & VMFlags.VM_FC); - int result = - (int) - (value1 & 0xFFffFFff - GetValue(cmd.IsByteMode, Mem, op2) & - 0xFFffFFff - FC & unchecked((int)0xFFffFFff)); - if (cmd.IsByteMode) - { - result &= 0xff; - } - flags = - (VMFlags) - ((result > value1 || result == value1 && FC != 0) - ? 1 - : 0 | (result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS))); - SetValue(cmd.IsByteMode, Mem, op1, result); - } - break; - - case VMCommands.VM_RET: - if (R[7] >= VM_MEMSIZE) - { - return (true); - } - setIP(GetValue(false, Mem, R[7] & VM_MEMMASK)); - R[7] += 4; - continue; - - case VMCommands.VM_STANDARD: - ExecuteStandardFilter((VMStandardFilters)(cmd.Op1.Data)); - break; - - case VMCommands.VM_PRINT: - break; - } - IP++; - --maxOpCount; + Mem[VM_GLOBALMEMADDR + i] = prg.GlobalData[i]; } } - - public void prepare(byte[] code, int codeSize, VMPreparedProgram prg) + var staticSize = Math.Min(prg.StaticData.Count, VM_GLOBALMEMSIZE - globalSize) & 0xffFFffFF; + if (staticSize != 0) { - InitBitInput(); - int cpLength = Math.Min(MAX_SIZE, codeSize); - - // memcpy(inBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); - - Buffer.BlockCopy(code, 0, InBuf, 0, cpLength); - byte xorSum = 0; - for (int i = 1; i < codeSize; i++) + for (var i = 0; i < staticSize; i++) + // memcpy(Mem+VM_GLOBALMEMADDR+GlobalSize,&Prg->StaticData[0],StaticSize); { - xorSum ^= code[i]; + Mem[VM_GLOBALMEMADDR + (int)globalSize + i] = prg.StaticData[i]; } + } + R[7] = VM_MEMSIZE; + flags = 0; - AddBits(8); + //UPGRADE_NOTE: There is an untranslated Statement. Please refer to original code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1153'" + var preparedCode = prg.AltCommands.Count != 0 ? prg.AltCommands : prg.Commands; - prg.CommandCount = 0; - if (xorSum == code[0]) + if (!ExecuteCode(preparedCode, prg.CommandCount)) + { + preparedCode[0].OpCode = VMCommands.VM_RET; + } + var newBlockPos = GetValue(false, Mem, VM_GLOBALMEMADDR + 0x20) & VM_MEMMASK; + var newBlockSize = GetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c) & VM_MEMMASK; + if ((newBlockPos + newBlockSize) >= VM_MEMSIZE) + { + newBlockPos = 0; + newBlockSize = 0; + } + + prg.FilteredDataOffset = newBlockPos; + prg.FilteredDataSize = newBlockSize; + + prg.GlobalData.Clear(); + + var dataSize = Math.Min( + GetValue(false, Mem, VM_GLOBALMEMADDR + 0x30), + VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE + ); + if (dataSize != 0) + { + //prg.GlobalData.Clear(); + // ->GlobalData.Add(dataSize+VM_FIXEDGLOBALSIZE); + prg.GlobalData.Capacity = dataSize + VM_FIXEDGLOBALSIZE; + for (var i = 0; i < dataSize + VM_FIXEDGLOBALSIZE; i++) + // memcpy(&Prg->GlobalData[0],&Mem[VM_GLOBALMEMADDR],DataSize+VM_FIXEDGLOBALSIZE); { - VMStandardFilters filterType = IsStandardFilter(code, codeSize); - if (filterType != VMStandardFilters.VMSF_NONE) - { - VMPreparedCommand curCmd = new VMPreparedCommand(); - curCmd.OpCode = VMCommands.VM_STANDARD; - curCmd.Op1.Data = (int)filterType; - curCmd.Op1.Type = VMOpType.VM_OPNONE; - curCmd.Op2.Type = VMOpType.VM_OPNONE; - codeSize = 0; - prg.Commands.Add(curCmd); - prg.CommandCount = prg.CommandCount + 1; + prg.GlobalData.Add(Mem[VM_GLOBALMEMADDR + i]); + } + } + } - // TODO - // curCmd->Op1.Data=FilterType; - // >>>>>> CurCmd->Op1.Addr=&CurCmd->Op1.Data; <<<<<<<<<< not set - // do i need to ? - // >>>>>> CurCmd->Op2.Addr=&CurCmd->Op2.Data; <<<<<<<<<< " - // CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE; - // CodeSize=0; - } - int dataFlag = GetBits(); - AddBits(1); + private bool setIP(int ip) + { + if ((ip) >= codeSize) + { + return (true); + } - // Read static data contained in DB operators. This data cannot be - // changed, - // it is a part of VM code, not a filter parameter. + if (--maxOpCount <= 0) + { + return (false); + } + + IP = ip; + return true; + } + + private bool ExecuteCode(List preparedCode, int cmdCount) + { + maxOpCount = 25000000; + codeSize = cmdCount; + IP = 0; + + while (true) + { + var cmd = preparedCode[IP]; + var op1 = GetOperand(cmd.Op1); + var op2 = GetOperand(cmd.Op2); + switch (cmd.OpCode) + { + case VMCommands.VM_MOV: + SetValue(cmd.IsByteMode, Mem, op1, GetValue(cmd.IsByteMode, Mem, op2)); + + // SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2)); + break; + + case VMCommands.VM_MOVB: + SetValue(true, Mem, op1, GetValue(true, Mem, op2)); + break; + + case VMCommands.VM_MOVD: + SetValue(false, Mem, op1, GetValue(false, Mem, op2)); + break; + + case VMCommands.VM_CMP: - if ((dataFlag & 0x8000) != 0) - { - long dataSize = ReadData(this) & 0xffFFffFFL + 1L; - for (int i = 0; inAddr < codeSize && i < dataSize; i++) { - prg.StaticData.Add((byte)(GetBits() >> 8)); - AddBits(8); + var value1 = (VMFlags)GetValue(cmd.IsByteMode, Mem, op1); + var result = value1 - GetValue(cmd.IsByteMode, Mem, op2); + + if (result == 0) + { + flags = VMFlags.VM_FZ; + } + else + { + flags = (VMFlags)( + (result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS) + ); + } + } + break; + + case VMCommands.VM_CMPB: + + { + var value1 = (VMFlags)GetValue(true, Mem, op1); + var result = value1 - GetValue(true, Mem, op2); + if (result == 0) + { + flags = VMFlags.VM_FZ; + } + else + { + flags = (VMFlags)( + (result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS) + ); + } + } + break; + + case VMCommands.VM_CMPD: + + { + var value1 = (VMFlags)GetValue(false, Mem, op1); + var result = value1 - GetValue(false, Mem, op2); + if (result == 0) + { + flags = VMFlags.VM_FZ; + } + else + { + flags = (VMFlags)( + (result > value1) ? 1 : 0 | (int)(result & VMFlags.VM_FS) + ); + } + } + break; + + case VMCommands.VM_ADD: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var result = (int)( + ((value1 + (long)GetValue(cmd.IsByteMode, Mem, op2))) + & unchecked((int)0xffffffff) + ); + if (cmd.IsByteMode) + { + result &= 0xff; + flags = (VMFlags)( + (result < value1) + ? 1 + : 0 + | ( + result == 0 + ? (int)VMFlags.VM_FZ + : (((result & 0x80) != 0) ? (int)VMFlags.VM_FS : 0) + ) + ); + + // Flags=(Result value1) ? 1 : 0 | (result & (int)VMFlags.VM_FS)) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); // (Cmd->ByteMode,Op1,Result); + } + break; + + case VMCommands.VM_SUBB: + SetValue( + true, + Mem, + op1, + (int)( + GetValue(true, Mem, op1) + & (0xFFffFFff - GetValue(true, Mem, op2)) + & unchecked((int)0xFFffFFff) + ) + ); + break; + + case VMCommands.VM_SUBD: + SetValue( + false, + Mem, + op1, + (int)( + GetValue(false, Mem, op1) + & (0xFFffFFff - GetValue(false, Mem, op2)) + & unchecked((int)0xFFffFFff) + ) + ); + break; + + case VMCommands.VM_JZ: + if ((flags & VMFlags.VM_FZ) != 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JNZ: + if ((flags & VMFlags.VM_FZ) == 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_INC: + + { + var result = (int)(GetValue(cmd.IsByteMode, Mem, op1) & (0xFFffFFffL + 1L)); + if (cmd.IsByteMode) + { + result &= 0xff; + } + + SetValue(cmd.IsByteMode, Mem, op1, result); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + } + break; + + case VMCommands.VM_INCB: + SetValue(true, Mem, op1, (int)(GetValue(true, Mem, op1) & (0xFFffFFffL + 1L))); + break; + + case VMCommands.VM_INCD: + SetValue( + false, + Mem, + op1, + (int)(GetValue(false, Mem, op1) & (0xFFffFFffL + 1L)) + ); + break; + + case VMCommands.VM_DEC: + + { + var result = (int)(GetValue(cmd.IsByteMode, Mem, op1) & (0xFFffFFff - 1)); + SetValue(cmd.IsByteMode, Mem, op1, result); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + } + break; + + case VMCommands.VM_DECB: + SetValue(true, Mem, op1, (int)(GetValue(true, Mem, op1) & (0xFFffFFff - 1))); + break; + + case VMCommands.VM_DECD: + SetValue(false, Mem, op1, (int)(GetValue(false, Mem, op1) & (0xFFffFFff - 1))); + break; + + case VMCommands.VM_JMP: + setIP(GetValue(false, Mem, op1)); + continue; + + case VMCommands.VM_XOR: + + { + var result = + GetValue(cmd.IsByteMode, Mem, op1) ^ GetValue(cmd.IsByteMode, Mem, op2); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_AND: + + { + var result = + GetValue(cmd.IsByteMode, Mem, op1) & GetValue(cmd.IsByteMode, Mem, op2); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_OR: + + { + var result = + GetValue(cmd.IsByteMode, Mem, op1) | GetValue(cmd.IsByteMode, Mem, op2); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_TEST: + + { + var result = + GetValue(cmd.IsByteMode, Mem, op1) & GetValue(cmd.IsByteMode, Mem, op2); + flags = (VMFlags)( + result == 0 ? (int)VMFlags.VM_FZ : result & (int)VMFlags.VM_FS + ); + } + break; + + case VMCommands.VM_JS: + if ((flags & VMFlags.VM_FS) != 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JNS: + if ((flags & VMFlags.VM_FS) == 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JB: + if ((flags & VMFlags.VM_FC) != 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JBE: + if ((flags & (VMFlags.VM_FC | VMFlags.VM_FZ)) != 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JA: + if ((flags & (VMFlags.VM_FC | VMFlags.VM_FZ)) == 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_JAE: + if ((flags & VMFlags.VM_FC) == 0) + { + setIP(GetValue(false, Mem, op1)); + continue; + } + break; + + case VMCommands.VM_PUSH: + R[7] -= 4; + SetValue(false, Mem, R[7] & VM_MEMMASK, GetValue(false, Mem, op1)); + break; + + case VMCommands.VM_POP: + SetValue(false, Mem, op1, GetValue(false, Mem, R[7] & VM_MEMMASK)); + R[7] += 4; + break; + + case VMCommands.VM_CALL: + R[7] -= 4; + SetValue(false, Mem, R[7] & VM_MEMMASK, IP + 1); + setIP(GetValue(false, Mem, op1)); + continue; + + case VMCommands.VM_NOT: + SetValue(cmd.IsByteMode, Mem, op1, ~GetValue(cmd.IsByteMode, Mem, op1)); + break; + + case VMCommands.VM_SHL: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var value2 = GetValue(cmd.IsByteMode, Mem, op2); + var result = value1 << value2; + flags = (VMFlags)( + (result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) + | ( + ((value1 << (value2 - 1)) & unchecked((int)0x80000000)) != 0 + ? (int)VMFlags.VM_FC + : 0 + ) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_SHR: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var value2 = GetValue(cmd.IsByteMode, Mem, op2); + var result = Utility.URShift(value1, value2); + flags = (VMFlags)( + (result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) + | ((Utility.URShift(value1, (value2 - 1))) & (int)VMFlags.VM_FC) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_SAR: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var value2 = GetValue(cmd.IsByteMode, Mem, op2); + var result = value1 >> value2; + flags = (VMFlags)( + (result == 0 ? (int)VMFlags.VM_FZ : (result & (int)VMFlags.VM_FS)) + | ((value1 >> (value2 - 1)) & (int)VMFlags.VM_FC) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_NEG: + + { + var result = -GetValue(cmd.IsByteMode, Mem, op1); + flags = (VMFlags)( + result == 0 + ? (int)VMFlags.VM_FZ + : (int)VMFlags.VM_FC | (result & (int)VMFlags.VM_FS) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_NEGB: + SetValue(true, Mem, op1, -GetValue(true, Mem, op1)); + break; + + case VMCommands.VM_NEGD: + SetValue(false, Mem, op1, -GetValue(false, Mem, op1)); + break; + + case VMCommands.VM_PUSHA: + + { + for (int i = 0, SP = R[7] - 4; i < regCount; i++, SP -= 4) + { + SetValue(false, Mem, SP & VM_MEMMASK, R[i]); + } + R[7] -= regCount * 4; + } + break; + + case VMCommands.VM_POPA: + + { + for (int i = 0, SP = R[7]; i < regCount; i++, SP += 4) + { + R[7 - i] = GetValue(false, Mem, SP & VM_MEMMASK); + } + } + break; + + case VMCommands.VM_PUSHF: + R[7] -= 4; + SetValue(false, Mem, R[7] & VM_MEMMASK, (int)flags); + break; + + case VMCommands.VM_POPF: + flags = (VMFlags)GetValue(false, Mem, R[7] & VM_MEMMASK); + R[7] += 4; + break; + + case VMCommands.VM_MOVZX: + SetValue(false, Mem, op1, GetValue(true, Mem, op2)); + break; + + case VMCommands.VM_MOVSX: + SetValue(false, Mem, op1, (byte)GetValue(true, Mem, op2)); + break; + + case VMCommands.VM_XCHG: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + SetValue(cmd.IsByteMode, Mem, op1, GetValue(cmd.IsByteMode, Mem, op2)); + SetValue(cmd.IsByteMode, Mem, op2, value1); + } + break; + + case VMCommands.VM_MUL: + + { + var result = (int)( + ( + GetValue(cmd.IsByteMode, Mem, op1) + & (0xFFffFFff * GetValue(cmd.IsByteMode, Mem, op2)) + & unchecked((int)0xFFffFFff) + ) & unchecked((int)0xFFffFFff) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_DIV: + + { + var divider = GetValue(cmd.IsByteMode, Mem, op2); + if (divider != 0) + { + var result = GetValue(cmd.IsByteMode, Mem, op1) / divider; + SetValue(cmd.IsByteMode, Mem, op1, result); + } + } + break; + + case VMCommands.VM_ADC: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var FC = (int)(flags & VMFlags.VM_FC); + var result = (int)( + value1 + & (0xFFffFFff + GetValue(cmd.IsByteMode, Mem, op2)) + & (0xFFffFFff + FC) + & unchecked((int)0xFFffFFff) + ); + if (cmd.IsByteMode) + { + result &= 0xff; + } + + flags = (VMFlags)( + (result < value1 || result == value1 && FC != 0) + ? 1 + : 0 + | ( + result == 0 + ? (int)VMFlags.VM_FZ + : (result & (int)VMFlags.VM_FS) + ) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_SBB: + + { + var value1 = GetValue(cmd.IsByteMode, Mem, op1); + var FC = (int)(flags & VMFlags.VM_FC); + var result = (int)( + value1 + & (0xFFffFFff - GetValue(cmd.IsByteMode, Mem, op2)) + & (0xFFffFFff - FC) + & unchecked((int)0xFFffFFff) + ); + if (cmd.IsByteMode) + { + result &= 0xff; + } + flags = (VMFlags)( + (result > value1 || result == value1 && FC != 0) + ? 1 + : 0 + | ( + result == 0 + ? (int)VMFlags.VM_FZ + : (result & (int)VMFlags.VM_FS) + ) + ); + SetValue(cmd.IsByteMode, Mem, op1, result); + } + break; + + case VMCommands.VM_RET: + if (R[7] >= VM_MEMSIZE) + { + return (true); + } + setIP(GetValue(false, Mem, R[7] & VM_MEMMASK)); + R[7] += 4; + continue; + + case VMCommands.VM_STANDARD: + ExecuteStandardFilter((VMStandardFilters)(cmd.Op1.Data)); + break; + + case VMCommands.VM_PRINT: + break; + } + IP++; + --maxOpCount; + } + } + + public void prepare(byte[] code, int codeSize, VMPreparedProgram prg) + { + InitBitInput(); + var cpLength = Math.Min(MAX_SIZE, codeSize); + + // memcpy(inBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); + + Buffer.BlockCopy(code, 0, InBuf, 0, cpLength); + byte xorSum = 0; + for (var i = 1; i < codeSize; i++) + { + xorSum ^= code[i]; + } + + AddBits(8); + + prg.CommandCount = 0; + if (xorSum == code[0]) + { + var filterType = IsStandardFilter(code, codeSize); + if (filterType != VMStandardFilters.VMSF_NONE) + { + var curCmd = new VMPreparedCommand(); + curCmd.OpCode = VMCommands.VM_STANDARD; + curCmd.Op1.Data = (int)filterType; + curCmd.Op1.Type = VMOpType.VM_OPNONE; + curCmd.Op2.Type = VMOpType.VM_OPNONE; + codeSize = 0; + prg.Commands.Add(curCmd); + prg.CommandCount++; + + // TODO + // curCmd->Op1.Data=FilterType; + // >>>>>> CurCmd->Op1.Addr=&CurCmd->Op1.Data; <<<<<<<<<< not set + // do i need to ? + // >>>>>> CurCmd->Op2.Addr=&CurCmd->Op2.Data; <<<<<<<<<< " + // CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE; + // CodeSize=0; + } + var dataFlag = GetBits(); + AddBits(1); + + // Read static data contained in DB operators. This data cannot be + // changed, + // it is a part of VM code, not a filter parameter. + + if ((dataFlag & 0x8000) != 0) + { + var dataSize = ReadData(this) & (0xffFFffFFL + 1L); + for (var i = 0; inAddr < codeSize && i < dataSize; i++) + { + prg.StaticData.Add((byte)(GetBits() >> 8)); + AddBits(8); + } + } + + while (inAddr < codeSize) + { + var curCmd = new VMPreparedCommand(); + var data = GetBits(); + if ((data & 0x8000) == 0) + { + curCmd.OpCode = (VMCommands)((data >> 12)); + AddBits(4); + } + else + { + curCmd.OpCode = (VMCommands)((data >> 10) - 24); + AddBits(6); + } + if ((VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] & VMCmdFlags.VMCF_BYTEMODE) != 0) + { + curCmd.IsByteMode = (GetBits() >> 15) == 1; + AddBits(1); + } + else + { + curCmd.IsByteMode = false; + } + curCmd.Op1.Type = VMOpType.VM_OPNONE; + curCmd.Op2.Type = VMOpType.VM_OPNONE; + + var opNum = (VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] & VMCmdFlags.VMCF_OPMASK); + + // TODO >>> CurCmd->Op1.Addr=CurCmd->Op2.Addr=NULL; << 0) + { + decodeArg(curCmd.Op1, curCmd.IsByteMode); + if (opNum == 2) + { + decodeArg(curCmd.Op2, curCmd.IsByteMode); + } + else + { + if ( + curCmd.Op1.Type == VMOpType.VM_OPINT + && ( + VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] + & (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC) + ) != 0 + ) + { + var distance = curCmd.Op1.Data; + if (distance >= 256) + { + distance -= 256; + } + else + { + if (distance >= 136) + { + distance -= 264; + } + else + { + if (distance >= 16) + { + distance -= 8; + } + else + { + if (distance >= 8) + { + distance -= 16; + } + } + } + distance += prg.CommandCount; + } + curCmd.Op1.Data = distance; + } } } + prg.CommandCount++; + prg.Commands.Add(curCmd); + } + } + var curCmd2 = new VMPreparedCommand(); + curCmd2.OpCode = VMCommands.VM_RET; - while (inAddr < codeSize) + // TODO CurCmd->Op1.Addr=&CurCmd->Op1.Data; + // CurCmd->Op2.Addr=&CurCmd->Op2.Data; + curCmd2.Op1.Type = VMOpType.VM_OPNONE; + curCmd2.Op2.Type = VMOpType.VM_OPNONE; + + // for (int i=0;iCmd[I]; + // if (Cmd->Op1.Addr==NULL) + // Cmd->Op1.Addr=&Cmd->Op1.Data; + // if (Cmd->Op2.Addr==NULL) + // Cmd->Op2.Addr=&Cmd->Op2.Data; + // } + + prg.Commands.Add(curCmd2); + prg.CommandCount++; + + // #ifdef VM_OPTIMIZE + if (codeSize != 0) + { + optimize(prg); + } + } + + private void decodeArg(VMPreparedOperand op, bool byteMode) + { + var data = GetBits(); + if ((data & 0x8000) != 0) + { + op.Type = VMOpType.VM_OPREG; + op.Data = (data >> 12) & 7; + op.Offset = op.Data; + AddBits(4); + } + else + { + if ((data & 0xc000) == 0) + { + op.Type = VMOpType.VM_OPINT; + if (byteMode) { - VMPreparedCommand curCmd = new VMPreparedCommand(); - int data = GetBits(); - if ((data & 0x8000) == 0) + op.Data = (data >> 6) & 0xff; + AddBits(10); + } + else + { + AddBits(2); + op.Data = ReadData(this); + } + } + else + { + op.Type = VMOpType.VM_OPREGMEM; + if ((data & 0x2000) == 0) + { + op.Data = (data >> 10) & 7; + op.Offset = op.Data; + op.Base = 0; + AddBits(6); + } + else + { + if ((data & 0x1000) == 0) { - curCmd.OpCode = (VMCommands)((data >> 12)); + op.Data = (data >> 9) & 7; + op.Offset = op.Data; + AddBits(7); + } + else + { + op.Data = 0; AddBits(4); } - else - { - curCmd.OpCode = (VMCommands)((data >> 10) - 24); - AddBits(6); - } - if ((VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] & VMCmdFlags.VMCF_BYTEMODE) != 0) - { - curCmd.IsByteMode = (GetBits() >> 15) == 1 ? true : false; - AddBits(1); - } - else - { - curCmd.IsByteMode = false; - } - curCmd.Op1.Type = VMOpType.VM_OPNONE; - curCmd.Op2.Type = VMOpType.VM_OPNONE; - - int opNum = (VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] & VMCmdFlags.VMCF_OPMASK); - - // TODO >>> CurCmd->Op1.Addr=CurCmd->Op2.Addr=NULL; << 0) - { - decodeArg(curCmd.Op1, curCmd.IsByteMode); - if (opNum == 2) - { - decodeArg(curCmd.Op2, curCmd.IsByteMode); - } - else - { - if (curCmd.Op1.Type == VMOpType.VM_OPINT && - (VMCmdFlags.VM_CmdFlags[(int)curCmd.OpCode] & - (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC)) != 0) - { - int distance = curCmd.Op1.Data; - if (distance >= 256) - { - distance -= 256; - } - else - { - if (distance >= 136) - { - distance -= 264; - } - else - { - if (distance >= 16) - { - distance -= 8; - } - else - { - if (distance >= 8) - { - distance -= 16; - } - } - } - distance += prg.CommandCount; - } - curCmd.Op1.Data = distance; - } - } - } - prg.CommandCount = (prg.CommandCount + 1); - prg.Commands.Add(curCmd); - } - } - VMPreparedCommand curCmd2 = new VMPreparedCommand(); - curCmd2.OpCode = VMCommands.VM_RET; - - // TODO CurCmd->Op1.Addr=&CurCmd->Op1.Data; - // CurCmd->Op2.Addr=&CurCmd->Op2.Data; - curCmd2.Op1.Type = VMOpType.VM_OPNONE; - curCmd2.Op2.Type = VMOpType.VM_OPNONE; - - // for (int i=0;iCmd[I]; - // if (Cmd->Op1.Addr==NULL) - // Cmd->Op1.Addr=&Cmd->Op1.Data; - // if (Cmd->Op2.Addr==NULL) - // Cmd->Op2.Addr=&Cmd->Op2.Data; - // } - - prg.Commands.Add(curCmd2); - prg.CommandCount = prg.CommandCount + 1; - - // #ifdef VM_OPTIMIZE - if (codeSize != 0) - { - optimize(prg); - } - } - - private void decodeArg(VMPreparedOperand op, bool byteMode) - { - int data = GetBits(); - if ((data & 0x8000) != 0) - { - op.Type = VMOpType.VM_OPREG; - op.Data = (data >> 12) & 7; - op.Offset = op.Data; - AddBits(4); - } - else - { - if ((data & 0xc000) == 0) - { - op.Type = VMOpType.VM_OPINT; - if (byteMode) - { - op.Data = (data >> 6) & 0xff; - AddBits(10); - } - else - { - AddBits(2); - op.Data = ReadData(this); - } - } - else - { - op.Type = VMOpType.VM_OPREGMEM; - if ((data & 0x2000) == 0) - { - op.Data = (data >> 10) & 7; - op.Offset = op.Data; - op.Base = 0; - AddBits(6); - } - else - { - if ((data & 0x1000) == 0) - { - op.Data = (data >> 9) & 7; - op.Offset = op.Data; - AddBits(7); - } - else - { - op.Data = 0; - AddBits(4); - } - op.Base = ReadData(this); - } - } - } - } - - private void optimize(VMPreparedProgram prg) - { - //UPGRADE_NOTE: There is an untranslated Statement. Please refer to original code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1153'" - - List commands = prg.Commands; - - //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" - foreach (VMPreparedCommand cmd in commands) - { - switch (cmd.OpCode) - { - case VMCommands.VM_MOV: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_MOVB : VMCommands.VM_MOVD; - continue; - - case VMCommands.VM_CMP: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_CMPB : VMCommands.VM_CMPD; - continue; - } - if ((VMCmdFlags.VM_CmdFlags[(int)cmd.OpCode] & VMCmdFlags.VMCF_CHFLAGS) == 0) - { - continue; - } - bool flagsRequired = false; - - for (int i = commands.IndexOf(cmd) + 1; i < commands.Count; i++) - { - int flags = VMCmdFlags.VM_CmdFlags[(int)commands[i].OpCode]; - if ((flags & (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC | VMCmdFlags.VMCF_USEFLAGS)) != 0) - { - flagsRequired = true; - break; - } - if ((flags & VMCmdFlags.VMCF_CHFLAGS) != 0) - { - break; - } - } - if (flagsRequired) - { - continue; - } - switch (cmd.OpCode) - { - case VMCommands.VM_ADD: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_ADDB : VMCommands.VM_ADDD; - continue; - - case VMCommands.VM_SUB: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_SUBB : VMCommands.VM_SUBD; - continue; - - case VMCommands.VM_INC: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_INCB : VMCommands.VM_INCD; - continue; - - case VMCommands.VM_DEC: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_DECB : VMCommands.VM_DECD; - continue; - - case VMCommands.VM_NEG: - cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_NEGB : VMCommands.VM_NEGD; - continue; - } - } - } - - internal static int ReadData(BitInput rarVM) - { - int data = rarVM.GetBits(); - switch (data & 0xc000) - { - case 0: - rarVM.AddBits(6); - return ((data >> 10) & 0xf); - - case 0x4000: - if ((data & 0x3c00) == 0) - { - data = unchecked((int)0xffffff00) | ((data >> 2) & 0xff); - rarVM.AddBits(14); - } - else - { - data = (data >> 6) & 0xff; - rarVM.AddBits(10); - } - return (data); - - case 0x8000: - rarVM.AddBits(2); - data = rarVM.GetBits(); - rarVM.AddBits(16); - return (data); - - default: - rarVM.AddBits(2); - data = (rarVM.GetBits() << 16); - rarVM.AddBits(16); - data |= rarVM.GetBits(); - rarVM.AddBits(16); - return (data); - } - } - - private VMStandardFilters IsStandardFilter(byte[] code, int codeSize) - { - VMStandardFilterSignature[] stdList = - { - new VMStandardFilterSignature(53, 0xad576887, - VMStandardFilters.VMSF_E8), - new VMStandardFilterSignature(57, 0x3cd7e57e, - VMStandardFilters.VMSF_E8E9), - new VMStandardFilterSignature(120, 0x3769893f, - VMStandardFilters.VMSF_ITANIUM), - new VMStandardFilterSignature(29, 0x0e06077d, - VMStandardFilters.VMSF_DELTA), - new VMStandardFilterSignature(149, 0x1c2c5dc8, - VMStandardFilters.VMSF_RGB), - new VMStandardFilterSignature(216, 0xbc85e701, - VMStandardFilters.VMSF_AUDIO), - new VMStandardFilterSignature(40, 0x46b9c560, - VMStandardFilters.VMSF_UPCASE) - }; - uint CodeCRC = RarCRC.CheckCrc(0xffffffff, code, 0, code.Length) ^ 0xffffffff; - for (int i = 0; i < stdList.Length; i++) - { - if (stdList[i].CRC == CodeCRC && stdList[i].Length == code.Length) - { - return (stdList[i].Type); - } - } - return (VMStandardFilters.VMSF_NONE); - } - - private void ExecuteStandardFilter(VMStandardFilters filterType) - { - switch (filterType) - { - case VMStandardFilters.VMSF_E8: - case VMStandardFilters.VMSF_E8E9: - { - int dataSize = R[4]; - long fileOffset = R[6] & unchecked((int)0xFFffFFff); - - if (dataSize >= VM_GLOBALMEMADDR) - { - break; - } - int fileSize = 0x1000000; - byte cmpByte2 = (byte)((filterType == VMStandardFilters.VMSF_E8E9) ? 0xe9 : 0xe8); - for (int curPos = 0; curPos < dataSize - 4;) - { - byte curByte = Mem[curPos++]; - if (curByte == 0xe8 || curByte == cmpByte2) - { - // #ifdef PRESENT_INT32 - // sint32 Offset=CurPos+FileOffset; - // sint32 Addr=GET_VALUE(false,Data); - // if (Addr<0) - // { - // if (Addr+Offset>=0) - // SET_VALUE(false,Data,Addr+FileSize); - // } - // else - // if (Addr= VM_GLOBALMEMADDR) - { - break; - } - int curPos = 0; - - //UPGRADE_NOTE: Final was removed from the declaration of 'Masks '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" - byte[] Masks = { 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0 }; - fileOffset = Utility.URShift(fileOffset, 4); - - while (curPos < dataSize - 21) - { - int Byte = (Mem[curPos] & 0x1f) - 0x10; - if (Byte >= 0) - { - byte cmdMask = Masks[Byte]; - if (cmdMask != 0) - { - for (int i = 0; i <= 2; i++) - { - if ((cmdMask & (1 << i)) != 0) - { - int startPos = i * 41 + 5; - int opType = filterItanium_GetBits(curPos, startPos + 37, 4); - if (opType == 5) - { - int offset = filterItanium_GetBits(curPos, startPos + 13, 20); - filterItanium_SetBits(curPos, (int)(offset - fileOffset) & 0xfffff, - startPos + 13, 20); - } - } - } - } - } - curPos += 16; - fileOffset++; - } - } - break; - - case VMStandardFilters.VMSF_DELTA: - { - int dataSize = R[4] & unchecked((int)0xFFffFFff); - int channels = R[0] & unchecked((int)0xFFffFFff); - int srcPos = 0; - int border = (dataSize * 2) & unchecked((int)0xFFffFFff); - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); - if (dataSize >= VM_GLOBALMEMADDR / 2) - { - break; - } - - // bytes from same channels are grouped to continual data blocks, - // so we need to place them back to their interleaving positions - - for (int curChannel = 0; curChannel < channels; curChannel++) - { - byte PrevByte = 0; - for (int destPos = dataSize + curChannel; destPos < border; destPos += channels) - { - Mem[destPos] = (PrevByte = (byte)(PrevByte - Mem[srcPos++])); - } - } - } - break; - - case VMStandardFilters.VMSF_RGB: - { - // byte *SrcData=Mem,*DestData=SrcData+DataSize; - int dataSize = R[4], width = R[0] - 3, posR = R[1]; - int channels = 3; - int srcPos = 0; - int destDataPos = dataSize; - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); - if (dataSize >= VM_GLOBALMEMADDR / 2 || posR < 0) - { - break; - } - for (int curChannel = 0; curChannel < channels; curChannel++) - { - long prevByte = 0; - - for (int i = curChannel; i < dataSize; i += channels) - { - long predicted; - int upperPos = i - width; - if (upperPos >= 3) - { - int upperDataPos = destDataPos + upperPos; - int upperByte = Mem[upperDataPos] & 0xff; - int upperLeftByte = Mem[upperDataPos - 3] & 0xff; - predicted = prevByte + upperByte - upperLeftByte; - int pa = Math.Abs((int)(predicted - prevByte)); - int pb = Math.Abs((int)(predicted - upperByte)); - int pc = Math.Abs((int)(predicted - upperLeftByte)); - if (pa <= pb && pa <= pc) - { - predicted = prevByte; - } - else - { - if (pb <= pc) - { - predicted = upperByte; - } - else - { - predicted = upperLeftByte; - } - } - } - else - { - predicted = prevByte; - } - - prevByte = (predicted - Mem[srcPos++] & 0xff) & 0xff; - Mem[destDataPos + i] = (byte)(prevByte & 0xff); - } - } - for (int i = posR, border = dataSize - 2; i < border; i += 3) - { - byte G = Mem[destDataPos + i + 1]; - Mem[destDataPos + i] = (byte)(Mem[destDataPos + i] + G); - Mem[destDataPos + i + 2] = (byte)(Mem[destDataPos + i + 2] + G); - } - } - break; - - case VMStandardFilters.VMSF_AUDIO: - { - int dataSize = R[4], channels = R[0]; - int srcPos = 0; - int destDataPos = dataSize; - - //byte *SrcData=Mem,*DestData=SrcData+DataSize; - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); - if (dataSize >= VM_GLOBALMEMADDR / 2) - { - break; - } - for (int curChannel = 0; curChannel < channels; curChannel++) - { - long prevByte = 0; - long prevDelta = 0; - long[] Dif = new long[7]; - int D1 = 0, D2 = 0, D3; - int K1 = 0, K2 = 0, K3 = 0; - - for (int i = curChannel, byteCount = 0; i < dataSize; i += channels, byteCount++) - { - D3 = D2; - D2 = (int)(prevDelta - D1); - D1 = (int)prevDelta; - - long predicted = 8 * prevByte + K1 * D1 + K2 * D2 + K3 * D3; - predicted = Utility.URShift(predicted, 3) & 0xff; - - long curByte = Mem[srcPos++]; - - predicted -= curByte; - Mem[destDataPos + i] = (byte)predicted; - prevDelta = (byte)(predicted - prevByte); - - //fix java byte - if (prevDelta >= 128) - { - prevDelta = 0 - (256 - prevDelta); - } - prevByte = predicted; - - //fix java byte - if (curByte >= 128) - { - curByte = 0 - (256 - curByte); - } - int D = ((int)curByte) << 3; - - Dif[0] += Math.Abs(D); - Dif[1] += Math.Abs(D - D1); - Dif[2] += Math.Abs(D + D1); - Dif[3] += Math.Abs(D - D2); - Dif[4] += Math.Abs(D + D2); - Dif[5] += Math.Abs(D - D3); - Dif[6] += Math.Abs(D + D3); - - if ((byteCount & 0x1f) == 0) - { - long minDif = Dif[0], numMinDif = 0; - Dif[0] = 0; - for (int j = 1; j < Dif.Length; j++) - { - if (Dif[j] < minDif) - { - minDif = Dif[j]; - numMinDif = j; - } - Dif[j] = 0; - } - switch ((int)numMinDif) - { - case 1: - if (K1 >= -16) - { - K1--; - } - break; - - case 2: - if (K1 < 16) - { - K1++; - } - break; - - case 3: - if (K2 >= -16) - { - K2--; - } - break; - - case 4: - if (K2 < 16) - { - K2++; - } - break; - - case 5: - if (K3 >= -16) - { - K3--; - } - break; - - case 6: - if (K3 < 16) - { - K3++; - } - break; - } - } - } - } - } - break; - - case VMStandardFilters.VMSF_UPCASE: - { - int dataSize = R[4], srcPos = 0, destPos = dataSize; - if (dataSize >= VM_GLOBALMEMADDR / 2) - { - break; - } - while (srcPos < dataSize) - { - byte curByte = Mem[srcPos++]; - if (curByte == 2 && (curByte = Mem[srcPos++]) != 2) - { - curByte = (byte)(curByte - 32); - } - Mem[destPos++] = curByte; - } - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize); - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); - } - break; - } - } - - private void filterItanium_SetBits(int curPos, int bitField, int bitPos, int bitCount) - { - int inAddr = bitPos / 8; - int inBit = bitPos & 7; - int andMask = Utility.URShift(unchecked((int)0xffffffff), (32 - bitCount)); - andMask = ~(andMask << inBit); - - bitField <<= inBit; - - for (int i = 0; i < 4; i++) - { - Mem[curPos + inAddr + i] &= (byte)(andMask); - Mem[curPos + inAddr + i] |= (byte)(bitField); - andMask = (Utility.URShift(andMask, 8)) | unchecked((int)0xff000000); - bitField = Utility.URShift(bitField, 8); - } - } - - private int filterItanium_GetBits(int curPos, int bitPos, int bitCount) - { - int inAddr = bitPos / 8; - int inBit = bitPos & 7; - int bitField = Mem[curPos + inAddr++] & 0xff; - bitField |= (Mem[curPos + inAddr++] & 0xff) << 8; - bitField |= (Mem[curPos + inAddr++] & 0xff) << 16; - bitField |= (Mem[curPos + inAddr] & 0xff) << 24; - bitField = Utility.URShift(bitField, inBit); - return (bitField & (Utility.URShift(unchecked((int)0xffffffff), (32 - bitCount)))); - } - - public void setMemory(int pos, byte[] data, int offset, int dataSize) - { - if (pos < VM_MEMSIZE) - { - //&& data!=Mem+Pos) - //memmove(Mem+Pos,Data,Min(DataSize,VM_MEMSIZE-Pos)); - for (int i = 0; i < Math.Min(data.Length - offset, dataSize); i++) - { - if ((VM_MEMSIZE - pos) < i) - { - break; - } - Mem[pos + i] = data[offset + i]; + op.Base = ReadData(this); } } } } - // + private void optimize(VMPreparedProgram prg) + { + //UPGRADE_NOTE: There is an untranslated Statement. Please refer to original code. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1153'" + + var commands = prg.Commands; + + //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'" + foreach (var cmd in commands) + { + switch (cmd.OpCode) + { + case VMCommands.VM_MOV: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_MOVB : VMCommands.VM_MOVD; + continue; + + case VMCommands.VM_CMP: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_CMPB : VMCommands.VM_CMPD; + continue; + } + if ((VMCmdFlags.VM_CmdFlags[(int)cmd.OpCode] & VMCmdFlags.VMCF_CHFLAGS) == 0) + { + continue; + } + var flagsRequired = false; + + for (var i = commands.IndexOf(cmd) + 1; i < commands.Count; i++) + { + int flags = VMCmdFlags.VM_CmdFlags[(int)commands[i].OpCode]; + if ( + ( + flags + & (VMCmdFlags.VMCF_JUMP | VMCmdFlags.VMCF_PROC | VMCmdFlags.VMCF_USEFLAGS) + ) != 0 + ) + { + flagsRequired = true; + break; + } + if ((flags & VMCmdFlags.VMCF_CHFLAGS) != 0) + { + break; + } + } + if (flagsRequired) + { + continue; + } + switch (cmd.OpCode) + { + case VMCommands.VM_ADD: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_ADDB : VMCommands.VM_ADDD; + continue; + + case VMCommands.VM_SUB: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_SUBB : VMCommands.VM_SUBD; + continue; + + case VMCommands.VM_INC: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_INCB : VMCommands.VM_INCD; + continue; + + case VMCommands.VM_DEC: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_DECB : VMCommands.VM_DECD; + continue; + + case VMCommands.VM_NEG: + cmd.OpCode = cmd.IsByteMode ? VMCommands.VM_NEGB : VMCommands.VM_NEGD; + continue; + } + } + } + + internal static int ReadData(BitInput rarVM) + { + var data = rarVM.GetBits(); + switch (data & 0xc000) + { + case 0: + rarVM.AddBits(6); + return ((data >> 10) & 0xf); + + case 0x4000: + if ((data & 0x3c00) == 0) + { + data = unchecked((int)0xffffff00) | ((data >> 2) & 0xff); + rarVM.AddBits(14); + } + else + { + data = (data >> 6) & 0xff; + rarVM.AddBits(10); + } + return (data); + + case 0x8000: + rarVM.AddBits(2); + data = rarVM.GetBits(); + rarVM.AddBits(16); + return (data); + + default: + rarVM.AddBits(2); + data = (rarVM.GetBits() << 16); + rarVM.AddBits(16); + data |= rarVM.GetBits(); + rarVM.AddBits(16); + return (data); + } + } + + private VMStandardFilters IsStandardFilter(byte[] code, int codeSize) + { + VMStandardFilterSignature[] stdList = + { + new VMStandardFilterSignature(53, 0xad576887, VMStandardFilters.VMSF_E8), + new VMStandardFilterSignature(57, 0x3cd7e57e, VMStandardFilters.VMSF_E8E9), + new VMStandardFilterSignature(120, 0x3769893f, VMStandardFilters.VMSF_ITANIUM), + new VMStandardFilterSignature(29, 0x0e06077d, VMStandardFilters.VMSF_DELTA), + new VMStandardFilterSignature(149, 0x1c2c5dc8, VMStandardFilters.VMSF_RGB), + new VMStandardFilterSignature(216, 0xbc85e701, VMStandardFilters.VMSF_AUDIO), + new VMStandardFilterSignature(40, 0x46b9c560, VMStandardFilters.VMSF_UPCASE) + }; + var CodeCRC = RarCRC.CheckCrc(0xffffffff, code, 0, code.Length) ^ 0xffffffff; + for (var i = 0; i < stdList.Length; i++) + { + if (stdList[i].CRC == CodeCRC && stdList[i].Length == code.Length) + { + return (stdList[i].Type); + } + } + return (VMStandardFilters.VMSF_NONE); + } + + private void ExecuteStandardFilter(VMStandardFilters filterType) + { + switch (filterType) + { + case VMStandardFilters.VMSF_E8: + case VMStandardFilters.VMSF_E8E9: + + { + var dataSize = R[4]; + long fileOffset = R[6] & unchecked((int)0xFFffFFff); + + if (dataSize >= VM_GLOBALMEMADDR) + { + break; + } + var fileSize = 0x1000000; + var cmpByte2 = (byte)( + (filterType == VMStandardFilters.VMSF_E8E9) ? 0xe9 : 0xe8 + ); + for (var curPos = 0; curPos < dataSize - 4; ) + { + var curByte = Mem[curPos++]; + if (curByte == 0xe8 || curByte == cmpByte2) + { + // #ifdef PRESENT_INT32 + // sint32 Offset=CurPos+FileOffset; + // sint32 Addr=GET_VALUE(false,Data); + // if (Addr<0) + // { + // if (Addr+Offset>=0) + // SET_VALUE(false,Data,Addr+FileSize); + // } + // else + // if (Addr= VM_GLOBALMEMADDR) + { + break; + } + var curPos = 0; + + //UPGRADE_NOTE: Final was removed from the declaration of 'Masks '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + byte[] Masks = { 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0 }; + fileOffset = Utility.URShift(fileOffset, 4); + + while (curPos < dataSize - 21) + { + var Byte = (Mem[curPos] & 0x1f) - 0x10; + if (Byte >= 0) + { + var cmdMask = Masks[Byte]; + if (cmdMask != 0) + { + for (var i = 0; i <= 2; i++) + { + if ((cmdMask & (1 << i)) != 0) + { + var startPos = (i * 41) + 5; + var opType = filterItanium_GetBits( + curPos, + startPos + 37, + 4 + ); + if (opType == 5) + { + var offset = filterItanium_GetBits( + curPos, + startPos + 13, + 20 + ); + filterItanium_SetBits( + curPos, + (int)(offset - fileOffset) & 0xfffff, + startPos + 13, + 20 + ); + } + } + } + } + } + curPos += 16; + fileOffset++; + } + } + break; + + case VMStandardFilters.VMSF_DELTA: + + { + var dataSize = R[4] & unchecked((int)0xFFffFFff); + var channels = R[0] & unchecked((int)0xFFffFFff); + var srcPos = 0; + var border = (dataSize * 2) & unchecked((int)0xFFffFFff); + SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + if (dataSize >= VM_GLOBALMEMADDR / 2) + { + break; + } + + // bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions + + for (var curChannel = 0; curChannel < channels; curChannel++) + { + byte PrevByte = 0; + for ( + var destPos = dataSize + curChannel; + destPos < border; + destPos += channels + ) + { + Mem[destPos] = (PrevByte = (byte)(PrevByte - Mem[srcPos++])); + } + } + } + break; + + case VMStandardFilters.VMSF_RGB: + + { + // byte *SrcData=Mem,*DestData=SrcData+DataSize; + int dataSize = R[4], + width = R[0] - 3, + posR = R[1]; + var channels = 3; + var srcPos = 0; + var destDataPos = dataSize; + SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + if (dataSize >= VM_GLOBALMEMADDR / 2 || posR < 0) + { + break; + } + for (var curChannel = 0; curChannel < channels; curChannel++) + { + long prevByte = 0; + + for (var i = curChannel; i < dataSize; i += channels) + { + long predicted; + var upperPos = i - width; + if (upperPos >= 3) + { + var upperDataPos = destDataPos + upperPos; + var upperByte = Mem[upperDataPos] & 0xff; + var upperLeftByte = Mem[upperDataPos - 3] & 0xff; + predicted = prevByte + upperByte - upperLeftByte; + var pa = Math.Abs((int)(predicted - prevByte)); + var pb = Math.Abs((int)(predicted - upperByte)); + var pc = Math.Abs((int)(predicted - upperLeftByte)); + if (pa <= pb && pa <= pc) + { + predicted = prevByte; + } + else + { + if (pb <= pc) + { + predicted = upperByte; + } + else + { + predicted = upperLeftByte; + } + } + } + else + { + predicted = prevByte; + } + + prevByte = ((predicted - Mem[srcPos++]) & 0xff) & 0xff; + Mem[destDataPos + i] = (byte)(prevByte & 0xff); + } + } + for (int i = posR, border = dataSize - 2; i < border; i += 3) + { + var G = Mem[destDataPos + i + 1]; + Mem[destDataPos + i] = (byte)(Mem[destDataPos + i] + G); + Mem[destDataPos + i + 2] = (byte)(Mem[destDataPos + i + 2] + G); + } + } + break; + + case VMStandardFilters.VMSF_AUDIO: + + { + int dataSize = R[4], + channels = R[0]; + var srcPos = 0; + var destDataPos = dataSize; + + //byte *SrcData=Mem,*DestData=SrcData+DataSize; + SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + if (dataSize >= VM_GLOBALMEMADDR / 2) + { + break; + } + for (var curChannel = 0; curChannel < channels; curChannel++) + { + long prevByte = 0; + long prevDelta = 0; + var Dif = new long[7]; + int D1 = 0, + D2 = 0, + D3; + int K1 = 0, + K2 = 0, + K3 = 0; + + for ( + int i = curChannel, byteCount = 0; + i < dataSize; + i += channels, byteCount++ + ) + { + D3 = D2; + D2 = (int)(prevDelta - D1); + D1 = (int)prevDelta; + + var predicted = (8 * prevByte) + (K1 * D1) + (K2 * D2) + (K3 * D3); + predicted = Utility.URShift(predicted, 3) & 0xff; + + long curByte = Mem[srcPos++]; + + predicted -= curByte; + Mem[destDataPos + i] = (byte)predicted; + prevDelta = (byte)(predicted - prevByte); + + //fix java byte + if (prevDelta >= 128) + { + prevDelta = 0 - (256 - prevDelta); + } + prevByte = predicted; + + //fix java byte + if (curByte >= 128) + { + curByte = 0 - (256 - curByte); + } + var D = ((int)curByte) << 3; + + Dif[0] += Math.Abs(D); + Dif[1] += Math.Abs(D - D1); + Dif[2] += Math.Abs(D + D1); + Dif[3] += Math.Abs(D - D2); + Dif[4] += Math.Abs(D + D2); + Dif[5] += Math.Abs(D - D3); + Dif[6] += Math.Abs(D + D3); + + if ((byteCount & 0x1f) == 0) + { + long minDif = Dif[0], + numMinDif = 0; + Dif[0] = 0; + for (var j = 1; j < Dif.Length; j++) + { + if (Dif[j] < minDif) + { + minDif = Dif[j]; + numMinDif = j; + } + Dif[j] = 0; + } + switch ((int)numMinDif) + { + case 1: + if (K1 >= -16) + { + K1--; + } + break; + + case 2: + if (K1 < 16) + { + K1++; + } + break; + + case 3: + if (K2 >= -16) + { + K2--; + } + break; + + case 4: + if (K2 < 16) + { + K2++; + } + break; + + case 5: + if (K3 >= -16) + { + K3--; + } + break; + + case 6: + if (K3 < 16) + { + K3++; + } + break; + } + } + } + } + } + break; + + case VMStandardFilters.VMSF_UPCASE: + + { + int dataSize = R[4], + srcPos = 0, + destPos = dataSize; + if (dataSize >= VM_GLOBALMEMADDR / 2) + { + break; + } + while (srcPos < dataSize) + { + var curByte = Mem[srcPos++]; + if (curByte == 2 && (curByte = Mem[srcPos++]) != 2) + { + curByte = (byte)(curByte - 32); + } + Mem[destPos++] = curByte; + } + SetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize); + SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + } + break; + } + } + + private void filterItanium_SetBits(int curPos, int bitField, int bitPos, int bitCount) + { + var inAddr = bitPos / 8; + var inBit = bitPos & 7; + var andMask = Utility.URShift(unchecked((int)0xffffffff), (32 - bitCount)); + andMask = ~(andMask << inBit); + + bitField <<= inBit; + + for (var i = 0; i < 4; i++) + { + Mem[curPos + inAddr + i] &= (byte)(andMask); + Mem[curPos + inAddr + i] |= (byte)(bitField); + andMask = (Utility.URShift(andMask, 8)) | unchecked((int)0xff000000); + bitField = Utility.URShift(bitField, 8); + } + } + + private int filterItanium_GetBits(int curPos, int bitPos, int bitCount) + { + var inAddr = bitPos / 8; + var inBit = bitPos & 7; + var bitField = Mem[curPos + inAddr++] & 0xff; + bitField |= (Mem[curPos + inAddr++] & 0xff) << 8; + bitField |= (Mem[curPos + inAddr++] & 0xff) << 16; + bitField |= (Mem[curPos + inAddr] & 0xff) << 24; + bitField = Utility.URShift(bitField, inBit); + return (bitField & (Utility.URShift(unchecked((int)0xffffffff), (32 - bitCount)))); + } + + public void setMemory(int pos, byte[] data, int offset, int dataSize) + { + if (pos < VM_MEMSIZE) + { + //&& data!=Mem+Pos) + //memmove(Mem+Pos,Data,Min(DataSize,VM_MEMSIZE-Pos)); + for (var i = 0; i < Math.Min(data.Length - offset, dataSize); i++) + { + if ((VM_MEMSIZE - pos) < i) + { + break; + } + Mem[pos + i] = data[offset + i]; + } + } + } } + +// diff --git a/src/SharpCompress/Compressors/Rar/VM/VMCmdFlags.cs b/src/SharpCompress/Compressors/Rar/VM/VMCmdFlags.cs index 55ae8dfe..00de0b6a 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMCmdFlags.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMCmdFlags.cs @@ -1,46 +1,58 @@ -namespace SharpCompress.Compressors.Rar.VM -{ - internal class VMCmdFlags - { - public const byte VMCF_OP0 = 0; - public const byte VMCF_OP1 = 1; - public const byte VMCF_OP2 = 2; - public const byte VMCF_OPMASK = 3; - public const byte VMCF_BYTEMODE = 4; - public const byte VMCF_JUMP = 8; - public const byte VMCF_PROC = 16; - public const byte VMCF_USEFLAGS = 32; - public const byte VMCF_CHFLAGS = 64; +namespace SharpCompress.Compressors.Rar.VM; - public static byte[] VM_CmdFlags = - { - VMCF_OP2 | VMCF_BYTEMODE, VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, VMCF_OP1 | VMCF_JUMP, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, - VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, VMCF_OP1, VMCF_OP1, - VMCF_OP1 | VMCF_PROC, VMCF_OP0 | VMCF_PROC, VMCF_OP1 | VMCF_BYTEMODE, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, - VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, VMCF_OP0, VMCF_OP0, - VMCF_OP0 | VMCF_USEFLAGS, VMCF_OP0 | VMCF_CHFLAGS, VMCF_OP2, VMCF_OP2, - VMCF_OP2 | VMCF_BYTEMODE, VMCF_OP2 | VMCF_BYTEMODE, - VMCF_OP2 | VMCF_BYTEMODE, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS, - VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS, VMCF_OP0 - }; - } -} \ No newline at end of file +internal class VMCmdFlags +{ + public const byte VMCF_OP0 = 0; + public const byte VMCF_OP1 = 1; + public const byte VMCF_OP2 = 2; + public const byte VMCF_OPMASK = 3; + public const byte VMCF_BYTEMODE = 4; + public const byte VMCF_JUMP = 8; + public const byte VMCF_PROC = 16; + public const byte VMCF_USEFLAGS = 32; + public const byte VMCF_CHFLAGS = 64; + + public static byte[] VM_CmdFlags = + { + VMCF_OP2 | VMCF_BYTEMODE, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP1 | VMCF_JUMP, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS, + VMCF_OP1, + VMCF_OP1, + VMCF_OP1 | VMCF_PROC, + VMCF_OP0 | VMCF_PROC, + VMCF_OP1 | VMCF_BYTEMODE, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS, + VMCF_OP0, + VMCF_OP0, + VMCF_OP0 | VMCF_USEFLAGS, + VMCF_OP0 | VMCF_CHFLAGS, + VMCF_OP2, + VMCF_OP2, + VMCF_OP2 | VMCF_BYTEMODE, + VMCF_OP2 | VMCF_BYTEMODE, + VMCF_OP2 | VMCF_BYTEMODE, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS, + VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS, + VMCF_OP0 + }; +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMCommands.cs b/src/SharpCompress/Compressors/Rar/VM/VMCommands.cs index 5164c4c5..7bb1a396 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMCommands.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMCommands.cs @@ -1,78 +1,70 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal enum VMCommands { - internal enum VMCommands - { - VM_MOV = 0, - VM_CMP = 1, - VM_ADD = 2, - VM_SUB = 3, - VM_JZ = 4, - VM_JNZ = 5, - VM_INC = 6, + VM_MOV = 0, + VM_CMP = 1, + VM_ADD = 2, + VM_SUB = 3, + VM_JZ = 4, + VM_JNZ = 5, + VM_INC = 6, - VM_DEC = - 7, - VM_JMP = 8, - VM_XOR = 9, - VM_AND = 10, - VM_OR = 11, - VM_TEST = 12, + VM_DEC = 7, + VM_JMP = 8, + VM_XOR = 9, + VM_AND = 10, + VM_OR = 11, + VM_TEST = 12, - VM_JS = - 13, - VM_JNS = 14, - VM_JB = 15, - VM_JBE = 16, - VM_JA = 17, - VM_JAE = 18, + VM_JS = 13, + VM_JNS = 14, + VM_JB = 15, + VM_JBE = 16, + VM_JA = 17, + VM_JAE = 18, - VM_PUSH = - 19, - VM_POP = 20, - VM_CALL = 21, - VM_RET = 22, - VM_NOT = 23, - VM_SHL = 24, + VM_PUSH = 19, + VM_POP = 20, + VM_CALL = 21, + VM_RET = 22, + VM_NOT = 23, + VM_SHL = 24, - VM_SHR = - 25, - VM_SAR = 26, - VM_NEG = 27, - VM_PUSHA = 28, - VM_POPA = 29, - VM_PUSHF = 30, + VM_SHR = 25, + VM_SAR = 26, + VM_NEG = 27, + VM_PUSHA = 28, + VM_POPA = 29, + VM_PUSHF = 30, - VM_POPF = - 31, - VM_MOVZX = 32, - VM_MOVSX = 33, - VM_XCHG = 34, - VM_MUL = 35, - VM_DIV = 36, + VM_POPF = 31, + VM_MOVZX = 32, + VM_MOVSX = 33, + VM_XCHG = 34, + VM_MUL = 35, + VM_DIV = 36, - VM_ADC = - 37, - VM_SBB = 38, - VM_PRINT = 39, + VM_ADC = 37, + VM_SBB = 38, + VM_PRINT = 39, - VM_MOVB = 40, - VM_MOVD = 41, - VM_CMPB = 42, - VM_CMPD = 43, + VM_MOVB = 40, + VM_MOVD = 41, + VM_CMPB = 42, + VM_CMPD = 43, - VM_ADDB = 44, - VM_ADDD = 45, - VM_SUBB = 46, - VM_SUBD = 47, - VM_INCB = 48, - VM_INCD = 49, + VM_ADDB = 44, + VM_ADDD = 45, + VM_SUBB = 46, + VM_SUBD = 47, + VM_INCB = 48, + VM_INCD = 49, - VM_DECB = - 50, - VM_DECD = 51, - VM_NEGB = 52, - VM_NEGD = 53, + VM_DECB = 50, + VM_DECD = 51, + VM_NEGB = 52, + VM_NEGD = 53, - VM_STANDARD = 54 - } -} \ No newline at end of file + VM_STANDARD = 54 +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMFlags.cs b/src/SharpCompress/Compressors/Rar/VM/VMFlags.cs index f0c8fd72..fa9a72e7 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMFlags.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMFlags.cs @@ -1,10 +1,9 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal enum VMFlags { - internal enum VMFlags - { - None = 0, - VM_FC = 1, - VM_FZ = 2, - VM_FS = 80000000 - } -} \ No newline at end of file + None = 0, + VM_FC = 1, + VM_FZ = 2, + VM_FS = 80000000 +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMOpType.cs b/src/SharpCompress/Compressors/Rar/VM/VMOpType.cs index 9ea99d1a..bcc4214e 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMOpType.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMOpType.cs @@ -1,10 +1,9 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal enum VMOpType { - internal enum VMOpType - { - VM_OPREG = 0, - VM_OPINT = 1, - VM_OPREGMEM = 2, - VM_OPNONE = 3 - } -} \ No newline at end of file + VM_OPREG = 0, + VM_OPINT = 1, + VM_OPREGMEM = 2, + VM_OPNONE = 3 +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMPreparedCommand.cs b/src/SharpCompress/Compressors/Rar/VM/VMPreparedCommand.cs index fc3238b5..e6fed0b1 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMPreparedCommand.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMPreparedCommand.cs @@ -1,17 +1,16 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal class VMPreparedCommand { - internal class VMPreparedCommand + internal VMPreparedCommand() { - internal VMPreparedCommand() - { - Op1 = new VMPreparedOperand(); - Op2 = new VMPreparedOperand(); - } - - internal VMCommands OpCode { get; set; } - internal bool IsByteMode { get; set; } - internal VMPreparedOperand Op1 { get; } - - internal VMPreparedOperand Op2 { get; } + Op1 = new VMPreparedOperand(); + Op2 = new VMPreparedOperand(); } -} \ No newline at end of file + + internal VMCommands OpCode { get; set; } + internal bool IsByteMode { get; set; } + internal VMPreparedOperand Op1 { get; } + + internal VMPreparedOperand Op2 { get; } +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMPreparedOperand.cs b/src/SharpCompress/Compressors/Rar/VM/VMPreparedOperand.cs index b8cb10d9..af1be450 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMPreparedOperand.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMPreparedOperand.cs @@ -1,10 +1,9 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal class VMPreparedOperand { - internal class VMPreparedOperand - { - internal VMOpType Type { get; set; } - internal int Data { get; set; } - internal int Base { get; set; } - internal int Offset { get; set; } - } -} \ No newline at end of file + internal VMOpType Type { get; set; } + internal int Data { get; set; } + internal int Base { get; set; } + internal int Offset { get; set; } +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMPreparedProgram.cs b/src/SharpCompress/Compressors/Rar/VM/VMPreparedProgram.cs index 94da8a76..c0006e09 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMPreparedProgram.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMPreparedProgram.cs @@ -1,21 +1,20 @@ using System.Collections.Generic; -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal class VMPreparedProgram { - internal class VMPreparedProgram - { - internal List Commands = new List(); - internal List AltCommands = new List(); + internal List Commands = new List(); + internal List AltCommands = new List(); - public int CommandCount { get; set; } + public int CommandCount { get; set; } - internal List GlobalData = new List(); - internal List StaticData = new List(); + internal List GlobalData = new List(); + internal List StaticData = new List(); - // static data contained in DB operators - internal int[] InitR = new int[7]; + // static data contained in DB operators + internal int[] InitR = new int[7]; - internal int FilteredDataOffset { get; set; } - internal int FilteredDataSize { get; set; } - } -} \ No newline at end of file + internal int FilteredDataOffset { get; set; } + internal int FilteredDataSize { get; set; } +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMStandardFilterSignature.cs b/src/SharpCompress/Compressors/Rar/VM/VMStandardFilterSignature.cs index a5812ed0..a9096041 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMStandardFilterSignature.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMStandardFilterSignature.cs @@ -1,18 +1,17 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal class VMStandardFilterSignature { - internal class VMStandardFilterSignature + internal VMStandardFilterSignature(int length, uint crc, VMStandardFilters type) { - internal VMStandardFilterSignature(int length, uint crc, VMStandardFilters type) - { - Length = length; - CRC = crc; - Type = type; - } - - internal int Length { get; } - - internal uint CRC { get; } - - internal VMStandardFilters Type { get; } + Length = length; + CRC = crc; + Type = type; } -} \ No newline at end of file + + internal int Length { get; } + + internal uint CRC { get; } + + internal VMStandardFilters Type { get; } +} diff --git a/src/SharpCompress/Compressors/Rar/VM/VMStandardFilters.cs b/src/SharpCompress/Compressors/Rar/VM/VMStandardFilters.cs index 07f74348..4b90b019 100644 --- a/src/SharpCompress/Compressors/Rar/VM/VMStandardFilters.cs +++ b/src/SharpCompress/Compressors/Rar/VM/VMStandardFilters.cs @@ -1,14 +1,13 @@ -namespace SharpCompress.Compressors.Rar.VM +namespace SharpCompress.Compressors.Rar.VM; + +internal enum VMStandardFilters { - internal enum VMStandardFilters - { - VMSF_NONE = 0, - VMSF_E8 = 1, - VMSF_E8E9 = 2, - VMSF_ITANIUM = 3, - VMSF_RGB = 4, - VMSF_AUDIO = 5, - VMSF_DELTA = 6, - VMSF_UPCASE = 7 - } -} \ No newline at end of file + VMSF_NONE = 0, + VMSF_E8 = 1, + VMSF_E8E9 = 2, + VMSF_ITANIUM = 3, + VMSF_RGB = 4, + VMSF_AUDIO = 5, + VMSF_DELTA = 6, + VMSF_UPCASE = 7 +} diff --git a/src/SharpCompress/Compressors/Xz/BinaryUtils.cs b/src/SharpCompress/Compressors/Xz/BinaryUtils.cs index 12f34bf7..8e08ff98 100644 --- a/src/SharpCompress/Compressors/Xz/BinaryUtils.cs +++ b/src/SharpCompress/Compressors/Xz/BinaryUtils.cs @@ -1,59 +1,56 @@ -using System; +using System; using System.Buffers.Binary; using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public static class BinaryUtils { - public static class BinaryUtils + public static int ReadLittleEndianInt32(this BinaryReader reader) { - public static int ReadLittleEndianInt32(this BinaryReader reader) + var bytes = reader.ReadBytes(4); + return BinaryPrimitives.ReadInt32LittleEndian(bytes); + } + + internal static uint ReadLittleEndianUInt32(this BinaryReader reader) => + unchecked((uint)ReadLittleEndianInt32(reader)); + + public static int ReadLittleEndianInt32(this Stream stream) + { + Span bytes = stackalloc byte[4]; + var read = stream.ReadFully(bytes); + if (!read) { - byte[] bytes = reader.ReadBytes(4); - return BinaryPrimitives.ReadInt32LittleEndian(bytes); + throw new EndOfStreamException(); + } + return BinaryPrimitives.ReadInt32LittleEndian(bytes); + } + + internal static uint ReadLittleEndianUInt32(this Stream stream) => + unchecked((uint)ReadLittleEndianInt32(stream)); + + internal static byte[] ToBigEndianBytes(this uint uint32) + { + var result = BitConverter.GetBytes(uint32); + + if (BitConverter.IsLittleEndian) + { + Array.Reverse(result); } - internal static uint ReadLittleEndianUInt32(this BinaryReader reader) + return result; + } + + public static byte[] ToLittleEndianBytes(this uint uint32) + { + var result = BitConverter.GetBytes(uint32); + + if (!BitConverter.IsLittleEndian) { - return unchecked((uint)ReadLittleEndianInt32(reader)); - } - public static int ReadLittleEndianInt32(this Stream stream) - { - Span bytes = stackalloc byte[4]; - var read = stream.ReadFully(bytes); - if (!read) - { - throw new EndOfStreamException(); - } - return BinaryPrimitives.ReadInt32LittleEndian(bytes); + Array.Reverse(result); } - internal static uint ReadLittleEndianUInt32(this Stream stream) - { - return unchecked((uint)ReadLittleEndianInt32(stream)); - } - - internal static byte[] ToBigEndianBytes(this uint uint32) - { - var result = BitConverter.GetBytes(uint32); - - if (BitConverter.IsLittleEndian) - { - Array.Reverse(result); - } - - return result; - } - - internal static byte[] ToLittleEndianBytes(this uint uint32) - { - var result = BitConverter.GetBytes(uint32); - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(result); - } - - return result; - } + return result; } } diff --git a/src/SharpCompress/Compressors/Xz/CheckType.cs b/src/SharpCompress/Compressors/Xz/CheckType.cs index 19c8019a..a94f8261 100644 --- a/src/SharpCompress/Compressors/Xz/CheckType.cs +++ b/src/SharpCompress/Compressors/Xz/CheckType.cs @@ -1,10 +1,9 @@ -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +public enum CheckType : byte { - public enum CheckType : byte - { - NONE = 0x00, - CRC32 = 0x01, - CRC64 = 0x04, - SHA256 = 0x0A - } -} \ No newline at end of file + NONE = 0x00, + CRC32 = 0x01, + CRC64 = 0x04, + SHA256 = 0x0A +} diff --git a/src/SharpCompress/Compressors/Xz/Crc32.cs b/src/SharpCompress/Compressors/Xz/Crc32.cs index 01a90791..611238c5 100644 --- a/src/SharpCompress/Compressors/Xz/Crc32.cs +++ b/src/SharpCompress/Compressors/Xz/Crc32.cs @@ -1,75 +1,68 @@ -#nullable disable +#nullable disable using System; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public static class Crc32 { - internal static class Crc32 + public const uint DefaultPolynomial = 0xedb88320u; + public const uint DefaultSeed = 0xffffffffu; + + private static uint[] defaultTable; + + public static uint Compute(byte[] buffer) => Compute(DefaultSeed, buffer); + + public static uint Compute(uint seed, byte[] buffer) => + Compute(DefaultPolynomial, seed, buffer); + + public static uint Compute(uint polynomial, uint seed, byte[] buffer) => + ~CalculateHash(InitializeTable(polynomial), seed, buffer); + + private static uint[] InitializeTable(uint polynomial) { - public const UInt32 DefaultPolynomial = 0xedb88320u; - public const UInt32 DefaultSeed = 0xffffffffu; - - private static UInt32[] defaultTable; - - public static UInt32 Compute(byte[] buffer) + if (polynomial == DefaultPolynomial && defaultTable != null) { - return Compute(DefaultSeed, buffer); + return defaultTable; } - public static UInt32 Compute(UInt32 seed, byte[] buffer) + var createTable = new uint[256]; + for (var i = 0; i < 256; i++) { - return Compute(DefaultPolynomial, seed, buffer); - } - - public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) - { - return ~CalculateHash(InitializeTable(polynomial), seed, buffer); - } - - private static UInt32[] InitializeTable(UInt32 polynomial) - { - if (polynomial == DefaultPolynomial && defaultTable != null) + var entry = (uint)i; + for (var j = 0; j < 8; j++) { - return defaultTable; - } - - var createTable = new UInt32[256]; - for (var i = 0; i < 256; i++) - { - var entry = (UInt32)i; - for (var j = 0; j < 8; j++) + if ((entry & 1) == 1) { - if ((entry & 1) == 1) - { - entry = (entry >> 1) ^ polynomial; - } - else - { - entry = entry >> 1; - } + entry = (entry >> 1) ^ polynomial; + } + else + { + entry >>= 1; } - - createTable[i] = entry; } - if (polynomial == DefaultPolynomial) - { - defaultTable = createTable; - } - - return createTable; + createTable[i] = entry; } - private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, ReadOnlySpan buffer) + if (polynomial == DefaultPolynomial) { - var crc = seed; - int len = buffer.Length; - for (var i = 0; i < len; i++) - { - crc = (crc >> 8) ^ table[(buffer[i] ^ crc) & 0xff]; - } - - return crc; + defaultTable = createTable; } + + return createTable; + } + + private static uint CalculateHash(uint[] table, uint seed, ReadOnlySpan buffer) + { + var crc = seed; + var len = buffer.Length; + for (var i = 0; i < len; i++) + { + crc = (crc >> 8) ^ table[(buffer[i] ^ crc) & 0xff]; + } + + return crc; } } diff --git a/src/SharpCompress/Compressors/Xz/Crc64.cs b/src/SharpCompress/Compressors/Xz/Crc64.cs index 1cc9e1b3..2c7df9d5 100644 --- a/src/SharpCompress/Compressors/Xz/Crc64.cs +++ b/src/SharpCompress/Compressors/Xz/Crc64.cs @@ -1,67 +1,62 @@ -#nullable disable +#nullable disable using System; -using System.Collections.Generic; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public static class Crc64 { - internal static class Crc64 + public const ulong DefaultSeed = 0x0; + + internal static ulong[] Table; + + public const ulong Iso3309Polynomial = 0xD800000000000000; + + public static ulong Compute(byte[] buffer) => Compute(DefaultSeed, buffer); + + public static ulong Compute(ulong seed, byte[] buffer) { - public const UInt64 DefaultSeed = 0x0; + Table ??= CreateTable(Iso3309Polynomial); - internal static UInt64[] Table; - - public const UInt64 Iso3309Polynomial = 0xD800000000000000; - - public static UInt64 Compute(byte[] buffer) - { - return Compute(DefaultSeed, buffer); - } - - public static UInt64 Compute(UInt64 seed, byte[] buffer) - { - Table ??= CreateTable(Iso3309Polynomial); - - return CalculateHash(seed, Table, buffer); - } - - public static UInt64 CalculateHash(UInt64 seed, UInt64[] table, ReadOnlySpan buffer) - { - var crc = seed; - int len = buffer.Length; - for (var i = 0; i < len; i++) - { - unchecked - { - crc = (crc >> 8) ^ table[(buffer[i] ^ crc) & 0xff]; - } - } - - return crc; - } - - public static ulong[] CreateTable(ulong polynomial) - { - var createTable = new UInt64[256]; - for (var i = 0; i < 256; ++i) - { - var entry = (UInt64)i; - for (var j = 0; j < 8; ++j) - { - if ((entry & 1) == 1) - { - entry = (entry >> 1) ^ polynomial; - } - else - { - entry = entry >> 1; - } - } - - createTable[i] = entry; - } - return createTable; - } + return CalculateHash(seed, Table, buffer); } + public static ulong CalculateHash(ulong seed, ulong[] table, ReadOnlySpan buffer) + { + var crc = seed; + var len = buffer.Length; + for (var i = 0; i < len; i++) + { + unchecked + { + crc = (crc >> 8) ^ table[(buffer[i] ^ crc) & 0xff]; + } + } + + return crc; + } + + public static ulong[] CreateTable(ulong polynomial) + { + var createTable = new ulong[256]; + for (var i = 0; i < 256; ++i) + { + var entry = (ulong)i; + for (var j = 0; j < 8; ++j) + { + if ((entry & 1) == 1) + { + entry = (entry >> 1) ^ polynomial; + } + else + { + entry >>= 1; + } + } + + createTable[i] = entry; + } + return createTable; + } } diff --git a/src/SharpCompress/Compressors/Xz/Filters/ArmFilter.cs b/src/SharpCompress/Compressors/Xz/Filters/ArmFilter.cs index 9f8b5ad7..af1e99d3 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/ArmFilter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/ArmFilter.cs @@ -4,65 +4,53 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class ArmFilter : BlockFilter { - internal class ArmFilter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) + if (properties.Length != 0 && properties.Length != 4) { - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("ARM properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("ARM properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_ARM_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + throw new InvalidDataException("ARM properties unexpected length"); } - public override void ValidateFilter() + if (properties.Length == 4) { + // Even XZ doesn't support it. + throw new InvalidDataException("ARM properties offset is not supported"); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.ARMConverter(buffer, _ip); - _ip += (UInt32)bytesRead; - return bytesRead; - } - - public override void SetBaseStream(Stream stream) - { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_ARM_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.ARMConverter(buffer, _ip); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/Filters/ArmThumbFilter.cs b/src/SharpCompress/Compressors/Xz/Filters/ArmThumbFilter.cs index db79e5e9..f3ec7b1b 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/ArmThumbFilter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/ArmThumbFilter.cs @@ -4,66 +4,53 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class ArmThumbFilter : BlockFilter { - internal class ArmThumbFilter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) + if (properties.Length != 0 && properties.Length != 4) { - - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("ARM Thumb properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("ARM Thumb properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_ARMTHUMB_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + throw new InvalidDataException("ARM Thumb properties unexpected length"); } - public override void ValidateFilter() + if (properties.Length == 4) { + // Even XZ doesn't support it. + throw new InvalidDataException("ARM Thumb properties offset is not supported"); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.ARMTConverter(buffer, _ip); - _ip += (UInt32)bytesRead; - return bytesRead; - } - - public override void SetBaseStream(Stream stream) - { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_ARMTHUMB_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.ARMTConverter(buffer, _ip); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/Filters/BlockFilter.cs b/src/SharpCompress/Compressors/Xz/Filters/BlockFilter.cs index 2f7e2769..ecfe7ae0 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/BlockFilter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/BlockFilter.cs @@ -2,62 +2,64 @@ using System; using System.Collections.Generic; using System.IO; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public abstract class BlockFilter : ReadOnlyStream { - internal abstract class BlockFilter : ReadOnlyStream + [CLSCompliant(false)] + public enum FilterTypes : ulong { - public enum FilterTypes : ulong - { - DELTA = 0x03, - ARCH_x86_FILTER = 0x04, - ARCH_PowerPC_FILTER = 0x05, - ARCH_IA64_FILTER = 0x06, - ARCH_ARM_FILTER = 0x07, - ARCH_ARMTHUMB_FILTER = 0x08, - ARCH_SPARC_FILTER = 0x09, - LZMA2 = 0x21 - } - - private static readonly Dictionary FilterMap = new Dictionary - { - {FilterTypes.ARCH_x86_FILTER, typeof(X86Filter) }, - {FilterTypes.ARCH_PowerPC_FILTER, typeof(PowerPCFilter) }, - {FilterTypes.ARCH_IA64_FILTER, typeof(IA64Filter) }, - {FilterTypes.ARCH_ARM_FILTER, typeof(ArmFilter) }, - {FilterTypes.ARCH_ARMTHUMB_FILTER, typeof(ArmThumbFilter) }, - {FilterTypes.ARCH_SPARC_FILTER, typeof(SparcFilter) }, - {FilterTypes.LZMA2, typeof(Lzma2Filter) } - }; - - public abstract bool AllowAsLast { get; } - public abstract bool AllowAsNonLast { get; } - public abstract bool ChangesDataSize { get; } - - public abstract void Init(byte[] properties); - public abstract void ValidateFilter(); - - public FilterTypes FilterType { get; set; } - public static BlockFilter Read(BinaryReader reader) - { - var filterType = (FilterTypes)reader.ReadXZInteger(); - if (!FilterMap.ContainsKey(filterType)) - { - throw new NotImplementedException($"Filter {filterType} has not yet been implemented"); - } - - var filter = (BlockFilter)Activator.CreateInstance(FilterMap[filterType])!; - - var sizeOfProperties = reader.ReadXZInteger(); - if (sizeOfProperties > int.MaxValue) - { - throw new InvalidDataException("Block filter information too large"); - } - - byte[] properties = reader.ReadBytes((int)sizeOfProperties); - filter.Init(properties); - return filter; - } - - public abstract void SetBaseStream(Stream stream); + DELTA = 0x03, + ARCH_x86_FILTER = 0x04, + ARCH_PowerPC_FILTER = 0x05, + ARCH_IA64_FILTER = 0x06, + ARCH_ARM_FILTER = 0x07, + ARCH_ARMTHUMB_FILTER = 0x08, + ARCH_SPARC_FILTER = 0x09, + LZMA2 = 0x21 } + + private static readonly Dictionary FilterMap = new Dictionary< + FilterTypes, + Type + > + { + { FilterTypes.ARCH_x86_FILTER, typeof(X86Filter) }, + { FilterTypes.ARCH_PowerPC_FILTER, typeof(PowerPCFilter) }, + { FilterTypes.ARCH_IA64_FILTER, typeof(IA64Filter) }, + { FilterTypes.ARCH_ARM_FILTER, typeof(ArmFilter) }, + { FilterTypes.ARCH_ARMTHUMB_FILTER, typeof(ArmThumbFilter) }, + { FilterTypes.ARCH_SPARC_FILTER, typeof(SparcFilter) }, + { FilterTypes.LZMA2, typeof(Lzma2Filter) } + }; + + public abstract bool AllowAsLast { get; } + public abstract bool AllowAsNonLast { get; } + public abstract bool ChangesDataSize { get; } + + public abstract void Init(byte[] properties); + public abstract void ValidateFilter(); + + public static BlockFilter Read(BinaryReader reader) + { + var filterType = (FilterTypes)reader.ReadXZInteger(); + if (!FilterMap.ContainsKey(filterType)) + { + throw new NotImplementedException($"Filter {filterType} has not yet been implemented"); + } + + var filter = (BlockFilter)Activator.CreateInstance(FilterMap[filterType])!; + + var sizeOfProperties = reader.ReadXZInteger(); + if (sizeOfProperties > int.MaxValue) + { + throw new InvalidDataException("Block filter information too large"); + } + + var properties = reader.ReadBytes((int)sizeOfProperties); + filter.Init(properties); + return filter; + } + + public abstract void SetBaseStream(Stream stream); } diff --git a/src/SharpCompress/Compressors/Xz/Filters/IA64Filter.cs b/src/SharpCompress/Compressors/Xz/Filters/IA64Filter.cs index a4fb9f1f..dc04c71b 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/IA64Filter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/IA64Filter.cs @@ -4,64 +4,53 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class IA64Filter : BlockFilter { - internal class IA64Filter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) + if (properties.Length != 0 && properties.Length != 4) { - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("IA64 properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("IA64 properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_IA64_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + throw new InvalidDataException("IA64 properties unexpected length"); } - public override void ValidateFilter() + if (properties.Length == 4) { + // Even XZ doesn't support it. + throw new InvalidDataException("IA64 properties offset is not supported"); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.IA64Converter(buffer, _ip); - _ip += (UInt32)bytesRead; - return bytesRead; - } - public override void SetBaseStream(Stream stream) - { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_IA64_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.IA64Converter(buffer, _ip); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/Filters/Lzma2Filter.cs b/src/SharpCompress/Compressors/Xz/Filters/Lzma2Filter.cs index 0a842f68..bed59b76 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/Lzma2Filter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/Lzma2Filter.cs @@ -1,67 +1,58 @@ -using System; +using System; using System.IO; using SharpCompress.Compressors.LZMA; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +[CLSCompliant(false)] +public class Lzma2Filter : BlockFilter { - internal class Lzma2Filter : BlockFilter + public override bool AllowAsLast => true; + public override bool AllowAsNonLast => false; + public override bool ChangesDataSize => true; + + private byte _dictionarySize; + public uint DictionarySize { - public override bool AllowAsLast => true; - public override bool AllowAsNonLast => false; - public override bool ChangesDataSize => true; - - private byte _dictionarySize; - public uint DictionarySize + get { - get + if (_dictionarySize > 40) { - if (_dictionarySize > 40) - { - throw new OverflowException("Dictionary size greater than UInt32.Max"); - } - - if (_dictionarySize == 40) - { - return uint.MaxValue; - } - int mantissa = 2 | (_dictionarySize & 1); - int exponent = _dictionarySize / 2 + 11; - return (uint)mantissa << exponent; - } - } - - public override void Init(byte[] properties) - { - if (properties.Length != 1) - { - throw new InvalidDataException("LZMA properties unexpected length"); + throw new OverflowException("Dictionary size greater than UInt32.Max"); } - _dictionarySize = (byte)(properties[0] & 0x3F); - var reserved = properties[0] & 0xC0; - if (reserved != 0) + if (_dictionarySize == 40) { - throw new InvalidDataException("Reserved bits used in LZMA properties"); + return uint.MaxValue; } - } - - public override void ValidateFilter() - { - } - - public override void SetBaseStream(Stream stream) - { - BaseStream = new LzmaStream(new[] { _dictionarySize }, stream); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return BaseStream.Read(buffer, offset, count); - } - - public override int ReadByte() - { - return BaseStream.ReadByte(); + var mantissa = 2 | (_dictionarySize & 1); + var exponent = (_dictionarySize / 2) + 11; + return (uint)mantissa << exponent; } } + + public override void Init(byte[] properties) + { + if (properties.Length != 1) + { + throw new InvalidDataException("LZMA properties unexpected length"); + } + + _dictionarySize = (byte)(properties[0] & 0x3F); + var reserved = properties[0] & 0xC0; + if (reserved != 0) + { + throw new InvalidDataException("Reserved bits used in LZMA properties"); + } + } + + public override void ValidateFilter() { } + + public override void SetBaseStream(Stream stream) => + BaseStream = new LzmaStream(new[] { _dictionarySize }, stream); + + public override int Read(byte[] buffer, int offset, int count) => + BaseStream.Read(buffer, offset, count); + + public override int ReadByte() => BaseStream.ReadByte(); } diff --git a/src/SharpCompress/Compressors/Xz/Filters/PowerPCFilter.cs b/src/SharpCompress/Compressors/Xz/Filters/PowerPCFilter.cs index 9c072521..7a03a3fe 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/PowerPCFilter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/PowerPCFilter.cs @@ -4,63 +4,53 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class PowerPCFilter : BlockFilter { - internal class PowerPCFilter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) + if (properties.Length != 0 && properties.Length != 4) { - - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("PPC properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("PPC properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_PowerPC_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + throw new InvalidDataException("PPC properties unexpected length"); } - public override void ValidateFilter() { } - - public override int Read(byte[] buffer, int offset, int count) + if (properties.Length == 4) { - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.PowerPCConverter(buffer, _ip); - _ip += (UInt32)bytesRead; - return bytesRead; - } + // Even XZ doesn't support it. + throw new InvalidDataException("PPC properties offset is not supported"); - public override void SetBaseStream(Stream stream) - { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_PowerPC_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.PowerPCConverter(buffer, _ip); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/Filters/SparcFilter.cs b/src/SharpCompress/Compressors/Xz/Filters/SparcFilter.cs index eb031e97..9b74d344 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/SparcFilter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/SparcFilter.cs @@ -4,62 +4,53 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class SparcFilter : BlockFilter { - internal class SparcFilter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) { - - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("SPARC properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("SPARC properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_SPARC_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + if (properties.Length != 0 && properties.Length != 4) + { + throw new InvalidDataException("SPARC properties unexpected length"); } - public override void ValidateFilter() { + if (properties.Length == 4) + { + // Even XZ doesn't support it. + throw new InvalidDataException("SPARC properties offset is not supported"); - } - - public override int Read(byte[] buffer, int offset, int count){ - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.SPARCConverter(buffer, _ip); - _ip += (UInt32)bytesRead; - return bytesRead; - } - - public override void SetBaseStream(Stream stream) { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_SPARC_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.SPARCConverter(buffer, _ip); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/Filters/X86Filter.cs b/src/SharpCompress/Compressors/Xz/Filters/X86Filter.cs index 43115e42..74dbfb1d 100644 --- a/src/SharpCompress/Compressors/Xz/Filters/X86Filter.cs +++ b/src/SharpCompress/Compressors/Xz/Filters/X86Filter.cs @@ -4,67 +4,55 @@ * @TODO Properties offset */ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Compressors.Filters; -namespace SharpCompress.Compressors.Xz.Filters +namespace SharpCompress.Compressors.Xz.Filters; + +public class X86Filter : BlockFilter { - internal class X86Filter : BlockFilter + public override bool AllowAsLast => false; + + public override bool AllowAsNonLast => true; + + public override bool ChangesDataSize => false; + + private uint _ip = 0; + + private uint _state = 0; + + //private UInt32 _offset = 0; + + public override void Init(byte[] properties) { - public override bool AllowAsLast => false; - - public override bool AllowAsNonLast => true; - - public override bool ChangesDataSize => false; - - private UInt32 _ip = 0; - - private UInt32 _state = 0; - - //private UInt32 _offset = 0; - - public override void Init(byte[] properties) + if (properties.Length != 0 && properties.Length != 4) { - if (properties.Length != 0 && properties.Length != 4) - { - throw new InvalidDataException("X86 properties unexpected length"); - } - - if (properties.Length == 4) - { - // Even XZ doesn't support it. - throw new InvalidDataException("X86 properties offset is not supported"); - - //_offset = BitConverter.ToUInt32(properties, 0); - // - //if (_offset % (UInt32)BranchExec.Alignment.ARCH_x86_ALIGNMENT != 0) - //{ - // throw new InvalidDataException("Filter offset does not match alignment"); - //} - } + throw new InvalidDataException("X86 properties unexpected length"); } - public override void ValidateFilter() + if (properties.Length == 4) { + // Even XZ doesn't support it. + throw new InvalidDataException("X86 properties offset is not supported"); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = BaseStream.Read(buffer, offset, count); - BranchExecFilter.X86Converter(buffer, _ip, ref _state); - _ip += (UInt32)bytesRead; - return bytesRead; - } - - public override void SetBaseStream(Stream stream) - { - BaseStream = stream; + //_offset = BitConverter.ToUInt32(properties, 0); + // + //if (_offset % (UInt32)BranchExec.Alignment.ARCH_x86_ALIGNMENT != 0) + //{ + // throw new InvalidDataException("Filter offset does not match alignment"); + //} } } + + public override void ValidateFilter() { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = BaseStream.Read(buffer, offset, count); + BranchExecFilter.X86Converter(buffer, _ip, ref _state); + _ip += (uint)bytesRead; + return bytesRead; + } + + public override void SetBaseStream(Stream stream) => BaseStream = stream; } diff --git a/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs b/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs index 27f94644..a38505da 100644 --- a/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs +++ b/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs @@ -1,42 +1,41 @@ -using System; +using System; using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +internal static class MultiByteIntegers { - internal static class MultiByteIntegers + public static ulong ReadXZInteger(this BinaryReader reader, int MaxBytes = 9) { - public static ulong ReadXZInteger(this BinaryReader reader, int MaxBytes = 9) + if (MaxBytes <= 0) { - if (MaxBytes <= 0) - { - throw new ArgumentOutOfRangeException(nameof(MaxBytes)); - } - - if (MaxBytes > 9) - { - MaxBytes = 9; - } - - byte LastByte = reader.ReadByte(); - ulong Output = (ulong)LastByte & 0x7F; - - int i = 0; - while ((LastByte & 0x80) != 0) - { - if (++i >= MaxBytes) - { - throw new InvalidDataException(); - } - - LastByte = reader.ReadByte(); - if (LastByte == 0) - { - throw new InvalidDataException(); - } - - Output |= ((ulong)(LastByte & 0x7F)) << (i * 7); - } - return Output; + throw new ArgumentOutOfRangeException(nameof(MaxBytes)); } + + if (MaxBytes > 9) + { + MaxBytes = 9; + } + + var LastByte = reader.ReadByte(); + var Output = (ulong)LastByte & 0x7F; + + var i = 0; + while ((LastByte & 0x80) != 0) + { + if (++i >= MaxBytes) + { + throw new InvalidDataException(); + } + + LastByte = reader.ReadByte(); + if (LastByte == 0) + { + throw new InvalidDataException(); + } + + Output |= ((ulong)(LastByte & 0x7F)) << (i * 7); + } + return Output; } } diff --git a/src/SharpCompress/Compressors/Xz/ReadOnlyStream.cs b/src/SharpCompress/Compressors/Xz/ReadOnlyStream.cs index 0c1b53b4..71614df9 100644 --- a/src/SharpCompress/Compressors/Xz/ReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Xz/ReadOnlyStream.cs @@ -3,44 +3,32 @@ using System; using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +public abstract class ReadOnlyStream : Stream { - public abstract class ReadOnlyStream : Stream + public Stream BaseStream { get; protected set; } + + public override bool CanRead => BaseStream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { - public Stream BaseStream { get; protected set; } - - public override bool CanRead => BaseStream.CanRead; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } + + public override void Flush() => throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); } diff --git a/src/SharpCompress/Compressors/Xz/XZBlock.cs b/src/SharpCompress/Compressors/Xz/XZBlock.cs index 6827093b..5b948500 100644 --- a/src/SharpCompress/Compressors/Xz/XZBlock.cs +++ b/src/SharpCompress/Compressors/Xz/XZBlock.cs @@ -1,211 +1,217 @@ -#nullable disable +#nullable disable +using System; using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Compressors.Xz.Filters; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public sealed class XZBlock : XZReadOnlyStream { - internal sealed class XZBlock : XZReadOnlyStream + public int BlockHeaderSize => (_blockHeaderSizeByte + 1) * 4; + public ulong? CompressedSize { get; private set; } + public ulong? UncompressedSize { get; private set; } + public Stack Filters { get; private set; } = new Stack(); + public bool HeaderIsLoaded { get; private set; } + private CheckType _checkType; + private readonly int _checkSize; + private bool _streamConnected; + private int _numFilters; + private byte _blockHeaderSizeByte; + private Stream _decomStream; + private bool _endOfStream; + private bool _paddingSkipped; + private bool _crcChecked; + private ulong _bytesRead; + + public XZBlock(Stream stream, CheckType checkType, int checkSize) : base(stream) { - public int BlockHeaderSize => (_blockHeaderSizeByte + 1) * 4; - public ulong? CompressedSize { get; private set; } - public ulong? UncompressedSize { get; private set; } - public Stack Filters { get; private set; } = new Stack(); - public bool HeaderIsLoaded { get; private set; } - private CheckType _checkType; - private readonly int _checkSize; - private bool _streamConnected; - private int _numFilters; - private byte _blockHeaderSizeByte; - private Stream _decomStream; - private bool _endOfStream; - private bool _paddingSkipped; - private bool _crcChecked; - private ulong _bytesRead; + _checkType = checkType; + _checkSize = checkSize; + } - public XZBlock(Stream stream, CheckType checkType, int checkSize) - : base(stream) + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = 0; + if (!HeaderIsLoaded) { - _checkType = checkType; - _checkSize = checkSize; + LoadHeader(); } - public override int Read(byte[] buffer, int offset, int count) + if (!_streamConnected) { - int bytesRead = 0; - if (!HeaderIsLoaded) - { - LoadHeader(); - } - - if (!_streamConnected) - { - ConnectStream(); - } - - if (!_endOfStream) - { - bytesRead = _decomStream.Read(buffer, offset, count); - } - - if (bytesRead != count) - { - _endOfStream = true; - } - - if (_endOfStream && !_paddingSkipped) - { - SkipPadding(); - } - - if (_endOfStream && !_crcChecked) - { - CheckCrc(); - } - - _bytesRead += (ulong)bytesRead; - return bytesRead; + ConnectStream(); } - private void SkipPadding() + if (!_endOfStream) { - int bytes = (int)(BaseStream.Position % 4); - if (bytes > 0) - { - byte[] paddingBytes = new byte[4 - bytes]; - BaseStream.Read(paddingBytes, 0, paddingBytes.Length); - if (paddingBytes.Any(b => b != 0)) - { - throw new InvalidDataException("Padding bytes were non-null"); - } - } - _paddingSkipped = true; + bytesRead = _decomStream.Read(buffer, offset, count); } - private void CheckCrc() + if (bytesRead != count) { - byte[] crc = new byte[_checkSize]; - BaseStream.Read(crc, 0, _checkSize); - // Actually do a check (and read in the bytes - // into the function throughout the stream read). - _crcChecked = true; + _endOfStream = true; } - private void ConnectStream() + if (_endOfStream && !_paddingSkipped) { - _decomStream = BaseStream; - while (Filters.Any()) - { - BlockFilter filter = Filters.Pop(); - filter.SetBaseStream(_decomStream); - _decomStream = filter; - } - _streamConnected = true; + SkipPadding(); } - private void LoadHeader() + if (_endOfStream && !_crcChecked) { - ReadHeaderSize(); - byte[] headerCache = CacheHeader(); - - using (var cache = new MemoryStream(headerCache)) - using (var cachedReader = new BinaryReader(cache)) - { - cachedReader.BaseStream.Position = 1; // skip the header size byte - ReadBlockFlags(cachedReader); - ReadFilters(cachedReader); - } - HeaderIsLoaded = true; + CheckCrc(); } - private void ReadHeaderSize() + _bytesRead += (ulong)bytesRead; + return bytesRead; + } + + private void SkipPadding() + { + var bytes = (int)(BaseStream.Position % 4); + if (bytes > 0) { - _blockHeaderSizeByte = (byte)BaseStream.ReadByte(); - if (_blockHeaderSizeByte == 0) + var paddingBytes = new byte[4 - bytes]; + BaseStream.Read(paddingBytes, 0, paddingBytes.Length); + if (paddingBytes.Any(b => b != 0)) { - throw new XZIndexMarkerReachedException(); + throw new InvalidDataException("Padding bytes were non-null"); } } + _paddingSkipped = true; + } - private byte[] CacheHeader() + private void CheckCrc() + { + var crc = new byte[_checkSize]; + BaseStream.Read(crc, 0, _checkSize); + // Actually do a check (and read in the bytes + // into the function throughout the stream read). + _crcChecked = true; + } + + private void ConnectStream() + { + _decomStream = BaseStream; + while (Filters.Any()) { - byte[] blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4]; - blockHeaderWithoutCrc[0] = _blockHeaderSizeByte; - var read = BaseStream.Read(blockHeaderWithoutCrc, 1, BlockHeaderSize - 5); - if (read != BlockHeaderSize - 5) - { - throw new EndOfStreamException("Reached end of stream unexectedly"); - } + var filter = Filters.Pop(); + filter.SetBaseStream(_decomStream); + _decomStream = filter; + } + _streamConnected = true; + } - uint crc = BaseStream.ReadLittleEndianUInt32(); - uint calcCrc = Crc32.Compute(blockHeaderWithoutCrc); - if (crc != calcCrc) - { - throw new InvalidDataException("Block header corrupt"); - } + private void LoadHeader() + { + ReadHeaderSize(); + var headerCache = CacheHeader(); - return blockHeaderWithoutCrc; + using (var cache = new MemoryStream(headerCache)) + using (var cachedReader = new BinaryReader(cache)) + { + cachedReader.BaseStream.Position = 1; // skip the header size byte + ReadBlockFlags(cachedReader); + ReadFilters(cachedReader); + } + HeaderIsLoaded = true; + } + + private void ReadHeaderSize() + { + _blockHeaderSizeByte = (byte)BaseStream.ReadByte(); + if (_blockHeaderSizeByte == 0) + { + throw new XZIndexMarkerReachedException(); + } + } + + private byte[] CacheHeader() + { + var blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4]; + blockHeaderWithoutCrc[0] = _blockHeaderSizeByte; + var read = BaseStream.Read(blockHeaderWithoutCrc, 1, BlockHeaderSize - 5); + if (read != BlockHeaderSize - 5) + { + throw new EndOfStreamException("Reached end of stream unexectedly"); } - private void ReadBlockFlags(BinaryReader reader) + var crc = BaseStream.ReadLittleEndianUInt32(); + var calcCrc = Crc32.Compute(blockHeaderWithoutCrc); + if (crc != calcCrc) { - var blockFlags = reader.ReadByte(); - _numFilters = (blockFlags & 0x03) + 1; - byte reserved = (byte)(blockFlags & 0x3C); - - if (reserved != 0) - { - throw new InvalidDataException("Reserved bytes used, perhaps an unknown XZ implementation"); - } - - bool compressedSizePresent = (blockFlags & 0x40) != 0; - bool uncompressedSizePresent = (blockFlags & 0x80) != 0; - - if (compressedSizePresent) - { - CompressedSize = reader.ReadXZInteger(); - } - - if (uncompressedSizePresent) - { - UncompressedSize = reader.ReadXZInteger(); - } + throw new InvalidDataException("Block header corrupt"); } - private void ReadFilters(BinaryReader reader, long baseStreamOffset = 0) + return blockHeaderWithoutCrc; + } + + private void ReadBlockFlags(BinaryReader reader) + { + var blockFlags = reader.ReadByte(); + _numFilters = (blockFlags & 0x03) + 1; + var reserved = (byte)(blockFlags & 0x3C); + + if (reserved != 0) { - int nonLastSizeChangers = 0; - for (int i = 0; i < _numFilters; i++) - { - var filter = BlockFilter.Read(reader); - if ((i + 1 == _numFilters && !filter.AllowAsLast) - || (i + 1 < _numFilters && !filter.AllowAsNonLast)) - { - throw new InvalidDataException("Block Filters in bad order"); - } + throw new InvalidDataException( + "Reserved bytes used, perhaps an unknown XZ implementation" + ); + } - if (filter.ChangesDataSize && i + 1 < _numFilters) - { - nonLastSizeChangers++; - } + var compressedSizePresent = (blockFlags & 0x40) != 0; + var uncompressedSizePresent = (blockFlags & 0x80) != 0; - filter.ValidateFilter(); - Filters.Push(filter); - } - if (nonLastSizeChangers > 2) + if (compressedSizePresent) + { + CompressedSize = reader.ReadXZInteger(); + } + + if (uncompressedSizePresent) + { + UncompressedSize = reader.ReadXZInteger(); + } + } + + private void ReadFilters(BinaryReader reader, long baseStreamOffset = 0) + { + var nonLastSizeChangers = 0; + for (var i = 0; i < _numFilters; i++) + { + var filter = BlockFilter.Read(reader); + if ( + (i + 1 == _numFilters && !filter.AllowAsLast) + || (i + 1 < _numFilters && !filter.AllowAsNonLast) + ) { - throw new InvalidDataException("More than two non-last block filters cannot change stream size"); + throw new InvalidDataException("Block Filters in bad order"); } - int blockHeaderPaddingSize = BlockHeaderSize - - (4 + (int)(reader.BaseStream.Position - baseStreamOffset)); - byte[] blockHeaderPadding = reader.ReadBytes(blockHeaderPaddingSize); - if (!blockHeaderPadding.All(b => b == 0)) + if (filter.ChangesDataSize && i + 1 < _numFilters) { - throw new InvalidDataException("Block header contains unknown fields"); + nonLastSizeChangers++; } + + filter.ValidateFilter(); + Filters.Push(filter); + } + if (nonLastSizeChangers > 2) + { + throw new InvalidDataException( + "More than two non-last block filters cannot change stream size" + ); + } + + var blockHeaderPaddingSize = + BlockHeaderSize - (4 + (int)(reader.BaseStream.Position - baseStreamOffset)); + var blockHeaderPadding = reader.ReadBytes(blockHeaderPaddingSize); + if (!blockHeaderPadding.All(b => b == 0)) + { + throw new InvalidDataException("Block header contains unknown fields"); } } } diff --git a/src/SharpCompress/Compressors/Xz/XZFooter.cs b/src/SharpCompress/Compressors/Xz/XZFooter.cs index 52f72619..9751fc2b 100644 --- a/src/SharpCompress/Compressors/Xz/XZFooter.cs +++ b/src/SharpCompress/Compressors/Xz/XZFooter.cs @@ -1,53 +1,53 @@ using System; using System.IO; -using System.Linq; using System.Text; using SharpCompress.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +public class XZFooter { - public class XZFooter + private readonly BinaryReader _reader; + private static ReadOnlySpan _magicBytes => "YZ"u8; + public long StreamStartPosition { get; private set; } + public long BackwardSize { get; private set; } + public byte[]? StreamFlags { get; private set; } + + public XZFooter(BinaryReader reader) { - private readonly BinaryReader _reader; - private static ReadOnlySpan _magicBytes => new byte[] { 0x59, 0x5A }; - public long StreamStartPosition { get; private set; } - public long BackwardSize { get; private set; } - public byte[]? StreamFlags { get; private set; } + _reader = reader; + StreamStartPosition = reader.BaseStream.Position; + } - public XZFooter(BinaryReader reader) + public static XZFooter FromStream(Stream stream) + { + var footer = new XZFooter( + new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8) + ); + footer.Process(); + return footer; + } + + public void Process() + { + var crc = _reader.ReadLittleEndianUInt32(); + var footerBytes = _reader.ReadBytes(6); + var myCrc = Crc32.Compute(footerBytes); + if (crc != myCrc) { - _reader = reader; - StreamStartPosition = reader.BaseStream.Position; + throw new InvalidDataException("Footer corrupt"); } - public static XZFooter FromStream(Stream stream) + using (var stream = new MemoryStream(footerBytes)) + using (var reader = new BinaryReader(stream)) { - var footer = new XZFooter(new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8)); - footer.Process(); - return footer; + BackwardSize = (reader.ReadLittleEndianUInt32() + 1) * 4; + StreamFlags = reader.ReadBytes(2); } - - public void Process() + var magBy = _reader.ReadBytes(2); + if (!magBy.AsSpan().SequenceEqual(_magicBytes)) { - uint crc = _reader.ReadLittleEndianUInt32(); - byte[] footerBytes = _reader.ReadBytes(6); - uint myCrc = Crc32.Compute(footerBytes); - if (crc != myCrc) - { - throw new InvalidDataException("Footer corrupt"); - } - - using (var stream = new MemoryStream(footerBytes)) - using (var reader = new BinaryReader(stream)) - { - BackwardSize = (reader.ReadLittleEndianUInt32() + 1) * 4; - StreamFlags = reader.ReadBytes(2); - } - byte[] magBy = _reader.ReadBytes(2); - if (!magBy.AsSpan().SequenceEqual(_magicBytes)) - { - throw new InvalidDataException("Magic footer missing"); - } + throw new InvalidDataException("Magic footer missing"); } } } diff --git a/src/SharpCompress/Compressors/Xz/XZHeader.cs b/src/SharpCompress/Compressors/Xz/XZHeader.cs index 8934e135..a5ed8c4a 100644 --- a/src/SharpCompress/Compressors/Xz/XZHeader.cs +++ b/src/SharpCompress/Compressors/Xz/XZHeader.cs @@ -1,61 +1,58 @@ -using System; using System.IO; using System.Linq; using System.Text; using SharpCompress.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +public class XZHeader { - public class XZHeader + private readonly BinaryReader _reader; + private readonly byte[] MagicHeader = { 0xFD, 0x37, 0x7A, 0x58, 0x5a, 0x00 }; + + public CheckType BlockCheckType { get; private set; } + public int BlockCheckSize => ((((int)BlockCheckType) + 2) / 3) * 4; + + public XZHeader(BinaryReader reader) => _reader = reader; + + public static XZHeader FromStream(Stream stream) { - private readonly BinaryReader _reader; - private readonly byte[] MagicHeader = { 0xFD, 0x37, 0x7A, 0x58, 0x5a, 0x00 }; + var header = new XZHeader( + new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8) + ); + header.Process(); + return header; + } - public CheckType BlockCheckType { get; private set; } - public int BlockCheckSize => ((((int)BlockCheckType) + 2) / 3) * 4; + public void Process() + { + CheckMagicBytes(_reader.ReadBytes(6)); + ProcessStreamFlags(); + } - public XZHeader(BinaryReader reader) + private void ProcessStreamFlags() + { + var streamFlags = _reader.ReadBytes(2); + var crc = _reader.ReadLittleEndianUInt32(); + var calcCrc = Crc32.Compute(streamFlags); + if (crc != calcCrc) { - _reader = reader; + throw new InvalidDataException("Stream header corrupt"); } - public static XZHeader FromStream(Stream stream) + BlockCheckType = (CheckType)(streamFlags[1] & 0x0F); + var futureUse = (byte)(streamFlags[1] & 0xF0); + if (futureUse != 0 || streamFlags[0] != 0) { - var header = new XZHeader(new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8)); - header.Process(); - return header; + throw new InvalidDataException("Unknown XZ Stream Version"); } + } - public void Process() + private void CheckMagicBytes(byte[] header) + { + if (!header.SequenceEqual(MagicHeader)) { - CheckMagicBytes(_reader.ReadBytes(6)); - ProcessStreamFlags(); - } - - private void ProcessStreamFlags() - { - byte[] streamFlags = _reader.ReadBytes(2); - UInt32 crc = _reader.ReadLittleEndianUInt32(); - UInt32 calcCrc = Crc32.Compute(streamFlags); - if (crc != calcCrc) - { - throw new InvalidDataException("Stream header corrupt"); - } - - BlockCheckType = (CheckType)(streamFlags[1] & 0x0F); - byte futureUse = (byte)(streamFlags[1] & 0xF0); - if (futureUse != 0 || streamFlags[0] != 0) - { - throw new InvalidDataException("Unknown XZ Stream Version"); - } - } - - private void CheckMagicBytes(byte[] header) - { - if (!header.SequenceEqual(MagicHeader)) - { - throw new InvalidDataException("Invalid XZ Stream"); - } + throw new InvalidDataException("Invalid XZ Stream"); } } } diff --git a/src/SharpCompress/Compressors/Xz/XZIndex.cs b/src/SharpCompress/Compressors/Xz/XZIndex.cs index 997d53c3..631f27b2 100644 --- a/src/SharpCompress/Compressors/Xz/XZIndex.cs +++ b/src/SharpCompress/Compressors/Xz/XZIndex.cs @@ -5,78 +5,80 @@ using System.Linq; using System.Text; using SharpCompress.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public class XZIndex { - [CLSCompliant(false)] - public class XZIndex + private readonly BinaryReader _reader; + public long StreamStartPosition { get; private set; } + public ulong NumberOfRecords { get; private set; } + public List Records { get; } = new List(); + + private readonly bool _indexMarkerAlreadyVerified; + + public XZIndex(BinaryReader reader, bool indexMarkerAlreadyVerified) { - private readonly BinaryReader _reader; - public long StreamStartPosition { get; private set; } - public ulong NumberOfRecords { get; private set; } - public List Records { get; } = new List(); - - private readonly bool _indexMarkerAlreadyVerified; - - public XZIndex(BinaryReader reader, bool indexMarkerAlreadyVerified) + _reader = reader; + _indexMarkerAlreadyVerified = indexMarkerAlreadyVerified; + StreamStartPosition = reader.BaseStream.Position; + if (indexMarkerAlreadyVerified) { - _reader = reader; - _indexMarkerAlreadyVerified = indexMarkerAlreadyVerified; - StreamStartPosition = reader.BaseStream.Position; - if (indexMarkerAlreadyVerified) - { - StreamStartPosition--; - } - } - - public static XZIndex FromStream(Stream stream, bool indexMarkerAlreadyVerified) - { - var index = new XZIndex(new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8), indexMarkerAlreadyVerified); - index.Process(); - return index; - } - - public void Process() - { - if (!_indexMarkerAlreadyVerified) - { - VerifyIndexMarker(); - } - - NumberOfRecords = _reader.ReadXZInteger(); - for (ulong i = 0; i < NumberOfRecords; i++) - { - Records.Add(XZIndexRecord.FromBinaryReader(_reader)); - } - SkipPadding(); - VerifyCrc32(); - } - - private void VerifyIndexMarker() - { - byte marker = _reader.ReadByte(); - if (marker != 0) - { - throw new InvalidDataException("Not an index block"); - } - } - - private void SkipPadding() - { - int bytes = (int)(_reader.BaseStream.Position - StreamStartPosition) % 4; - if (bytes > 0) - { - byte[] paddingBytes = _reader.ReadBytes(4 - bytes); - if (paddingBytes.Any(b => b != 0)) - { - throw new InvalidDataException("Padding bytes were non-null"); - } - } - } - - private void VerifyCrc32() - { - uint crc = _reader.ReadLittleEndianUInt32(); - // TODO verify this matches + StreamStartPosition--; } } + + public static XZIndex FromStream(Stream stream, bool indexMarkerAlreadyVerified) + { + var index = new XZIndex( + new BinaryReader(NonDisposingStream.Create(stream), Encoding.UTF8), + indexMarkerAlreadyVerified + ); + index.Process(); + return index; + } + + public void Process() + { + if (!_indexMarkerAlreadyVerified) + { + VerifyIndexMarker(); + } + + NumberOfRecords = _reader.ReadXZInteger(); + for (ulong i = 0; i < NumberOfRecords; i++) + { + Records.Add(XZIndexRecord.FromBinaryReader(_reader)); + } + SkipPadding(); + VerifyCrc32(); + } + + private void VerifyIndexMarker() + { + var marker = _reader.ReadByte(); + if (marker != 0) + { + throw new InvalidDataException("Not an index block"); + } + } + + private void SkipPadding() + { + var bytes = (int)(_reader.BaseStream.Position - StreamStartPosition) % 4; + if (bytes > 0) + { + var paddingBytes = _reader.ReadBytes(4 - bytes); + if (paddingBytes.Any(b => b != 0)) + { + throw new InvalidDataException("Padding bytes were non-null"); + } + } + } + + private void VerifyCrc32() + { + var crc = _reader.ReadLittleEndianUInt32(); + // TODO verify this matches + } } diff --git a/src/SharpCompress/Compressors/Xz/XZIndexMarkerReachedException.cs b/src/SharpCompress/Compressors/Xz/XZIndexMarkerReachedException.cs index 387a2a1e..bb006a35 100644 --- a/src/SharpCompress/Compressors/Xz/XZIndexMarkerReachedException.cs +++ b/src/SharpCompress/Compressors/Xz/XZIndexMarkerReachedException.cs @@ -1,8 +1,5 @@ -using System; +using System; -namespace SharpCompress.Compressors.Xz -{ - public class XZIndexMarkerReachedException : Exception - { - } -} +namespace SharpCompress.Compressors.Xz; + +public class XZIndexMarkerReachedException : Exception { } diff --git a/src/SharpCompress/Compressors/Xz/XZIndexRecord.cs b/src/SharpCompress/Compressors/Xz/XZIndexRecord.cs index f0ee443e..e05b988b 100644 --- a/src/SharpCompress/Compressors/Xz/XZIndexRecord.cs +++ b/src/SharpCompress/Compressors/Xz/XZIndexRecord.cs @@ -1,22 +1,21 @@ -using System; +using System; using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public class XZIndexRecord { - [CLSCompliant(false)] - public class XZIndexRecord + public ulong UnpaddedSize { get; private set; } + public ulong UncompressedSize { get; private set; } + + protected XZIndexRecord() { } + + public static XZIndexRecord FromBinaryReader(BinaryReader br) { - public ulong UnpaddedSize { get; private set; } - public ulong UncompressedSize { get; private set; } - - protected XZIndexRecord() { } - - public static XZIndexRecord FromBinaryReader(BinaryReader br) - { - var record = new XZIndexRecord(); - record.UnpaddedSize = br.ReadXZInteger(); - record.UncompressedSize = br.ReadXZInteger(); - return record; - } + var record = new XZIndexRecord(); + record.UnpaddedSize = br.ReadXZInteger(); + record.UncompressedSize = br.ReadXZInteger(); + return record; } } diff --git a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs index fa376c51..4c1aa02b 100644 --- a/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZReadOnlyStream.cs @@ -1,16 +1,15 @@ -using System.IO; +using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +public abstract class XZReadOnlyStream : ReadOnlyStream { - public abstract class XZReadOnlyStream : ReadOnlyStream + public XZReadOnlyStream(Stream stream) { - public XZReadOnlyStream(Stream stream) + BaseStream = stream; + if (!BaseStream.CanRead) { - BaseStream = stream; - if (!BaseStream.CanRead) - { - throw new InvalidDataException("Must be able to read from stream"); - } + throw new InvalidDataException("Must be able to read from stream"); } } } diff --git a/src/SharpCompress/Compressors/Xz/XZStream.cs b/src/SharpCompress/Compressors/Xz/XZStream.cs index 49b5e801..78e97a10 100644 --- a/src/SharpCompress/Compressors/Xz/XZStream.cs +++ b/src/SharpCompress/Compressors/Xz/XZStream.cs @@ -1,133 +1,123 @@ -#nullable disable +#nullable disable using System; using System.IO; -namespace SharpCompress.Compressors.Xz +namespace SharpCompress.Compressors.Xz; + +[CLSCompliant(false)] +public sealed class XZStream : XZReadOnlyStream { - [CLSCompliant(false)] - public sealed class XZStream : XZReadOnlyStream + public static bool IsXZStream(Stream stream) { - public static bool IsXZStream(Stream stream) + try + { + return null != XZHeader.FromStream(stream); + } + catch (Exception) + { + return false; + } + } + + private void AssertBlockCheckTypeIsSupported() + { + switch (Header.BlockCheckType) + { + case CheckType.NONE: + break; + case CheckType.CRC32: + break; + case CheckType.CRC64: + break; + case CheckType.SHA256: + throw new NotImplementedException(); + default: + throw new NotSupportedException("Check Type unknown to this version of decoder."); + } + } + + public XZHeader Header { get; private set; } + public XZIndex Index { get; private set; } + public XZFooter Footer { get; private set; } + public bool HeaderIsRead { get; private set; } + private XZBlock _currentBlock; + + private bool _endOfStream; + + public XZStream(Stream stream) : base(stream) { } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = 0; + if (_endOfStream) + { + return bytesRead; + } + + if (!HeaderIsRead) + { + ReadHeader(); + } + + bytesRead = ReadBlocks(buffer, offset, count); + if (bytesRead < count) + { + _endOfStream = true; + ReadIndex(); + ReadFooter(); + } + return bytesRead; + } + + private void ReadHeader() + { + Header = XZHeader.FromStream(BaseStream); + AssertBlockCheckTypeIsSupported(); + HeaderIsRead = true; + } + + private void ReadIndex() => Index = XZIndex.FromStream(BaseStream, true); + + // TODO veryfy Index + private void ReadFooter() => Footer = XZFooter.FromStream(BaseStream); + + // TODO verify footer + private int ReadBlocks(byte[] buffer, int offset, int count) + { + var bytesRead = 0; + if (_currentBlock is null) + { + NextBlock(); + } + + for (; ; ) { try { - return null != XZHeader.FromStream(stream); - } - catch (Exception) - { - return false; - } - } - - private void AssertBlockCheckTypeIsSupported() - { - switch (Header.BlockCheckType) - { - case CheckType.NONE: - break; - case CheckType.CRC32: - break; - case CheckType.CRC64: - break; - case CheckType.SHA256: - throw new NotImplementedException(); - default: - throw new NotSupportedException("Check Type unknown to this version of decoder."); - } - } - public XZHeader Header { get; private set; } - public XZIndex Index { get; private set; } - public XZFooter Footer { get; private set; } - public bool HeaderIsRead { get; private set; } - private XZBlock _currentBlock; - - private bool _endOfStream; - - public XZStream(Stream stream) : base(stream) - { - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = 0; - if (_endOfStream) - { - return bytesRead; - } - - if (!HeaderIsRead) - { - ReadHeader(); - } - - bytesRead = ReadBlocks(buffer, offset, count); - if (bytesRead < count) - { - _endOfStream = true; - ReadIndex(); - ReadFooter(); - } - return bytesRead; - } - - private void ReadHeader() - { - Header = XZHeader.FromStream(BaseStream); - AssertBlockCheckTypeIsSupported(); - HeaderIsRead = true; - } - - private void ReadIndex() - { - Index = XZIndex.FromStream(BaseStream, true); - // TODO veryfy Index - } - - private void ReadFooter() - { - Footer = XZFooter.FromStream(BaseStream); - // TODO verify footer - } - - private int ReadBlocks(byte[] buffer, int offset, int count) - { - int bytesRead = 0; - if (_currentBlock is null) - { - NextBlock(); - } - - for (; ; ) - { - try - { - if (bytesRead >= count) - { - break; - } - - int remaining = count - bytesRead; - int newOffset = offset + bytesRead; - int justRead = _currentBlock.Read(buffer, newOffset, remaining); - if (justRead < remaining) - { - NextBlock(); - } - - bytesRead += justRead; - } - catch (XZIndexMarkerReachedException) + if (bytesRead >= count) { break; } - } - return bytesRead; - } - private void NextBlock() - { - _currentBlock = new XZBlock(BaseStream, Header.BlockCheckType, Header.BlockCheckSize); + var remaining = count - bytesRead; + var newOffset = offset + bytesRead; + var justRead = _currentBlock.Read(buffer, newOffset, remaining); + if (justRead < remaining) + { + NextBlock(); + } + + bytesRead += justRead; + } + catch (XZIndexMarkerReachedException) + { + break; + } } + return bytesRead; } + + private void NextBlock() => + _currentBlock = new XZBlock(BaseStream, Header.BlockCheckType, Header.BlockCheckSize); } diff --git a/src/SharpCompress/Crypto/Crc32Stream.cs b/src/SharpCompress/Crypto/Crc32Stream.cs index 2651bfbb..1360e1d5 100644 --- a/src/SharpCompress/Crypto/Crc32Stream.cs +++ b/src/SharpCompress/Crypto/Crc32Stream.cs @@ -1,141 +1,131 @@ -#nullable disable +#nullable disable using System; using System.IO; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +[CLSCompliant(false)] +public sealed class Crc32Stream : Stream { - internal sealed class Crc32Stream : Stream + public const uint DefaultPolynomial = 0xedb88320u; + public const uint DefaultSeed = 0xffffffffu; + + private static uint[] defaultTable; + + private readonly uint[] table; + private uint hash; + + private readonly Stream stream; + + public Crc32Stream(Stream stream) : this(stream, DefaultPolynomial, DefaultSeed) { } + + public Crc32Stream(Stream stream, uint polynomial, uint seed) { - public const uint DefaultPolynomial = 0xedb88320u; - public const uint DefaultSeed = 0xffffffffu; + this.stream = stream; + table = InitializeTable(polynomial); + hash = seed; + } - private static uint[] defaultTable; + public Stream WrappedStream => stream; - private readonly uint[] table; - private uint hash; + public override void Flush() => stream.Flush(); - private readonly Stream stream; + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); - public Crc32Stream(Stream stream) - : this(stream, DefaultPolynomial, DefaultSeed) - { - } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public Crc32Stream(Stream stream, uint polynomial, uint seed) - { - this.stream = stream; - table = InitializeTable(polynomial); - hash = seed; - } - - public Stream WrappedStream => stream; - - public override void Flush() - { - stream.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); #if !NETFRAMEWORK && !NETSTANDARD2_0 - public override void Write(ReadOnlySpan buffer) - { - stream.Write(buffer); + public override void Write(ReadOnlySpan buffer) + { + stream.Write(buffer); - hash = CalculateCrc(table, hash, buffer); - } + hash = CalculateCrc(table, hash, buffer); + } #endif - public override void Write(byte[] buffer, int offset, int count) - { - stream.Write(buffer, offset, count); - hash = CalculateCrc(table, hash, buffer.AsSpan(offset, count)); - } - - public override void WriteByte(byte value) - { - stream.WriteByte(value); - hash = CalculateCrc(table, hash, value); - } - - public override bool CanRead => stream.CanRead; - public override bool CanSeek => false; - public override bool CanWrite => stream.CanWrite; - public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public uint Crc => ~hash; - - public static uint Compute(byte[] buffer) - { - return Compute(DefaultSeed, buffer); - } - - public static uint Compute(uint seed, byte[] buffer) - { - return Compute(DefaultPolynomial, seed, buffer); - } - - public static uint Compute(uint polynomial, uint seed, ReadOnlySpan buffer) - { - return ~CalculateCrc(InitializeTable(polynomial), seed, buffer); - } - - private static uint[] InitializeTable(uint polynomial) - { - if (polynomial == DefaultPolynomial && defaultTable != null) - { - return defaultTable; - } - - var createTable = new uint[256]; - for (var i = 0; i < 256; i++) - { - var entry = (uint)i; - for (var j = 0; j < 8; j++) - { - if ((entry & 1) == 1) - { - entry = (entry >> 1) ^ polynomial; - } - else - { - entry = entry >> 1; - } - } - - createTable[i] = entry; - } - - if (polynomial == DefaultPolynomial) - { - defaultTable = createTable; - } - - return createTable; - } - - private static uint CalculateCrc(uint[] table, uint crc, ReadOnlySpan buffer) - { - unchecked - { - for (int i = 0; i < buffer.Length; i++) - { - crc = CalculateCrc(table, crc, buffer[i]); - } - } - return crc; - } - - private static uint CalculateCrc(uint[] table, uint crc, byte b) - { - return (crc >> 8) ^ table[(crc ^ b) & 0xFF]; - } + public override void Write(byte[] buffer, int offset, int count) + { + stream.Write(buffer, offset, count); + hash = CalculateCrc(table, hash, buffer.AsSpan(offset, count)); } + + public override void WriteByte(byte value) + { + stream.WriteByte(value); + hash = CalculateCrc(table, hash, value); + } + + public override bool CanRead => stream.CanRead; + public override bool CanSeek => false; + public override bool CanWrite => stream.CanWrite; + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public uint Crc => ~hash; + + public static uint Compute(byte[] buffer) => Compute(DefaultSeed, buffer); + + public static uint Compute(uint seed, byte[] buffer) => + Compute(DefaultPolynomial, seed, buffer); + + public static uint Compute(uint polynomial, uint seed, ReadOnlySpan buffer) => + ~CalculateCrc(InitializeTable(polynomial), seed, buffer); + + private static uint[] InitializeTable(uint polynomial) + { + if (polynomial == DefaultPolynomial && defaultTable != null) + { + return defaultTable; + } + + var createTable = new uint[256]; + for (var i = 0; i < 256; i++) + { + var entry = (uint)i; + for (var j = 0; j < 8; j++) + { + if ((entry & 1) == 1) + { + entry = (entry >> 1) ^ polynomial; + } + else + { + entry >>= 1; + } + } + + createTable[i] = entry; + } + + if (polynomial == DefaultPolynomial) + { + defaultTable = createTable; + } + + return createTable; + } + + private static uint CalculateCrc(uint[] table, uint crc, ReadOnlySpan buffer) + { + unchecked + { + for (var i = 0; i < buffer.Length; i++) + { + crc = CalculateCrc(table, crc, buffer[i]); + } + } + return crc; + } + + private static uint CalculateCrc(uint[] table, uint crc, byte b) => + (crc >> 8) ^ table[(crc ^ b) & 0xFF]; } diff --git a/src/SharpCompress/Crypto/CryptoException.cs b/src/SharpCompress/Crypto/CryptoException.cs index 6e8b9f14..575c34d9 100644 --- a/src/SharpCompress/Crypto/CryptoException.cs +++ b/src/SharpCompress/Crypto/CryptoException.cs @@ -1,25 +1,12 @@ -using System; +using System; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +public class CryptoException : Exception { - public class CryptoException - : Exception - { - public CryptoException() - { - } + public CryptoException() { } - public CryptoException( - string message) - : base(message) - { - } + public CryptoException(string message) : base(message) { } - public CryptoException( - string message, - Exception exception) - : base(message, exception) - { - } - } -} \ No newline at end of file + public CryptoException(string message, Exception exception) : base(message, exception) { } +} diff --git a/src/SharpCompress/Crypto/DataLengthException.cs b/src/SharpCompress/Crypto/DataLengthException.cs index b82a9e9c..bc6cd183 100644 --- a/src/SharpCompress/Crypto/DataLengthException.cs +++ b/src/SharpCompress/Crypto/DataLengthException.cs @@ -1,35 +1,22 @@ -using System; +using System; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +public class DataLengthException : CryptoException { - public class DataLengthException - : CryptoException - { - /** - * base constructor. - */ + /** + * base constructor. + */ - public DataLengthException() - { - } + public DataLengthException() { } - /** - * create a DataLengthException with the given message. - * - * @param message the message to be carried with the exception. - */ + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ - public DataLengthException( - string message) - : base(message) - { - } + public DataLengthException(string message) : base(message) { } - public DataLengthException( - string message, - Exception exception) - : base(message, exception) - { - } - } -} \ No newline at end of file + public DataLengthException(string message, Exception exception) : base(message, exception) { } +} diff --git a/src/SharpCompress/Crypto/IBlockCipher.cs b/src/SharpCompress/Crypto/IBlockCipher.cs index 61a37c46..50eb5fb3 100644 --- a/src/SharpCompress/Crypto/IBlockCipher.cs +++ b/src/SharpCompress/Crypto/IBlockCipher.cs @@ -1,34 +1,33 @@ -using System; +using System; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +/// Base interface for a symmetric key block cipher. +public interface IBlockCipher { - /// Base interface for a symmetric key block cipher. - public interface IBlockCipher - { - /// The name of the algorithm this cipher implements. - string AlgorithmName { get; } + /// The name of the algorithm this cipher implements. + string AlgorithmName { get; } - /// Initialise the cipher. - /// Initialise for encryption if true, for decryption if false. - /// The key or other data required by the cipher. - void Init(bool forEncryption, ICipherParameters parameters); + /// Initialise the cipher. + /// Initialise for encryption if true, for decryption if false. + /// The key or other data required by the cipher. + void Init(bool forEncryption, ICipherParameters parameters); - /// The block size for this cipher, in bytes. - int GetBlockSize(); + /// The block size for this cipher, in bytes. + int GetBlockSize(); - /// Indicates whether this cipher can handle partial blocks. - bool IsPartialBlockOkay { get; } + /// Indicates whether this cipher can handle partial blocks. + bool IsPartialBlockOkay { get; } - /// Process a block. - /// The input buffer. - /// The output buffer. - /// If input block is wrong size, or outBuf too small. - /// The number of bytes processed and produced. - int ProcessBlock(ReadOnlySpan inBuf, Span outBuf); + /// Process a block. + /// The input buffer. + /// The output buffer. + /// If input block is wrong size, or outBuf too small. + /// The number of bytes processed and produced. + int ProcessBlock(ReadOnlySpan inBuf, Span outBuf); - /// - /// Reset the cipher to the same state as it was after the last init (if there was one). - /// - void Reset(); - } -} \ No newline at end of file + /// + /// Reset the cipher to the same state as it was after the last init (if there was one). + /// + void Reset(); +} diff --git a/src/SharpCompress/Crypto/ICipherParameters.cs b/src/SharpCompress/Crypto/ICipherParameters.cs index d6e4af8b..9934637a 100644 --- a/src/SharpCompress/Crypto/ICipherParameters.cs +++ b/src/SharpCompress/Crypto/ICipherParameters.cs @@ -1,6 +1,3 @@ -namespace SharpCompress.Crypto -{ - public interface ICipherParameters - { - } -} \ No newline at end of file +namespace SharpCompress.Crypto; + +public interface ICipherParameters { } diff --git a/src/SharpCompress/Crypto/KeyParameter.cs b/src/SharpCompress/Crypto/KeyParameter.cs index 7bf2dad8..fce334f9 100644 --- a/src/SharpCompress/Crypto/KeyParameter.cs +++ b/src/SharpCompress/Crypto/KeyParameter.cs @@ -1,48 +1,39 @@ using System; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +public class KeyParameter : ICipherParameters { - public class KeyParameter - : ICipherParameters + private readonly byte[] key; + + public KeyParameter(byte[] key) { - private readonly byte[] key; - - public KeyParameter( - byte[] key) + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - - this.key = (byte[])key.Clone(); + throw new ArgumentNullException(nameof(key)); } - public KeyParameter( - byte[] key, - int keyOff, - int keyLen) - { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - if (keyOff < 0 || keyOff > key.Length) - { - throw new ArgumentOutOfRangeException(nameof(keyOff)); - } - if (keyLen < 0 || (keyOff + keyLen) > key.Length) - { - throw new ArgumentOutOfRangeException(nameof(keyLen)); - } - - this.key = new byte[keyLen]; - Array.Copy(key, keyOff, this.key, 0, keyLen); - } - - public byte[] GetKey() - { - return (byte[])key.Clone(); - } + this.key = (byte[])key.Clone(); } -} \ No newline at end of file + + public KeyParameter(byte[] key, int keyOff, int keyLen) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + if (keyOff < 0 || keyOff > key.Length) + { + throw new ArgumentOutOfRangeException(nameof(keyOff)); + } + if (keyLen < 0 || (keyOff + keyLen) > key.Length) + { + throw new ArgumentOutOfRangeException(nameof(keyLen)); + } + + this.key = new byte[keyLen]; + Array.Copy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] GetKey() => (byte[])key.Clone(); +} diff --git a/src/SharpCompress/Crypto/RijndaelEngine.cs b/src/SharpCompress/Crypto/RijndaelEngine.cs index b1adaa76..7635d7a0 100644 --- a/src/SharpCompress/Crypto/RijndaelEngine.cs +++ b/src/SharpCompress/Crypto/RijndaelEngine.cs @@ -1,703 +1,1908 @@ using System; -namespace SharpCompress.Crypto +namespace SharpCompress.Crypto; + +public sealed class RijndaelEngine : IBlockCipher { - public sealed class RijndaelEngine - : IBlockCipher + private const int MAXROUNDS = 14; + + private const int MAXKC = (256 / 4); + + private static ReadOnlySpan Logtable => + new byte[] + { + 0, + 0, + 25, + 1, + 50, + 2, + 26, + 198, + 75, + 199, + 27, + 104, + 51, + 238, + 223, + 3, + 100, + 4, + 224, + 14, + 52, + 141, + 129, + 239, + 76, + 113, + 8, + 200, + 248, + 105, + 28, + 193, + 125, + 194, + 29, + 181, + 249, + 185, + 39, + 106, + 77, + 228, + 166, + 114, + 154, + 201, + 9, + 120, + 101, + 47, + 138, + 5, + 33, + 15, + 225, + 36, + 18, + 240, + 130, + 69, + 53, + 147, + 218, + 142, + 150, + 143, + 219, + 189, + 54, + 208, + 206, + 148, + 19, + 92, + 210, + 241, + 64, + 70, + 131, + 56, + 102, + 221, + 253, + 48, + 191, + 6, + 139, + 98, + 179, + 37, + 226, + 152, + 34, + 136, + 145, + 16, + 126, + 110, + 72, + 195, + 163, + 182, + 30, + 66, + 58, + 107, + 40, + 84, + 250, + 133, + 61, + 186, + 43, + 121, + 10, + 21, + 155, + 159, + 94, + 202, + 78, + 212, + 172, + 229, + 243, + 115, + 167, + 87, + 175, + 88, + 168, + 80, + 244, + 234, + 214, + 116, + 79, + 174, + 233, + 213, + 231, + 230, + 173, + 232, + 44, + 215, + 117, + 122, + 235, + 22, + 11, + 245, + 89, + 203, + 95, + 176, + 156, + 169, + 81, + 160, + 127, + 12, + 246, + 111, + 23, + 196, + 73, + 236, + 216, + 67, + 31, + 45, + 164, + 118, + 123, + 183, + 204, + 187, + 62, + 90, + 251, + 96, + 177, + 134, + 59, + 82, + 161, + 108, + 170, + 85, + 41, + 157, + 151, + 178, + 135, + 144, + 97, + 190, + 220, + 252, + 188, + 149, + 207, + 205, + 55, + 63, + 91, + 209, + 83, + 57, + 132, + 60, + 65, + 162, + 109, + 71, + 20, + 42, + 158, + 93, + 86, + 242, + 211, + 171, + 68, + 17, + 146, + 217, + 35, + 32, + 46, + 137, + 180, + 124, + 184, + 38, + 119, + 153, + 227, + 165, + 103, + 74, + 237, + 222, + 197, + 49, + 254, + 24, + 13, + 99, + 140, + 128, + 192, + 247, + 112, + 7 + }; + + private static ReadOnlySpan Alogtable => + new byte[] + { + 0, + 3, + 5, + 15, + 17, + 51, + 85, + 255, + 26, + 46, + 114, + 150, + 161, + 248, + 19, + 53, + 95, + 225, + 56, + 72, + 216, + 115, + 149, + 164, + 247, + 2, + 6, + 10, + 30, + 34, + 102, + 170, + 229, + 52, + 92, + 228, + 55, + 89, + 235, + 38, + 106, + 190, + 217, + 112, + 144, + 171, + 230, + 49, + 83, + 245, + 4, + 12, + 20, + 60, + 68, + 204, + 79, + 209, + 104, + 184, + 211, + 110, + 178, + 205, + 76, + 212, + 103, + 169, + 224, + 59, + 77, + 215, + 98, + 166, + 241, + 8, + 24, + 40, + 120, + 136, + 131, + 158, + 185, + 208, + 107, + 189, + 220, + 127, + 129, + 152, + 179, + 206, + 73, + 219, + 118, + 154, + 181, + 196, + 87, + 249, + 16, + 48, + 80, + 240, + 11, + 29, + 39, + 105, + 187, + 214, + 97, + 163, + 254, + 25, + 43, + 125, + 135, + 146, + 173, + 236, + 47, + 113, + 147, + 174, + 233, + 32, + 96, + 160, + 251, + 22, + 58, + 78, + 210, + 109, + 183, + 194, + 93, + 231, + 50, + 86, + 250, + 21, + 63, + 65, + 195, + 94, + 226, + 61, + 71, + 201, + 64, + 192, + 91, + 237, + 44, + 116, + 156, + 191, + 218, + 117, + 159, + 186, + 213, + 100, + 172, + 239, + 42, + 126, + 130, + 157, + 188, + 223, + 122, + 142, + 137, + 128, + 155, + 182, + 193, + 88, + 232, + 35, + 101, + 175, + 234, + 37, + 111, + 177, + 200, + 67, + 197, + 84, + 252, + 31, + 33, + 99, + 165, + 244, + 7, + 9, + 27, + 45, + 119, + 153, + 176, + 203, + 70, + 202, + 69, + 207, + 74, + 222, + 121, + 139, + 134, + 145, + 168, + 227, + 62, + 66, + 198, + 81, + 243, + 14, + 18, + 54, + 90, + 238, + 41, + 123, + 141, + 140, + 143, + 138, + 133, + 148, + 167, + 242, + 13, + 23, + 57, + 75, + 221, + 124, + 132, + 151, + 162, + 253, + 28, + 36, + 108, + 180, + 199, + 82, + 246, + 1, + 3, + 5, + 15, + 17, + 51, + 85, + 255, + 26, + 46, + 114, + 150, + 161, + 248, + 19, + 53, + 95, + 225, + 56, + 72, + 216, + 115, + 149, + 164, + 247, + 2, + 6, + 10, + 30, + 34, + 102, + 170, + 229, + 52, + 92, + 228, + 55, + 89, + 235, + 38, + 106, + 190, + 217, + 112, + 144, + 171, + 230, + 49, + 83, + 245, + 4, + 12, + 20, + 60, + 68, + 204, + 79, + 209, + 104, + 184, + 211, + 110, + 178, + 205, + 76, + 212, + 103, + 169, + 224, + 59, + 77, + 215, + 98, + 166, + 241, + 8, + 24, + 40, + 120, + 136, + 131, + 158, + 185, + 208, + 107, + 189, + 220, + 127, + 129, + 152, + 179, + 206, + 73, + 219, + 118, + 154, + 181, + 196, + 87, + 249, + 16, + 48, + 80, + 240, + 11, + 29, + 39, + 105, + 187, + 214, + 97, + 163, + 254, + 25, + 43, + 125, + 135, + 146, + 173, + 236, + 47, + 113, + 147, + 174, + 233, + 32, + 96, + 160, + 251, + 22, + 58, + 78, + 210, + 109, + 183, + 194, + 93, + 231, + 50, + 86, + 250, + 21, + 63, + 65, + 195, + 94, + 226, + 61, + 71, + 201, + 64, + 192, + 91, + 237, + 44, + 116, + 156, + 191, + 218, + 117, + 159, + 186, + 213, + 100, + 172, + 239, + 42, + 126, + 130, + 157, + 188, + 223, + 122, + 142, + 137, + 128, + 155, + 182, + 193, + 88, + 232, + 35, + 101, + 175, + 234, + 37, + 111, + 177, + 200, + 67, + 197, + 84, + 252, + 31, + 33, + 99, + 165, + 244, + 7, + 9, + 27, + 45, + 119, + 153, + 176, + 203, + 70, + 202, + 69, + 207, + 74, + 222, + 121, + 139, + 134, + 145, + 168, + 227, + 62, + 66, + 198, + 81, + 243, + 14, + 18, + 54, + 90, + 238, + 41, + 123, + 141, + 140, + 143, + 138, + 133, + 148, + 167, + 242, + 13, + 23, + 57, + 75, + 221, + 124, + 132, + 151, + 162, + 253, + 28, + 36, + 108, + 180, + 199, + 82, + 246, + 1 + }; + + private static ReadOnlySpan S => + new byte[] + { + 99, + 124, + 119, + 123, + 242, + 107, + 111, + 197, + 48, + 1, + 103, + 43, + 254, + 215, + 171, + 118, + 202, + 130, + 201, + 125, + 250, + 89, + 71, + 240, + 173, + 212, + 162, + 175, + 156, + 164, + 114, + 192, + 183, + 253, + 147, + 38, + 54, + 63, + 247, + 204, + 52, + 165, + 229, + 241, + 113, + 216, + 49, + 21, + 4, + 199, + 35, + 195, + 24, + 150, + 5, + 154, + 7, + 18, + 128, + 226, + 235, + 39, + 178, + 117, + 9, + 131, + 44, + 26, + 27, + 110, + 90, + 160, + 82, + 59, + 214, + 179, + 41, + 227, + 47, + 132, + 83, + 209, + 0, + 237, + 32, + 252, + 177, + 91, + 106, + 203, + 190, + 57, + 74, + 76, + 88, + 207, + 208, + 239, + 170, + 251, + 67, + 77, + 51, + 133, + 69, + 249, + 2, + 127, + 80, + 60, + 159, + 168, + 81, + 163, + 64, + 143, + 146, + 157, + 56, + 245, + 188, + 182, + 218, + 33, + 16, + 255, + 243, + 210, + 205, + 12, + 19, + 236, + 95, + 151, + 68, + 23, + 196, + 167, + 126, + 61, + 100, + 93, + 25, + 115, + 96, + 129, + 79, + 220, + 34, + 42, + 144, + 136, + 70, + 238, + 184, + 20, + 222, + 94, + 11, + 219, + 224, + 50, + 58, + 10, + 73, + 6, + 36, + 92, + 194, + 211, + 172, + 98, + 145, + 149, + 228, + 121, + 231, + 200, + 55, + 109, + 141, + 213, + 78, + 169, + 108, + 86, + 244, + 234, + 101, + 122, + 174, + 8, + 186, + 120, + 37, + 46, + 28, + 166, + 180, + 198, + 232, + 221, + 116, + 31, + 75, + 189, + 139, + 138, + 112, + 62, + 181, + 102, + 72, + 3, + 246, + 14, + 97, + 53, + 87, + 185, + 134, + 193, + 29, + 158, + 225, + 248, + 152, + 17, + 105, + 217, + 142, + 148, + 155, + 30, + 135, + 233, + 206, + 85, + 40, + 223, + 140, + 161, + 137, + 13, + 191, + 230, + 66, + 104, + 65, + 153, + 45, + 15, + 176, + 84, + 187, + 22 + }; + + private static ReadOnlySpan Si => + new byte[] + { + 82, + 9, + 106, + 213, + 48, + 54, + 165, + 56, + 191, + 64, + 163, + 158, + 129, + 243, + 215, + 251, + 124, + 227, + 57, + 130, + 155, + 47, + 255, + 135, + 52, + 142, + 67, + 68, + 196, + 222, + 233, + 203, + 84, + 123, + 148, + 50, + 166, + 194, + 35, + 61, + 238, + 76, + 149, + 11, + 66, + 250, + 195, + 78, + 8, + 46, + 161, + 102, + 40, + 217, + 36, + 178, + 118, + 91, + 162, + 73, + 109, + 139, + 209, + 37, + 114, + 248, + 246, + 100, + 134, + 104, + 152, + 22, + 212, + 164, + 92, + 204, + 93, + 101, + 182, + 146, + 108, + 112, + 72, + 80, + 253, + 237, + 185, + 218, + 94, + 21, + 70, + 87, + 167, + 141, + 157, + 132, + 144, + 216, + 171, + 0, + 140, + 188, + 211, + 10, + 247, + 228, + 88, + 5, + 184, + 179, + 69, + 6, + 208, + 44, + 30, + 143, + 202, + 63, + 15, + 2, + 193, + 175, + 189, + 3, + 1, + 19, + 138, + 107, + 58, + 145, + 17, + 65, + 79, + 103, + 220, + 234, + 151, + 242, + 207, + 206, + 240, + 180, + 230, + 115, + 150, + 172, + 116, + 34, + 231, + 173, + 53, + 133, + 226, + 249, + 55, + 232, + 28, + 117, + 223, + 110, + 71, + 241, + 26, + 113, + 29, + 41, + 197, + 137, + 111, + 183, + 98, + 14, + 170, + 24, + 190, + 27, + 252, + 86, + 62, + 75, + 198, + 210, + 121, + 32, + 154, + 219, + 192, + 254, + 120, + 205, + 90, + 244, + 31, + 221, + 168, + 51, + 136, + 7, + 199, + 49, + 177, + 18, + 16, + 89, + 39, + 128, + 236, + 95, + 96, + 81, + 127, + 169, + 25, + 181, + 74, + 13, + 45, + 229, + 122, + 159, + 147, + 201, + 156, + 239, + 160, + 224, + 59, + 77, + 174, + 42, + 245, + 176, + 200, + 235, + 187, + 60, + 131, + 83, + 153, + 97, + 23, + 43, + 4, + 126, + 186, + 119, + 214, + 38, + 225, + 105, + 20, + 99, + 85, + 33, + 12, + 125 + }; + + private static ReadOnlySpan rcon => + new byte[] + { + 0x01, + 0x02, + 0x04, + 0x08, + 0x10, + 0x20, + 0x40, + 0x80, + 0x1b, + 0x36, + 0x6c, + 0xd8, + 0xab, + 0x4d, + 0x9a, + 0x2f, + 0x5e, + 0xbc, + 0x63, + 0xc6, + 0x97, + 0x35, + 0x6a, + 0xd4, + 0xb3, + 0x7d, + 0xfa, + 0xef, + 0xc5, + 0x91 + }; + + private static readonly byte[][] shifts0 = { - private const int MAXROUNDS = 14; + new byte[] { 0, 8, 16, 24 }, + new byte[] { 0, 8, 16, 24 }, + new byte[] { 0, 8, 16, 24 }, + new byte[] { 0, 8, 16, 32 }, + new byte[] { 0, 8, 24, 32 } + }; - private const int MAXKC = (256 / 4); + private static readonly byte[][] shifts1 = + { + new byte[] { 0, 24, 16, 8 }, + new byte[] { 0, 32, 24, 16 }, + new byte[] { 0, 40, 32, 24 }, + new byte[] { 0, 48, 40, 24 }, + new byte[] { 0, 56, 40, 32 } + }; - private static ReadOnlySpan Logtable => new byte[] + /** + * multiply two elements of GF(2^m) + * needed for MixColumn and InvMixColumn + */ + + private byte Mul0x2(int b) + { + if (b != 0) { - 0, 0, 25, 1, 50, 2, 26, 198, - 75, 199, 27, 104, 51, 238, 223, 3, - 100, 4, 224, 14, 52, 141, 129, 239, - 76, 113, 8, 200, 248, 105, 28, 193, - 125, 194, 29, 181, 249, 185, 39, 106, - 77, 228, 166, 114, 154, 201, 9, 120, - 101, 47, 138, 5, 33, 15, 225, 36, - 18, 240, 130, 69, 53, 147, 218, 142, - 150, 143, 219, 189, 54, 208, 206, 148, - 19, 92, 210, 241, 64, 70, 131, 56, - 102, 221, 253, 48, 191, 6, 139, 98, - 179, 37, 226, 152, 34, 136, 145, 16, - 126, 110, 72, 195, 163, 182, 30, 66, - 58, 107, 40, 84, 250, 133, 61, 186, - 43, 121, 10, 21, 155, 159, 94, 202, - 78, 212, 172, 229, 243, 115, 167, 87, - 175, 88, 168, 80, 244, 234, 214, 116, - 79, 174, 233, 213, 231, 230, 173, 232, - 44, 215, 117, 122, 235, 22, 11, 245, - 89, 203, 95, 176, 156, 169, 81, 160, - 127, 12, 246, 111, 23, 196, 73, 236, - 216, 67, 31, 45, 164, 118, 123, 183, - 204, 187, 62, 90, 251, 96, 177, 134, - 59, 82, 161, 108, 170, 85, 41, 157, - 151, 178, 135, 144, 97, 190, 220, 252, - 188, 149, 207, 205, 55, 63, 91, 209, - 83, 57, 132, 60, 65, 162, 109, 71, - 20, 42, 158, 93, 86, 242, 211, 171, - 68, 17, 146, 217, 35, 32, 46, 137, - 180, 124, 184, 38, 119, 153, 227, 165, - 103, 74, 237, 222, 197, 49, 254, 24, - 13, 99, 140, 128, 192, 247, 112, 7 + return Alogtable[25 + (Logtable[b] & 0xff)]; + } + return 0; + } + + private byte Mul0x3(int b) + { + if (b != 0) + { + return Alogtable[1 + (Logtable[b] & 0xff)]; + } + return 0; + } + + private byte Mul0x9(int b) + { + if (b >= 0) + { + return Alogtable[199 + b]; + } + return 0; + } + + private byte Mul0xb(int b) + { + if (b >= 0) + { + return Alogtable[104 + b]; + } + return 0; + } + + private byte Mul0xd(int b) + { + if (b >= 0) + { + return Alogtable[238 + b]; + } + return 0; + } + + private byte Mul0xe(int b) + { + if (b >= 0) + { + return Alogtable[223 + b]; + } + return 0; + } + + /** + * xor corresponding text input and round key input bytes + */ + + private void KeyAddition(long[] rk) + { + A0 ^= rk[0]; + A1 ^= rk[1]; + A2 ^= rk[2]; + A3 ^= rk[3]; + } + + private long Shift(long r, int shift) + { + //return (((long)((ulong) r >> shift) | (r << (BC - shift)))) & BC_MASK; + + var temp = (ulong)r >> shift; + + // NB: This corrects for Mono Bug #79087 (fixed in 1.1.17) + if (shift > 31) + { + temp &= 0xFFFFFFFFUL; + } + + return ((long)temp | (r << (BC - shift))) & BC_MASK; + } + + /** + * Row 0 remains unchanged + * The other three rows are shifted a variable amount + */ + + private void ShiftRow(byte[] shiftsSC) + { + A1 = Shift(A1, shiftsSC[1]); + A2 = Shift(A2, shiftsSC[2]); + A3 = Shift(A3, shiftsSC[3]); + } + + private long ApplyS(long r, ReadOnlySpan box) + { + long res = 0; + + for (var j = 0; j < BC; j += 8) + { + res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j; + } + + return res; + } + + /** + * Replace every byte of the input by the byte at that place + * in the nonlinear S-box + */ + + private void Substitution(ReadOnlySpan box) + { + A0 = ApplyS(A0, box); + A1 = ApplyS(A1, box); + A2 = ApplyS(A2, box); + A3 = ApplyS(A3, box); + } + + /** + * Mix the bytes of every column in a linear way + */ + + private void MixColumn() + { + long r0, + r1, + r2, + r3; + + r0 = r1 = r2 = r3 = 0; + + for (var j = 0; j < BC; j += 8) + { + var a0 = (int)((A0 >> j) & 0xff); + var a1 = (int)((A1 >> j) & 0xff); + var a2 = (int)((A2 >> j) & 0xff); + var a3 = (int)((A3 >> j) & 0xff); + + r0 |= (long)((Mul0x2(a0) ^ Mul0x3(a1) ^ a2 ^ a3) & 0xff) << j; + + r1 |= (long)((Mul0x2(a1) ^ Mul0x3(a2) ^ a3 ^ a0) & 0xff) << j; + + r2 |= (long)((Mul0x2(a2) ^ Mul0x3(a3) ^ a0 ^ a1) & 0xff) << j; + + r3 |= (long)((Mul0x2(a3) ^ Mul0x3(a0) ^ a1 ^ a2) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Mix the bytes of every column in a linear way + * This is the opposite operation of Mixcolumn + */ + + private void InvMixColumn() + { + long r0, + r1, + r2, + r3; + + r0 = r1 = r2 = r3 = 0; + for (var j = 0; j < BC; j += 8) + { + var a0 = (int)((A0 >> j) & 0xff); + var a1 = (int)((A1 >> j) & 0xff); + var a2 = (int)((A2 >> j) & 0xff); + var a3 = (int)((A3 >> j) & 0xff); + + // + // pre-lookup the log table + // + a0 = (a0 != 0) ? (Logtable[a0 & 0xff] & 0xff) : -1; + a1 = (a1 != 0) ? (Logtable[a1 & 0xff] & 0xff) : -1; + a2 = (a2 != 0) ? (Logtable[a2 & 0xff] & 0xff) : -1; + a3 = (a3 != 0) ? (Logtable[a3 & 0xff] & 0xff) : -1; + + r0 |= (long)((Mul0xe(a0) ^ Mul0xb(a1) ^ Mul0xd(a2) ^ Mul0x9(a3)) & 0xff) << j; + + r1 |= (long)((Mul0xe(a1) ^ Mul0xb(a2) ^ Mul0xd(a3) ^ Mul0x9(a0)) & 0xff) << j; + + r2 |= (long)((Mul0xe(a2) ^ Mul0xb(a3) ^ Mul0xd(a0) ^ Mul0x9(a1)) & 0xff) << j; + + r3 |= (long)((Mul0xe(a3) ^ Mul0xb(a0) ^ Mul0xd(a1) ^ Mul0x9(a2)) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on keyBits and blockBits + */ + + private long[][] GenerateWorkingKey(byte[] key) + { + int t, + rconpointer = 0; + var keyBits = key.Length * 8; + var tk = new byte[4, MAXKC]; + + //long[,] W = new long[MAXROUNDS+1,4]; + var W = new long[MAXROUNDS + 1][]; + + for (var i = 0; i < MAXROUNDS + 1; i++) + { + W[i] = new long[4]; + } + + var KC = keyBits switch + { + 128 => 4, + 160 => 5, + 192 => 6, + 224 => 7, + 256 => 8, + _ => throw new ArgumentException("Key length not 128/160/192/224/256 bits."), }; - - private static ReadOnlySpan Alogtable => new byte[] + if (keyBits >= blockBits) { - 0, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, - 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, - 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, - 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, - 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, - 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, - 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, - 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, - 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, - 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, - 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, - 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, - 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, - 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, - 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, - 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1, - 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, - 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, - 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, - 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, - 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, - 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, - 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, - 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, - 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, - 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, - 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, - 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, - 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, - 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, - 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, - 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1 - }; - - private static ReadOnlySpan S => new byte[] + ROUNDS = KC + 6; + } + else { - 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, - 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, - 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, - 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, - 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, - 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, - 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, - 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, - 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, - 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, - 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, - 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, - 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, - 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, - 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, - 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 - }; + ROUNDS = (BC / 8) + 6; + } - private static ReadOnlySpan Si => new byte[] + // + // copy the key into the processing area + // + var index = 0; + + for (var i = 0; i < key.Length; i++) { - 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, - 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, - 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, - 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, - 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, - 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, - 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, - 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, - 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, - 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, - 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, - 252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, - 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, - 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, - 160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, - 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125 - }; + tk[i % 4, i / 4] = key[index++]; + } - private static ReadOnlySpan rcon => new byte[] + t = 0; + + // + // copy values into round key array + // + for (var j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, - 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 - }; - - private static readonly byte[][] shifts0 = - { - new byte[] {0, 8, 16, 24}, - new byte[] {0, 8, 16, 24}, - new byte[] {0, 8, 16, 24}, - new byte[] {0, 8, 16, 32}, - new byte[] {0, 8, 24, 32} - }; - - private static readonly byte[][] shifts1 = - { - new byte[] {0, 24, 16, 8}, - new byte[] {0, 32, 24, 16}, - new byte[] {0, 40, 32, 24}, - new byte[] {0, 48, 40, 24}, - new byte[] {0, 56, 40, 32} - }; - - /** - * multiply two elements of GF(2^m) - * needed for MixColumn and InvMixColumn - */ - - private byte Mul0x2( - int b) - { - if (b != 0) + for (var i = 0; i < 4; i++) { - return Alogtable[25 + (Logtable[b] & 0xff)]; + W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % BC); } - return 0; } - private byte Mul0x3( - int b) + // + // while not enough round key material calculated + // calculate new values + // + while (t < (ROUNDS + 1) * (BC / 8)) { - if (b != 0) + for (var i = 0; i < 4; i++) { - return Alogtable[1 + (Logtable[b] & 0xff)]; + tk[i, 0] ^= S[tk[(i + 1) % 4, KC - 1] & 0xff]; } - return 0; - } + tk[0, 0] ^= rcon[rconpointer++]; - private byte Mul0x9( - int b) - { - if (b >= 0) + if (KC <= 6) { - return Alogtable[199 + b]; - } - return 0; - } - - private byte Mul0xb( - int b) - { - if (b >= 0) - { - return Alogtable[104 + b]; - } - return 0; - } - - private byte Mul0xd( - int b) - { - if (b >= 0) - { - return Alogtable[238 + b]; - } - return 0; - } - - private byte Mul0xe( - int b) - { - if (b >= 0) - { - return Alogtable[223 + b]; - } - return 0; - } - - /** - * xor corresponding text input and round key input bytes - */ - - private void KeyAddition(long[] rk) - { - A0 ^= rk[0]; - A1 ^= rk[1]; - A2 ^= rk[2]; - A3 ^= rk[3]; - } - - private long Shift( - long r, - int shift) - { - //return (((long)((ulong) r >> shift) | (r << (BC - shift)))) & BC_MASK; - - ulong temp = (ulong)r >> shift; - - // NB: This corrects for Mono Bug #79087 (fixed in 1.1.17) - if (shift > 31) - { - temp &= 0xFFFFFFFFUL; - } - - return ((long)temp | (r << (BC - shift))) & BC_MASK; - } - - /** - * Row 0 remains unchanged - * The other three rows are shifted a variable amount - */ - - private void ShiftRow(byte[] shiftsSC) - { - A1 = Shift(A1, shiftsSC[1]); - A2 = Shift(A2, shiftsSC[2]); - A3 = Shift(A3, shiftsSC[3]); - } - - private long ApplyS(long r, ReadOnlySpan box) - { - long res = 0; - - for (int j = 0; j < BC; j += 8) - { - res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j; - } - - return res; - } - - /** - * Replace every byte of the input by the byte at that place - * in the nonlinear S-box - */ - - private void Substitution(ReadOnlySpan box) - { - A0 = ApplyS(A0, box); - A1 = ApplyS(A1, box); - A2 = ApplyS(A2, box); - A3 = ApplyS(A3, box); - } - - /** - * Mix the bytes of every column in a linear way - */ - - private void MixColumn() - { - long r0, r1, r2, r3; - - r0 = r1 = r2 = r3 = 0; - - for (int j = 0; j < BC; j += 8) - { - int a0 = (int)((A0 >> j) & 0xff); - int a1 = (int)((A1 >> j) & 0xff); - int a2 = (int)((A2 >> j) & 0xff); - int a3 = (int)((A3 >> j) & 0xff); - - r0 |= (long)((Mul0x2(a0) ^ Mul0x3(a1) ^ a2 ^ a3) & 0xff) << j; - - r1 |= (long)((Mul0x2(a1) ^ Mul0x3(a2) ^ a3 ^ a0) & 0xff) << j; - - r2 |= (long)((Mul0x2(a2) ^ Mul0x3(a3) ^ a0 ^ a1) & 0xff) << j; - - r3 |= (long)((Mul0x2(a3) ^ Mul0x3(a0) ^ a1 ^ a2) & 0xff) << j; - } - - A0 = r0; - A1 = r1; - A2 = r2; - A3 = r3; - } - - /** - * Mix the bytes of every column in a linear way - * This is the opposite operation of Mixcolumn - */ - - private void InvMixColumn() - { - long r0, r1, r2, r3; - - r0 = r1 = r2 = r3 = 0; - for (int j = 0; j < BC; j += 8) - { - int a0 = (int)((A0 >> j) & 0xff); - int a1 = (int)((A1 >> j) & 0xff); - int a2 = (int)((A2 >> j) & 0xff); - int a3 = (int)((A3 >> j) & 0xff); - - // - // pre-lookup the log table - // - a0 = (a0 != 0) ? (Logtable[a0 & 0xff] & 0xff) : -1; - a1 = (a1 != 0) ? (Logtable[a1 & 0xff] & 0xff) : -1; - a2 = (a2 != 0) ? (Logtable[a2 & 0xff] & 0xff) : -1; - a3 = (a3 != 0) ? (Logtable[a3 & 0xff] & 0xff) : -1; - - r0 |= (long)((Mul0xe(a0) ^ Mul0xb(a1) ^ Mul0xd(a2) ^ Mul0x9(a3)) & 0xff) << j; - - r1 |= (long)((Mul0xe(a1) ^ Mul0xb(a2) ^ Mul0xd(a3) ^ Mul0x9(a0)) & 0xff) << j; - - r2 |= (long)((Mul0xe(a2) ^ Mul0xb(a3) ^ Mul0xd(a0) ^ Mul0x9(a1)) & 0xff) << j; - - r3 |= (long)((Mul0xe(a3) ^ Mul0xb(a0) ^ Mul0xd(a1) ^ Mul0x9(a2)) & 0xff) << j; - } - - A0 = r0; - A1 = r1; - A2 = r2; - A3 = r3; - } - - /** - * Calculate the necessary round keys - * The number of calculations depends on keyBits and blockBits - */ - - private long[][] GenerateWorkingKey( - byte[] key) - { - int t, rconpointer = 0; - int keyBits = key.Length * 8; - byte[,] tk = new byte[4, MAXKC]; - - //long[,] W = new long[MAXROUNDS+1,4]; - long[][] W = new long[MAXROUNDS + 1][]; - - for (int i = 0; i < MAXROUNDS + 1; i++) - { - W[i] = new long[4]; - } - - var KC = keyBits switch - { - 128 => 4, - 160 => 5, - 192 => 6, - 224 => 7, - 256 => 8, - _ => throw new ArgumentException("Key length not 128/160/192/224/256 bits."), - }; - if (keyBits >= blockBits) - { - ROUNDS = KC + 6; + for (var j = 1; j < KC; j++) + { + for (var i = 0; i < 4; i++) + { + tk[i, j] ^= tk[i, j - 1]; + } + } } else { - ROUNDS = (BC / 8) + 6; + for (var j = 1; j < 4; j++) + { + for (var i = 0; i < 4; i++) + { + tk[i, j] ^= tk[i, j - 1]; + } + } + for (var i = 0; i < 4; i++) + { + tk[i, 4] ^= S[tk[i, 3] & 0xff]; + } + for (var j = 5; j < KC; j++) + { + for (var i = 0; i < 4; i++) + { + tk[i, j] ^= tk[i, j - 1]; + } + } } - // - // copy the key into the processing area - // - int index = 0; - - for (int i = 0; i < key.Length; i++) - { - tk[i % 4, i / 4] = key[index++]; - } - - t = 0; - // // copy values into round key array // - for (int j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) + for (var j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) { - for (int i = 0; i < 4; i++) + for (var i = 0; i < 4; i++) { - W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % BC); + W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % (BC)); } } + } + return W; + } - // - // while not enough round key material calculated - // calculate new values - // - while (t < (ROUNDS + 1) * (BC / 8)) - { - for (int i = 0; i < 4; i++) - { - tk[i, 0] ^= S[tk[(i + 1) % 4, KC - 1] & 0xff]; - } - tk[0, 0] ^= rcon[rconpointer++]; + private readonly int BC; + private readonly long BC_MASK; + private int ROUNDS; + private readonly int blockBits; + private long[][]? workingKey; + private long A0, + A1, + A2, + A3; + private bool forEncryption; + private readonly byte[] shifts0SC; + private readonly byte[] shifts1SC; - if (KC <= 6) - { - for (int j = 1; j < KC; j++) - { - for (int i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - } - else - { - for (int j = 1; j < 4; j++) - { - for (int i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - for (int i = 0; i < 4; i++) - { - tk[i, 4] ^= S[tk[i, 3] & 0xff]; - } - for (int j = 5; j < KC; j++) - { - for (int i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - } + /** + * default constructor - 128 bit block size. + */ - // - // copy values into round key array - // - for (int j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) - { - for (int i = 0; i < 4; i++) - { - W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % (BC)); - } - } - } - return W; + public RijndaelEngine() : this(128) { } + + /** + * basic constructor - set the cipher up for a given blocksize + * + * @param blocksize the blocksize in bits, must be 128, 192, or 256. + */ + + public RijndaelEngine(int blockBits) + { + switch (blockBits) + { + case 128: + BC = 32; + BC_MASK = 0xffffffffL; + shifts0SC = shifts0[0]; + shifts1SC = shifts1[0]; + break; + case 160: + BC = 40; + BC_MASK = 0xffffffffffL; + shifts0SC = shifts0[1]; + shifts1SC = shifts1[1]; + break; + case 192: + BC = 48; + BC_MASK = 0xffffffffffffL; + shifts0SC = shifts0[2]; + shifts1SC = shifts1[2]; + break; + case 224: + BC = 56; + BC_MASK = 0xffffffffffffffL; + shifts0SC = shifts0[3]; + shifts1SC = shifts1[3]; + break; + case 256: + BC = 64; + BC_MASK = unchecked((long)0xffffffffffffffffL); + shifts0SC = shifts0[4]; + shifts1SC = shifts1[4]; + break; + default: + throw new ArgumentException("unknown blocksize to Rijndael"); } - private readonly int BC; - private readonly long BC_MASK; - private int ROUNDS; - private readonly int blockBits; - private long[][]? workingKey; - private long A0, A1, A2, A3; - private bool forEncryption; - private readonly byte[] shifts0SC; - private readonly byte[] shifts1SC; + this.blockBits = blockBits; + } - /** - * default constructor - 128 bit block size. - */ + /** + * initialise a Rijndael cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param parameters the parameters required to set up the cipher. + * @exception ArgumentException if the parameters argument is + * inappropriate. + */ - public RijndaelEngine() - : this(128) + public void Init(bool forEncryption, ICipherParameters parameters) + { + if (parameters is KeyParameter parameter) { + workingKey = GenerateWorkingKey(parameter.GetKey()); + this.forEncryption = forEncryption; + return; } - /** - * basic constructor - set the cipher up for a given blocksize - * - * @param blocksize the blocksize in bits, must be 128, 192, or 256. - */ + throw new ArgumentException( + "invalid parameter passed to Rijndael init - " + parameters.GetType() + ); + } - public RijndaelEngine( - int blockBits) + public string AlgorithmName => "Rijndael"; + + public bool IsPartialBlockOkay => false; + + public int GetBlockSize() => BC / 2; + + public int ProcessBlock(ReadOnlySpan input, Span output) + { + if (workingKey is null) { - switch (blockBits) - { - case 128: - BC = 32; - BC_MASK = 0xffffffffL; - shifts0SC = shifts0[0]; - shifts1SC = shifts1[0]; - break; - case 160: - BC = 40; - BC_MASK = 0xffffffffffL; - shifts0SC = shifts0[1]; - shifts1SC = shifts1[1]; - break; - case 192: - BC = 48; - BC_MASK = 0xffffffffffffL; - shifts0SC = shifts0[2]; - shifts1SC = shifts1[2]; - break; - case 224: - BC = 56; - BC_MASK = 0xffffffffffffffL; - shifts0SC = shifts0[3]; - shifts1SC = shifts1[3]; - break; - case 256: - BC = 64; - BC_MASK = unchecked((long)0xffffffffffffffffL); - shifts0SC = shifts0[4]; - shifts1SC = shifts1[4]; - break; - default: - throw new ArgumentException("unknown blocksize to Rijndael"); - } - - this.blockBits = blockBits; + throw new InvalidOperationException("Rijndael engine not initialised"); } - /** - * initialise a Rijndael cipher. - * - * @param forEncryption whether or not we are for encryption. - * @param parameters the parameters required to set up the cipher. - * @exception ArgumentException if the parameters argument is - * inappropriate. - */ - - public void Init( - bool forEncryption, - ICipherParameters parameters) + if (BC / 2 > input.Length) { - var parameter = parameters as KeyParameter; - if (parameter != null) - { - workingKey = GenerateWorkingKey(parameter.GetKey()); - this.forEncryption = forEncryption; - return; - } - - throw new ArgumentException("invalid parameter passed to Rijndael init - " + parameters.GetType()); + throw new DataLengthException("input buffer too short"); } - public string AlgorithmName => "Rijndael"; - - public bool IsPartialBlockOkay => false; - - public int GetBlockSize() + if (BC / 2 > output.Length) { - return BC / 2; + throw new DataLengthException("output buffer too short"); } - public int ProcessBlock(ReadOnlySpan input, Span output) + UnPackBlock(input); + + if (forEncryption) { - if (workingKey is null) - { - throw new InvalidOperationException("Rijndael engine not initialised"); - } - - if (BC / 2 > input.Length) - { - throw new DataLengthException("input buffer too short"); - } - - if (BC / 2 > output.Length) - { - throw new DataLengthException("output buffer too short"); - } - - UnPackBlock(input); - - if (forEncryption) - { - EncryptBlock(workingKey); - } - else - { - DecryptBlock(workingKey); - } - - PackBlock(output); - - return BC / 2; + EncryptBlock(workingKey); + } + else + { + DecryptBlock(workingKey); } - public void Reset() + PackBlock(output); + + return BC / 2; + } + + public void Reset() { } + + private void UnPackBlock(ReadOnlySpan bytes) + { + var index = 0; + + A0 = bytes[index++] & 0xff; + A1 = bytes[index++] & 0xff; + A2 = bytes[index++] & 0xff; + A3 = bytes[index++] & 0xff; + + for (var j = 8; j != BC; j += 8) { - } - - private void UnPackBlock(ReadOnlySpan bytes) - { - int index = 0; - - A0 = bytes[index++] & 0xff; - A1 = bytes[index++] & 0xff; - A2 = bytes[index++] & 0xff; - A3 = bytes[index++] & 0xff; - - for (int j = 8; j != BC; j += 8) - { - A0 |= (long)(bytes[index++] & 0xff) << j; - A1 |= (long)(bytes[index++] & 0xff) << j; - A2 |= (long)(bytes[index++] & 0xff) << j; - A3 |= (long)(bytes[index++] & 0xff) << j; - } - } - - private void PackBlock(Span bytes) - { - int index = 0; - - for (int j = 0; j != BC; j += 8) - { - bytes[index++] = (byte)(A0 >> j); - bytes[index++] = (byte)(A1 >> j); - bytes[index++] = (byte)(A2 >> j); - bytes[index++] = (byte)(A3 >> j); - } - } - - private void EncryptBlock(long[][] rk) - { - int r; - - // - // begin with a key addition - // - KeyAddition(rk[0]); - - // - // ROUNDS-1 ordinary rounds - // - for (r = 1; r < ROUNDS; r++) - { - Substitution(S); - ShiftRow(shifts0SC); - MixColumn(); - KeyAddition(rk[r]); - } - - // - // Last round is special: there is no MixColumn - // - Substitution(S); - ShiftRow(shifts0SC); - KeyAddition(rk[ROUNDS]); - } - - private void DecryptBlock(long[][] rk) - { - int r; - - // To decrypt: apply the inverse operations of the encrypt routine, - // in opposite order - // - // (KeyAddition is an involution: it 's equal to its inverse) - // (the inverse of Substitution with table S is Substitution with the inverse table of S) - // (the inverse of Shiftrow is Shiftrow over a suitable distance) - // - - // First the special round: - // without InvMixColumn - // with extra KeyAddition - // - KeyAddition(rk[ROUNDS]); - Substitution(Si); - ShiftRow(shifts1SC); - - // - // ROUNDS-1 ordinary rounds - // - for (r = ROUNDS - 1; r > 0; r--) - { - KeyAddition(rk[r]); - InvMixColumn(); - Substitution(Si); - ShiftRow(shifts1SC); - } - - // - // End with the extra key addition - // - KeyAddition(rk[0]); + A0 |= (long)(bytes[index++] & 0xff) << j; + A1 |= (long)(bytes[index++] & 0xff) << j; + A2 |= (long)(bytes[index++] & 0xff) << j; + A3 |= (long)(bytes[index++] & 0xff) << j; } } -} \ No newline at end of file + + private void PackBlock(Span bytes) + { + var index = 0; + + for (var j = 0; j != BC; j += 8) + { + bytes[index++] = (byte)(A0 >> j); + bytes[index++] = (byte)(A1 >> j); + bytes[index++] = (byte)(A2 >> j); + bytes[index++] = (byte)(A3 >> j); + } + } + + private void EncryptBlock(long[][] rk) + { + int r; + + // + // begin with a key addition + // + KeyAddition(rk[0]); + + // + // ROUNDS-1 ordinary rounds + // + for (r = 1; r < ROUNDS; r++) + { + Substitution(S); + ShiftRow(shifts0SC); + MixColumn(); + KeyAddition(rk[r]); + } + + // + // Last round is special: there is no MixColumn + // + Substitution(S); + ShiftRow(shifts0SC); + KeyAddition(rk[ROUNDS]); + } + + private void DecryptBlock(long[][] rk) + { + int r; + + // To decrypt: apply the inverse operations of the encrypt routine, + // in opposite order + // + // (KeyAddition is an involution: it 's equal to its inverse) + // (the inverse of Substitution with table S is Substitution with the inverse table of S) + // (the inverse of Shiftrow is Shiftrow over a suitable distance) + // + + // First the special round: + // without InvMixColumn + // with extra KeyAddition + // + KeyAddition(rk[ROUNDS]); + Substitution(Si); + ShiftRow(shifts1SC); + + // + // ROUNDS-1 ordinary rounds + // + for (r = ROUNDS - 1; r > 0; r--) + { + KeyAddition(rk[r]); + InvMixColumn(); + Substitution(Si); + ShiftRow(shifts1SC); + } + + // + // End with the extra key addition + // + KeyAddition(rk[0]); + } +} diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index 9f5cb001..7ec68508 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -1,92 +1,87 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using SharpCompress.Archives; using SharpCompress.Common; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +/// +public abstract class Factory : IFactory { - /// - public abstract class Factory : IFactory + static Factory() { - static Factory() + RegisterFactory(new ZipFactory()); + RegisterFactory(new RarFactory()); + RegisterFactory(new SevenZipFactory()); + RegisterFactory(new GZipFactory()); + RegisterFactory(new TarFactory()); + } + + private static readonly HashSet _factories = new HashSet(); + + /// + /// Gets the collection of registered . + /// + public static IEnumerable Factories => _factories; + + /// + /// Registers an archive factory. + /// + /// The factory to register. + /// must not be null. + public static void RegisterFactory(Factory factory) + { + factory.CheckNotNull(nameof(factory)); + + _factories.Add(factory); + } + + /// + public abstract string Name { get; } + + /// + public virtual ArchiveType? KnownArchiveType => null; + + /// + public abstract IEnumerable GetSupportedExtensions(); + + /// + public abstract bool IsArchive(Stream stream, string? password = null); + + /// + public virtual FileInfo? GetFilePart(int index, FileInfo part1) => null; + + /// + /// Tries to open an from a . + /// + /// + /// This method provides extra insight to support loading compressed TAR files. + /// + /// + /// + /// + /// + internal virtual bool TryOpenReader( + RewindableStream rewindableStream, + ReaderOptions options, + out IReader? reader + ) + { + reader = null; + + if (this is IReaderFactory readerFactory) { - RegisterFactory(new ZipFactory()); - RegisterFactory(new RarFactory()); - RegisterFactory(new SevenZipFactory()); - RegisterFactory(new GZipFactory()); - RegisterFactory(new TarFactory()); - } - - private static readonly HashSet _factories = new HashSet(); - - /// - /// Gets the collection of registered . - /// - public static IEnumerable Factories => _factories; - - /// - /// Registers an archive factory. - /// - /// The factory to register. - /// must not be null. - public static void RegisterFactory(Factory factory) - { - factory.CheckNotNull(nameof(factory)); - - _factories.Add(factory); - } - - /// - public abstract string Name { get; } - - /// - public virtual ArchiveType? KnownArchiveType => null; - - /// - public abstract IEnumerable GetSupportedExtensions(); - - /// - public abstract bool IsArchive(Stream stream, string? password = null); - - /// - public virtual FileInfo? GetFilePart(int index, FileInfo part1) - { - return null; - } - - /// - /// Tries to open an from a . - /// - /// - /// This method provides extra insight to support loading compressed TAR files. - /// - /// - /// - /// - /// - internal virtual bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) - { - reader = null; - - if (this is IReaderFactory readerFactory) + rewindableStream.Rewind(false); + if (IsArchive(rewindableStream, options.Password)) { - rewindableStream.Rewind(false); - if (this.IsArchive(rewindableStream, options.Password)) - { - rewindableStream.Rewind(true); - reader = readerFactory.OpenReader(rewindableStream, options); - return true; - } + rewindableStream.Rewind(true); + reader = readerFactory.OpenReader(rewindableStream, options); + return true; } - - return false; - } + } + + return false; } } diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index fe6f4260..5e522f5b 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Linq; using SharpCompress.Archives; using SharpCompress.Archives.GZip; @@ -15,129 +13,118 @@ using SharpCompress.Readers.Tar; using SharpCompress.Writers; using SharpCompress.Writers.GZip; -namespace SharpCompress.Factories -{ - /// - /// Represents the foundation factory of GZip archive. - /// - public class GZipFactory : Factory, +namespace SharpCompress.Factories; + +/// +/// Represents the foundation factory of GZip archive. +/// +public class GZipFactory + : Factory, IArchiveFactory, IMultiArchiveFactory, IReaderFactory, IWriterFactory, IWriteableArchiveFactory +{ + #region IFactory + + /// + public override string Name => "GZip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.GZip; + + /// + public override IEnumerable GetSupportedExtensions() { - #region IFactory + yield return "gz"; + } - /// - public override string Name => "GZip"; + /// + public override bool IsArchive(Stream stream, string? password = null) => + GZipArchive.IsGZipFile(stream); - /// - public override ArchiveType? KnownArchiveType => ArchiveType.GZip; + #endregion - /// - public override IEnumerable GetSupportedExtensions() + #region IArchiveFactory + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => + GZipArchive.Open(stream, readerOptions); + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + GZipArchive.Open(fileInfo, readerOptions); + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => + GZipArchive.Open(streams, readerOptions); + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => + GZipArchive.Open(fileInfos, readerOptions); + + #endregion + + #region IReaderFactory + + /// + internal override bool TryOpenReader( + RewindableStream rewindableStream, + ReaderOptions options, + out IReader? reader + ) + { + reader = null; + + rewindableStream.Rewind(false); + if (GZipArchive.IsGZipFile(rewindableStream)) { - yield return "gz"; - } - - /// - public override bool IsArchive(Stream stream, string? password = null) - { - return GZipArchive.IsGZipFile(stream); - } - - #endregion - - #region IArchiveFactory - - /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - return GZipArchive.Open(stream, readerOptions); - } - - /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - return GZipArchive.Open(fileInfo, readerOptions); - } - - #endregion - - #region IMultiArchiveFactory - - /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) - { - return GZipArchive.Open(streams, readerOptions); - } - - /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) - { - return GZipArchive.Open(fileInfos, readerOptions); - } - - #endregion - - #region IReaderFactory - - /// - internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) - { - reader = null; - rewindableStream.Rewind(false); - if (GZipArchive.IsGZipFile(rewindableStream)) + var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) { - rewindableStream.Rewind(false); - var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - reader = new TarReader(rewindableStream, options, CompressionType.GZip); - return true; - } - rewindableStream.Rewind(true); - reader = OpenReader(rewindableStream, options); + reader = new TarReader(rewindableStream, options, CompressionType.GZip); return true; } - - return false; + + rewindableStream.Rewind(true); + reader = OpenReader(rewindableStream, options); + return true; } - /// - public IReader OpenReader(Stream stream, ReaderOptions? options) - { - return GZipReader.Open(stream, options); - } - - #endregion - - #region IWriterFactory - - /// - public IWriter Open(Stream stream, WriterOptions writerOptions) - { - if (writerOptions.CompressionType != CompressionType.GZip) - { - throw new InvalidFormatException("GZip archives only support GZip compression type."); - } - return new GZipWriter(stream, new GZipWriterOptions(writerOptions)); - } - - #endregion - - #region IWriteableArchiveFactory - - /// - public IWritableArchive CreateWriteableArchive() - { - return GZipArchive.Create(); - } - - #endregion + return false; } + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) => + GZipReader.Open(stream, options); + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) + { + if (writerOptions.CompressionType != CompressionType.GZip) + { + throw new InvalidFormatException("GZip archives only support GZip compression type."); + } + return new GZipWriter(stream, new GZipWriterOptions(writerOptions)); + } + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() => GZipArchive.Create(); + + #endregion } diff --git a/src/SharpCompress/Factories/IFactory.cs b/src/SharpCompress/Factories/IFactory.cs index 82e27f11..08bedd50 100644 --- a/src/SharpCompress/Factories/IFactory.cs +++ b/src/SharpCompress/Factories/IFactory.cs @@ -1,55 +1,49 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +/// +/// Represents the foundation of an archive factory. +/// +/// +/// To get extended functionality, this type can be cast to:
+///
+///
+///
+///
+public interface IFactory { /// - /// Represents the foundation of an archive factory. + /// Gets the archive Type name /// - /// - /// To get extended functionality, this type can be cast to:
- ///
- ///
- ///
- ///
- public interface IFactory - { - /// - /// Gets the archive Type name - /// - string Name { get; } + string Name { get; } - /// - /// Gets the archive Type in case it is a well known archive format. - /// - Common.ArchiveType? KnownArchiveType { get; } + /// + /// Gets the archive Type in case it is a well known archive format. + /// + Common.ArchiveType? KnownArchiveType { get; } - /// - /// returns the extensions typically used by this archive type. - /// - /// - IEnumerable GetSupportedExtensions(); + /// + /// returns the extensions typically used by this archive type. + /// + /// + IEnumerable GetSupportedExtensions(); - /// - /// Returns true if the stream represents an archive of the format defined by this type. - /// - /// A stream, pointing to the beginning of the archive. - /// optional password - bool IsArchive(Stream stream, string? password = null); + /// + /// Returns true if the stream represents an archive of the format defined by this type. + /// + /// A stream, pointing to the beginning of the archive. + /// optional password + bool IsArchive(Stream stream, string? password = null); - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// Path to the first part. - /// - /// The path to the requested part, - /// or NULL if the part does not exist. - /// - FileInfo? GetFilePart(int index, FileInfo part1); - } + /// + /// From a passed in archive (zip, rar, 7z, 001), return all parts. + /// + /// Path to the first part. + /// + /// The path to the requested part, + /// or NULL if the part does not exist. + /// + FileInfo? GetFilePart(int index, FileInfo part1); } - diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index 7d6c16ba..cfdb7ff2 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -1,93 +1,72 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; using SharpCompress.Archives; using SharpCompress.Archives.Rar; using SharpCompress.Common; -using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Rar; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +/// +/// Represents the foundation factory of RAR archive. +/// +public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReaderFactory { - /// - /// Represents the foundation factory of RAR archive. - /// - public class RarFactory : Factory, - IArchiveFactory, - IMultiArchiveFactory, - IReaderFactory + #region IArchive + + /// + public override string Name => "Rar"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Rar; + + /// + public override IEnumerable GetSupportedExtensions() { - #region IArchive - - /// - public override string Name => "Rar"; - - /// - public override ArchiveType? KnownArchiveType => ArchiveType.Rar; - - /// - public override IEnumerable GetSupportedExtensions() - { - yield return "rar"; - yield return "cbr"; - } - - /// - public override bool IsArchive(Stream stream, string? password = null) - { - return RarArchive.IsRarFile(stream); - } - - /// - public override FileInfo? GetFilePart(int index, FileInfo part1) - { - return RarArchiveVolumeFactory.GetFilePart(index, part1); - } - - #endregion - - #region IArchiveFactory - - /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - return RarArchive.Open(stream, readerOptions); - } - - /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - return RarArchive.Open(fileInfo, readerOptions); - } - - #endregion - - #region IMultiArchiveFactory - - /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) - { - return RarArchive.Open(streams, readerOptions); - } - - /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) - { - return RarArchive.Open(fileInfos, readerOptions); - } - - #endregion - - #region IReaderFactory - - /// - public IReader OpenReader(Stream stream, ReaderOptions? options) - { - return RarReader.Open(stream, options); - } - - #endregion + yield return "rar"; + yield return "cbr"; } + + /// + public override bool IsArchive(Stream stream, string? password = null) => + RarArchive.IsRarFile(stream); + + /// + public override FileInfo? GetFilePart(int index, FileInfo part1) => + RarArchiveVolumeFactory.GetFilePart(index, part1); + + #endregion + + #region IArchiveFactory + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => + RarArchive.Open(stream, readerOptions); + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + RarArchive.Open(fileInfo, readerOptions); + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => + RarArchive.Open(streams, readerOptions); + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => + RarArchive.Open(fileInfos, readerOptions); + + #endregion + + #region IReaderFactory + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) => + RarReader.Open(stream, options); + + #endregion } diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index c7d4dd8b..cfc99d57 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -1,85 +1,73 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; using SharpCompress.Archives; using SharpCompress.Archives.SevenZip; using SharpCompress.Common; -using SharpCompress.Common.SevenZip; using SharpCompress.IO; using SharpCompress.Readers; -namespace SharpCompress.Factories +namespace SharpCompress.Factories; + +/// +/// Represents the foundation factory of 7Zip archive. +/// +public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory { - /// - /// Represents the foundation factory of 7Zip archive. - /// - public class SevenZipFactory : Factory, - IArchiveFactory, - IMultiArchiveFactory + #region IFactory + + /// + public override string Name => "7Zip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.SevenZip; + + /// + public override IEnumerable GetSupportedExtensions() { - #region IFactory - - /// - public override string Name => "7Zip"; - - /// - public override ArchiveType? KnownArchiveType => ArchiveType.SevenZip; - - /// - public override IEnumerable GetSupportedExtensions() - { - yield return "7z"; - } - - /// - public override bool IsArchive(Stream stream, string? password = null) - { - return SevenZipArchive.IsSevenZipFile(stream); - } - - #endregion - - #region IArchiveFactory - - /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - return SevenZipArchive.Open(stream, readerOptions); - } - - /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - return SevenZipArchive.Open(fileInfo, readerOptions); - } - - #endregion - - #region IMultiArchiveFactory - - /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) - { - return SevenZipArchive.Open(streams, readerOptions); - } - - /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) - { - return SevenZipArchive.Open(fileInfos, readerOptions); - } - - #endregion - - #region reader - - internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) - { - reader = null; - return false; - } - - #endregion + yield return "7z"; } + + /// + public override bool IsArchive(Stream stream, string? password = null) => + SevenZipArchive.IsSevenZipFile(stream); + + #endregion + + #region IArchiveFactory + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => + SevenZipArchive.Open(stream, readerOptions); + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + SevenZipArchive.Open(fileInfo, readerOptions); + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => + SevenZipArchive.Open(streams, readerOptions); + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => + SevenZipArchive.Open(fileInfos, readerOptions); + + #endregion + + #region reader + + internal override bool TryOpenReader( + RewindableStream rewindableStream, + ReaderOptions options, + out IReader? reader + ) + { + reader = null; + return false; + } + + #endregion } diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 4214ca93..61965b86 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -1,11 +1,8 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; using SharpCompress.Archives; using SharpCompress.Archives.Tar; -using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; @@ -17,181 +14,174 @@ using SharpCompress.Readers.Tar; using SharpCompress.Writers; using SharpCompress.Writers.Tar; -namespace SharpCompress.Factories -{ - /// - /// Represents the foundation factory of TAR archive. - /// - public class TarFactory : Factory, +namespace SharpCompress.Factories; + +/// +/// Represents the foundation factory of TAR archive. +/// +public class TarFactory + : Factory, IArchiveFactory, IMultiArchiveFactory, IReaderFactory, IWriterFactory, IWriteableArchiveFactory +{ + #region IFactory + + /// + public override string Name => "Tar"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Tar; + + /// + public override IEnumerable GetSupportedExtensions() { - #region IFactory + // from https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files - /// - public override string Name => "Tar"; + yield return "tar"; - /// - public override ArchiveType? KnownArchiveType => ArchiveType.Tar; + // gzip + yield return "taz"; + yield return "tgz"; - /// - public override IEnumerable GetSupportedExtensions() + // bzip2 + yield return "tb2"; + yield return "tbz"; + yield return "tbz2"; + yield return "tz2"; + + // lzma + // yield return "tlz"; // unsupported + + // xz + // yield return "txz"; // unsupported + + // compress + yield return "tZ"; + yield return "taZ"; + + // zstd + // yield return "tzst"; // unsupported + } + + /// + public override bool IsArchive(Stream stream, string? password = null) => + TarArchive.IsTarFile(stream); + + #endregion + + #region IArchiveFactory + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => + TarArchive.Open(stream, readerOptions); + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + TarArchive.Open(fileInfo, readerOptions); + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => + TarArchive.Open(streams, readerOptions); + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => + TarArchive.Open(fileInfos, readerOptions); + + #endregion + + #region IReaderFactory + + /// + internal override bool TryOpenReader( + RewindableStream rewindableStream, + ReaderOptions options, + out IReader? reader + ) + { + reader = null; + + rewindableStream.Rewind(false); + if (TarArchive.IsTarFile(rewindableStream)) { - // from https://en.wikipedia.org/wiki/Tar_(computing)#Suffixes_for_compressed_files - - yield return "tar"; - - // gzip - yield return "taz"; - yield return "tgz"; - - // bzip2 - yield return "tb2"; - yield return "tbz"; - yield return "tbz2"; - yield return "tz2"; - - // lzma - // yield return "tlz"; // unsupported - - // xz - // yield return "txz"; // unsupported - - // compress - yield return "tZ"; - yield return "taZ"; - - // zstd - // yield return "tzst"; // unsupported + rewindableStream.Rewind(true); + reader = OpenReader(rewindableStream, options); + return true; } - /// - public override bool IsArchive(Stream stream, string? password = null) + rewindableStream.Rewind(false); + if (BZip2Stream.IsBZip2(rewindableStream)) { - return TarArchive.IsTarFile(stream); - } - - #endregion - - #region IArchiveFactory - - /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - return TarArchive.Open(stream, readerOptions); - } - - /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - return TarArchive.Open(fileInfo, readerOptions); - } - - #endregion - - #region IMultiArchiveFactory - - /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) - { - return TarArchive.Open(streams, readerOptions); - } - - /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) - { - return TarArchive.Open(fileInfos, readerOptions); - } - - #endregion - - #region IReaderFactory - - /// - internal override bool TryOpenReader(RewindableStream rewindableStream, ReaderOptions options, out IReader? reader) - { - reader = null; - rewindableStream.Rewind(false); - if (TarArchive.IsTarFile(rewindableStream)) + var testStream = new BZip2Stream( + NonDisposingStream.Create(rewindableStream), + CompressionMode.Decompress, + false + ); + if (TarArchive.IsTarFile(testStream)) { rewindableStream.Rewind(true); - reader = OpenReader(rewindableStream, options); + reader = new TarReader(rewindableStream, options, CompressionType.BZip2); return true; } + } + rewindableStream.Rewind(false); + if (LZipStream.IsLZipFile(rewindableStream)) + { rewindableStream.Rewind(false); - if (BZip2Stream.IsBZip2(rewindableStream)) - { - rewindableStream.Rewind(false); - var testStream = new BZip2Stream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress, false); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - reader = new TarReader(rewindableStream, options, CompressionType.BZip2); - return true; - } - } - - rewindableStream.Rewind(false); - if (LZipStream.IsLZipFile(rewindableStream)) - { - rewindableStream.Rewind(false); - var testStream = new LZipStream(NonDisposingStream.Create(rewindableStream), CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - reader = new TarReader(rewindableStream, options, CompressionType.LZip); - return true; - } - } - - rewindableStream.Rewind(false); - if (XZStream.IsXZStream(rewindableStream)) + var testStream = new LZipStream( + NonDisposingStream.Create(rewindableStream), + CompressionMode.Decompress + ); + if (TarArchive.IsTarFile(testStream)) { rewindableStream.Rewind(true); - var testStream = new XZStream(rewindableStream); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - reader = new TarReader(rewindableStream, options, CompressionType.Xz); - return true; - } + reader = new TarReader(rewindableStream, options, CompressionType.LZip); + return true; } - - return false; } - /// - public IReader OpenReader(Stream stream, ReaderOptions? options) + rewindableStream.Rewind(false); + if (XZStream.IsXZStream(rewindableStream)) { - return TarReader.Open(stream, options); + rewindableStream.Rewind(true); + var testStream = new XZStream(rewindableStream); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + reader = new TarReader(rewindableStream, options, CompressionType.Xz); + return true; + } } - #endregion - - #region IWriterFactory - - /// - public IWriter Open(Stream stream, WriterOptions writerOptions) - { - return new TarWriter(stream, new TarWriterOptions(writerOptions)); - } - - #endregion - - #region IWriteableArchiveFactory - - /// - public IWritableArchive CreateWriteableArchive() - { - return TarArchive.Create(); - } - - #endregion - + return false; } + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) => + TarReader.Open(stream, options); + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) => + new TarWriter(stream, new TarWriterOptions(writerOptions)); + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() => TarArchive.Create(); + + #endregion } diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index a3e6e757..dca697dc 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -1,140 +1,123 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; -using SharpCompress.Common.Tar.Headers; -using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Zip; using SharpCompress.Writers; using SharpCompress.Writers.Zip; -namespace SharpCompress.Factories -{ - /// - /// Represents the foundation factory of ZIP archive. - /// - public class ZipFactory : Factory, +namespace SharpCompress.Factories; + +/// +/// Represents the foundation factory of ZIP archive. +/// +public class ZipFactory + : Factory, IArchiveFactory, IMultiArchiveFactory, IReaderFactory, IWriterFactory, IWriteableArchiveFactory +{ + #region IFactory + + /// + public override string Name => "Zip"; + + /// + public override ArchiveType? KnownArchiveType => ArchiveType.Zip; + + /// + public override IEnumerable GetSupportedExtensions() { - #region IFactory + yield return "zip"; + yield return "zipx"; + yield return "cbz"; + } - /// - public override string Name => "Zip"; + /// + public override bool IsArchive(Stream stream, string? password = null) + { + var startPosition = stream.CanSeek ? stream.Position : -1; - /// - public override ArchiveType? KnownArchiveType => ArchiveType.Zip; + // probe for single volume zip - /// - public override IEnumerable GetSupportedExtensions() + if (ZipArchive.IsZipFile(stream, password)) { - yield return "zip"; - yield return "zipx"; - yield return "cbz"; + return true; } - /// - public override bool IsArchive(Stream stream, string? password = null) + // probe for a multipart zip + + if (!stream.CanSeek) { - var startPosition = stream.CanSeek - ? stream.Position - : -1; - - // probe for single volume zip - - if (ZipArchive.IsZipFile(stream, password)) - return true; - - // probe for a multipart zip - - if (!stream.CanSeek) - return false; - - stream.Position = startPosition; - - //test the zip (last) file of a multipart zip - if (ZipArchive.IsZipMulti(stream)) - return true; - return false; } - /// - public override FileInfo? GetFilePart(int index, FileInfo part1) + stream.Position = startPosition; + + //test the zip (last) file of a multipart zip + if (ZipArchive.IsZipMulti(stream)) { - return ZipArchiveVolumeFactory.GetFilePart(index, part1); + return true; } - #endregion - - #region IArchiveFactory - - /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - return ZipArchive.Open(stream, readerOptions); - } - - /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - return ZipArchive.Open(fileInfo, readerOptions); - } - - #endregion - - #region IMultiArchiveFactory - - /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) - { - return ZipArchive.Open(streams, readerOptions); - } - - /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) - { - return ZipArchive.Open(fileInfos, readerOptions); - } - - #endregion - - #region IReaderFactory - - /// - public IReader OpenReader(Stream stream, ReaderOptions? options) - { - return ZipReader.Open(stream, options); - } - - #endregion - - #region IWriterFactory - - /// - public IWriter Open(Stream stream, WriterOptions writerOptions) - { - return new ZipWriter(stream, new ZipWriterOptions(writerOptions)); - } - - #endregion - - #region IWriteableArchiveFactory - - /// - public IWritableArchive CreateWriteableArchive() - { - return ZipArchive.Create(); - } - - #endregion + return false; } + + /// + public override FileInfo? GetFilePart(int index, FileInfo part1) => + ZipArchiveVolumeFactory.GetFilePart(index, part1); + + #endregion + + #region IArchiveFactory + + /// + public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => + ZipArchive.Open(stream, readerOptions); + + /// + public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + ZipArchive.Open(fileInfo, readerOptions); + + #endregion + + #region IMultiArchiveFactory + + /// + public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => + ZipArchive.Open(streams, readerOptions); + + /// + public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => + ZipArchive.Open(fileInfos, readerOptions); + + #endregion + + #region IReaderFactory + + /// + public IReader OpenReader(Stream stream, ReaderOptions? options) => + ZipReader.Open(stream, options); + + #endregion + + #region IWriterFactory + + /// + public IWriter Open(Stream stream, WriterOptions writerOptions) => + new ZipWriter(stream, new ZipWriterOptions(writerOptions)); + + #endregion + + #region IWriteableArchiveFactory + + /// + public IWritableArchive CreateWriteableArchive() => ZipArchive.Create(); + + #endregion } diff --git a/src/SharpCompress/IO/BufferedSubStream.cs b/src/SharpCompress/IO/BufferedSubStream.cs index 9b6ae6cd..99e2bfef 100644 --- a/src/SharpCompress/IO/BufferedSubStream.cs +++ b/src/SharpCompress/IO/BufferedSubStream.cs @@ -1,83 +1,76 @@ using System; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +internal class BufferedSubStream : NonDisposingStream { - internal class BufferedSubStream : NonDisposingStream + private long position; + private int cacheOffset; + private int cacheLength; + private readonly byte[] cache; + + public BufferedSubStream(Stream stream, long origin, long bytesToRead) + : base(stream, throwOnDispose: false) { - private long position; - private int cacheOffset; - private int cacheLength; - private readonly byte[] cache; - - public BufferedSubStream(Stream stream, long origin, long bytesToRead) : base(stream, throwOnDispose: false) - { - position = origin; - BytesLeftToRead = bytesToRead; - cache = new byte[32 << 10]; - } - - private long BytesLeftToRead { get; set; } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => BytesLeftToRead; - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count > BytesLeftToRead) - { - count = (int)BytesLeftToRead; - } - - if (count > 0) - { - if (cacheLength == 0) - { - cacheOffset = 0; - Stream.Position = position; - cacheLength = Stream.Read(cache, 0, cache.Length); - position += cacheLength; - } - - if (count > cacheLength) - { - count = cacheLength; - } - - Buffer.BlockCopy(cache, cacheOffset, buffer, offset, count); - cacheOffset += count; - cacheLength -= count; - BytesLeftToRead -= count; - } - - return count; - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + position = origin; + BytesLeftToRead = bytesToRead; + cache = new byte[32 << 10]; } -} \ No newline at end of file + + private long BytesLeftToRead { get; set; } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => BytesLeftToRead; + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (count > BytesLeftToRead) + { + count = (int)BytesLeftToRead; + } + + if (count > 0) + { + if (cacheLength == 0) + { + cacheOffset = 0; + Stream.Position = position; + cacheLength = Stream.Read(cache, 0, cache.Length); + position += cacheLength; + } + + if (count > cacheLength) + { + count = cacheLength; + } + + Buffer.BlockCopy(cache, cacheOffset, buffer, offset, count); + cacheOffset += count; + cacheLength -= count; + BytesLeftToRead -= count; + } + + return count; + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/src/SharpCompress/IO/CountingWritableSubStream.cs b/src/SharpCompress/IO/CountingWritableSubStream.cs index b94303bf..206eae7c 100644 --- a/src/SharpCompress/IO/CountingWritableSubStream.cs +++ b/src/SharpCompress/IO/CountingWritableSubStream.cs @@ -1,56 +1,46 @@ using System; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +internal class CountingWritableSubStream : NonDisposingStream { - internal class CountingWritableSubStream : NonDisposingStream + internal CountingWritableSubStream(Stream stream) : base(stream, throwOnDispose: false) { } + + public ulong Count { get; private set; } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override void Flush() => Stream.Flush(); + + public override long Length => throw new NotSupportedException(); + + public override long Position { - internal CountingWritableSubStream(Stream stream) : base(stream, throwOnDispose: false) - { - } - - public ulong Count { get; private set; } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override void Flush() - { - Stream.Flush(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - Stream.Write(buffer, offset, count); - Count += (uint)count; - } - - public override void WriteByte(byte value) - { - Stream.WriteByte(value); - ++Count; - } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } -} \ No newline at end of file + + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + Stream.Write(buffer, offset, count); + Count += (uint)count; + } + + public override void WriteByte(byte value) + { + Stream.WriteByte(value); + ++Count; + } +} diff --git a/src/SharpCompress/IO/ListeningStream.cs b/src/SharpCompress/IO/ListeningStream.cs index 1bab99d2..a1bf715b 100644 --- a/src/SharpCompress/IO/ListeningStream.cs +++ b/src/SharpCompress/IO/ListeningStream.cs @@ -1,79 +1,71 @@ using System.IO; using SharpCompress.Common; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +internal class ListeningStream : Stream { - internal class ListeningStream : Stream + private long currentEntryTotalReadBytes; + private readonly IExtractionListener listener; + + public ListeningStream(IExtractionListener listener, Stream stream) { - private long currentEntryTotalReadBytes; - private readonly IExtractionListener listener; - - public ListeningStream(IExtractionListener listener, Stream stream) - { - Stream = stream; - this.listener = listener; - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - Stream.Dispose(); - } - base.Dispose(disposing); - } - - public Stream Stream { get; } - - public override bool CanRead => Stream.CanRead; - - public override bool CanSeek => Stream.CanSeek; - - public override bool CanWrite => Stream.CanWrite; - - public override void Flush() - { - Stream.Flush(); - } - - public override long Length => Stream.Length; - - public override long Position { get => Stream.Position; set => Stream.Position = value; } - - public override int Read(byte[] buffer, int offset, int count) - { - int read = Stream.Read(buffer, offset, count); - currentEntryTotalReadBytes += read; - listener.FireCompressedBytesRead(currentEntryTotalReadBytes, currentEntryTotalReadBytes); - return read; - } - - public override int ReadByte() - { - int value = Stream.ReadByte(); - if (value == -1) - { - return -1; - } - - ++currentEntryTotalReadBytes; - listener.FireCompressedBytesRead(currentEntryTotalReadBytes, currentEntryTotalReadBytes); - return value; - } - - public override long Seek(long offset, SeekOrigin origin) - { - return Stream.Seek(offset, origin); - } - - public override void SetLength(long value) - { - Stream.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - Stream.Write(buffer, offset, count); - } + Stream = stream; + this.listener = listener; } -} \ No newline at end of file + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Stream.Dispose(); + } + base.Dispose(disposing); + } + + public Stream Stream { get; } + + public override bool CanRead => Stream.CanRead; + + public override bool CanSeek => Stream.CanSeek; + + public override bool CanWrite => Stream.CanWrite; + + public override void Flush() => Stream.Flush(); + + public override long Length => Stream.Length; + + public override long Position + { + get => Stream.Position; + set => Stream.Position = value; + } + + public override int Read(byte[] buffer, int offset, int count) + { + var read = Stream.Read(buffer, offset, count); + currentEntryTotalReadBytes += read; + listener.FireCompressedBytesRead(currentEntryTotalReadBytes, currentEntryTotalReadBytes); + return read; + } + + public override int ReadByte() + { + var value = Stream.ReadByte(); + if (value == -1) + { + return -1; + } + + ++currentEntryTotalReadBytes; + listener.FireCompressedBytesRead(currentEntryTotalReadBytes, currentEntryTotalReadBytes); + return value; + } + + 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) => + Stream.Write(buffer, offset, count); +} diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index 5b3e7ffe..833effc5 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -2,210 +2,155 @@ using System.Buffers.Binary; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +internal class MarkingBinaryReader : BinaryReader { - internal class MarkingBinaryReader : BinaryReader + public MarkingBinaryReader(Stream stream) : base(stream) { } + + public virtual long CurrentReadByteCount { get; protected set; } + + public virtual void Mark() => CurrentReadByteCount = 0; + + public override int Read() => throw new NotSupportedException(); + + public override int Read(byte[] buffer, int index, int count) => + throw new NotSupportedException(); + + public override int Read(char[] buffer, int index, int count) => + throw new NotSupportedException(); + + public override bool ReadBoolean() => ReadByte() != 0; + + // NOTE: there is a somewhat fragile dependency on the internals of this class + // with RarCrcBinaryReader and RarCryptoBinaryReader. + // + // RarCrcBinaryReader/RarCryptoBinaryReader need to override any specific methods + // that call directly to the base BinaryReader and do not delegate to other methods + // in this class so that it can track the each byte being read. + // + // if altering this class in a way that changes the implementation be sure to + // update RarCrcBinaryReader/RarCryptoBinaryReader. + public override byte ReadByte() { - public MarkingBinaryReader(Stream stream) - : base(stream) + CurrentReadByteCount++; + return base.ReadByte(); + } + + public override byte[] ReadBytes(int count) + { + CurrentReadByteCount += count; + var bytes = base.ReadBytes(count); + if (bytes.Length != count) { + throw new EndOfStreamException( + string.Format( + "Could not read the requested amount of bytes. End of stream reached. Requested: {0} Read: {1}", + count, + bytes.Length + ) + ); } + return bytes; + } - public virtual long CurrentReadByteCount { get; protected set; } + public override char ReadChar() => throw new NotSupportedException(); - public virtual void Mark() + public override char[] ReadChars(int count) => throw new NotSupportedException(); + + public override double ReadDouble() => throw new NotSupportedException(); + + public override short ReadInt16() => BinaryPrimitives.ReadInt16LittleEndian(ReadBytes(2)); + + public override int ReadInt32() => BinaryPrimitives.ReadInt32LittleEndian(ReadBytes(4)); + + public override long ReadInt64() => BinaryPrimitives.ReadInt64LittleEndian(ReadBytes(8)); + + public override sbyte ReadSByte() => (sbyte)ReadByte(); + + public override float ReadSingle() => throw new NotSupportedException(); + + public override string ReadString() => throw new NotSupportedException(); + + public override ushort ReadUInt16() => BinaryPrimitives.ReadUInt16LittleEndian(ReadBytes(2)); + + public override uint ReadUInt32() => BinaryPrimitives.ReadUInt32LittleEndian(ReadBytes(4)); + + public override ulong ReadUInt64() => BinaryPrimitives.ReadUInt64LittleEndian(ReadBytes(8)); + + // RAR5 style variable length encoded value + // maximum value of 0xffffffffffffffff (64 bits) + // technote: "implies max 10 bytes consumed" -- but not really because we could extend indefinitely using 0x80 0x80 ... 0x80 00 + // + // Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data + // and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence. + // So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present, + // contains next 7 bits and so on. + public ulong ReadRarVInt(int maxBytes = 10) => + // hopefully this gets inlined + DoReadRarVInt((maxBytes - 1) * 7); + + private ulong DoReadRarVInt(int maxShift) + { + var shift = 0; + ulong result = 0; + do { - CurrentReadByteCount = 0; - } - - public override int Read() - { - throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int index, int count) - { - throw new NotSupportedException(); - } - - public override int Read(char[] buffer, int index, int count) - { - throw new NotSupportedException(); - } - - public override bool ReadBoolean() - { - return ReadByte() != 0; - } - - // NOTE: there is a somewhat fragile dependency on the internals of this class - // with RarCrcBinaryReader and RarCryptoBinaryReader. - // - // RarCrcBinaryReader/RarCryptoBinaryReader need to override any specific methods - // that call directly to the base BinaryReader and do not delegate to other methods - // in this class so that it can track the each byte being read. - // - // if altering this class in a way that changes the implementation be sure to - // update RarCrcBinaryReader/RarCryptoBinaryReader. - public override byte ReadByte() - { - CurrentReadByteCount++; - return base.ReadByte(); - } - - public override byte[] ReadBytes(int count) - { - CurrentReadByteCount += count; - var bytes = base.ReadBytes(count); - if (bytes.Length != count) + var b0 = ReadByte(); + var b1 = ((uint)b0) & 0x7f; + ulong n = b1; + var shifted = n << shift; + if (n != shifted >> shift) { - throw new EndOfStreamException(string.Format("Could not read the requested amount of bytes. End of stream reached. Requested: {0} Read: {1}", count, bytes.Length)); + // overflow + break; } - return bytes; - } - - public override char ReadChar() - { - throw new NotSupportedException(); - } - - public override char[] ReadChars(int count) - { - throw new NotSupportedException(); - } - - public override double ReadDouble() - { - throw new NotSupportedException(); - } - - public override short ReadInt16() - { - return BinaryPrimitives.ReadInt16LittleEndian(ReadBytes(2)); - } - - public override int ReadInt32() - { - return BinaryPrimitives.ReadInt32LittleEndian(ReadBytes(4)); - } - - public override long ReadInt64() - { - return BinaryPrimitives.ReadInt64LittleEndian(ReadBytes(8)); - } - - public override sbyte ReadSByte() - { - return (sbyte)ReadByte(); - } - - public override float ReadSingle() - { - throw new NotSupportedException(); - } - - public override string ReadString() - { - throw new NotSupportedException(); - } - - public override ushort ReadUInt16() - { - return BinaryPrimitives.ReadUInt16LittleEndian(ReadBytes(2)); - } - - public override uint ReadUInt32() - { - return BinaryPrimitives.ReadUInt32LittleEndian(ReadBytes(4)); - } - - public override ulong ReadUInt64() - { - return BinaryPrimitives.ReadUInt64LittleEndian(ReadBytes(8)); - } - - // RAR5 style variable length encoded value - // maximum value of 0xffffffffffffffff (64 bits) - // technote: "implies max 10 bytes consumed" -- but not really because we could extend indefinitely using 0x80 0x80 ... 0x80 00 - // - // Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data - // and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence. - // So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present, - // contains next 7 bits and so on. - public ulong ReadRarVInt(int maxBytes = 10) - { - // hopefully this gets inlined - return DoReadRarVInt((maxBytes - 1) * 7); - } - - private ulong DoReadRarVInt(int maxShift) - { - int shift = 0; - ulong result = 0; - do + result |= shifted; + if (b0 == b1) { - byte b0 = ReadByte(); - uint b1 = ((uint)b0) & 0x7f; - ulong n = b1; - ulong shifted = n << shift; - if (n != shifted >> shift) - { - // overflow - break; - } - result |= shifted; - if (b0 == b1) - { - return result; - } - shift += 7; - } while (shift <= maxShift); + return result; + } + shift += 7; + } while (shift <= maxShift); - throw new FormatException("malformed vint"); - } + throw new FormatException("malformed vint"); + } - public uint ReadRarVIntUInt32(int maxBytes = 5) + public uint ReadRarVIntUInt32(int maxBytes = 5) => + // hopefully this gets inlined + DoReadRarVIntUInt32((maxBytes - 1) * 7); + + public ushort ReadRarVIntUInt16(int maxBytes = 3) => + // hopefully this gets inlined + checked((ushort)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + + public byte ReadRarVIntByte(int maxBytes = 2) => + // hopefully this gets inlined + checked((byte)DoReadRarVIntUInt32((maxBytes - 1) * 7)); + + private uint DoReadRarVIntUInt32(int maxShift) + { + var shift = 0; + uint result = 0; + do { - // hopefully this gets inlined - return DoReadRarVIntUInt32((maxBytes - 1) * 7); - } - - public ushort ReadRarVIntUInt16(int maxBytes = 3) - { - // hopefully this gets inlined - return checked((ushort)DoReadRarVIntUInt32((maxBytes - 1) * 7)); - } - - public byte ReadRarVIntByte(int maxBytes = 2) - { - // hopefully this gets inlined - return checked((byte)DoReadRarVIntUInt32((maxBytes - 1) * 7)); - } - - private uint DoReadRarVIntUInt32(int maxShift) - { - int shift = 0; - uint result = 0; - do + var b0 = ReadByte(); + var b1 = ((uint)b0) & 0x7f; + var n = b1; + var shifted = n << shift; + if (n != shifted >> shift) { - byte b0 = ReadByte(); - uint b1 = ((uint)b0) & 0x7f; - uint n = b1; - uint shifted = n << shift; - if (n != shifted >> shift) - { - // overflow - break; - } - result |= shifted; - if (b0 == b1) - { - return result; - } - shift += 7; - } while (shift <= maxShift); + // overflow + break; + } + result |= shifted; + if (b0 == b1) + { + return result; + } + shift += 7; + } while (shift <= maxShift); - throw new FormatException("malformed vint"); - } + throw new FormatException("malformed vint"); } } diff --git a/src/SharpCompress/IO/NonDisposingStream.cs b/src/SharpCompress/IO/NonDisposingStream.cs index 12b9a6cf..334296d7 100644 --- a/src/SharpCompress/IO/NonDisposingStream.cs +++ b/src/SharpCompress/IO/NonDisposingStream.cs @@ -1,84 +1,73 @@ using System; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +public class NonDisposingStream : Stream { - public class NonDisposingStream : Stream + public static NonDisposingStream Create(Stream stream, bool throwOnDispose = false) { - public static NonDisposingStream Create(Stream stream, bool throwOnDispose = false) + if ( + stream is NonDisposingStream nonDisposingStream + && nonDisposingStream.ThrowOnDispose == throwOnDispose + ) { - if (stream is NonDisposingStream nonDisposingStream && nonDisposingStream.ThrowOnDispose == throwOnDispose) - { - return nonDisposingStream; - } - return new NonDisposingStream(stream, throwOnDispose); + return nonDisposingStream; } + return new NonDisposingStream(stream, throwOnDispose); + } - protected NonDisposingStream(Stream stream, bool throwOnDispose = false) + protected NonDisposingStream(Stream stream, bool throwOnDispose = false) + { + Stream = stream; + ThrowOnDispose = throwOnDispose; + } + + public bool ThrowOnDispose { get; set; } + + protected override void Dispose(bool disposing) + { + if (ThrowOnDispose) { - Stream = stream; - ThrowOnDispose = throwOnDispose; + throw new InvalidOperationException( + $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}" + ); } + } - public bool ThrowOnDispose { get; set; } + protected Stream Stream { get; } - protected override void Dispose(bool disposing) - { - if (ThrowOnDispose) - { - throw new InvalidOperationException($"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}"); - } - } + public override bool CanRead => Stream.CanRead; - protected Stream Stream { get; } + public override bool CanSeek => Stream.CanSeek; - public override bool CanRead => Stream.CanRead; + public override bool CanWrite => Stream.CanWrite; - public override bool CanSeek => Stream.CanSeek; + public override void Flush() => Stream.Flush(); - public override bool CanWrite => Stream.CanWrite; + public override long Length => Stream.Length; - public override void Flush() - { - Stream.Flush(); - } + public override long Position + { + get => Stream.Position; + set => Stream.Position = value; + } - public override long Length => Stream.Length; + public override int Read(byte[] buffer, int offset, int count) => + Stream.Read(buffer, offset, count); - public override long Position { get => Stream.Position; set => Stream.Position = value; } + public override long Seek(long offset, SeekOrigin origin) => Stream.Seek(offset, origin); - public override int Read(byte[] buffer, int offset, int count) - { - return Stream.Read(buffer, offset, count); - } + public override void SetLength(long value) => Stream.SetLength(value); - public override long Seek(long offset, SeekOrigin origin) - { - return Stream.Seek(offset, origin); - } - - public override void SetLength(long value) - { - Stream.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - Stream.Write(buffer, offset, count); - } + public override void Write(byte[] buffer, int offset, int count) => + Stream.Write(buffer, offset, count); #if !NETFRAMEWORK && !NETSTANDARD2_0 - public override int Read(Span buffer) - { - return Stream.Read(buffer); - } + public override int Read(Span buffer) => Stream.Read(buffer); - public override void Write(ReadOnlySpan buffer) - { - Stream.Write(buffer); - } + public override void Write(ReadOnlySpan buffer) => Stream.Write(buffer); #endif - } } diff --git a/src/SharpCompress/IO/ReadOnlySubStream.cs b/src/SharpCompress/IO/ReadOnlySubStream.cs index 4ba326dd..bb095925 100644 --- a/src/SharpCompress/IO/ReadOnlySubStream.cs +++ b/src/SharpCompress/IO/ReadOnlySubStream.cs @@ -1,96 +1,85 @@ using System; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +internal class ReadOnlySubStream : NonDisposingStream { - internal class ReadOnlySubStream : NonDisposingStream + public ReadOnlySubStream(Stream stream, long bytesToRead) : this(stream, null, bytesToRead) { } + + public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead) + : base(stream, throwOnDispose: false) { - public ReadOnlySubStream(Stream stream, long bytesToRead) - : this(stream, null, bytesToRead) + if (origin != null) { + stream.Position = origin.Value; } + BytesLeftToRead = bytesToRead; + } - public ReadOnlySubStream(Stream stream, long? origin, long bytesToRead) - : base(stream, throwOnDispose: false) + private long BytesLeftToRead { get; set; } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (BytesLeftToRead < count) { - if (origin != null) - { - stream.Position = origin.Value; - } - BytesLeftToRead = bytesToRead; + count = (int)BytesLeftToRead; } - - private long BytesLeftToRead { get; set; } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override void Flush() + var read = Stream.Read(buffer, offset, count); + if (read > 0) { - throw new NotSupportedException(); + BytesLeftToRead -= read; } + return read; + } - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - public override int Read(byte[] buffer, int offset, int count) + public override int ReadByte() + { + if (BytesLeftToRead <= 0) { - if (BytesLeftToRead < count) - { - count = (int)BytesLeftToRead; - } - int read = Stream.Read(buffer, offset, count); - if (read > 0) - { - BytesLeftToRead -= read; - } - return read; + return -1; } - - public override int ReadByte() + var value = Stream.ReadByte(); + if (value != -1) { - if (BytesLeftToRead <= 0) - { - return -1; - } - int value = Stream.ReadByte(); - if (value != -1) - { - --BytesLeftToRead; - } - return value; + --BytesLeftToRead; } + return value; + } #if !NETFRAMEWORK && !NETSTANDARD2_0 - public override int Read(Span buffer) + public override int Read(Span buffer) + { + var slice_len = BytesLeftToRead < buffer.Length ? BytesLeftToRead : buffer.Length; + var read = Stream.Read(buffer.Slice(0, (int)slice_len)); + if (read > 0) { - var slice_len = BytesLeftToRead < buffer.Length ? BytesLeftToRead : buffer.Length; - var read = Stream.Read(buffer.Slice(0,(int)slice_len)); - if (read > 0) - { - BytesLeftToRead -= read; - } - return read; + BytesLeftToRead -= read; } + return read; + } #endif - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) - { - throw new NotSupportedException(); - } + public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); } diff --git a/src/SharpCompress/IO/RewindableStream.cs b/src/SharpCompress/IO/RewindableStream.cs index fc430aae..ccdc8ee0 100644 --- a/src/SharpCompress/IO/RewindableStream.cs +++ b/src/SharpCompress/IO/RewindableStream.cs @@ -1,165 +1,149 @@ using System; using System.IO; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +public class RewindableStream : Stream { - internal class RewindableStream : Stream + private readonly Stream stream; + private MemoryStream bufferStream = new MemoryStream(); + private bool isRewound; + private bool isDisposed; + + public RewindableStream(Stream stream) => this.stream = stream; + + internal bool IsRecording { get; private set; } + + protected override void Dispose(bool disposing) { - private readonly Stream stream; - private MemoryStream bufferStream = new MemoryStream(); - private bool isRewound; - private bool isDisposed; - - public RewindableStream(Stream stream) + if (isDisposed) { - this.stream = stream; + return; } - - internal bool IsRecording { get; private set; } - - protected override void Dispose(bool disposing) + isDisposed = true; + base.Dispose(disposing); + if (disposing) { - if (isDisposed) - { - return; - } - isDisposed = true; - base.Dispose(disposing); - if (disposing) - { - stream.Dispose(); - } + stream.Dispose(); } + } - public void Rewind(bool stopRecording) + public void Rewind(bool stopRecording) + { + isRewound = true; + IsRecording = !stopRecording; + bufferStream.Position = 0; + } + + public void Rewind(MemoryStream buffer) + { + if (bufferStream.Position >= buffer.Length) { - isRewound = true; - IsRecording = !stopRecording; + bufferStream.Position -= buffer.Length; + } + else + { + bufferStream.TransferTo(buffer); + //create new memorystream to allow proper resizing as memorystream could be a user provided buffer + //https://github.com/adamhathcock/sharpcompress/issues/306 + bufferStream = new MemoryStream(); + buffer.Position = 0; + buffer.TransferTo(bufferStream); bufferStream.Position = 0; } + isRewound = true; + } - public void Rewind(MemoryStream buffer) + public void StartRecording() + { + //if (isRewound && bufferStream.Position != 0) + // throw new System.NotImplementedException(); + if (bufferStream.Position != 0) { - if (bufferStream.Position >= buffer.Length) + var data = bufferStream.ToArray(); + var position = bufferStream.Position; + bufferStream.SetLength(0); + bufferStream.Write(data, (int)position, data.Length - (int)position); + bufferStream.Position = 0; + } + IsRecording = true; + } + + public override bool CanRead => true; + + public override bool CanSeek => stream.CanSeek; + + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => stream.Length; + + public override long Position + { + get => stream.Position + bufferStream.Position - bufferStream.Length; + set + { + if (!isRewound) { - bufferStream.Position -= buffer.Length; + stream.Position = value; + } + else if (value < stream.Position - bufferStream.Length || value >= stream.Position) + { + stream.Position = value; + isRewound = false; + bufferStream.SetLength(0); } else { - - bufferStream.TransferTo(buffer); - //create new memorystream to allow proper resizing as memorystream could be a user provided buffer - //https://github.com/adamhathcock/sharpcompress/issues/306 - bufferStream = new MemoryStream(); - buffer.Position = 0; - buffer.TransferTo(bufferStream); - bufferStream.Position = 0; + bufferStream.Position = value - stream.Position + bufferStream.Length; } - isRewound = true; } + } - public void StartRecording() + public override int Read(byte[] buffer, int offset, int count) + { + //don't actually read if we don't really want to read anything + //currently a network stream bug on Windows for .NET Core + if (count == 0) { - //if (isRewound && bufferStream.Position != 0) - // throw new System.NotImplementedException(); - if (bufferStream.Position != 0) + return 0; + } + int read; + if (isRewound && bufferStream.Position != bufferStream.Length) + { + // don't read more than left + var readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position)); + read = bufferStream.Read(buffer, offset, readCount); + if (read < readCount) { - byte[] data = bufferStream.ToArray(); - long position = bufferStream.Position; + var tempRead = stream.Read(buffer, offset + read, count - read); + if (IsRecording) + { + bufferStream.Write(buffer, offset + read, tempRead); + } + read += tempRead; + } + if (bufferStream.Position == bufferStream.Length && !IsRecording) + { + isRewound = false; bufferStream.SetLength(0); - bufferStream.Write(data, (int)position, data.Length - (int)position); - bufferStream.Position = 0; - } - IsRecording = true; - } - - public override bool CanRead => true; - - public override bool CanSeek => stream.CanSeek; - - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => stream.Length; - - public override long Position - { - get => stream.Position + bufferStream.Position - bufferStream.Length; - set - { - if (!isRewound) - { - stream.Position = value; - } - else if (value < stream.Position - bufferStream.Length || value >= stream.Position) - { - stream.Position = value; - isRewound = false; - bufferStream.SetLength(0); - } - else - { - bufferStream.Position = value - stream.Position + bufferStream.Length; - } - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - //don't actually read if we don't really want to read anything - //currently a network stream bug on Windows for .NET Core - if (count == 0) - { - return 0; - } - int read; - if (isRewound && bufferStream.Position != bufferStream.Length) - { - // don't read more than left - int readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position)); - read = bufferStream.Read(buffer, offset, readCount); - if (read < readCount) - { - int tempRead = stream.Read(buffer, offset + read, count - read); - if (IsRecording) - { - bufferStream.Write(buffer, offset + read, tempRead); - } - read += tempRead; - } - if (bufferStream.Position == bufferStream.Length && !IsRecording) - { - isRewound = false; - bufferStream.SetLength(0); - } - return read; - } - - read = stream.Read(buffer, offset, count); - if (IsRecording) - { - bufferStream.Write(buffer, offset, read); } return read; } - public override long Seek(long offset, SeekOrigin origin) + read = stream.Read(buffer, offset, count); + if (IsRecording) { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); + bufferStream.Write(buffer, offset, read); } + return read; } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); } diff --git a/src/SharpCompress/IO/SourceStream.cs b/src/SharpCompress/IO/SourceStream.cs index e23f7eb7..9344ad8d 100644 --- a/src/SharpCompress/IO/SourceStream.cs +++ b/src/SharpCompress/IO/SourceStream.cs @@ -2,223 +2,237 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Readers; -namespace SharpCompress.IO +namespace SharpCompress.IO; + +public class SourceStream : Stream { - public class SourceStream : Stream + private long _prevSize; + private List _files; + private List _streams; + private Func _getFilePart; + private Func _getStreamPart; + private int _stream; + + public SourceStream(FileInfo file, Func getPart, ReaderOptions options) + : this(null, null, file, getPart, options) { } + + public SourceStream(Stream stream, Func getPart, ReaderOptions options) + : this(stream, getPart, null, null, options) { } + + private SourceStream( + Stream? stream, + Func? getStreamPart, + FileInfo? file, + Func? getFilePart, + ReaderOptions options + ) { - private long _prevSize; - private List _files; - private List _streams; - private Func _getFilePart; - private Func _getStreamPart; - private int _stream; + ReaderOptions = options; + _files = new List(); + _streams = new List(); + IsFileMode = file != null; + IsVolumes = false; - public SourceStream(FileInfo file, Func getPart, ReaderOptions options) : this(null, null, file, getPart, options) + if (!IsFileMode) { - } - - public SourceStream(Stream stream, Func getPart, ReaderOptions options) : this(stream, getPart, null, null, options) - { - } - - private SourceStream(Stream? stream, Func? getStreamPart, FileInfo? file, Func? getFilePart, ReaderOptions options) - { - this.ReaderOptions = options; - _files = new List(); - _streams = new List(); - IsFileMode = file != null; - IsVolumes = false; - - if (!IsFileMode) + _streams.Add(stream!); + _getStreamPart = getStreamPart!; + _getFilePart = new Func(a => null!); + if (stream is FileStream) { - _streams.Add(stream!); - _getStreamPart = getStreamPart!; - _getFilePart = new Func(a => null!); - if (stream! is FileStream) - _files.Add(new FileInfo(((FileStream)stream!).Name)); + _files.Add(new FileInfo(((FileStream)stream!).Name)); + } + } + else + { + _files.Add(file!); + _streams.Add(_files[0].OpenRead()); + _getFilePart = getFilePart!; + _getStreamPart = new Func(a => null!); + } + _stream = 0; + _prevSize = 0; + } + + public void LoadAllParts() + { + for (var i = 1; SetStream(i); i++) { } + SetStream(0); + } + + public bool IsVolumes { get; set; } + + public ReaderOptions ReaderOptions { get; } + public bool IsFileMode { get; } + + public IEnumerable Files => _files; + public IEnumerable Streams => _streams; + + private Stream Current => _streams[_stream]; + + public bool LoadStream(int index) //ensure all parts to id are loaded + { + while (_streams.Count <= index) + { + if (IsFileMode) + { + var f = _getFilePart(_streams.Count); + if (f == null) + { + _stream = _streams.Count - 1; + return false; + } + //throw new Exception($"File part {idx} not available."); + _files.Add(f); + _streams.Add(_files.Last().OpenRead()); } else { - _files.Add(file!); - _streams.Add(_files[0].OpenRead()); - _getFilePart = getFilePart!; - _getStreamPart = new Func(a => null!); + var s = _getStreamPart(_streams.Count); + if (s == null) + { + _stream = _streams.Count - 1; + return false; + } + //throw new Exception($"Stream part {idx} not available."); + _streams.Add(s); + if (s is FileStream) + { + _files.Add(new FileInfo(((FileStream)s).Name)); + } } - _stream = 0; - _prevSize = 0; + } + return true; + } + + public bool SetStream(int idx) //allow caller to switch part in multipart + { + if (LoadStream(idx)) + { + _stream = idx; } - public void LoadAllParts() + return _stream == idx; + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => (!IsVolumes ? _streams.Sum(a => a.Length) : Current.Length); + + public override long Position + { + get => _prevSize + Current.Position; //_prevSize is 0 for multivolume + set => Seek(value, SeekOrigin.Begin); + } + + public override void Flush() => Current.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (count <= 0) { - for (int i = 1; SetStream(i); i++) + return 0; + } + + var total = count; + var r = -1; + + while (count != 0 && r != 0) + { + r = Current.Read( + buffer, + offset, + (int)Math.Min(count, Current.Length - Current.Position) + ); + count -= r; + offset += r; + + if (!IsVolumes && count != 0 && Current.Position == Current.Length) { + var length = Current.Length; + + // Load next file if present + if (!SetStream(_stream + 1)) + { + break; + } + + // Current stream switched + // Add length of previous stream + _prevSize += length; + Current.Seek(0, SeekOrigin.Begin); } + } + + return total - count; + } + + public override long Seek(long offset, SeekOrigin origin) + { + var pos = Position; + switch (origin) + { + case SeekOrigin.Begin: + pos = offset; + break; + case SeekOrigin.Current: + pos += offset; + break; + case SeekOrigin.End: + pos = Length + offset; + break; + } + + _prevSize = 0; + if (!IsVolumes) + { SetStream(0); - } - - public bool IsVolumes { get; set; } - - public ReaderOptions ReaderOptions { get; } - public bool IsFileMode { get; } - - public IEnumerable Files => _files; - public IEnumerable Streams => _streams; - - private Stream Current => _streams[_stream]; - public bool LoadStream(int index) //ensure all parts to id are loaded - { - while (_streams.Count <= index) + while (_prevSize + Current.Length < pos) { - if (IsFileMode) - { - FileInfo? f = _getFilePart(_streams.Count); - if (f == null) - { - _stream = _streams.Count - 1; - return false; - } - //throw new Exception($"File part {idx} not available."); - _files.Add(f); - _streams.Add(_files.Last().OpenRead()); - } - else - { - Stream? s = _getStreamPart(_streams.Count); - if (s == null) - { - _stream = _streams.Count - 1; - return false; - } - //throw new Exception($"Stream part {idx} not available."); - _streams.Add(s); - if (s is FileStream) - _files.Add(new FileInfo(((FileStream)s).Name)); - } - } - return true; - } - public bool SetStream(int idx) //allow caller to switch part in multipart - { - if (LoadStream(idx)) - _stream = idx; - return _stream == idx; - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => (!IsVolumes ? _streams.Sum(a => a.Length) : Current.Length); - - public override long Position - { - get => _prevSize + Current.Position; //_prevSize is 0 for multivolume - set => Seek(value, SeekOrigin.Begin); - } - - public override void Flush() - { - Current.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (count <= 0) - return 0; - - int total = count; - int r = -1; - - while (count != 0 && r != 0) - { - r = Current.Read(buffer, offset, (int)Math.Min(count, Current.Length - Current.Position)); - count -= r; - offset += r; - - if (!IsVolumes && count != 0 && Current.Position == Current.Length) - { - var length = Current.Length; - - // Load next file if present - if (!SetStream(_stream + 1)) - { - break; - } - - // Current stream switched - // Add length of previous stream - _prevSize += length; - Current.Seek(0, SeekOrigin.Begin); - } - } - - return total - count; - } - - public override long Seek(long offset, SeekOrigin origin) - { - long pos = this.Position; - switch (origin) - { - case SeekOrigin.Begin: pos = offset; break; - case SeekOrigin.Current: pos += offset; break; - case SeekOrigin.End: pos = Length + offset; break; - } - - _prevSize = 0; - if (!IsVolumes) - { - SetStream(0); - while (_prevSize + Current.Length < pos) - { - _prevSize += Current.Length; - SetStream(_stream + 1); - } - } - - if (pos != _prevSize + Current.Position) - Current.Seek(pos - _prevSize, SeekOrigin.Begin); - return pos; - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override void Close() - { - if (this.IsFileMode || !this.ReaderOptions.LeaveStreamOpen) //close if file mode or options specify it - { - foreach (Stream stream in _streams) - { - try - { - if (stream != null) - stream.Dispose(); - } - catch { } - } - _streams.Clear(); - _files.Clear(); + _prevSize += Current.Length; + SetStream(_stream + 1); } } - protected override void Dispose(bool disposing) + if (pos != _prevSize + Current.Position) { - this.Close(); - base.Dispose(disposing); + Current.Seek(pos - _prevSize, SeekOrigin.Begin); + } + + return pos; + } + + public override void SetLength(long value) => throw new NotImplementedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotImplementedException(); + + public override void Close() + { + if (IsFileMode || !ReaderOptions.LeaveStreamOpen) //close if file mode or options specify it + { + foreach (var stream in _streams) + { + try + { + stream?.Dispose(); + } + catch { } + } + _streams.Clear(); + _files.Clear(); } } + + protected override void Dispose(bool disposing) + { + Close(); + base.Dispose(disposing); + } } diff --git a/src/SharpCompress/IO/StreamingMode.cs b/src/SharpCompress/IO/StreamingMode.cs index 29d3a147..0fdb674d 100644 --- a/src/SharpCompress/IO/StreamingMode.cs +++ b/src/SharpCompress/IO/StreamingMode.cs @@ -1,8 +1,7 @@ -namespace SharpCompress.IO +namespace SharpCompress.IO; + +public enum StreamingMode { - internal enum StreamingMode - { - Streaming, - Seekable - } -} \ No newline at end of file + Streaming, + Seekable +} diff --git a/src/SharpCompress/Lazy.cs b/src/SharpCompress/Lazy.cs index eab72849..7a6abd55 100644 --- a/src/SharpCompress/Lazy.cs +++ b/src/SharpCompress/Lazy.cs @@ -2,30 +2,26 @@ using System; -namespace SharpCompress +namespace SharpCompress; + +public class Lazy { - public class Lazy + private readonly Func _lazyFunc; + private bool _evaluated; + private T _value; + + public Lazy(Func lazyFunc) => _lazyFunc = lazyFunc; + + public T Value { - private readonly Func _lazyFunc; - private bool _evaluated; - private T _value; - - public Lazy(Func lazyFunc) + get { - _lazyFunc = lazyFunc; - } - - public T Value - { - get + if (!_evaluated) { - if (!_evaluated) - { - _value = _lazyFunc(); - _evaluated = true; - } - return _value; + _value = _lazyFunc(); + _evaluated = true; } + return _value; } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/LazyReadOnlyCollection.cs b/src/SharpCompress/LazyReadOnlyCollection.cs index 415e9904..8f5ee834 100644 --- a/src/SharpCompress/LazyReadOnlyCollection.cs +++ b/src/SharpCompress/LazyReadOnlyCollection.cs @@ -4,150 +4,123 @@ using System; using System.Collections; using System.Collections.Generic; -namespace SharpCompress +namespace SharpCompress; + +internal sealed class LazyReadOnlyCollection : ICollection { - internal sealed class LazyReadOnlyCollection : ICollection + private readonly List backing = new List(); + private readonly IEnumerator source; + private bool fullyLoaded; + + public LazyReadOnlyCollection(IEnumerable source) => this.source = source.GetEnumerator(); + + private class LazyLoader : IEnumerator { - private readonly List backing = new List(); - private readonly IEnumerator source; - private bool fullyLoaded; + private readonly LazyReadOnlyCollection lazyReadOnlyCollection; + private bool disposed; + private int index = -1; - public LazyReadOnlyCollection(IEnumerable source) + internal LazyLoader(LazyReadOnlyCollection lazyReadOnlyCollection) => + this.lazyReadOnlyCollection = lazyReadOnlyCollection; + + #region IEnumerator Members + + public T Current => lazyReadOnlyCollection.backing[index]; + + #endregion + + #region IDisposable Members + + public void Dispose() { - this.source = source.GetEnumerator(); - } - - private class LazyLoader : IEnumerator - { - private readonly LazyReadOnlyCollection lazyReadOnlyCollection; - private bool disposed; - private int index = -1; - - internal LazyLoader(LazyReadOnlyCollection lazyReadOnlyCollection) + if (!disposed) { - this.lazyReadOnlyCollection = lazyReadOnlyCollection; + disposed = true; } - - #region IEnumerator Members - - public T Current => lazyReadOnlyCollection.backing[index]; - - #endregion - - #region IDisposable Members - - public void Dispose() - { - if (!disposed) - { - disposed = true; - } - } - - #endregion - - #region IEnumerator Members - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - if (index + 1 < lazyReadOnlyCollection.backing.Count) - { - index++; - return true; - } - if (!lazyReadOnlyCollection.fullyLoaded && lazyReadOnlyCollection.source.MoveNext()) - { - lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current); - index++; - return true; - } - lazyReadOnlyCollection.fullyLoaded = true; - return false; - } - - public void Reset() - { - throw new NotSupportedException(); - } - - #endregion - } - - internal void EnsureFullyLoaded() - { - if (!fullyLoaded) - { - this.ForEach(x => { }); - fullyLoaded = true; - } - } - - internal IEnumerable GetLoaded() - { - return backing; - } - - #region ICollection Members - - public void Add(T item) - { - throw new NotSupportedException(); - } - - public void Clear() - { - throw new NotSupportedException(); - } - - public bool Contains(T item) - { - EnsureFullyLoaded(); - return backing.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - EnsureFullyLoaded(); - backing.CopyTo(array, arrayIndex); - } - - public int Count - { - get - { - EnsureFullyLoaded(); - return backing.Count; - } - } - - public bool IsReadOnly => true; - - public bool Remove(T item) - { - throw new NotSupportedException(); } #endregion - #region IEnumerable Members + #region IEnumerator Members - //TODO check for concurrent access - public IEnumerator GetEnumerator() + object IEnumerator.Current => Current; + + public bool MoveNext() { - return new LazyLoader(this); + if (index + 1 < lazyReadOnlyCollection.backing.Count) + { + index++; + return true; + } + if (!lazyReadOnlyCollection.fullyLoaded && lazyReadOnlyCollection.source.MoveNext()) + { + lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current); + index++; + return true; + } + lazyReadOnlyCollection.fullyLoaded = true; + return false; } - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public void Reset() => throw new NotSupportedException(); #endregion } + + internal void EnsureFullyLoaded() + { + if (!fullyLoaded) + { + this.ForEach(x => { }); + fullyLoaded = true; + } + } + + internal IEnumerable GetLoaded() => backing; + + #region ICollection Members + + public void Add(T item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public bool Contains(T item) + { + EnsureFullyLoaded(); + return backing.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + EnsureFullyLoaded(); + backing.CopyTo(array, arrayIndex); + } + + public int Count + { + get + { + EnsureFullyLoaded(); + return backing.Count; + } + } + + public bool IsReadOnly => true; + + public bool Remove(T item) => throw new NotSupportedException(); + + #endregion + + #region IEnumerable Members + + //TODO check for concurrent access + public IEnumerator GetEnumerator() => new LazyLoader(this); + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion } diff --git a/src/SharpCompress/Polyfills/StreamExtensions.cs b/src/SharpCompress/Polyfills/StreamExtensions.cs index 6dacef75..ab118a4f 100644 --- a/src/SharpCompress/Polyfills/StreamExtensions.cs +++ b/src/SharpCompress/Polyfills/StreamExtensions.cs @@ -1,45 +1,44 @@ -#if NETFRAMEWORK || NETSTANDARD2_0 +#if NETFRAMEWORK || NETSTANDARD2_0 using System; using System.Buffers; using System.IO; -namespace SharpCompress +namespace SharpCompress; + +internal static class StreamExtensions { - internal static class StreamExtensions + internal static int Read(this Stream stream, Span buffer) { - internal static int Read(this Stream stream, Span buffer) + var temp = ArrayPool.Shared.Rent(buffer.Length); + + try { - byte[] temp = ArrayPool.Shared.Rent(buffer.Length); + var read = stream.Read(temp, 0, buffer.Length); - try - { - int read = stream.Read(temp, 0, buffer.Length); + temp.AsSpan(0, read).CopyTo(buffer); - temp.AsSpan(0, read).CopyTo(buffer); - - return read; - } - finally - { - ArrayPool.Shared.Return(temp); - } + return read; } - - internal static void Write(this Stream stream, ReadOnlySpan buffer) + finally { - byte[] temp = ArrayPool.Shared.Rent(buffer.Length); + ArrayPool.Shared.Return(temp); + } + } - buffer.CopyTo(temp); + internal static void Write(this Stream stream, ReadOnlySpan buffer) + { + var temp = ArrayPool.Shared.Rent(buffer.Length); - try - { - stream.Write(temp, 0, buffer.Length); - } - finally - { - ArrayPool.Shared.Return(temp); - } + buffer.CopyTo(temp); + + try + { + stream.Write(temp, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(temp); } } } diff --git a/src/SharpCompress/Polyfills/StringExtensions.cs b/src/SharpCompress/Polyfills/StringExtensions.cs index e58aff4d..57d1e1bc 100644 --- a/src/SharpCompress/Polyfills/StringExtensions.cs +++ b/src/SharpCompress/Polyfills/StringExtensions.cs @@ -1,19 +1,13 @@ #if NETFRAMEWORK || NETSTANDARD2_0 -namespace SharpCompress -{ - internal static class StringExtensions - { - internal static bool EndsWith(this string text, char value) - { - return text.Length > 0 && text[text.Length - 1] == value; - } +namespace SharpCompress; - internal static bool Contains(this string text, char value) - { - return text.IndexOf(value) > -1; - } - } +internal static class StringExtensions +{ + internal static bool EndsWith(this string text, char value) => + text.Length > 0 && text[text.Length - 1] == value; + + internal static bool Contains(this string text, char value) => text.IndexOf(value) > -1; } -#endif \ No newline at end of file +#endif diff --git a/src/SharpCompress/ReadOnlyCollection.cs b/src/SharpCompress/ReadOnlyCollection.cs index 32c2abc8..745a59aa 100644 --- a/src/SharpCompress/ReadOnlyCollection.cs +++ b/src/SharpCompress/ReadOnlyCollection.cs @@ -1,55 +1,30 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; -namespace SharpCompress +namespace SharpCompress; + +public class ReadOnlyCollection : ICollection { - internal class ReadOnlyCollection : ICollection - { - private readonly ICollection collection; + private readonly ICollection collection; - public ReadOnlyCollection(ICollection collection) - { - this.collection = collection; - } + public ReadOnlyCollection(ICollection collection) => this.collection = collection; - public void Add(T item) - { - throw new NotSupportedException(); - } + public void Add(T item) => throw new NotSupportedException(); - public void Clear() - { - throw new NotSupportedException(); - } + public void Clear() => throw new NotSupportedException(); - public bool Contains(T item) - { - return collection.Contains(item); - } + public bool Contains(T item) => collection.Contains(item); - public void CopyTo(T[] array, int arrayIndex) - { - collection.CopyTo(array, arrayIndex); - } + public void CopyTo(T[] array, int arrayIndex) => collection.CopyTo(array, arrayIndex); - public int Count => collection.Count; + public int Count => collection.Count; - public bool IsReadOnly => true; + public bool IsReadOnly => true; - public bool Remove(T item) - { - throw new NotSupportedException(); - } + public bool Remove(T item) => throw new NotSupportedException(); - public IEnumerator GetEnumerator() - { - return collection.GetEnumerator(); - } + public IEnumerator GetEnumerator() => collection.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotSupportedException(); - } - } -} \ No newline at end of file + IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(); +} diff --git a/src/SharpCompress/Readers/AbstractReader.cs b/src/SharpCompress/Readers/AbstractReader.cs index 619d834e..86150744 100644 --- a/src/SharpCompress/Readers/AbstractReader.cs +++ b/src/SharpCompress/Readers/AbstractReader.cs @@ -4,234 +4,238 @@ using System.IO; using System.Linq; using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +/// +/// A generic push reader that reads unseekable comrpessed streams. +/// +public abstract class AbstractReader : IReader, IReaderExtractionListener + where TEntry : Entry + where TVolume : Volume { - /// - /// A generic push reader that reads unseekable comrpessed streams. - /// - public abstract class AbstractReader : IReader, IReaderExtractionListener - where TEntry : Entry - where TVolume : Volume + private bool completed; + private IEnumerator? entriesForCurrentReadStream; + private bool wroteCurrentEntry; + + public event EventHandler>? EntryExtractionProgress; + + public event EventHandler? CompressedBytesRead; + public event EventHandler? FilePartExtractionBegin; + + internal AbstractReader(ReaderOptions options, ArchiveType archiveType) { - private bool completed; - private IEnumerator? entriesForCurrentReadStream; - private bool wroteCurrentEntry; + ArchiveType = archiveType; + Options = options; + } - public event EventHandler>? EntryExtractionProgress; + internal ReaderOptions Options { get; } - public event EventHandler? CompressedBytesRead; - public event EventHandler? FilePartExtractionBegin; + public ArchiveType ArchiveType { get; } - internal AbstractReader(ReaderOptions options, ArchiveType archiveType) + /// + /// Current volume that the current entry resides in + /// + public abstract TVolume Volume { get; } + + /// + /// Current file entry + /// + public TEntry Entry => entriesForCurrentReadStream!.Current; + + #region IDisposable Members + + public void Dispose() + { + entriesForCurrentReadStream?.Dispose(); + Volume?.Dispose(); + } + + #endregion + + public bool Cancelled { get; private set; } + + /// + /// Indicates that the remaining entries are not required. + /// On dispose of an EntryStream, the stream will not skip to the end of the entry. + /// An attempt to move to the next entry will throw an exception, as the compressed stream is not positioned at an entry boundary. + /// + public void Cancel() + { + if (!completed) { - ArchiveType = archiveType; - Options = options; + Cancelled = true; } + } - internal ReaderOptions Options { get; } - - public ArchiveType ArchiveType { get; } - - /// - /// Current volume that the current entry resides in - /// - public abstract TVolume Volume { get; } - - /// - /// Current file entry - /// - public TEntry Entry => entriesForCurrentReadStream!.Current; - - #region IDisposable Members - - public void Dispose() + public bool MoveToNextEntry() + { + if (completed) { - entriesForCurrentReadStream?.Dispose(); - Volume?.Dispose(); - } - - #endregion - - public bool Cancelled { get; private set; } - - /// - /// Indicates that the remaining entries are not required. - /// On dispose of an EntryStream, the stream will not skip to the end of the entry. - /// An attempt to move to the next entry will throw an exception, as the compressed stream is not positioned at an entry boundary. - /// - public void Cancel() - { - if (!completed) - { - Cancelled = true; - } - } - - public bool MoveToNextEntry() - { - if (completed) - { - return false; - } - if (Cancelled) - { - throw new InvalidOperationException("Reader has been cancelled."); - } - if (entriesForCurrentReadStream is null) - { - return LoadStreamForReading(RequestInitialStream()); - } - if (!wroteCurrentEntry) - { - SkipEntry(); - } - wroteCurrentEntry = false; - if (NextEntryForCurrentStream()) - { - return true; - } - completed = true; return false; } - - protected bool LoadStreamForReading(Stream stream) + if (Cancelled) { - entriesForCurrentReadStream?.Dispose(); - if ((stream is null) || (!stream.CanRead)) + throw new InvalidOperationException("Reader has been cancelled."); + } + if (entriesForCurrentReadStream is null) + { + return LoadStreamForReading(RequestInitialStream()); + } + if (!wroteCurrentEntry) + { + SkipEntry(); + } + wroteCurrentEntry = false; + if (NextEntryForCurrentStream()) + { + return true; + } + completed = true; + return false; + } + + protected bool LoadStreamForReading(Stream stream) + { + entriesForCurrentReadStream?.Dispose(); + if ((stream is null) || (!stream.CanRead)) + { + throw new MultipartStreamRequiredException( + "File is split into multiple archives: '" + + Entry.Key + + "'. A new readable stream is required. Use Cancel if it was intended." + ); + } + entriesForCurrentReadStream = GetEntries(stream).GetEnumerator(); + return entriesForCurrentReadStream.MoveNext(); + } + + protected virtual Stream RequestInitialStream() => Volume.Stream; + + internal virtual bool NextEntryForCurrentStream() => entriesForCurrentReadStream!.MoveNext(); + + protected abstract IEnumerable GetEntries(Stream stream); + + #region Entry Skip/Write + + private void SkipEntry() + { + if (!Entry.IsDirectory) + { + Skip(); + } + } + + private void Skip() + { + var part = Entry.Parts.First(); + + if (ArchiveType != ArchiveType.Rar && !Entry.IsSolid && Entry.CompressedSize > 0) + { + //not solid and has a known compressed size then we can skip raw bytes. + var rawStream = part.GetRawStream(); + + if (rawStream != null) { - throw new MultipartStreamRequiredException("File is split into multiple archives: '" - + Entry.Key + - "'. A new readable stream is required. Use Cancel if it was intended."); - } - entriesForCurrentReadStream = GetEntries(stream).GetEnumerator(); - return entriesForCurrentReadStream.MoveNext(); - } - - protected virtual Stream RequestInitialStream() - { - return Volume.Stream; - } - - internal virtual bool NextEntryForCurrentStream() - { - return entriesForCurrentReadStream!.MoveNext(); - } - - protected abstract IEnumerable GetEntries(Stream stream); - - #region Entry Skip/Write - - private void SkipEntry() - { - if (!Entry.IsDirectory) - { - Skip(); + var bytesToAdvance = Entry.CompressedSize; + rawStream.Skip(bytesToAdvance); + part.Skipped = true; + return; } } + //don't know the size so we have to try to decompress to skip + using var s = OpenEntryStream(); + s.SkipEntry(); + } - private void Skip() + public void WriteEntryTo(Stream writableStream) + { + if (wroteCurrentEntry) { - var part = Entry.Parts.First(); - - if (ArchiveType != ArchiveType.Rar - && !Entry.IsSolid - && Entry.CompressedSize > 0) - { - //not solid and has a known compressed size then we can skip raw bytes. - var rawStream = part.GetRawStream(); - - if (rawStream != null) - { - var bytesToAdvance = Entry.CompressedSize; - rawStream.Skip(bytesToAdvance); - part.Skipped = true; - return; - } - } - //don't know the size so we have to try to decompress to skip - using (var s = OpenEntryStream()) - { - s.SkipEntry(); - } + throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } - public void WriteEntryTo(Stream writableStream) + if (writableStream is null) { - if (wroteCurrentEntry) - { - throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); - } - - if (writableStream is null) - { - throw new ArgumentNullException(nameof(writableStream)); - } - if (!writableStream.CanWrite) - { - throw new ArgumentException("A writable Stream was required. Use Cancel if that was intended."); - } - - Write(writableStream); - wroteCurrentEntry = true; + throw new ArgumentNullException(nameof(writableStream)); + } + if (!writableStream.CanWrite) + { + throw new ArgumentException( + "A writable Stream was required. Use Cancel if that was intended." + ); } - internal void Write(Stream writeStream) + Write(writableStream); + wroteCurrentEntry = true; + } + + internal void Write(Stream writeStream) + { + var streamListener = this as IReaderExtractionListener; + using Stream s = OpenEntryStream(); + s.TransferTo(writeStream, Entry, streamListener); + } + + public EntryStream OpenEntryStream() + { + if (wroteCurrentEntry) { - var streamListener = this as IReaderExtractionListener; - using (Stream s = OpenEntryStream()) - { - s.TransferTo(writeStream, Entry, streamListener); - } + throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } + var stream = GetEntryStream(); + wroteCurrentEntry = true; + return stream; + } - public EntryStream OpenEntryStream() - { - if (wroteCurrentEntry) - { - throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); - } - var stream = GetEntryStream(); - wroteCurrentEntry = true; - return stream; - } + /// + /// Retains a reference to the entry stream, so we can check whether it completed later. + /// + protected EntryStream CreateEntryStream(Stream decompressed) => + new EntryStream(this, decompressed); - /// - /// Retains a reference to the entry stream, so we can check whether it completed later. - /// - protected EntryStream CreateEntryStream(Stream decompressed) - { - return new EntryStream(this, decompressed); - } + protected virtual EntryStream GetEntryStream() => + CreateEntryStream(Entry.Parts.First().GetCompressedStream()); - protected virtual EntryStream GetEntryStream() - { - return CreateEntryStream(Entry.Parts.First().GetCompressedStream()); - } + #endregion - #endregion + IEntry IReader.Entry => Entry; - IEntry IReader.Entry => Entry; - - void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes) - { - CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs( + void IExtractionListener.FireCompressedBytesRead( + long currentPartCompressedBytes, + long compressedReadBytes + ) => + CompressedBytesRead?.Invoke( + this, + new CompressedBytesReadEventArgs( currentFilePartCompressedBytesRead: currentPartCompressedBytes, compressedBytesRead: compressedReadBytes - )); - } + ) + ); - void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize) - { - FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs( + void IExtractionListener.FireFilePartExtractionBegin( + string name, + long size, + long compressedSize + ) => + FilePartExtractionBegin?.Invoke( + this, + new FilePartExtractionBeginEventArgs( compressedSize: compressedSize, size: size, name: name - )); - } + ) + ); - void IReaderExtractionListener.FireEntryExtractionProgress(Entry entry, long bytesTransferred, int iterations) - { - EntryExtractionProgress?.Invoke(this, new ReaderExtractionEventArgs(entry, new ReaderProgress(entry, bytesTransferred, iterations))); - } - } + void IReaderExtractionListener.FireEntryExtractionProgress( + Entry entry, + long bytesTransferred, + int iterations + ) => + EntryExtractionProgress?.Invoke( + this, + new ReaderExtractionEventArgs( + entry, + new ReaderProgress(entry, bytesTransferred, iterations) + ) + ); } diff --git a/src/SharpCompress/Readers/GZip/GZipReader.cs b/src/SharpCompress/Readers/GZip/GZipReader.cs index f4b889d0..84129d20 100644 --- a/src/SharpCompress/Readers/GZip/GZipReader.cs +++ b/src/SharpCompress/Readers/GZip/GZipReader.cs @@ -3,37 +3,31 @@ using System.IO; using SharpCompress.Common; using SharpCompress.Common.GZip; -namespace SharpCompress.Readers.GZip +namespace SharpCompress.Readers.GZip; + +public class GZipReader : AbstractReader { - public class GZipReader : AbstractReader + internal GZipReader(Stream stream, ReaderOptions options) : base(options, ArchiveType.GZip) => + Volume = new GZipVolume(stream, options); + + public override GZipVolume Volume { get; } + + #region Open + + /// + /// Opens a GZipReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static GZipReader Open(Stream stream, ReaderOptions? options = null) { - internal GZipReader(Stream stream, ReaderOptions options) - : base(options, ArchiveType.GZip) - { - Volume = new GZipVolume(stream, options); - } - - public override GZipVolume Volume { get; } - - #region Open - - /// - /// Opens a GZipReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static GZipReader Open(Stream stream, ReaderOptions? options = null) - { - stream.CheckNotNull(nameof(stream)); - return new GZipReader(stream, options ?? new ReaderOptions()); - } - - #endregion Open - - protected override IEnumerable GetEntries(Stream stream) - { - return GZipEntry.GetEntries(stream, Options); - } + stream.CheckNotNull(nameof(stream)); + return new GZipReader(stream, options ?? new ReaderOptions()); } + + #endregion Open + + protected override IEnumerable GetEntries(Stream stream) => + GZipEntry.GetEntries(stream, Options); } diff --git a/src/SharpCompress/Readers/IReader.cs b/src/SharpCompress/Readers/IReader.cs index db11164d..50fc7f4d 100644 --- a/src/SharpCompress/Readers/IReader.cs +++ b/src/SharpCompress/Readers/IReader.cs @@ -1,39 +1,38 @@ -using System; +using System; using System.IO; using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public interface IReader : IDisposable { - public interface IReader : IDisposable - { - event EventHandler> EntryExtractionProgress; + event EventHandler> EntryExtractionProgress; - event EventHandler CompressedBytesRead; - event EventHandler FilePartExtractionBegin; + event EventHandler CompressedBytesRead; + event EventHandler FilePartExtractionBegin; - ArchiveType ArchiveType { get; } + ArchiveType ArchiveType { get; } - IEntry Entry { get; } + IEntry Entry { get; } - /// - /// Decompresses the current entry to the stream. This cannot be called twice for the current entry. - /// - /// - void WriteEntryTo(Stream writableStream); + /// + /// Decompresses the current entry to the stream. This cannot be called twice for the current entry. + /// + /// + void WriteEntryTo(Stream writableStream); - bool Cancelled { get; } - void Cancel(); + bool Cancelled { get; } + void Cancel(); - /// - /// Moves to the next entry by reading more data from the underlying stream. This skips if data has not been read. - /// - /// - bool MoveToNextEntry(); + /// + /// Moves to the next entry by reading more data from the underlying stream. This skips if data has not been read. + /// + /// + bool MoveToNextEntry(); - /// - /// Opens the current entry as a stream that will decompress as it is read. - /// Read the entire stream or use SkipEntry on EntryStream. - /// - EntryStream OpenEntryStream(); - } -} \ No newline at end of file + /// + /// Opens the current entry as a stream that will decompress as it is read. + /// Read the entire stream or use SkipEntry on EntryStream. + /// + EntryStream OpenEntryStream(); +} diff --git a/src/SharpCompress/Readers/IReaderExtensions.cs b/src/SharpCompress/Readers/IReaderExtensions.cs index 502fc8ad..0e51d72a 100644 --- a/src/SharpCompress/Readers/IReaderExtensions.cs +++ b/src/SharpCompress/Readers/IReaderExtensions.cs @@ -1,63 +1,68 @@ using System.IO; using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public static class IReaderExtensions { - public static class IReaderExtensions + public static void WriteEntryTo(this IReader reader, string filePath) { - public static void WriteEntryTo(this IReader reader, string filePath) - { - using (Stream stream = File.Open(filePath, FileMode.Create, FileAccess.Write)) - { - reader.WriteEntryTo(stream); - } - } + using Stream stream = File.Open(filePath, FileMode.Create, FileAccess.Write); + reader.WriteEntryTo(stream); + } - public static void WriteEntryTo(this IReader reader, FileInfo filePath) - { - using (Stream stream = filePath.Open(FileMode.Create)) - { - reader.WriteEntryTo(stream); - } - } + public static void WriteEntryTo(this IReader reader, FileInfo filePath) + { + using Stream stream = filePath.Open(FileMode.Create); + reader.WriteEntryTo(stream); + } - /// - /// Extract all remaining unread entries to specific directory, retaining filename - /// - public static void WriteAllToDirectory(this IReader reader, string destinationDirectory, - ExtractionOptions? options = null) + /// + /// Extract all remaining unread entries to specific directory, retaining filename + /// + public static void WriteAllToDirectory( + this IReader reader, + string destinationDirectory, + ExtractionOptions? options = null + ) + { + while (reader.MoveToNextEntry()) { - while (reader.MoveToNextEntry()) - { - reader.WriteEntryToDirectory(destinationDirectory, options); - } - } - - /// - /// Extract to specific directory, retaining filename - /// - public static void WriteEntryToDirectory(this IReader reader, string destinationDirectory, - ExtractionOptions? options = null) - { - ExtractionMethods.WriteEntryToDirectory(reader.Entry, destinationDirectory, options, - reader.WriteEntryToFile); - } - - /// - /// Extract to specific file - /// - public static void WriteEntryToFile(this IReader reader, - string destinationFileName, - ExtractionOptions? options = null) - { - ExtractionMethods.WriteEntryToFile(reader.Entry, destinationFileName, options, - (x, fm) => - { - using (FileStream fs = File.Open(destinationFileName, fm)) - { - reader.WriteEntryTo(fs); - } - }); + reader.WriteEntryToDirectory(destinationDirectory, options); } } -} \ No newline at end of file + + /// + /// Extract to specific directory, retaining filename + /// + public static void WriteEntryToDirectory( + this IReader reader, + string destinationDirectory, + ExtractionOptions? options = null + ) => + ExtractionMethods.WriteEntryToDirectory( + reader.Entry, + destinationDirectory, + options, + reader.WriteEntryToFile + ); + + /// + /// Extract to specific file + /// + public static void WriteEntryToFile( + this IReader reader, + string destinationFileName, + ExtractionOptions? options = null + ) => + ExtractionMethods.WriteEntryToFile( + reader.Entry, + destinationFileName, + options, + (x, fm) => + { + using var fs = File.Open(destinationFileName, fm); + reader.WriteEntryTo(fs); + } + ); +} diff --git a/src/SharpCompress/Readers/IReaderExtractionListener.cs b/src/SharpCompress/Readers/IReaderExtractionListener.cs index 4a4adc4e..49e5dae4 100644 --- a/src/SharpCompress/Readers/IReaderExtractionListener.cs +++ b/src/SharpCompress/Readers/IReaderExtractionListener.cs @@ -1,9 +1,8 @@ -using SharpCompress.Common; +using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public interface IReaderExtractionListener : IExtractionListener { - internal interface IReaderExtractionListener : IExtractionListener - { - void FireEntryExtractionProgress(Entry entry, long sizeTransferred, int iterations); - } -} \ No newline at end of file + void FireEntryExtractionProgress(Entry entry, long sizeTransferred, int iterations); +} diff --git a/src/SharpCompress/Readers/IReaderFactory.cs b/src/SharpCompress/Readers/IReaderFactory.cs index 1f6f7408..08c190a3 100644 --- a/src/SharpCompress/Readers/IReaderFactory.cs +++ b/src/SharpCompress/Readers/IReaderFactory.cs @@ -1,23 +1,14 @@ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SharpCompress.IO; +namespace SharpCompress.Readers; -namespace SharpCompress.Readers +public interface IReaderFactory : Factories.IFactory { - public interface IReaderFactory : Factories.IFactory - { - /// - /// Opens a Reader for Non-seeking usage - /// - /// - /// - /// - IReader OpenReader(Stream stream, ReaderOptions? options); - } + /// + /// Opens a Reader for Non-seeking usage + /// + /// + /// + /// + IReader OpenReader(Stream stream, ReaderOptions? options); } - diff --git a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs index 6de11a24..76899e0c 100644 --- a/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/MultiVolumeRarReader.cs @@ -7,116 +7,107 @@ using System.Linq; using SharpCompress.Common; using SharpCompress.Common.Rar; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +internal class MultiVolumeRarReader : RarReader { - internal class MultiVolumeRarReader : RarReader + private readonly IEnumerator streams; + private Stream tempStream; + + internal MultiVolumeRarReader(IEnumerable streams, ReaderOptions options) + : base(options) => this.streams = streams.GetEnumerator(); + + internal override void ValidateArchive(RarVolume archive) { } + + protected override Stream RequestInitialStream() { - private readonly IEnumerator streams; + if (streams.MoveNext()) + { + return streams.Current; + } + throw new MultiVolumeExtractionException( + "No stream provided when requested by MultiVolumeRarReader" + ); + } + + internal override bool NextEntryForCurrentStream() + { + if (!base.NextEntryForCurrentStream()) + { + // if we're got another stream to try to process then do so + return streams.MoveNext() && LoadStreamForReading(streams.Current); + } + return true; + } + + protected override IEnumerable CreateFilePartEnumerableForCurrentEntry() + { + var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream); + tempStream = null; + return enumerator; + } + + private class MultiVolumeStreamEnumerator : IEnumerable, IEnumerator + { + private readonly MultiVolumeRarReader reader; + private readonly IEnumerator nextReadableStreams; private Stream tempStream; + private bool isFirst = true; - internal MultiVolumeRarReader(IEnumerable streams, ReaderOptions options) - : base(options) + internal MultiVolumeStreamEnumerator( + MultiVolumeRarReader r, + IEnumerator nextReadableStreams, + Stream tempStream + ) { - this.streams = streams.GetEnumerator(); + reader = r; + this.nextReadableStreams = nextReadableStreams; + this.tempStream = tempStream; } - internal override void ValidateArchive(RarVolume archive) + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => this; + + public FilePart Current { get; private set; } + + public void Dispose() { } + + object IEnumerator.Current => Current; + + public bool MoveNext() { - } - - protected override Stream RequestInitialStream() - { - if (streams.MoveNext()) + if (isFirst) { - return streams.Current; - } - throw new MultiVolumeExtractionException("No stream provided when requested by MultiVolumeRarReader"); - } - - internal override bool NextEntryForCurrentStream() - { - if (!base.NextEntryForCurrentStream()) - { - // if we're got another stream to try to process then do so - return streams.MoveNext() && LoadStreamForReading(streams.Current); - } - return true; - } - - protected override IEnumerable CreateFilePartEnumerableForCurrentEntry() - { - var enumerator = new MultiVolumeStreamEnumerator(this, streams, tempStream); - tempStream = null; - return enumerator; - } - - private class MultiVolumeStreamEnumerator : IEnumerable, IEnumerator - { - private readonly MultiVolumeRarReader reader; - private readonly IEnumerator nextReadableStreams; - private Stream tempStream; - private bool isFirst = true; - - internal MultiVolumeStreamEnumerator(MultiVolumeRarReader r, IEnumerator nextReadableStreams, - Stream tempStream) - { - reader = r; - this.nextReadableStreams = nextReadableStreams; - this.tempStream = tempStream; - } - - public IEnumerator GetEnumerator() - { - return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this; - } - - public FilePart Current { get; private set; } - - public void Dispose() - { - } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - if (isFirst) - { - Current = reader.Entry.Parts.First(); - isFirst = false; //first stream already to go - return true; - } - - if (!reader.Entry.IsSplitAfter) - { - return false; - } - if (tempStream != null) - { - reader.LoadStreamForReading(tempStream); - tempStream = null; - } - else if (!nextReadableStreams.MoveNext()) - { - throw new MultiVolumeExtractionException("No stream provided when requested by MultiVolumeRarReader"); - } - else - { - reader.LoadStreamForReading(nextReadableStreams.Current); - } - Current = reader.Entry.Parts.First(); + isFirst = false; //first stream already to go return true; } - public void Reset() + if (!reader.Entry.IsSplitAfter) { + return false; } + if (tempStream != null) + { + reader.LoadStreamForReading(tempStream); + tempStream = null; + } + else if (!nextReadableStreams.MoveNext()) + { + throw new MultiVolumeExtractionException( + "No stream provided when requested by MultiVolumeRarReader" + ); + } + else + { + reader.LoadStreamForReading(nextReadableStreams.Current); + } + + Current = reader.Entry.Parts.First(); + return true; } + + public void Reset() { } } -} \ No newline at end of file +} diff --git a/src/SharpCompress/Readers/Rar/NonSeekableStreamFilePart.cs b/src/SharpCompress/Readers/Rar/NonSeekableStreamFilePart.cs index 76c9f201..09753b95 100644 --- a/src/SharpCompress/Readers/Rar/NonSeekableStreamFilePart.cs +++ b/src/SharpCompress/Readers/Rar/NonSeekableStreamFilePart.cs @@ -2,20 +2,14 @@ using System.IO; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +internal class NonSeekableStreamFilePart : RarFilePart { - internal class NonSeekableStreamFilePart : RarFilePart - { - internal NonSeekableStreamFilePart(MarkHeader mh, FileHeader fh, int index = 0) - : base(mh, fh, index) - { - } + internal NonSeekableStreamFilePart(MarkHeader mh, FileHeader fh, int index = 0) + : base(mh, fh, index) { } - internal override Stream GetCompressedStream() - { - return FileHeader.PackedStream; - } + internal override Stream GetCompressedStream() => FileHeader.PackedStream; - internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName; - } + internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName; } diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index c51534bc..71d671a3 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -5,73 +5,72 @@ using SharpCompress.Common; using SharpCompress.Common.Rar; using SharpCompress.Compressors.Rar; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +/// +/// This class faciliates Reading a Rar Archive in a non-seekable forward-only manner +/// +public abstract class RarReader : AbstractReader { + private RarVolume? volume; + internal Lazy UnpackV2017 { get; } = + new Lazy(() => new Compressors.Rar.UnpackV2017.Unpack()); + internal Lazy UnpackV1 { get; } = + new Lazy(() => new Compressors.Rar.UnpackV1.Unpack()); + + internal RarReader(ReaderOptions options) : base(options, ArchiveType.Rar) { } + + internal abstract void ValidateArchive(RarVolume archive); + + public override RarVolume Volume => volume!; + /// - /// This class faciliates Reading a Rar Archive in a non-seekable forward-only manner + /// Opens a RarReader for Non-seeking usage with a single volume /// - public abstract class RarReader : AbstractReader + /// + /// + /// + public static RarReader Open(Stream stream, ReaderOptions? options = null) { - private RarVolume? volume; - internal Lazy UnpackV2017 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV2017.Unpack()); - internal Lazy UnpackV1 { get; } = new Lazy(() => new SharpCompress.Compressors.Rar.UnpackV1.Unpack()); + stream.CheckNotNull(nameof(stream)); + return new SingleVolumeRarReader(stream, options ?? new ReaderOptions()); + } - internal RarReader(ReaderOptions options) - : base(options, ArchiveType.Rar) + /// + /// Opens a RarReader for Non-seeking usage with multiple volumes + /// + /// + /// + /// + public static RarReader Open(IEnumerable streams, ReaderOptions? options = null) + { + streams.CheckNotNull(nameof(streams)); + return new MultiVolumeRarReader(streams, options ?? new ReaderOptions()); + } + + protected override IEnumerable GetEntries(Stream stream) + { + volume = new RarReaderVolume(stream, Options); + foreach (var fp in volume.ReadFileParts()) { - } - - internal abstract void ValidateArchive(RarVolume archive); - - public override RarVolume Volume => volume!; - - /// - /// Opens a RarReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static RarReader Open(Stream stream, ReaderOptions? options = null) - { - stream.CheckNotNull(nameof(stream)); - return new SingleVolumeRarReader(stream, options ?? new ReaderOptions()); - } - - /// - /// Opens a RarReader for Non-seeking usage with multiple volumes - /// - /// - /// - /// - public static RarReader Open(IEnumerable streams, ReaderOptions? options = null) - { - streams.CheckNotNull(nameof(streams)); - return new MultiVolumeRarReader(streams, options ?? new ReaderOptions()); - } - - protected override IEnumerable GetEntries(Stream stream) - { - volume = new RarReaderVolume(stream, Options); - foreach (RarFilePart fp in volume.ReadFileParts()) - { - ValidateArchive(volume); - yield return new RarReaderEntry(volume.IsSolidArchive, fp); - } - } - - protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() - { - return Entry.Parts; - } - - protected override EntryStream GetEntryStream() - { - var stream = new MultiVolumeReadOnlyStream(CreateFilePartEnumerableForCurrentEntry().Cast(), this); - if (Entry.IsRarV3) - { - return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); - } - return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); + ValidateArchive(volume); + yield return new RarReaderEntry(volume.IsSolidArchive, fp); } } + + protected virtual IEnumerable CreateFilePartEnumerableForCurrentEntry() => + Entry.Parts; + + protected override EntryStream GetEntryStream() + { + var stream = new MultiVolumeReadOnlyStream( + CreateFilePartEnumerableForCurrentEntry().Cast(), + this + ); + if (Entry.IsRarV3) + { + return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream)); + } + return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream)); + } } diff --git a/src/SharpCompress/Readers/Rar/RarReaderEntry.cs b/src/SharpCompress/Readers/Rar/RarReaderEntry.cs index 2e29d615..d6f7222f 100644 --- a/src/SharpCompress/Readers/Rar/RarReaderEntry.cs +++ b/src/SharpCompress/Readers/Rar/RarReaderEntry.cs @@ -1,34 +1,33 @@ -using System.Collections.Generic; +using System.Collections.Generic; using SharpCompress.Common; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +public class RarReaderEntry : RarEntry { - public class RarReaderEntry : RarEntry + internal RarReaderEntry(bool solid, RarFilePart part) { - internal RarReaderEntry(bool solid, RarFilePart part) - { - Part = part; - IsSolid = solid; - } - - internal RarFilePart Part { get; } - - internal override IEnumerable Parts => Part.AsEnumerable(); - - internal override FileHeader FileHeader => Part.FileHeader; - - public override CompressionType CompressionType => CompressionType.Rar; - - /// - /// The compressed file size - /// - public override long CompressedSize => Part.FileHeader.CompressedSize; - - /// - /// The uncompressed file size - /// - public override long Size => Part.FileHeader.UncompressedSize; + Part = part; + IsSolid = solid; } -} \ No newline at end of file + + internal RarFilePart Part { get; } + + internal override IEnumerable Parts => Part.AsEnumerable(); + + internal override FileHeader FileHeader => Part.FileHeader; + + public override CompressionType CompressionType => CompressionType.Rar; + + /// + /// The compressed file size + /// + public override long CompressedSize => Part.FileHeader.CompressedSize; + + /// + /// The uncompressed file size + /// + public override long Size => Part.FileHeader.UncompressedSize; +} diff --git a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs index fa3bbdf1..85beb897 100644 --- a/src/SharpCompress/Readers/Rar/RarReaderVolume.cs +++ b/src/SharpCompress/Readers/Rar/RarReaderVolume.cs @@ -4,23 +4,15 @@ using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; using SharpCompress.IO; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +public class RarReaderVolume : RarVolume { - public class RarReaderVolume : RarVolume - { - internal RarReaderVolume(Stream stream, ReaderOptions options, int index = 0) - : base(StreamingMode.Streaming, stream, options, index) - { - } + internal RarReaderVolume(Stream stream, ReaderOptions options, int index = 0) + : base(StreamingMode.Streaming, stream, options, index) { } - internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) - { - return new NonSeekableStreamFilePart(markHeader, fileHeader, this.Index); - } + internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) => + new NonSeekableStreamFilePart(markHeader, fileHeader, Index); - internal override IEnumerable ReadFileParts() - { - return GetVolumeFileParts(); - } - } + internal override IEnumerable ReadFileParts() => GetVolumeFileParts(); } diff --git a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs index 4d3cf411..f3ab98b4 100644 --- a/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs +++ b/src/SharpCompress/Readers/Rar/SingleVolumeRarReader.cs @@ -2,30 +2,24 @@ using SharpCompress.Common; using SharpCompress.Common.Rar; -namespace SharpCompress.Readers.Rar +namespace SharpCompress.Readers.Rar; + +internal class SingleVolumeRarReader : RarReader { - internal class SingleVolumeRarReader : RarReader + private readonly Stream stream; + + internal SingleVolumeRarReader(Stream stream, ReaderOptions options) : base(options) => + this.stream = stream; + + internal override void ValidateArchive(RarVolume archive) { - private readonly Stream stream; - - internal SingleVolumeRarReader(Stream stream, ReaderOptions options) - : base(options) + if (archive.IsMultiVolume) { - this.stream = stream; - } - - internal override void ValidateArchive(RarVolume archive) - { - if (archive.IsMultiVolume) - { - var msg = "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."; - throw new MultiVolumeExtractionException(msg); - } - } - - protected override Stream RequestInitialStream() - { - return stream; + var msg = + "Streamed archive is a Multi-volume archive. Use different RarReader method to extract."; + throw new MultiVolumeExtractionException(msg); } } -} \ No newline at end of file + + protected override Stream RequestInitialStream() => stream; +} diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 5f75ed0d..a079c235 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -1,37 +1,36 @@ using System; using System.IO; using System.Linq; - -using SharpCompress.Common; using SharpCompress.IO; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public static class ReaderFactory { - public static class ReaderFactory + /// + /// Opens a Reader for Non-seeking usage + /// + /// + /// + /// + public static IReader Open(Stream stream, ReaderOptions? options = null) { - /// - /// Opens a Reader for Non-seeking usage - /// - /// - /// - /// - public static IReader Open(Stream stream, ReaderOptions? options = null) + stream.CheckNotNull(nameof(stream)); + options ??= new ReaderOptions() { LeaveStreamOpen = false }; + + var rewindableStream = new RewindableStream(stream); + rewindableStream.StartRecording(); + + foreach (var factory in Factories.Factory.Factories.OfType()) { - stream.CheckNotNull(nameof(stream)); - options = options ?? new ReaderOptions() + if (factory.TryOpenReader(rewindableStream, options, out var reader) && reader != null) { - LeaveStreamOpen = false - }; - - RewindableStream rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); - - foreach(var factory in Factories.Factory.Factories.OfType()) - { - if (factory.TryOpenReader(rewindableStream, options, out var reader) && reader != null) return reader; - } - - throw new InvalidOperationException("Cannot determine compressed stream type. Supported Reader Formats: Zip, GZip, BZip2, Tar, Rar, LZip, XZ"); + return reader; + } } + + throw new InvalidOperationException( + "Cannot determine compressed stream type. Supported Reader Formats: Zip, GZip, BZip2, Tar, Rar, LZip, XZ" + ); } } diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index 110b0c33..706829cb 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -1,16 +1,15 @@ -using SharpCompress.Common; +using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public class ReaderOptions : OptionsBase { - public class ReaderOptions : OptionsBase - { - /// - /// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file) - /// - public bool LookForHeader { get; set; } + /// + /// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file) + /// + public bool LookForHeader { get; set; } - public string? Password { get; set; } + public string? Password { get; set; } - public bool DisableCheckIncomplete { get; set; } - } -} \ No newline at end of file + public bool DisableCheckIncomplete { get; set; } +} diff --git a/src/SharpCompress/Readers/ReaderProgress.cs b/src/SharpCompress/Readers/ReaderProgress.cs index 7b6f099d..2cffab9a 100644 --- a/src/SharpCompress/Readers/ReaderProgress.cs +++ b/src/SharpCompress/Readers/ReaderProgress.cs @@ -1,24 +1,21 @@ - - using System; using SharpCompress.Common; -namespace SharpCompress.Readers +namespace SharpCompress.Readers; + +public class ReaderProgress { - public class ReaderProgress + private readonly IEntry _entry; + public long BytesTransferred { get; } + public int Iterations { get; } + + public int PercentageRead => (int)Math.Round(PercentageReadExact); + public double PercentageReadExact => (float)BytesTransferred / _entry.Size * 100; + + public ReaderProgress(IEntry entry, long bytesTransferred, int iterations) { - private readonly IEntry _entry; - public long BytesTransferred { get; } - public int Iterations { get; } - - public int PercentageRead => (int)Math.Round(PercentageReadExact); - public double PercentageReadExact => (float)BytesTransferred / _entry.Size * 100; - - public ReaderProgress(IEntry entry, long bytesTransferred, int iterations) - { - _entry = entry; - BytesTransferred = bytesTransferred; - Iterations = iterations; - } + _entry = entry; + BytesTransferred = bytesTransferred; + Iterations = iterations; } } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index a2b7ecc7..c9802e46 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -12,113 +12,119 @@ using SharpCompress.IO; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.Xz; -namespace SharpCompress.Readers.Tar +namespace SharpCompress.Readers.Tar; + +public class TarReader : AbstractReader { - public class TarReader : AbstractReader + private readonly CompressionType compressionType; + + internal TarReader(Stream stream, ReaderOptions options, CompressionType compressionType) + : base(options, ArchiveType.Tar) { - private readonly CompressionType compressionType; + this.compressionType = compressionType; + Volume = new TarVolume(stream, options); + } - internal TarReader(Stream stream, ReaderOptions options, CompressionType compressionType) - : base(options, ArchiveType.Tar) + public override TarVolume Volume { get; } + + protected override Stream RequestInitialStream() + { + var stream = base.RequestInitialStream(); + switch (compressionType) { - this.compressionType = compressionType; - Volume = new TarVolume(stream, options); - } - - public override TarVolume Volume { get; } - - protected override Stream RequestInitialStream() - { - var stream = base.RequestInitialStream(); - switch (compressionType) + case CompressionType.BZip2: { - case CompressionType.BZip2: - { - return new BZip2Stream(stream, CompressionMode.Decompress, false); - } - case CompressionType.GZip: - { - return new GZipStream(stream, CompressionMode.Decompress); - } - case CompressionType.LZip: - { - return new LZipStream(stream, CompressionMode.Decompress); - } - case CompressionType.Xz: - { - return new XZStream(stream); - } - case CompressionType.None: - { - return stream; - } - default: - { - throw new NotSupportedException("Invalid compression type: " + compressionType); - } + return new BZip2Stream(stream, CompressionMode.Decompress, false); } - } - - #region Open - - /// - /// Opens a TarReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static TarReader Open(Stream stream, ReaderOptions? options = null) - { - stream.CheckNotNull(nameof(stream)); - options = options ?? new ReaderOptions(); - RewindableStream rewindableStream = new RewindableStream(stream); - rewindableStream.StartRecording(); - if (GZipArchive.IsGZipFile(rewindableStream)) + case CompressionType.GZip: { - rewindableStream.Rewind(false); - GZipStream testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.GZip); - } - throw new InvalidFormatException("Not a tar file."); + return new GZipStream(stream, CompressionMode.Decompress); } - - rewindableStream.Rewind(false); - if (BZip2Stream.IsBZip2(rewindableStream)) + case CompressionType.LZip: { - rewindableStream.Rewind(false); - BZip2Stream testStream = new BZip2Stream(rewindableStream, CompressionMode.Decompress, false); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.BZip2); - } - throw new InvalidFormatException("Not a tar file."); + return new LZipStream(stream, CompressionMode.Decompress); } - - rewindableStream.Rewind(false); - if (LZipStream.IsLZipFile(rewindableStream)) + case CompressionType.Xz: { - rewindableStream.Rewind(false); - LZipStream testStream = new LZipStream(rewindableStream, CompressionMode.Decompress); - if (TarArchive.IsTarFile(testStream)) - { - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.LZip); - } - throw new InvalidFormatException("Not a tar file."); + return new XZStream(stream); + } + case CompressionType.None: + { + return stream; + } + default: + { + throw new NotSupportedException("Invalid compression type: " + compressionType); } - rewindableStream.Rewind(true); - return new TarReader(rewindableStream, options, CompressionType.None); - } - - #endregion Open - - protected override IEnumerable GetEntries(Stream stream) - { - return TarEntry.GetEntries(StreamingMode.Streaming, stream, compressionType, Options.ArchiveEncoding); } } + + #region Open + + /// + /// Opens a TarReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static TarReader Open(Stream stream, ReaderOptions? options = null) + { + stream.CheckNotNull(nameof(stream)); + options = options ?? new ReaderOptions(); + RewindableStream rewindableStream = new RewindableStream(stream); + rewindableStream.StartRecording(); + if (GZipArchive.IsGZipFile(rewindableStream)) + { + rewindableStream.Rewind(false); + GZipStream testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + return new TarReader(rewindableStream, options, CompressionType.GZip); + } + throw new InvalidFormatException("Not a tar file."); + } + + rewindableStream.Rewind(false); + if (BZip2Stream.IsBZip2(rewindableStream)) + { + rewindableStream.Rewind(false); + BZip2Stream testStream = new BZip2Stream( + rewindableStream, + CompressionMode.Decompress, + false + ); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + return new TarReader(rewindableStream, options, CompressionType.BZip2); + } + throw new InvalidFormatException("Not a tar file."); + } + + rewindableStream.Rewind(false); + if (LZipStream.IsLZipFile(rewindableStream)) + { + rewindableStream.Rewind(false); + LZipStream testStream = new LZipStream(rewindableStream, CompressionMode.Decompress); + if (TarArchive.IsTarFile(testStream)) + { + rewindableStream.Rewind(true); + return new TarReader(rewindableStream, options, CompressionType.LZip); + } + throw new InvalidFormatException("Not a tar file."); + } + rewindableStream.Rewind(true); + return new TarReader(rewindableStream, options, CompressionType.None); + } + + #endregion Open + + protected override IEnumerable GetEntries(Stream stream) => + TarEntry.GetEntries( + StreamingMode.Streaming, + stream, + compressionType, + Options.ArchiveEncoding + ); } diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index 54a759b2..616e8057 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -1,57 +1,58 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using SharpCompress.Common; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; -namespace SharpCompress.Readers.Zip +namespace SharpCompress.Readers.Zip; + +public class ZipReader : AbstractReader { - public class ZipReader : AbstractReader + private readonly StreamingZipHeaderFactory _headerFactory; + + private ZipReader(Stream stream, ReaderOptions options) : base(options, ArchiveType.Zip) { - private readonly StreamingZipHeaderFactory _headerFactory; + Volume = new ZipVolume(stream, options); + _headerFactory = new StreamingZipHeaderFactory(options.Password, options.ArchiveEncoding); + } - private ZipReader(Stream stream, ReaderOptions options) - : base(options, ArchiveType.Zip) + public override ZipVolume Volume { get; } + + #region Open + + /// + /// Opens a ZipReader for Non-seeking usage with a single volume + /// + /// + /// + /// + public static ZipReader Open(Stream stream, ReaderOptions? options = null) + { + stream.CheckNotNull(nameof(stream)); + return new ZipReader(stream, options ?? new ReaderOptions()); + } + + #endregion Open + + protected override IEnumerable GetEntries(Stream stream) + { + foreach (var h in _headerFactory.ReadStreamHeader(stream)) { - Volume = new ZipVolume(stream, options); - _headerFactory = new StreamingZipHeaderFactory(options.Password, options.ArchiveEncoding); - } - - public override ZipVolume Volume { get; } - - #region Open - - /// - /// Opens a ZipReader for Non-seeking usage with a single volume - /// - /// - /// - /// - public static ZipReader Open(Stream stream, ReaderOptions? options = null) - { - stream.CheckNotNull(nameof(stream)); - return new ZipReader(stream, options ?? new ReaderOptions()); - } - - #endregion Open - - protected override IEnumerable GetEntries(Stream stream) - { - foreach (ZipHeader h in _headerFactory.ReadStreamHeader(stream)) + if (h != null) { - if (h != null) + switch (h.ZipHeaderType) { - switch (h.ZipHeaderType) + case ZipHeaderType.LocalEntry: + + { + yield return new ZipEntry( + new StreamingZipFilePart((LocalEntryHeader)h, stream) + ); + } + break; + case ZipHeaderType.DirectoryEnd: { - case ZipHeaderType.LocalEntry: - { - yield return new ZipEntry(new StreamingZipFilePart((LocalEntryHeader)h, stream)); - } - break; - case ZipHeaderType.DirectoryEnd: - { - yield break; - } + yield break; } } } diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 7be35930..c6c2f918 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -1,14 +1,12 @@ - + SharpCompress - Pure C# Decompression/Compression en-US - 0.32.2 - 0.32.2 - 0.32.2 + 0.33.0 + 0.33.0 + 0.33.0 Adam Hathcock - net461;netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0 - true - false + net462;netstandard2.0;netstandard2.1;net6.0;net7.0 SharpCompress ../../SharpCompress.snk true @@ -19,30 +17,25 @@ Copyright (c) 2014 Adam Hathcock false false - SharpCompress is a compression library for NET Standard 2.0/2.1/NET 5.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented. - latest - enable - true + SharpCompress is a compression library for NET Standard 2.0/2.1/NET 6.0/NET 7.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented. true true snupkg true - False - False - + - + - - + + - - + + diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index cbdb1ecb..9471c3e1 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -4,390 +4,391 @@ using System.Collections.Generic; using System.IO; using SharpCompress.Readers; -namespace SharpCompress +namespace SharpCompress; + +[CLSCompliant(false)] +public static class Utility { - internal static class Utility + public static ReadOnlyCollection ToReadOnly(this ICollection items) => + new ReadOnlyCollection(items); + + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Amount of bits to shift + /// The resulting number from the shift operation + public static int URShift(int number, int bits) { - public static ReadOnlyCollection ToReadOnly(this ICollection items) + if (number >= 0) { - return new ReadOnlyCollection(items); + return number >> bits; } + return (number >> bits) + (2 << ~bits); + } - /// - /// Performs an unsigned bitwise right shift with the specified number - /// - /// Number to operate on - /// Amount of bits to shift - /// The resulting number from the shift operation - public static int URShift(int number, int bits) + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Amount of bits to shift + /// The resulting number from the shift operation + public static long URShift(long number, int bits) + { + if (number >= 0) { - if (number >= 0) + return number >> bits; + } + return (number >> bits) + (2L << ~bits); + } + + public static void SetSize(this List list, int count) + { + if (count > list.Count) + { + // Ensure the list only needs to grow once + list.Capacity = count; + for (var i = list.Count; i < count; i++) { - return number >> bits; + list.Add(0x0); } - return (number >> bits) + (2 << ~bits); + } + else + { + list.RemoveRange(count, list.Count - count); + } + } + + public static void ForEach(this IEnumerable items, Action action) + { + foreach (var item in items) + { + action(item); + } + } + + public static void Copy( + Array sourceArray, + long sourceIndex, + Array destinationArray, + long destinationIndex, + long length + ) + { + if (sourceIndex > int.MaxValue || sourceIndex < int.MinValue) + { + throw new ArgumentOutOfRangeException(nameof(sourceIndex)); } - /// - /// Performs an unsigned bitwise right shift with the specified number - /// - /// Number to operate on - /// Amount of bits to shift - /// The resulting number from the shift operation - public static long URShift(long number, int bits) + if (destinationIndex > int.MaxValue || destinationIndex < int.MinValue) { - if (number >= 0) - { - return number >> bits; - } - return (number >> bits) + (2L << ~bits); + throw new ArgumentOutOfRangeException(nameof(destinationIndex)); } - public static void SetSize(this List list, int count) + if (length > int.MaxValue || length < int.MinValue) { - if (count > list.Count) + throw new ArgumentOutOfRangeException(nameof(length)); + } + + Array.Copy( + sourceArray, + (int)sourceIndex, + destinationArray, + (int)destinationIndex, + (int)length + ); + } + + public static IEnumerable AsEnumerable(this T item) + { + yield return item; + } + + public static void CheckNotNull(this object obj, string name) + { + if (obj is null) + { + throw new ArgumentNullException(name); + } + } + + public static void CheckNotNullOrEmpty(this string obj, string name) + { + obj.CheckNotNull(name); + if (obj.Length == 0) + { + throw new ArgumentException("String is empty.", name); + } + } + + public static void Skip(this Stream source, long advanceAmount) + { + if (source.CanSeek) + { + source.Position += advanceAmount; + return; + } + + var buffer = GetTransferByteArray(); + try + { + var read = 0; + var readCount = 0; + do { - // Ensure the list only needs to grow once - list.Capacity = count; - for (int i = list.Count; i < count; i++) + readCount = buffer.Length; + if (readCount > advanceAmount) { - list.Add(0x0); + readCount = (int)advanceAmount; } - } - else - { - list.RemoveRange(count, list.Count - count); - } - } - - public static void ForEach(this IEnumerable items, Action action) - { - foreach (T item in items) - { - action(item); - } - } - - public static void Copy(Array sourceArray, long sourceIndex, Array destinationArray, long destinationIndex, long length) - { - if (sourceIndex > Int32.MaxValue || sourceIndex < Int32.MinValue) - { - throw new ArgumentOutOfRangeException(nameof(sourceIndex)); - } - - if (destinationIndex > Int32.MaxValue || destinationIndex < Int32.MinValue) - { - throw new ArgumentOutOfRangeException(nameof(destinationIndex)); - } - - if (length > Int32.MaxValue || length < Int32.MinValue) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - Array.Copy(sourceArray, (int)sourceIndex, destinationArray, (int)destinationIndex, (int)length); - } - - public static IEnumerable AsEnumerable(this T item) - { - yield return item; - } - - public static void CheckNotNull(this object obj, string name) - { - if (obj is null) - { - throw new ArgumentNullException(name); - } - } - - public static void CheckNotNullOrEmpty(this string obj, string name) - { - obj.CheckNotNull(name); - if (obj.Length == 0) - { - throw new ArgumentException("String is empty.", name); - } - } - - public static void Skip(this Stream source, long advanceAmount) - { - if (source.CanSeek) - { - source.Position += advanceAmount; - return; - } - - byte[] buffer = GetTransferByteArray(); - try - { - int read = 0; - int readCount = 0; - do + read = source.Read(buffer, 0, readCount); + if (read <= 0) { - readCount = buffer.Length; - if (readCount > advanceAmount) - { - readCount = (int)advanceAmount; - } - read = source.Read(buffer, 0, readCount); - if (read <= 0) - { - break; - } - advanceAmount -= read; - if (advanceAmount == 0) - { - break; - } + break; } - while (true); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - public static void Skip(this Stream source) - { - byte[] buffer = GetTransferByteArray(); - try - { - do + advanceAmount -= read; + if (advanceAmount == 0) { + break; } - while (source.Read(buffer, 0, buffer.Length) == buffer.Length); - } - finally - { - ArrayPool.Shared.Return(buffer); - } + } while (true); } - - public static bool Find(this Stream source, byte[] array) + finally { - byte[] buffer = GetTransferByteArray(); - try - { - int count = 0; - var len = source.Read(buffer, 0, buffer.Length); + ArrayPool.Shared.Return(buffer); + } + } - do + public static void Skip(this Stream source) + { + var buffer = GetTransferByteArray(); + try + { + do { } while (source.Read(buffer, 0, buffer.Length) == buffer.Length); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + public static bool Find(this Stream source, byte[] array) + { + var buffer = GetTransferByteArray(); + try + { + var count = 0; + var len = source.Read(buffer, 0, buffer.Length); + + do + { + for (var i = 0; i < len; i++) { - for (int i = 0; i < len; i++) + if (array[count] == buffer[i]) { - if (array[count] == buffer[i]) + count++; + if (count == array.Length) { - count++; - if (count == array.Length) - { - source.Position = source.Position - len + i - array.Length +1; - return true; - } + source.Position = source.Position - len + i - array.Length + 1; + return true; } } } - while ((len = source.Read(buffer, 0, buffer.Length)) > 0); - } - finally + } while ((len = source.Read(buffer, 0, buffer.Length)) > 0); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + return false; + } + + public static DateTime DosDateToDateTime(ushort iDate, ushort iTime) + { + var year = (iDate / 512) + 1980; + var month = iDate % 512 / 32; + var day = iDate % 512 % 32; + var hour = iTime / 2048; + var minute = iTime % 2048 / 32; + var second = iTime % 2048 % 32 * 2; + + if (iDate == ushort.MaxValue || month == 0 || day == 0) + { + year = 1980; + month = 1; + day = 1; + } + + if (iTime == ushort.MaxValue) + { + hour = minute = second = 0; + } + + DateTime dt; + try + { + dt = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local); + } + catch + { + dt = new DateTime(); + } + return dt; + } + + public static uint DateTimeToDosTime(this DateTime? dateTime) + { + if (dateTime is null) + { + return 0; + } + + var localDateTime = dateTime.Value.ToLocalTime(); + + return (uint)( + (localDateTime.Second / 2) + | (localDateTime.Minute << 5) + | (localDateTime.Hour << 11) + | (localDateTime.Day << 16) + | (localDateTime.Month << 21) + | ((localDateTime.Year - 1980) << 25) + ); + } + + public static DateTime DosDateToDateTime(uint iTime) => + DosDateToDateTime((ushort)(iTime / 65536), (ushort)(iTime % 65536)); + + /// + /// Convert Unix time value to a DateTime object. + /// + /// The Unix time stamp you want to convert to DateTime. + /// Returns a DateTime object that represents value of the Unix time. + public static DateTime UnixTimeToDateTime(long unixtime) + { + var sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return sTime.AddSeconds(unixtime); + } + + public static long TransferTo(this Stream source, Stream destination) + { + var array = GetTransferByteArray(); + try + { + long total = 0; + while (ReadTransferBlock(source, array, out var count)) { - ArrayPool.Shared.Return(buffer); + total += count; + destination.Write(array, 0, count); } - - return false; + return total; } - - public static DateTime DosDateToDateTime(UInt16 iDate, UInt16 iTime) + finally { - int year = iDate / 512 + 1980; - int month = iDate % 512 / 32; - int day = iDate % 512 % 32; - int hour = iTime / 2048; - int minute = iTime % 2048 / 32; - int second = iTime % 2048 % 32 * 2; - - if (iDate == UInt16.MaxValue || month == 0 || day == 0) - { - year = 1980; - month = 1; - day = 1; - } - - if (iTime == UInt16.MaxValue) - { - hour = minute = second = 0; - } - - DateTime dt; - try - { - dt = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local); - } - catch - { - dt = new DateTime(); - } - return dt; - } - - public static uint DateTimeToDosTime(this DateTime? dateTime) - { - if (dateTime is null) - { - return 0; - } - - var localDateTime = dateTime.Value.ToLocalTime(); - - return (uint)( - (localDateTime.Second / 2) | (localDateTime.Minute << 5) | (localDateTime.Hour << 11) | - (localDateTime.Day << 16) | (localDateTime.Month << 21) | - ((localDateTime.Year - 1980) << 25)); - } - - public static DateTime DosDateToDateTime(UInt32 iTime) - { - return DosDateToDateTime((UInt16)(iTime / 65536), - (UInt16)(iTime % 65536)); - } - - /// - /// Convert Unix time value to a DateTime object. - /// - /// The Unix time stamp you want to convert to DateTime. - /// Returns a DateTime object that represents value of the Unix time. - public static DateTime UnixTimeToDateTime(long unixtime) - { - DateTime sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - return sTime.AddSeconds(unixtime); - } - - public static long TransferTo(this Stream source, Stream destination) - { - byte[] array = GetTransferByteArray(); - try - { - long total = 0; - while (ReadTransferBlock(source, array, out int count)) - { - total += count; - destination.Write(array, 0, count); - } - return total; - } - finally - { - ArrayPool.Shared.Return(array); - } - } - - public static long TransferTo(this Stream source, Stream destination, Common.Entry entry, IReaderExtractionListener readerExtractionListener) - { - byte[] array = GetTransferByteArray(); - try - { - var iterations = 0; - long total = 0; - while (ReadTransferBlock(source, array, out int count)) - { - total += count; - destination.Write(array, 0, count); - iterations++; - readerExtractionListener.FireEntryExtractionProgress(entry, total, iterations); - } - return total; - } - finally - { - ArrayPool.Shared.Return(array); - } - } - - private static bool ReadTransferBlock(Stream source, byte[] array, out int count) - { - return (count = source.Read(array, 0, array.Length)) != 0; - } - - private static byte[] GetTransferByteArray() - { - return ArrayPool.Shared.Rent(81920); - } - - public static bool ReadFully(this Stream stream, byte[] buffer) - { - int total = 0; - int read; - while ((read = stream.Read(buffer, total, buffer.Length - total)) > 0) - { - total += read; - if (total >= buffer.Length) - { - return true; - } - } - return (total >= buffer.Length); - } - - public static bool ReadFully(this Stream stream, Span buffer) - { - int total = 0; - int read; - while ((read = stream.Read(buffer.Slice(total, buffer.Length - total))) > 0) - { - total += read; - if (total >= buffer.Length) - { - return true; - } - } - return (total >= buffer.Length); - } - - public static string TrimNulls(this string source) - { - return source.Replace('\0', ' ').Trim(); - } - - /// - /// Swap the endianness of a UINT32 - /// - /// The UINT32 you want to swap his endianness - /// Return the new UINT32 in the other endianness format - public static UInt32 SwapUINT32(UInt32 number) - { - return (number >> 24) | - ((number << 8) & 0x00FF0000) | - ((number >> 8) & 0x0000FF00) | - (number << 24); - } - - /// - /// Insert a little endian UINT32 into a byte array - /// - /// The buffer to insert into - /// The UINT32 to insert - /// Offset of the buffer to insert into - public static void SetLittleUInt32(ref byte[] buffer, UInt32 number, long offset) - { - buffer[offset] = (byte)(number); - buffer[offset + 1] = (byte)(number >> 8); - buffer[offset + 2] = (byte)(number >> 16); - buffer[offset + 3] = (byte)(number >> 24); - } - - /// - /// Insert a big endian UINT32 into a byte array - /// - /// The buffer to insert into - /// The UINT32 to insert - /// Offset of the buffer to insert into - public static void SetBigUInt32(ref byte[] buffer, UInt32 number, long offset) - { - buffer[offset] = (byte)(number >> 24); - buffer[offset + 1] = (byte)(number >> 16); - buffer[offset + 2] = (byte)(number >> 8); - buffer[offset + 3] = (byte)number; + ArrayPool.Shared.Return(array); } } + + public static long TransferTo( + this Stream source, + Stream destination, + Common.Entry entry, + IReaderExtractionListener readerExtractionListener + ) + { + var array = GetTransferByteArray(); + try + { + var iterations = 0; + long total = 0; + while (ReadTransferBlock(source, array, out var count)) + { + total += count; + destination.Write(array, 0, count); + iterations++; + readerExtractionListener.FireEntryExtractionProgress(entry, total, iterations); + } + return total; + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + private static bool ReadTransferBlock(Stream source, byte[] array, out int count) => + (count = source.Read(array, 0, array.Length)) != 0; + + private static byte[] GetTransferByteArray() => ArrayPool.Shared.Rent(81920); + + public static bool ReadFully(this Stream stream, byte[] buffer) + { + var total = 0; + int read; + while ((read = stream.Read(buffer, total, buffer.Length - total)) > 0) + { + total += read; + if (total >= buffer.Length) + { + return true; + } + } + return (total >= buffer.Length); + } + + public static bool ReadFully(this Stream stream, Span buffer) + { + var total = 0; + int read; + while ((read = stream.Read(buffer.Slice(total, buffer.Length - total))) > 0) + { + total += read; + if (total >= buffer.Length) + { + return true; + } + } + return (total >= buffer.Length); + } + + public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim(); + + /// + /// Swap the endianness of a UINT32 + /// + /// The UINT32 you want to swap his endianness + /// Return the new UINT32 in the other endianness format + public static uint SwapUINT32(uint number) => + (number >> 24) + | ((number << 8) & 0x00FF0000) + | ((number >> 8) & 0x0000FF00) + | (number << 24); + + /// + /// Insert a little endian UINT32 into a byte array + /// + /// The buffer to insert into + /// The UINT32 to insert + /// Offset of the buffer to insert into + public static void SetLittleUInt32(ref byte[] buffer, uint number, long offset) + { + buffer[offset] = (byte)(number); + buffer[offset + 1] = (byte)(number >> 8); + buffer[offset + 2] = (byte)(number >> 16); + buffer[offset + 3] = (byte)(number >> 24); + } + + /// + /// Insert a big endian UINT32 into a byte array + /// + /// The buffer to insert into + /// The UINT32 to insert + /// Offset of the buffer to insert into + public static void SetBigUInt32(ref byte[] buffer, uint number, long offset) + { + buffer[offset] = (byte)(number >> 24); + buffer[offset + 1] = (byte)(number >> 16); + buffer[offset + 2] = (byte)(number >> 8); + buffer[offset + 3] = (byte)number; + } } diff --git a/src/SharpCompress/Writers/AbstractWriter.cs b/src/SharpCompress/Writers/AbstractWriter.cs index fad9bd1e..209c49f7 100644 --- a/src/SharpCompress/Writers/AbstractWriter.cs +++ b/src/SharpCompress/Writers/AbstractWriter.cs @@ -4,56 +4,52 @@ using System; using System.IO; using SharpCompress.Common; -namespace SharpCompress.Writers +namespace SharpCompress.Writers; + +public abstract class AbstractWriter : IWriter { - public abstract class AbstractWriter : IWriter + private bool _isDisposed; + + protected AbstractWriter(ArchiveType type, WriterOptions writerOptions) { - private bool _isDisposed; + WriterType = type; + WriterOptions = writerOptions; + } - protected AbstractWriter(ArchiveType type, WriterOptions writerOptions) + protected void InitalizeStream(Stream stream) => OutputStream = stream; + + protected Stream OutputStream { get; private set; } + + public ArchiveType WriterType { get; } + + protected WriterOptions WriterOptions { get; } + + public abstract void Write(string filename, Stream source, DateTime? modificationTime); + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposing) { - WriterType = type; - WriterOptions = writerOptions; - } - - protected void InitalizeStream(Stream stream) - { - OutputStream = stream; - } - - protected Stream OutputStream { get; private set; } - - public ArchiveType WriterType { get; } - - protected WriterOptions WriterOptions { get; } - - public abstract void Write(string filename, Stream source, DateTime? modificationTime); - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposing) - { - OutputStream.Dispose(); - } - } - - public void Dispose() - { - if (!_isDisposed) - { - GC.SuppressFinalize(this); - Dispose(true); - _isDisposed = true; - } - } - - ~AbstractWriter() - { - if (!_isDisposed) - { - Dispose(false); - _isDisposed = true; - } + OutputStream.Dispose(); } } -} \ No newline at end of file + + public void Dispose() + { + if (!_isDisposed) + { + GC.SuppressFinalize(this); + Dispose(true); + _isDisposed = true; + } + } + + ~AbstractWriter() + { + if (!_isDisposed) + { + Dispose(false); + _isDisposed = true; + } + } +} diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs index 12e820a2..5c990a7d 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriter.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs @@ -5,45 +5,49 @@ using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; using SharpCompress.IO; -namespace SharpCompress.Writers.GZip +namespace SharpCompress.Writers.GZip; + +public sealed class GZipWriter : AbstractWriter { - public sealed class GZipWriter : AbstractWriter + private bool _wroteToStream; + + public GZipWriter(Stream destination, GZipWriterOptions? options = null) + : base(ArchiveType.GZip, options ?? new GZipWriterOptions()) { - private bool _wroteToStream; - - public GZipWriter(Stream destination, GZipWriterOptions? options = null) - : base(ArchiveType.GZip, options ?? new GZipWriterOptions()) + if (WriterOptions.LeaveStreamOpen) { - if (WriterOptions.LeaveStreamOpen) - { - destination = NonDisposingStream.Create(destination); - } - InitalizeStream(new GZipStream(destination, CompressionMode.Compress, - options?.CompressionLevel ?? CompressionLevel.Default, - WriterOptions.ArchiveEncoding.GetEncoding())); + destination = NonDisposingStream.Create(destination); } + InitalizeStream( + new GZipStream( + destination, + CompressionMode.Compress, + options?.CompressionLevel ?? CompressionLevel.Default, + WriterOptions.ArchiveEncoding.GetEncoding() + ) + ); + } - protected override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) + { + if (isDisposing) { - if (isDisposing) - { - //dispose here to finish the GZip, GZip won't close the underlying stream - OutputStream.Dispose(); - } - base.Dispose(isDisposing); + //dispose here to finish the GZip, GZip won't close the underlying stream + OutputStream.Dispose(); } + base.Dispose(isDisposing); + } - public override void Write(string filename, Stream source, DateTime? modificationTime) + public override void Write(string filename, Stream source, DateTime? modificationTime) + { + if (_wroteToStream) { - if (_wroteToStream) - { - throw new ArgumentException("Can only write a single stream to a GZip file."); - } - GZipStream stream = (GZipStream)OutputStream; - stream.FileName = filename; - stream.LastModified = modificationTime; - source.TransferTo(stream); - _wroteToStream = true; + throw new ArgumentException("Can only write a single stream to a GZip file."); } + var stream = (GZipStream)OutputStream; + stream.FileName = filename; + stream.LastModified = modificationTime; + source.TransferTo(stream); + _wroteToStream = true; } } diff --git a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs index b6966e16..faff1cc0 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs @@ -1,28 +1,22 @@ -using SharpCompress.Common; +using SharpCompress.Common; using SharpCompress.Compressors.Deflate; -namespace SharpCompress.Writers.GZip +namespace SharpCompress.Writers.GZip; + +public class GZipWriterOptions : WriterOptions { - public class GZipWriterOptions : WriterOptions + public GZipWriterOptions() : base(CompressionType.GZip) { } + + internal GZipWriterOptions(WriterOptions options) : base(options.CompressionType) { - public GZipWriterOptions() - : base(CompressionType.GZip) + LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + + if (options is GZipWriterOptions writerOptions) { + CompressionLevel = writerOptions.CompressionLevel; } - - internal GZipWriterOptions(WriterOptions options) - : base(options.CompressionType) - { - LeaveStreamOpen = options.LeaveStreamOpen; - ArchiveEncoding = options.ArchiveEncoding; - - var writerOptions = options as GZipWriterOptions; - if (writerOptions != null) - { - CompressionLevel = writerOptions.CompressionLevel; - } - } - - public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Default; } -} \ No newline at end of file + + public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Default; +} diff --git a/src/SharpCompress/Writers/IWriter.cs b/src/SharpCompress/Writers/IWriter.cs index a15225bf..bde2fcd9 100644 --- a/src/SharpCompress/Writers/IWriter.cs +++ b/src/SharpCompress/Writers/IWriter.cs @@ -1,12 +1,11 @@ -using System; +using System; using System.IO; using SharpCompress.Common; -namespace SharpCompress.Writers +namespace SharpCompress.Writers; + +public interface IWriter : IDisposable { - public interface IWriter : IDisposable - { - ArchiveType WriterType { get; } - void Write(string filename, Stream source, DateTime? modificationTime); - } -} \ No newline at end of file + ArchiveType WriterType { get; } + void Write(string filename, Stream source, DateTime? modificationTime); +} diff --git a/src/SharpCompress/Writers/IWriterExtensions.cs b/src/SharpCompress/Writers/IWriterExtensions.cs index 5fc1c82d..56f3966a 100644 --- a/src/SharpCompress/Writers/IWriterExtensions.cs +++ b/src/SharpCompress/Writers/IWriterExtensions.cs @@ -1,58 +1,55 @@ using System; using System.IO; using System.Linq; -using System.Linq.Expressions; -namespace SharpCompress.Writers +namespace SharpCompress.Writers; + +public static class IWriterExtensions { - public static class IWriterExtensions + public static void Write(this IWriter writer, string entryPath, Stream source) => + writer.Write(entryPath, source, null); + + public static void Write(this IWriter writer, string entryPath, FileInfo source) { - public static void Write(this IWriter writer, string entryPath, Stream source) + if (!source.Exists) { - writer.Write(entryPath, source, null); + throw new ArgumentException("Source does not exist: " + source.FullName); + } + using var stream = source.OpenRead(); + writer.Write(entryPath, stream, source.LastWriteTime); + } + + public static void Write(this IWriter writer, string entryPath, string source) => + writer.Write(entryPath, new FileInfo(source)); + + public static void WriteAll( + this IWriter writer, + string directory, + string searchPattern = "*", + SearchOption option = SearchOption.TopDirectoryOnly + ) => writer.WriteAll(directory, searchPattern, null, option); + + public static void WriteAll( + this IWriter writer, + string directory, + string searchPattern = "*", + Func? fileSearchFunc = null, + SearchOption option = SearchOption.TopDirectoryOnly + ) + { + if (!Directory.Exists(directory)) + { + throw new ArgumentException("Directory does not exist: " + directory); } - public static void Write(this IWriter writer, string entryPath, FileInfo source) + fileSearchFunc ??= n => true; + foreach ( + var file in Directory + .EnumerateFiles(directory, searchPattern, option) + .Where(fileSearchFunc) + ) { - if (!source.Exists) - { - throw new ArgumentException("Source does not exist: " + source.FullName); - } - using (var stream = source.OpenRead()) - { - writer.Write(entryPath, stream, source.LastWriteTime); - } - } - - public static void Write(this IWriter writer, string entryPath, string source) - { - writer.Write(entryPath, new FileInfo(source)); - } - - public static void WriteAll(this IWriter writer, string directory, string searchPattern = "*", SearchOption option = SearchOption.TopDirectoryOnly) - { - writer.WriteAll(directory, searchPattern, null, option); - } - - public static void WriteAll(this IWriter writer, - string directory, - string searchPattern = "*", - Func? fileSearchFunc = null, - SearchOption option = SearchOption.TopDirectoryOnly) - { - if (!Directory.Exists(directory)) - { - throw new ArgumentException("Directory does not exist: " + directory); - } - - if (fileSearchFunc is null) - { - fileSearchFunc = n => true; - } - foreach (var file in Directory.EnumerateFiles(directory, searchPattern, option).Where(fileSearchFunc)) - { - writer.Write(file.Substring(directory.Length), file); - } + writer.Write(file.Substring(directory.Length), file); } } } diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs index 1aed7c72..8e7bc701 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SharpCompress.Factories; -namespace SharpCompress.Writers +namespace SharpCompress.Writers; + +public interface IWriterFactory : IFactory { - public interface IWriterFactory : IFactory - { - IWriter Open(Stream stream, WriterOptions writerOptions); - } + IWriter Open(Stream stream, WriterOptions writerOptions); } diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index c90e069e..068aeee1 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -8,120 +8,121 @@ using SharpCompress.Compressors.Deflate; using SharpCompress.Compressors.LZMA; using SharpCompress.IO; -namespace SharpCompress.Writers.Tar +namespace SharpCompress.Writers.Tar; + +public class TarWriter : AbstractWriter { - public class TarWriter : AbstractWriter + private readonly bool finalizeArchiveOnClose; + + public TarWriter(Stream destination, TarWriterOptions options) : base(ArchiveType.Tar, options) { - private readonly bool finalizeArchiveOnClose; + finalizeArchiveOnClose = options.FinalizeArchiveOnClose; - public TarWriter(Stream destination, TarWriterOptions options) - : base(ArchiveType.Tar, options) + if (!destination.CanWrite) { - finalizeArchiveOnClose = options.FinalizeArchiveOnClose; - - if (!destination.CanWrite) - { - throw new ArgumentException("Tars require writable streams."); - } - if (WriterOptions.LeaveStreamOpen) - { - destination = NonDisposingStream.Create(destination); - } - switch (options.CompressionType) - { - case CompressionType.None: - break; - case CompressionType.BZip2: - { - destination = new BZip2Stream(destination, CompressionMode.Compress, false); - } - break; - case CompressionType.GZip: - { - destination = new GZipStream(destination, CompressionMode.Compress); - } - break; - case CompressionType.LZip: - { - destination = new LZipStream(destination, CompressionMode.Compress); - } - break; - default: - { - throw new InvalidFormatException("Tar does not support compression: " + options.CompressionType); - } - } - InitalizeStream(destination); + throw new ArgumentException("Tars require writable streams."); } - - public override void Write(string filename, Stream source, DateTime? modificationTime) + if (WriterOptions.LeaveStreamOpen) { - Write(filename, source, modificationTime, null); + destination = NonDisposingStream.Create(destination); } - - private string NormalizeFilename(string filename) + switch (options.CompressionType) { - filename = filename.Replace('\\', '/'); + case CompressionType.None: + break; + case CompressionType.BZip2: - int pos = filename.IndexOf(':'); - if (pos >= 0) - { - filename = filename.Remove(0, pos + 1); - } - - return filename.Trim('/'); - } - - public void Write(string filename, Stream source, DateTime? modificationTime, long? size) - { - if (!source.CanSeek && size is null) - { - throw new ArgumentException("Seekable stream is required if no size is given."); - } - - long realSize = size ?? source.Length; - - TarHeader header = new TarHeader(WriterOptions.ArchiveEncoding); - - header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH; - header.Name = NormalizeFilename(filename); - header.Size = realSize; - header.Write(OutputStream); - - size = source.TransferTo(OutputStream); - PadTo512(size.Value); - } - - private void PadTo512(long size) - { - int zeros = unchecked((int)(((size + 511L) & ~511L) - size)); - - OutputStream.Write(stackalloc byte[zeros]); - } - - protected override void Dispose(bool isDisposing) - { - if (isDisposing) - { - if (finalizeArchiveOnClose) { - OutputStream.Write(stackalloc byte[1024]); + destination = new BZip2Stream(destination, CompressionMode.Compress, false); } - switch (OutputStream) + break; + case CompressionType.GZip: + { - case BZip2Stream b: - { - b.Finish(); - break; - } - case LZipStream l: - { - l.Finish(); - break; - } + destination = new GZipStream(destination, CompressionMode.Compress); + } + break; + case CompressionType.LZip: + + { + destination = new LZipStream(destination, CompressionMode.Compress); + } + break; + default: + { + throw new InvalidFormatException( + "Tar does not support compression: " + options.CompressionType + ); + } + } + InitalizeStream(destination); + } + + public override void Write(string filename, Stream source, DateTime? modificationTime) => + Write(filename, source, modificationTime, null); + + private string NormalizeFilename(string filename) + { + filename = filename.Replace('\\', '/'); + + var pos = filename.IndexOf(':'); + if (pos >= 0) + { + filename = filename.Remove(0, pos + 1); + } + + return filename.Trim('/'); + } + + public void Write(string filename, Stream source, DateTime? modificationTime, long? size) + { + if (!source.CanSeek && size is null) + { + throw new ArgumentException("Seekable stream is required if no size is given."); + } + + var realSize = size ?? source.Length; + + var header = new TarHeader(WriterOptions.ArchiveEncoding); + + header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH; + header.Name = NormalizeFilename(filename); + header.Size = realSize; + header.Write(OutputStream); + + size = source.TransferTo(OutputStream); + PadTo512(size.Value); + } + + private void PadTo512(long size) + { + var zeros = unchecked((int)(((size + 511L) & ~511L) - size)); + + OutputStream.Write(stackalloc byte[zeros]); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (finalizeArchiveOnClose) + { + OutputStream.Write(stackalloc byte[1024]); + } + switch (OutputStream) + { + case BZip2Stream b: + { + b.Finish(); + break; + } + case LZipStream l: + { + l.Finish(); + break; } } - base.Dispose(isDisposing); } + base.Dispose(isDisposing); } } diff --git a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs index 752d95cb..ca51ebb6 100755 --- a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs +++ b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs @@ -1,23 +1,17 @@ using SharpCompress.Common; -namespace SharpCompress.Writers.Tar +namespace SharpCompress.Writers.Tar; + +public class TarWriterOptions : WriterOptions { - public class TarWriterOptions : WriterOptions - { - /// - /// Indicates if archive should be finalized (by 2 empty blocks) on close. - /// - public bool FinalizeArchiveOnClose { get; } + /// + /// Indicates if archive should be finalized (by 2 empty blocks) on close. + /// + public bool FinalizeArchiveOnClose { get; } - public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose) - : base(compressionType) - { - FinalizeArchiveOnClose = finalizeArchiveOnClose; - } + public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose) + : base(compressionType) => FinalizeArchiveOnClose = finalizeArchiveOnClose; - internal TarWriterOptions(WriterOptions options) : this(options.CompressionType, true) - { - ArchiveEncoding = options.ArchiveEncoding; - } - } -} \ No newline at end of file + internal TarWriterOptions(WriterOptions options) : this(options.CompressionType, true) => + ArchiveEncoding = options.ArchiveEncoding; +} diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index 60aa613c..fcbf2d68 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -4,21 +4,21 @@ using System.Linq; using SharpCompress.Common; -namespace SharpCompress.Writers +namespace SharpCompress.Writers; + +public static class WriterFactory { - public static class WriterFactory + public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions writerOptions) { - public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions writerOptions) + var factory = Factories.Factory.Factories + .OfType() + .FirstOrDefault(item => item.KnownArchiveType == archiveType); + + if (factory != null) { - var factory = Factories.Factory - .Factories - .OfType() - .FirstOrDefault(item => item.KnownArchiveType == archiveType); - - if (factory != null) - return factory.Open(stream, writerOptions); - - throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); + return factory.Open(stream, writerOptions); } + + throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); } } diff --git a/src/SharpCompress/Writers/WriterOptions.cs b/src/SharpCompress/Writers/WriterOptions.cs index db1e2ffa..9d323c2b 100644 --- a/src/SharpCompress/Writers/WriterOptions.cs +++ b/src/SharpCompress/Writers/WriterOptions.cs @@ -1,18 +1,13 @@ using SharpCompress.Common; -namespace SharpCompress.Writers -{ - public class WriterOptions : OptionsBase - { - public WriterOptions(CompressionType compressionType) - { - CompressionType = compressionType; - } - public CompressionType CompressionType { get; set; } +namespace SharpCompress.Writers; - public static implicit operator WriterOptions(CompressionType compressionType) - { - return new WriterOptions(compressionType); - } - } -} \ No newline at end of file +public class WriterOptions : OptionsBase +{ + public WriterOptions(CompressionType compressionType) => CompressionType = compressionType; + + public CompressionType CompressionType { get; set; } + + public static implicit operator WriterOptions(CompressionType compressionType) => + new WriterOptions(compressionType); +} diff --git a/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs b/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs index d4609ef6..111c7239 100644 --- a/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs +++ b/src/SharpCompress/Writers/Zip/ZipCentralDirectoryEntry.cs @@ -6,145 +6,169 @@ using SharpCompress.Common; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; -namespace SharpCompress.Writers.Zip +namespace SharpCompress.Writers.Zip; + +internal class ZipCentralDirectoryEntry { - internal class ZipCentralDirectoryEntry + private readonly ZipCompressionMethod compression; + private readonly string fileName; + private readonly ArchiveEncoding archiveEncoding; + + public ZipCentralDirectoryEntry( + ZipCompressionMethod compression, + string fileName, + ulong headerOffset, + ArchiveEncoding archiveEncoding + ) { - private readonly ZipCompressionMethod compression; - private readonly string fileName; - private readonly ArchiveEncoding archiveEncoding; + this.compression = compression; + this.fileName = fileName; + HeaderOffset = headerOffset; + this.archiveEncoding = archiveEncoding; + } - public ZipCentralDirectoryEntry(ZipCompressionMethod compression, string fileName, ulong headerOffset, ArchiveEncoding archiveEncoding) + internal DateTime? ModificationTime { get; set; } + internal string? Comment { get; set; } + internal uint Crc { get; set; } + internal ulong Compressed { get; set; } + internal ulong Decompressed { get; set; } + internal ushort Zip64HeaderOffset { get; set; } + internal ulong HeaderOffset { get; } + + internal uint Write(Stream outputStream) + { + var encodedFilename = archiveEncoding.Encode(fileName); + var encodedComment = archiveEncoding.Encode(Comment ?? string.Empty); + + var zip64_stream = Compressed >= uint.MaxValue || Decompressed >= uint.MaxValue; + var zip64 = zip64_stream || HeaderOffset >= uint.MaxValue; + var usedCompression = compression; + + var compressedvalue = zip64 ? uint.MaxValue : (uint)Compressed; + var decompressedvalue = zip64 ? uint.MaxValue : (uint)Decompressed; + var headeroffsetvalue = zip64 ? uint.MaxValue : (uint)HeaderOffset; + var extralength = zip64 ? (2 + 2 + 8 + 8 + 8 + 4) : 0; + var version = (byte)(zip64 ? 45 : 20); // Version 20 required for deflate/encryption + + var flags = Equals(archiveEncoding.GetEncoding(), Encoding.UTF8) + ? HeaderFlags.Efs + : HeaderFlags.None; + if (!outputStream.CanSeek) { - this.compression = compression; - this.fileName = fileName; - HeaderOffset = headerOffset; - this.archiveEncoding = archiveEncoding; + // Cannot use data descriptors with zip64: + // https://blogs.oracle.com/xuemingshen/entry/is_zipinput_outputstream_handling_of + + // We check that streams are not written too large in the ZipWritingStream, + // so this extra guard is not required, but kept to simplify changing the code + // once the zip64 post-data issue is resolved + if (!zip64_stream) + { + flags |= HeaderFlags.UsePostDataDescriptor; + } + + if (usedCompression == ZipCompressionMethod.LZMA) + { + flags |= HeaderFlags.Bit1; // eos marker + } } - internal DateTime? ModificationTime { get; set; } - internal string? Comment { get; set; } - internal uint Crc { get; set; } - internal ulong Compressed { get; set; } - internal ulong Decompressed { get; set; } - internal ushort Zip64HeaderOffset { get; set; } - internal ulong HeaderOffset { get; } - - internal uint Write(Stream outputStream) + // Support for zero byte files + if (Decompressed == 0 && Compressed == 0) { - byte[] encodedFilename = archiveEncoding.Encode(fileName); - byte[] encodedComment = archiveEncoding.Encode(Comment ?? string.Empty); + usedCompression = ZipCompressionMethod.None; + } - var zip64_stream = Compressed >= uint.MaxValue || Decompressed >= uint.MaxValue; - var zip64 = zip64_stream || HeaderOffset >= uint.MaxValue; - var usedCompression = compression; + Span intBuf = stackalloc byte[] { 80, 75, 1, 2, version, 0, version, 0 }; + //constant sig, then version made by, then version to extract + outputStream.Write(intBuf); - var compressedvalue = zip64 ? uint.MaxValue : (uint)Compressed; - var decompressedvalue = zip64 ? uint.MaxValue : (uint)Decompressed; - var headeroffsetvalue = zip64 ? uint.MaxValue : (uint)HeaderOffset; - var extralength = zip64 ? (2 + 2 + 8 + 8 + 8 + 4) : 0; - var version = (byte)(zip64 ? 45 : 20); // Version 20 required for deflate/encryption + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); + outputStream.Write(intBuf.Slice(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)usedCompression); + outputStream.Write(intBuf.Slice(0, 2)); // zipping method + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ModificationTime.DateTimeToDosTime()); + outputStream.Write(intBuf.Slice(0, 4)); - HeaderFlags flags = Equals(archiveEncoding.GetEncoding(), Encoding.UTF8) ? HeaderFlags.Efs : HeaderFlags.None; - if (!outputStream.CanSeek) - { - // Cannot use data descriptors with zip64: - // https://blogs.oracle.com/xuemingshen/entry/is_zipinput_outputstream_handling_of + // zipping date and time + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, Crc); + outputStream.Write(intBuf.Slice(0, 4)); // file CRC + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressedvalue); + outputStream.Write(intBuf.Slice(0, 4)); // compressed file size + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, decompressedvalue); + outputStream.Write(intBuf.Slice(0, 4)); // uncompressed file size + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); + outputStream.Write(intBuf.Slice(0, 2)); // Filename in zip + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); + outputStream.Write(intBuf.Slice(0, 2)); // extra length + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); + outputStream.Write(intBuf.Slice(0, 2)); - // We check that streams are not written too large in the ZipWritingStream, - // so this extra guard is not required, but kept to simplify changing the code - // once the zip64 post-data issue is resolved - if (!zip64_stream) - { - flags |= HeaderFlags.UsePostDataDescriptor; - } + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); + outputStream.Write(intBuf.Slice(0, 2)); // disk=0 - if (usedCompression == ZipCompressionMethod.LZMA) - { - flags |= HeaderFlags.Bit1; // eos marker - } - } + // Internal file attributes: + // Bit 0: apparent ASCII/ text file + // Bit 1: reserved + // Bit 2: control field records precede logical records + // Bits 3 - 16: unused + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); + outputStream.Write(intBuf.Slice(0, 2)); // file type: binary, Internal file attributes - // Support for zero byte files - if (Decompressed == 0 && Compressed == 0) - { - usedCompression = ZipCompressionMethod.None; - } + // External flags are host-dependent, this might match DOS + // Bit 0: Read-Only + // Bit 1: Hidden + // Bit 2: System + // Bit 3: Label + // Bit 4: Directory + // Bit 5: Archive + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); + outputStream.Write(intBuf.Slice(0, 2)); // External file attributes + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x8100); + outputStream.Write(intBuf.Slice(0, 2)); - Span intBuf = stackalloc byte[] { 80, 75, 1, 2, version, 0, version, 0 }; - //constant sig, then version made by, then version to extract + // External file attributes (normal/readable) + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, headeroffsetvalue); + outputStream.Write(intBuf.Slice(0, 4)); // Offset of header + + outputStream.Write(encodedFilename, 0, encodedFilename.Length); + if (zip64) + { + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); + outputStream.Write(intBuf.Slice(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)(extralength - 4)); + outputStream.Write(intBuf.Slice(0, 2)); + + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Decompressed); outputStream.Write(intBuf); - - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - outputStream.Write(intBuf.Slice(0, 2)); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)usedCompression); - outputStream.Write(intBuf.Slice(0, 2)); // zipping method - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ModificationTime.DateTimeToDosTime()); - outputStream.Write(intBuf.Slice(0, 4)); - - // zipping date and time - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, Crc); - outputStream.Write(intBuf.Slice(0, 4)); // file CRC - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressedvalue); - outputStream.Write(intBuf.Slice(0, 4)); // compressed file size - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, decompressedvalue); - outputStream.Write(intBuf.Slice(0, 4)); // uncompressed file size - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); - outputStream.Write(intBuf.Slice(0, 2)); // Filename in zip - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); - outputStream.Write(intBuf.Slice(0, 2)); // extra length - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); - outputStream.Write(intBuf.Slice(0, 2)); - - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); - outputStream.Write(intBuf.Slice(0, 2)); // disk=0 - - // Internal file attributes: - // Bit 0: apparent ASCII/ text file - // Bit 1: reserved - // Bit 2: control field records precede logical records - // Bits 3 - 16: unused - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); - outputStream.Write(intBuf.Slice(0, 2)); // file type: binary, Internal file attributes - - // External flags are host-dependent, this might match DOS - // Bit 0: Read-Only - // Bit 1: Hidden - // Bit 2: System - // Bit 3: Label - // Bit 4: Directory - // Bit 5: Archive - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0); - outputStream.Write(intBuf.Slice(0, 2)); // External file attributes - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x8100); - outputStream.Write(intBuf.Slice(0, 2)); - - // External file attributes (normal/readable) - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, headeroffsetvalue); - outputStream.Write(intBuf.Slice(0, 4)); // Offset of header - - outputStream.Write(encodedFilename, 0, encodedFilename.Length); - if (zip64) - { - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); - outputStream.Write(intBuf.Slice(0, 2)); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)(extralength - 4)); - outputStream.Write(intBuf.Slice(0, 2)); - - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Decompressed); - outputStream.Write(intBuf); - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Compressed); - outputStream.Write(intBuf); - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, HeaderOffset); - outputStream.Write(intBuf); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - outputStream.Write(intBuf.Slice(0, 4)); // VolumeNumber = 0 - } - - outputStream.Write(encodedComment, 0, encodedComment.Length); - - return (uint)(8 + 2 + 2 + 4 + 4 + 4 + 4 + 2 + 2 + 2 - + 2 + 2 + 2 + 2 + 4 + encodedFilename.Length + extralength + encodedComment.Length); + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, Compressed); + outputStream.Write(intBuf); + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, HeaderOffset); + outputStream.Write(intBuf); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); + outputStream.Write(intBuf.Slice(0, 4)); // VolumeNumber = 0 } + + outputStream.Write(encodedComment, 0, encodedComment.Length); + + return (uint)( + 8 + + 2 + + 2 + + 4 + + 4 + + 4 + + 4 + + 2 + + 2 + + 2 + + 2 + + 2 + + 2 + + 2 + + 4 + + encodedFilename.Length + + extralength + + encodedComment.Length + ); } } diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 7ee74e19..e1fd222c 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -13,527 +13,562 @@ using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.PPMd; using SharpCompress.IO; -namespace SharpCompress.Writers.Zip +namespace SharpCompress.Writers.Zip; + +public class ZipWriter : AbstractWriter { - public class ZipWriter : AbstractWriter + private readonly CompressionType compressionType; + private readonly CompressionLevel compressionLevel; + private readonly List entries = new List(); + private readonly string zipComment; + private long streamPosition; + private PpmdProperties? ppmdProps; + private readonly bool isZip64; + + public ZipWriter(Stream destination, ZipWriterOptions zipWriterOptions) + : base(ArchiveType.Zip, zipWriterOptions) { - private readonly CompressionType compressionType; - private readonly CompressionLevel compressionLevel; - private readonly List entries = new List(); - private readonly string zipComment; - private long streamPosition; - private PpmdProperties? ppmdProps; - private readonly bool isZip64; - - public ZipWriter(Stream destination, ZipWriterOptions zipWriterOptions) - : base(ArchiveType.Zip, zipWriterOptions) + zipComment = zipWriterOptions.ArchiveComment ?? string.Empty; + isZip64 = zipWriterOptions.UseZip64; + if (destination.CanSeek) { - zipComment = zipWriterOptions.ArchiveComment ?? string.Empty; - isZip64 = zipWriterOptions.UseZip64; - if (destination.CanSeek) - { - streamPosition = destination.Position; - } - - compressionType = zipWriterOptions.CompressionType; - compressionLevel = zipWriterOptions.DeflateCompressionLevel; - - if (WriterOptions.LeaveStreamOpen) - { - destination = NonDisposingStream.Create(destination); - } - InitalizeStream(destination); + streamPosition = destination.Position; } - private PpmdProperties PpmdProperties + compressionType = zipWriterOptions.CompressionType; + compressionLevel = zipWriterOptions.DeflateCompressionLevel; + + if (WriterOptions.LeaveStreamOpen) { - get + destination = NonDisposingStream.Create(destination); + } + InitalizeStream(destination); + } + + private PpmdProperties PpmdProperties => ppmdProps ??= new PpmdProperties(); + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + ulong size = 0; + foreach (var entry in entries) { - return ppmdProps ??= new PpmdProperties(); + size += entry.Write(OutputStream); } + WriteEndRecord(size); + } + base.Dispose(isDisposing); + } + + private static ZipCompressionMethod ToZipCompressionMethod(CompressionType compressionType) + { + switch (compressionType) + { + case CompressionType.None: + { + return ZipCompressionMethod.None; + } + case CompressionType.Deflate: + { + return ZipCompressionMethod.Deflate; + } + case CompressionType.BZip2: + { + return ZipCompressionMethod.BZip2; + } + case CompressionType.LZMA: + { + return ZipCompressionMethod.LZMA; + } + case CompressionType.PPMd: + { + return ZipCompressionMethod.PPMd; + } + default: + throw new InvalidFormatException("Invalid compression method: " + compressionType); + } + } + + public override void Write(string entryPath, Stream source, DateTime? modificationTime) => + Write( + entryPath, + source, + new ZipWriterEntryOptions() { ModificationDateTime = modificationTime } + ); + + public void Write(string entryPath, Stream source, ZipWriterEntryOptions zipWriterEntryOptions) + { + using var output = WriteToStream(entryPath, zipWriterEntryOptions); + source.TransferTo(output); + } + + public Stream WriteToStream(string entryPath, ZipWriterEntryOptions options) + { + var compression = ToZipCompressionMethod(options.CompressionType ?? compressionType); + + entryPath = NormalizeFilename(entryPath); + options.ModificationDateTime ??= DateTime.Now; + options.EntryComment ??= string.Empty; + var entry = new ZipCentralDirectoryEntry( + compression, + entryPath, + (ulong)streamPosition, + WriterOptions.ArchiveEncoding + ) + { + Comment = options.EntryComment, + ModificationTime = options.ModificationDateTime + }; + + // Use the archive default setting for zip64 and allow overrides + var useZip64 = isZip64; + if (options.EnableZip64.HasValue) + { + useZip64 = options.EnableZip64.Value; } - protected override void Dispose(bool isDisposing) + var headersize = (uint)WriteHeader(entryPath, options, entry, useZip64); + streamPosition += headersize; + return new ZipWritingStream( + this, + OutputStream, + entry, + compression, + options.DeflateCompressionLevel ?? compressionLevel + ); + } + + private string NormalizeFilename(string filename) + { + filename = filename.Replace('\\', '/'); + + var pos = filename.IndexOf(':'); + if (pos >= 0) { - if (isDisposing) - { - ulong size = 0; - foreach (ZipCentralDirectoryEntry entry in entries) - { - size += entry.Write(OutputStream); - } - WriteEndRecord(size); - } - base.Dispose(isDisposing); + filename = filename.Remove(0, pos + 1); } - private static ZipCompressionMethod ToZipCompressionMethod(CompressionType compressionType) + return filename.Trim('/'); + } + + private int WriteHeader( + string filename, + ZipWriterEntryOptions zipWriterEntryOptions, + ZipCentralDirectoryEntry entry, + bool useZip64 + ) + { + // We err on the side of caution until the zip specification clarifies how to support this + if (!OutputStream.CanSeek && useZip64) { - switch (compressionType) - { - case CompressionType.None: - { - return ZipCompressionMethod.None; - } - case CompressionType.Deflate: - { - return ZipCompressionMethod.Deflate; - } - case CompressionType.BZip2: - { - return ZipCompressionMethod.BZip2; - } - case CompressionType.LZMA: - { - return ZipCompressionMethod.LZMA; - } - case CompressionType.PPMd: - { - return ZipCompressionMethod.PPMd; - } - default: - throw new InvalidFormatException("Invalid compression method: " + compressionType); - } + throw new NotSupportedException( + "Zip64 extensions are not supported on non-seekable streams" + ); } - public override void Write(string entryPath, Stream source, DateTime? modificationTime) + var explicitZipCompressionInfo = ToZipCompressionMethod( + zipWriterEntryOptions.CompressionType ?? compressionType + ); + var encodedFilename = WriterOptions.ArchiveEncoding.Encode(filename); + + Span intBuf = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ZipHeaderFactory.ENTRY_HEADER_BYTES); + OutputStream.Write(intBuf); + if (explicitZipCompressionInfo == ZipCompressionMethod.Deflate) { - Write(entryPath, source, new ZipWriterEntryOptions() + if (OutputStream.CanSeek && useZip64) { - ModificationDateTime = modificationTime - }); - } - - public void Write(string entryPath, Stream source, ZipWriterEntryOptions zipWriterEntryOptions) - { - using (Stream output = WriteToStream(entryPath, zipWriterEntryOptions)) - { - source.TransferTo(output); - } - } - - public Stream WriteToStream(string entryPath, ZipWriterEntryOptions options) - { - var compression = ToZipCompressionMethod(options.CompressionType ?? compressionType); - - entryPath = NormalizeFilename(entryPath); - options.ModificationDateTime ??= DateTime.Now; - options.EntryComment ??= string.Empty; - var entry = new ZipCentralDirectoryEntry(compression, entryPath, (ulong)streamPosition, WriterOptions.ArchiveEncoding) - { - Comment = options.EntryComment, - ModificationTime = options.ModificationDateTime - }; - - // Use the archive default setting for zip64 and allow overrides - var useZip64 = isZip64; - if (options.EnableZip64.HasValue) - { - useZip64 = options.EnableZip64.Value; - } - - var headersize = (uint)WriteHeader(entryPath, options, entry, useZip64); - streamPosition += headersize; - return new ZipWritingStream(this, OutputStream, entry, compression, - options.DeflateCompressionLevel ?? compressionLevel); - } - - private string NormalizeFilename(string filename) - { - filename = filename.Replace('\\', '/'); - - int pos = filename.IndexOf(':'); - if (pos >= 0) - { - filename = filename.Remove(0, pos + 1); - } - - return filename.Trim('/'); - } - - private int WriteHeader(string filename, ZipWriterEntryOptions zipWriterEntryOptions, ZipCentralDirectoryEntry entry, bool useZip64) - { - // We err on the side of caution until the zip specification clarifies how to support this - if (!OutputStream.CanSeek && useZip64) - { - throw new NotSupportedException("Zip64 extensions are not supported on non-seekable streams"); - } - - var explicitZipCompressionInfo = ToZipCompressionMethod(zipWriterEntryOptions.CompressionType ?? compressionType); - byte[] encodedFilename = WriterOptions.ArchiveEncoding.Encode(filename); - - Span intBuf = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ZipHeaderFactory.ENTRY_HEADER_BYTES); - OutputStream.Write(intBuf); - if (explicitZipCompressionInfo == ZipCompressionMethod.Deflate) - { - if (OutputStream.CanSeek && useZip64) - { - OutputStream.Write(stackalloc byte[] { 45, 0 }); //smallest allowed version for zip64 - } - else - { - OutputStream.Write(stackalloc byte[] { 20, 0 }); //older version which is more compatible - } + OutputStream.Write(stackalloc byte[] { 45, 0 }); //smallest allowed version for zip64 } else { - OutputStream.Write(stackalloc byte[] { 63, 0 }); //version says we used PPMd or LZMA + OutputStream.Write(stackalloc byte[] { 20, 0 }); //older version which is more compatible } - HeaderFlags flags = Equals(WriterOptions.ArchiveEncoding.GetEncoding(), Encoding.UTF8) ? HeaderFlags.Efs : 0; - if (!OutputStream.CanSeek) - { - flags |= HeaderFlags.UsePostDataDescriptor; + } + else + { + OutputStream.Write(stackalloc byte[] { 63, 0 }); //version says we used PPMd or LZMA + } + var flags = Equals(WriterOptions.ArchiveEncoding.GetEncoding(), Encoding.UTF8) + ? HeaderFlags.Efs + : 0; + if (!OutputStream.CanSeek) + { + flags |= HeaderFlags.UsePostDataDescriptor; - if (explicitZipCompressionInfo == ZipCompressionMethod.LZMA) + if (explicitZipCompressionInfo == ZipCompressionMethod.LZMA) + { + flags |= HeaderFlags.Bit1; // eos marker + } + } + + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); + OutputStream.Write(intBuf.Slice(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)explicitZipCompressionInfo); + OutputStream.Write(intBuf.Slice(0, 2)); // zipping method + BinaryPrimitives.WriteUInt32LittleEndian( + intBuf, + zipWriterEntryOptions.ModificationDateTime.DateTimeToDosTime() + ); + OutputStream.Write(intBuf); + + // zipping date and time + OutputStream.Write(stackalloc byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); + + // unused CRC, un/compressed size, updated later + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); + OutputStream.Write(intBuf.Slice(0, 2)); // filename length + + var extralength = 0; + if (OutputStream.CanSeek && useZip64) + { + extralength = 2 + 2 + 8 + 8; + } + + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); + OutputStream.Write(intBuf.Slice(0, 2)); // extra length + OutputStream.Write(encodedFilename, 0, encodedFilename.Length); + + if (extralength != 0) + { + OutputStream.Write(new byte[extralength], 0, extralength); // reserve space for zip64 data + entry.Zip64HeaderOffset = (ushort)(6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length); + } + + return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length + extralength; + } + + private void WriteFooter(uint crc, uint compressed, uint uncompressed) + { + Span intBuf = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc); + OutputStream.Write(intBuf); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressed); + OutputStream.Write(intBuf); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, uncompressed); + OutputStream.Write(intBuf); + } + + private void WriteEndRecord(ulong size) + { + var zip64EndOfCentralDirectoryNeeded = + entries.Count > ushort.MaxValue + || streamPosition >= uint.MaxValue + || size >= uint.MaxValue; + + var sizevalue = size >= uint.MaxValue ? uint.MaxValue : (uint)size; + var streampositionvalue = + streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streamPosition; + + Span intBuf = stackalloc byte[8]; + if (zip64EndOfCentralDirectoryNeeded) + { + var recordlen = 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8; + + // Write zip64 end of central directory record + OutputStream.Write(stackalloc byte[] { 80, 75, 6, 6 }); + + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)recordlen); + OutputStream.Write(intBuf); // Size of zip64 end of central directory record + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); + OutputStream.Write(intBuf.Slice(0, 2)); // Made by + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); + OutputStream.Write(intBuf.Slice(0, 2)); // Version needed + + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); + OutputStream.Write(intBuf.Slice(0, 4)); // Disk number + OutputStream.Write(intBuf.Slice(0, 4)); // Central dir disk + + // TODO: entries.Count is int, so max 2^31 files + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)entries.Count); + OutputStream.Write(intBuf); // Entries in this disk + OutputStream.Write(intBuf); // Total entries + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, size); + OutputStream.Write(intBuf); // Central Directory size + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition); + OutputStream.Write(intBuf); // Disk offset + + // Write zip64 end of central directory locator + OutputStream.Write(stackalloc byte[] { 80, 75, 6, 7 }); + + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); + OutputStream.Write(intBuf.Slice(0, 4)); // Entry disk + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition + size); + OutputStream.Write(intBuf); // Offset to the zip64 central directory + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 1); + OutputStream.Write(intBuf.Slice(0, 4)); // Number of disks + + streamPosition += 4 + 8 + recordlen + (4 + 4 + 8 + 4); + } + + // Write normal end of central directory record + OutputStream.Write(stackalloc byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)entries.Count); + OutputStream.Write(intBuf.Slice(0, 2)); + OutputStream.Write(intBuf.Slice(0, 2)); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, sizevalue); + OutputStream.Write(intBuf.Slice(0, 4)); + BinaryPrimitives.WriteUInt32LittleEndian(intBuf, streampositionvalue); + OutputStream.Write(intBuf.Slice(0, 4)); + var encodedComment = WriterOptions.ArchiveEncoding.Encode(zipComment); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); + OutputStream.Write(intBuf.Slice(0, 2)); + OutputStream.Write(encodedComment, 0, encodedComment.Length); + } + + #region Nested type: ZipWritingStream + + internal class ZipWritingStream : Stream + { + private readonly CRC32 crc = new CRC32(); + private readonly ZipCentralDirectoryEntry entry; + private readonly Stream originalStream; + private readonly Stream writeStream; + private readonly ZipWriter writer; + private readonly ZipCompressionMethod zipCompressionMethod; + private readonly CompressionLevel compressionLevel; + private CountingWritableSubStream? counting; + private ulong decompressed; + + // Flag to prevent throwing exceptions on Dispose + private bool limitsExceeded; + private bool isDisposed; + + internal ZipWritingStream( + ZipWriter writer, + Stream originalStream, + ZipCentralDirectoryEntry entry, + ZipCompressionMethod zipCompressionMethod, + CompressionLevel compressionLevel + ) + { + this.writer = writer; + this.originalStream = originalStream; + this.writer = writer; + this.entry = entry; + this.zipCompressionMethod = zipCompressionMethod; + this.compressionLevel = compressionLevel; + writeStream = GetWriteStream(originalStream); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + private Stream GetWriteStream(Stream writeStream) + { + counting = new CountingWritableSubStream(writeStream); + Stream output = counting; + switch (zipCompressionMethod) + { + case ZipCompressionMethod.None: { - flags |= HeaderFlags.Bit1; // eos marker + return output; + } + case ZipCompressionMethod.Deflate: + { + return new DeflateStream(counting, CompressionMode.Compress, compressionLevel); + } + case ZipCompressionMethod.BZip2: + { + return new BZip2Stream(counting, CompressionMode.Compress, false); + } + case ZipCompressionMethod.LZMA: + { + counting.WriteByte(9); + counting.WriteByte(20); + counting.WriteByte(5); + counting.WriteByte(0); + + var lzmaStream = new LzmaStream( + new LzmaEncoderProperties(!originalStream.CanSeek), + false, + counting + ); + counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length); + return lzmaStream; + } + case ZipCompressionMethod.PPMd: + { + counting.Write(writer.PpmdProperties.Properties, 0, 2); + return new PpmdStream(writer.PpmdProperties, counting, true); + } + default: + { + throw new NotSupportedException("CompressionMethod: " + zipCompressionMethod); } } - - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)flags); - OutputStream.Write(intBuf.Slice(0, 2)); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)explicitZipCompressionInfo); - OutputStream.Write(intBuf.Slice(0, 2)); // zipping method - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, zipWriterEntryOptions.ModificationDateTime.DateTimeToDosTime()); - OutputStream.Write(intBuf); - - // zipping date and time - OutputStream.Write(stackalloc byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); - - // unused CRC, un/compressed size, updated later - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedFilename.Length); - OutputStream.Write(intBuf.Slice(0, 2)); // filename length - - var extralength = 0; - if (OutputStream.CanSeek && useZip64) - { - extralength = 2 + 2 + 8 + 8; - } - - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)extralength); - OutputStream.Write(intBuf.Slice(0, 2)); // extra length - OutputStream.Write(encodedFilename, 0, encodedFilename.Length); - - if (extralength != 0) - { - OutputStream.Write(new byte[extralength], 0, extralength); // reserve space for zip64 data - entry.Zip64HeaderOffset = (ushort)(6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length); - } - - return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length + extralength; } - private void WriteFooter(uint crc, uint compressed, uint uncompressed) + protected override void Dispose(bool disposing) { - Span intBuf = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc); - OutputStream.Write(intBuf); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, compressed); - OutputStream.Write(intBuf); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, uncompressed); - OutputStream.Write(intBuf); - } - - private void WriteEndRecord(ulong size) - { - - var zip64EndOfCentralDirectoryNeeded = entries.Count > ushort.MaxValue || streamPosition >= uint.MaxValue || size >= uint.MaxValue; - - var sizevalue = size >= uint.MaxValue ? uint.MaxValue : (uint)size; - var streampositionvalue = streamPosition >= uint.MaxValue ? uint.MaxValue : (uint)streamPosition; - - Span intBuf = stackalloc byte[8]; - if (zip64EndOfCentralDirectoryNeeded) + if (isDisposed) { - var recordlen = 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8; - - // Write zip64 end of central directory record - OutputStream.Write(stackalloc byte[] { 80, 75, 6, 6 }); - - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)recordlen); - OutputStream.Write(intBuf); // Size of zip64 end of central directory record - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); - OutputStream.Write(intBuf.Slice(0, 2)); // Made by - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 45); - OutputStream.Write(intBuf.Slice(0, 2)); // Version needed - - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf.Slice(0, 4)); // Disk number - OutputStream.Write(intBuf.Slice(0, 4)); // Central dir disk - - // TODO: entries.Count is int, so max 2^31 files - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)entries.Count); - OutputStream.Write(intBuf); // Entries in this disk - OutputStream.Write(intBuf); // Total entries - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, size); - OutputStream.Write(intBuf); // Central Directory size - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition); - OutputStream.Write(intBuf); // Disk offset - - // Write zip64 end of central directory locator - OutputStream.Write(stackalloc byte[] { 80, 75, 6, 7 }); - - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 0); - OutputStream.Write(intBuf.Slice(0, 4)); // Entry disk - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, (ulong)streamPosition + size); - OutputStream.Write(intBuf); // Offset to the zip64 central directory - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, 1); - OutputStream.Write(intBuf.Slice(0, 4)); // Number of disks - - streamPosition += 4 + 8 + recordlen + (4 + 4 + 8 + 4); + return; } - // Write normal end of central directory record - OutputStream.Write(stackalloc byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)entries.Count); - OutputStream.Write(intBuf.Slice(0, 2)); - OutputStream.Write(intBuf.Slice(0, 2)); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, sizevalue); - OutputStream.Write(intBuf.Slice(0, 4)); - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, streampositionvalue); - OutputStream.Write(intBuf.Slice(0, 4)); - byte[] encodedComment = WriterOptions.ArchiveEncoding.Encode(zipComment); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)encodedComment.Length); - OutputStream.Write(intBuf.Slice(0, 2)); - OutputStream.Write(encodedComment, 0, encodedComment.Length); - } + isDisposed = true; - #region Nested type: ZipWritingStream - - internal class ZipWritingStream : Stream - { - private readonly CRC32 crc = new CRC32(); - private readonly ZipCentralDirectoryEntry entry; - private readonly Stream originalStream; - private readonly Stream writeStream; - private readonly ZipWriter writer; - private readonly ZipCompressionMethod zipCompressionMethod; - private readonly CompressionLevel compressionLevel; - private CountingWritableSubStream? counting; - private ulong decompressed; - - // Flag to prevent throwing exceptions on Dispose - private bool limitsExceeded; - private bool isDisposed; - - internal ZipWritingStream(ZipWriter writer, Stream originalStream, ZipCentralDirectoryEntry entry, - ZipCompressionMethod zipCompressionMethod, CompressionLevel compressionLevel) + base.Dispose(disposing); + if (disposing) { - this.writer = writer; - this.originalStream = originalStream; - this.writer = writer; - this.entry = entry; - this.zipCompressionMethod = zipCompressionMethod; - this.compressionLevel = compressionLevel; - writeStream = GetWriteStream(originalStream); - } + writeStream.Dispose(); - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotSupportedException(); - - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - - private Stream GetWriteStream(Stream writeStream) - { - counting = new CountingWritableSubStream(writeStream); - Stream output = counting; - switch (zipCompressionMethod) - { - case ZipCompressionMethod.None: - { - return output; - } - case ZipCompressionMethod.Deflate: - { - return new DeflateStream(counting, CompressionMode.Compress, compressionLevel); - } - case ZipCompressionMethod.BZip2: - { - return new BZip2Stream(counting, CompressionMode.Compress, false); - } - case ZipCompressionMethod.LZMA: - { - counting.WriteByte(9); - counting.WriteByte(20); - counting.WriteByte(5); - counting.WriteByte(0); - - LzmaStream lzmaStream = new LzmaStream(new LzmaEncoderProperties(!originalStream.CanSeek), - false, counting); - counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length); - return lzmaStream; - } - case ZipCompressionMethod.PPMd: - { - counting.Write(writer.PpmdProperties.Properties, 0, 2); - return new PpmdStream(writer.PpmdProperties, counting, true); - } - default: - { - throw new NotSupportedException("CompressionMethod: " + zipCompressionMethod); - } - } - } - - protected override void Dispose(bool disposing) - { - if (isDisposed) + if (limitsExceeded) { + // We have written invalid data into the archive, + // so we destroy it now, instead of allowing the user to continue + // with a defunct archive + originalStream.Dispose(); return; } - isDisposed = true; + entry.Crc = (uint)crc.Crc32Result; + entry.Compressed = counting!.Count; + entry.Decompressed = decompressed; - base.Dispose(disposing); - if (disposing) + var zip64 = + entry.Compressed >= uint.MaxValue || entry.Decompressed >= uint.MaxValue; + var compressedvalue = zip64 ? uint.MaxValue : (uint)counting.Count; + var decompressedvalue = zip64 ? uint.MaxValue : (uint)entry.Decompressed; + + if (originalStream.CanSeek) { - writeStream.Dispose(); + originalStream.Position = (long)(entry.HeaderOffset + 6); + originalStream.WriteByte(0); - if (limitsExceeded) + if (counting.Count == 0 && entry.Decompressed == 0) { - // We have written invalid data into the archive, - // so we destroy it now, instead of allowing the user to continue - // with a defunct archive - originalStream.Dispose(); - return; - } - - entry.Crc = (uint)crc.Crc32Result; - entry.Compressed = counting!.Count; - entry.Decompressed = decompressed; - - var zip64 = entry.Compressed >= uint.MaxValue || entry.Decompressed >= uint.MaxValue; - var compressedvalue = zip64 ? uint.MaxValue : (uint)counting.Count; - var decompressedvalue = zip64 ? uint.MaxValue : (uint)entry.Decompressed; - - if (originalStream.CanSeek) - { - originalStream.Position = (long)(entry.HeaderOffset + 6); + // set compression to STORED for zero byte files (no compression data) + originalStream.Position = (long)(entry.HeaderOffset + 8); + originalStream.WriteByte(0); originalStream.WriteByte(0); - - if (counting.Count == 0 && entry.Decompressed == 0) - { - // set compression to STORED for zero byte files (no compression data) - originalStream.Position = (long)(entry.HeaderOffset + 8); - originalStream.WriteByte(0); - originalStream.WriteByte(0); - } - - originalStream.Position = (long)(entry.HeaderOffset + 14); - - writer.WriteFooter(entry.Crc, compressedvalue, decompressedvalue); - - // Ideally, we should not throw from Dispose() - // We should not get here as the Write call checks the limits - if (zip64 && entry.Zip64HeaderOffset == 0) - { - throw new NotSupportedException("Attempted to write a stream that is larger than 4GiB without setting the zip64 option"); - } - - // If we have pre-allocated space for zip64 data, - // fill it out, even if it is not required - if (entry.Zip64HeaderOffset != 0) - { - originalStream.Position = (long)(entry.HeaderOffset + entry.Zip64HeaderOffset); - Span intBuf = stackalloc byte[8]; - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); - originalStream.Write(intBuf.Slice(0, 2)); - BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 8 + 8); - originalStream.Write(intBuf.Slice(0, 2)); - - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Decompressed); - originalStream.Write(intBuf); - BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Compressed); - originalStream.Write(intBuf); - } - - originalStream.Position = writer.streamPosition + (long)entry.Compressed; - writer.streamPosition += (long)entry.Compressed; } - else + + originalStream.Position = (long)(entry.HeaderOffset + 14); + + writer.WriteFooter(entry.Crc, compressedvalue, decompressedvalue); + + // Ideally, we should not throw from Dispose() + // We should not get here as the Write call checks the limits + if (zip64 && entry.Zip64HeaderOffset == 0) { - // We have a streaming archive, so we should add a post-data-descriptor, - // but we cannot as it does not hold the zip64 values - // Throwing an exception until the zip specification is clarified + throw new NotSupportedException( + "Attempted to write a stream that is larger than 4GiB without setting the zip64 option" + ); + } - // Ideally, we should not throw from Dispose() - // We should not get here as the Write call checks the limits - if (zip64) - { - throw new NotSupportedException("Streams larger than 4GiB are not supported for non-seekable streams"); - } + // If we have pre-allocated space for zip64 data, + // fill it out, even if it is not required + if (entry.Zip64HeaderOffset != 0) + { + originalStream.Position = (long)( + entry.HeaderOffset + entry.Zip64HeaderOffset + ); + Span intBuf = stackalloc byte[8]; + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 0x0001); + originalStream.Write(intBuf.Slice(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(intBuf, 8 + 8); + originalStream.Write(intBuf.Slice(0, 2)); - Span intBuf = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32LittleEndian(intBuf, ZipHeaderFactory.POST_DATA_DESCRIPTOR); + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Decompressed); + originalStream.Write(intBuf); + BinaryPrimitives.WriteUInt64LittleEndian(intBuf, entry.Compressed); originalStream.Write(intBuf); - writer.WriteFooter(entry.Crc, - compressedvalue, - decompressedvalue); - writer.streamPosition += (long)entry.Compressed + 16; } - writer.entries.Add(entry); + + originalStream.Position = writer.streamPosition + (long)entry.Compressed; + writer.streamPosition += (long)entry.Compressed; } - } - - public override void Flush() - { - writeStream.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - // We check the limits first, because we can keep the archive consistent - // if we can prevent the writes from happening - if (entry.Zip64HeaderOffset == 0) + else { - // Pre-check, the counting.Count is not exact, as we do not know the size before having actually compressed it - if (limitsExceeded || ((decompressed + (uint)count) > uint.MaxValue) || (counting!.Count + (uint)count) > uint.MaxValue) - { - throw new NotSupportedException("Attempted to write a stream that is larger than 4GiB without setting the zip64 option"); - } - } + // We have a streaming archive, so we should add a post-data-descriptor, + // but we cannot as it does not hold the zip64 values + // Throwing an exception until the zip specification is clarified - decompressed += (uint)count; - crc.SlurpBlock(buffer, offset, count); - writeStream.Write(buffer, offset, count); - - if (entry.Zip64HeaderOffset == 0) - { - // Post-check, this is accurate - if ((decompressed > uint.MaxValue) || counting!.Count > uint.MaxValue) + // Ideally, we should not throw from Dispose() + // We should not get here as the Write call checks the limits + if (zip64) { - // We have written the data, so the archive is now broken - // Throwing the exception here, allows us to avoid - // throwing an exception in Dispose() which is discouraged - // as it can mask other errors - limitsExceeded = true; - throw new NotSupportedException("Attempted to write a stream that is larger than 4GiB without setting the zip64 option"); + throw new NotSupportedException( + "Streams larger than 4GiB are not supported for non-seekable streams" + ); } + + Span intBuf = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian( + intBuf, + ZipHeaderFactory.POST_DATA_DESCRIPTOR + ); + originalStream.Write(intBuf); + writer.WriteFooter(entry.Crc, compressedvalue, decompressedvalue); + writer.streamPosition += (long)entry.Compressed + 16; } + writer.entries.Add(entry); } } - #endregion Nested type: ZipWritingStream + public override void Flush() => writeStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + // We check the limits first, because we can keep the archive consistent + // if we can prevent the writes from happening + if (entry.Zip64HeaderOffset == 0) + { + // Pre-check, the counting.Count is not exact, as we do not know the size before having actually compressed it + if ( + limitsExceeded + || ((decompressed + (uint)count) > uint.MaxValue) + || (counting!.Count + (uint)count) > uint.MaxValue + ) + { + throw new NotSupportedException( + "Attempted to write a stream that is larger than 4GiB without setting the zip64 option" + ); + } + } + + decompressed += (uint)count; + crc.SlurpBlock(buffer, offset, count); + writeStream.Write(buffer, offset, count); + + if (entry.Zip64HeaderOffset == 0) + { + // Post-check, this is accurate + if ((decompressed > uint.MaxValue) || counting!.Count > uint.MaxValue) + { + // We have written the data, so the archive is now broken + // Throwing the exception here, allows us to avoid + // throwing an exception in Dispose() which is discouraged + // as it can mask other errors + limitsExceeded = true; + throw new NotSupportedException( + "Attempted to write a stream that is larger than 4GiB without setting the zip64 option" + ); + } + } + } } + + #endregion Nested type: ZipWritingStream } diff --git a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs index e1ef77bf..a718f3b5 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs @@ -1,27 +1,27 @@ -using System; +using System; using SharpCompress.Common; using SharpCompress.Compressors.Deflate; -namespace SharpCompress.Writers.Zip +namespace SharpCompress.Writers.Zip; + +public class ZipWriterEntryOptions { - public class ZipWriterEntryOptions - { - public CompressionType? CompressionType { get; set; } - /// - /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default. - /// - public CompressionLevel? DeflateCompressionLevel { get; set; } + public CompressionType? CompressionType { get; set; } - public string? EntryComment { get; set; } + /// + /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default. + /// + public CompressionLevel? DeflateCompressionLevel { get; set; } - public DateTime? ModificationDateTime { get; set; } + public string? EntryComment { get; set; } - /// - /// Allocate an extra 20 bytes for this entry to store, - /// 64 bit length values, thus enabling streams - /// larger than 4GiB. - /// This option is not supported with non-seekable streams. - /// - public bool? EnableZip64 { get; set; } - } -} \ No newline at end of file + public DateTime? ModificationDateTime { get; set; } + + /// + /// Allocate an extra 20 bytes for this entry to store, + /// 64 bit length values, thus enabling streams + /// larger than 4GiB. + /// This option is not supported with non-seekable streams. + /// + public bool? EnableZip64 { get; set; } +} diff --git a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs index 969e2349..fc11ffbf 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs @@ -1,43 +1,38 @@ -using SharpCompress.Common; +using SharpCompress.Common; using SharpCompress.Compressors.Deflate; -namespace SharpCompress.Writers.Zip +namespace SharpCompress.Writers.Zip; + +public class ZipWriterOptions : WriterOptions { - public class ZipWriterOptions : WriterOptions + public ZipWriterOptions(CompressionType compressionType) : base(compressionType) { } + + internal ZipWriterOptions(WriterOptions options) : base(options.CompressionType) { - public ZipWriterOptions(CompressionType compressionType) - : base(compressionType) + LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + + if (options is ZipWriterOptions writerOptions) { + UseZip64 = writerOptions.UseZip64; + DeflateCompressionLevel = writerOptions.DeflateCompressionLevel; + ArchiveComment = writerOptions.ArchiveComment; } - - internal ZipWriterOptions(WriterOptions options) - : base(options.CompressionType) - { - LeaveStreamOpen = options.LeaveStreamOpen; - ArchiveEncoding = options.ArchiveEncoding; - - var writerOptions = options as ZipWriterOptions; - if (writerOptions != null) - { - UseZip64 = writerOptions.UseZip64; - DeflateCompressionLevel = writerOptions.DeflateCompressionLevel; - ArchiveComment = writerOptions.ArchiveComment; - } - } - /// - /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default. - /// - public CompressionLevel DeflateCompressionLevel { get; set; } = CompressionLevel.Default; - - public string? ArchiveComment { get; set; } - - /// - /// Sets a value indicating if zip64 support is enabled. - /// If this is not set, individual stream lengths cannot exceed 4 GiB. - /// This option is not supported for non-seekable streams. - /// Archives larger than 4GiB are supported as long as all streams - /// are less than 4GiB in length. - /// - public bool UseZip64 { get; set; } } -} \ No newline at end of file + + /// + /// When CompressionType.Deflate is used, this property is referenced. Defaults to CompressionLevel.Default. + /// + public CompressionLevel DeflateCompressionLevel { get; set; } = CompressionLevel.Default; + + public string? ArchiveComment { get; set; } + + /// + /// Sets a value indicating if zip64 support is enabled. + /// If this is not set, individual stream lengths cannot exceed 4 GiB. + /// This option is not supported for non-seekable streams. + /// Archives larger than 4GiB are supported as long as all streams + /// are less than 4GiB in length. + /// + public bool UseZip64 { get; set; } +} diff --git a/tests/SharpCompress.Test/ADCTest.cs b/tests/SharpCompress.Test/ADCTest.cs index 26ea2699..11bb2fb0 100644 --- a/tests/SharpCompress.Test/ADCTest.cs +++ b/tests/SharpCompress.Test/ADCTest.cs @@ -30,124 +30,96 @@ using SharpCompress.Compressors.Deflate; using SharpCompress.Crypto; using Xunit; -namespace SharpCompress.Test +namespace SharpCompress.Test; + +public class ADCTest : TestBase { - public class ADCTest : TestBase + [Fact] + public void TestBuffer() { - [Fact] - public void TestBuffer() + using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")); + var decompressed = new byte[decFs.Length]; + decFs.Read(decompressed, 0, decompressed.Length); + + using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")); + var compressed = new byte[cmpFs.Length]; + cmpFs.Read(compressed, 0, compressed.Length); + + ADCBase.Decompress(compressed, out var test); + + Assert.Equal(decompressed, test); + } + + [Fact] + public void TestBaseStream() + { + using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")); + var decompressed = new byte[decFs.Length]; + decFs.Read(decompressed, 0, decompressed.Length); + + using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")); + + ADCBase.Decompress(cmpFs, out var test); + + Assert.Equal(decompressed, test); + } + + [Fact] + public void TestADCStreamWholeChunk() + { + using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")); + var decompressed = new byte[decFs.Length]; + decFs.Read(decompressed, 0, decompressed.Length); + + using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")); + using var decStream = new ADCStream(cmpFs, CompressionMode.Decompress); + var test = new byte[262144]; + + decStream.Read(test, 0, test.Length); + + Assert.Equal(decompressed, test); + } + + [Fact] + public void TestADCStream() + { + using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")); + var decompressed = new byte[decFs.Length]; + decFs.Read(decompressed, 0, decompressed.Length); + + using var cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")); + using var decStream = new ADCStream(cmpFs, CompressionMode.Decompress); + using var decMs = new MemoryStream(); + var test = new byte[512]; + var count = 0; + + do { - using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"))) - { - byte[] decompressed = new byte[decFs.Length]; - decFs.Read(decompressed, 0, decompressed.Length); + count = decStream.Read(test, 0, test.Length); + decMs.Write(test, 0, count); + } while (count > 0); - using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"))) - { - byte[] compressed = new byte[cmpFs.Length]; - cmpFs.Read(compressed, 0, compressed.Length); - byte[] test; + Assert.Equal(decompressed, decMs.ToArray()); + } - ADCBase.Decompress(compressed, out test); + [Fact] + public void TestCrc32Stream() + { + using var decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + var crc32 = new CRC32().GetCrc32(decFs); + decFs.Seek(0, SeekOrigin.Begin); - Assert.Equal(decompressed, test); - } - } - } + var memory = new MemoryStream(); + var crcStream = new Crc32Stream(memory, 0xEDB88320, 0xFFFFFFFF); + decFs.CopyTo(crcStream); - [Fact] - public void TestBaseStream() - { - using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"))) - { - byte[] decompressed = new byte[decFs.Length]; - decFs.Read(decompressed, 0, decompressed.Length); + decFs.Seek(0, SeekOrigin.Begin); - using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"))) - { - byte[] test; + var crc32a = crcStream.Crc; - ADCBase.Decompress(cmpFs, out test); + var crc32b = Crc32Stream.Compute(memory.ToArray()); - Assert.Equal(decompressed, test); - } - } - } - - [Fact] - public void TestADCStreamWholeChunk() - { - using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"))) - { - byte[] decompressed = new byte[decFs.Length]; - decFs.Read(decompressed, 0, decompressed.Length); - - using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"))) - { - using (ADCStream decStream = new ADCStream(cmpFs, CompressionMode.Decompress)) - { - byte[] test = new byte[262144]; - - decStream.Read(test, 0, test.Length); - - Assert.Equal(decompressed, test); - } - } - } - } - - [Fact] - public void TestADCStream() - { - using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin"))) - { - byte[] decompressed = new byte[decFs.Length]; - decFs.Read(decompressed, 0, decompressed.Length); - - using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin"))) - { - using (ADCStream decStream = new ADCStream(cmpFs, CompressionMode.Decompress)) - { - using (MemoryStream decMs = new MemoryStream()) - { - byte[] test = new byte[512]; - int count = 0; - - do - { - count = decStream.Read(test, 0, test.Length); - decMs.Write(test, 0, count); - } - while (count > 0); - - Assert.Equal(decompressed, decMs.ToArray()); - } - } - } - } - } - - [Fact] - public void TestCrc32Stream() - { - using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"))) - { - var crc32 = new CRC32().GetCrc32(decFs); - decFs.Seek(0, SeekOrigin.Begin); - - var memory = new MemoryStream(); - var crcStream = new Crc32Stream(memory, 0xEDB88320, 0xFFFFFFFF); - decFs.CopyTo(crcStream); - - decFs.Seek(0, SeekOrigin.Begin); - - var crc32a = crcStream.Crc; - - var crc32b = Crc32Stream.Compute(memory.ToArray()); - - Assert.Equal(crc32, crc32a); - Assert.Equal(crc32, crc32b); - } - } + Assert.Equal(crc32, crc32a); + Assert.Equal(crc32, crc32b); } } diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 5c103b78..c44271e1 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -8,243 +8,278 @@ using SharpCompress.IO; using SharpCompress.Readers; using Xunit; -namespace SharpCompress.Test +namespace SharpCompress.Test; + +public class ArchiveTests : ReaderTests { - public class ArchiveTests : ReaderTests + protected void ArchiveGetParts(IEnumerable testArchives) { - protected void ArchiveGetParts(IEnumerable testArchives) + var arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray(); + var found = ArchiveFactory.GetFileParts(arcs[0]).ToArray(); + Assert.Equal(arcs.Length, found.Length); + for (var i = 0; i < arcs.Length; i++) { - string[] arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray(); - string[] found = ArchiveFactory.GetFileParts(arcs[0]).ToArray(); - Assert.Equal(arcs.Length, found.Length); - for (int i = 0; i < arcs.Length; i++) - Assert.Equal(arcs[i], found[i]); + Assert.Equal(arcs[i], found[i]); } + } - protected void ArchiveStreamReadExtractAll(string testArchive, CompressionType compression) + protected void ArchiveStreamReadExtractAll(string testArchive, CompressionType compression) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + ArchiveStreamReadExtractAll(new[] { testArchive }, compression); + } + + protected void ArchiveStreamReadExtractAll( + IEnumerable testArchives, + CompressionType compression + ) + { + foreach (var path in testArchives) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - ArchiveStreamReadExtractAll(testArchive.AsEnumerable(), compression); - } - - - protected void ArchiveStreamReadExtractAll(IEnumerable testArchives, CompressionType compression) - { - foreach (var path in testArchives) + using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) { - using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) + try { - try + using var archive = ArchiveFactory.Open(stream); + Assert.True(archive.IsSolid); + using (var reader = archive.ExtractAllEntries()) { - using (var archive = ArchiveFactory.Open(stream)) - { - Assert.True(archive.IsSolid); - using (var reader = archive.ExtractAllEntries()) - { - UseReader(reader, compression); - } - VerifyFiles(); - - if (archive.Entries.First().CompressionType == CompressionType.Rar) - { - stream.ThrowOnDispose = false; - return; - } - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = true - }); - } - stream.ThrowOnDispose = false; - } + UseReader(reader, compression); } - catch (Exception) + VerifyFiles(); + + if (archive.Entries.First().CompressionType == CompressionType.Rar) { - // Otherwise this will hide the original exception. stream.ThrowOnDispose = false; - throw; + return; } - } - VerifyFiles(); - } - } - - protected void ArchiveStreamRead(string testArchive, ReaderOptions readerOptions = null) - { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - ArchiveStreamRead(readerOptions, testArchive.AsEnumerable()); - } - - protected void ArchiveStreamRead(ReaderOptions readerOptions = null, params string[] testArchives) - { - ArchiveStreamRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x))); - } - - protected void ArchiveStreamRead(ReaderOptions readerOptions, IEnumerable testArchives) - { - foreach (var path in testArchives) - { - using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) - using (var archive = ArchiveFactory.Open(stream, readerOptions)) - { - try + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (IndexOutOfRangeException) - { - //SevenZipArchive_BZip2_Split test needs this - stream.ThrowOnDispose = false; - throw; + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); } stream.ThrowOnDispose = false; } - VerifyFiles(); - } - } - - protected void ArchiveStreamMultiRead(ReaderOptions readerOptions = null, params string[] testArchives) - { - ArchiveStreamMultiRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x))); - } - - protected void ArchiveStreamMultiRead(ReaderOptions readerOptions, IEnumerable testArchives) - { - using (var archive = ArchiveFactory.Open(testArchives.Select(a => new FileInfo(a)), readerOptions)) - { - try - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (IndexOutOfRangeException) - { - throw; - } - } - VerifyFiles(); - } - - protected void ArchiveOpenStreamRead(ReaderOptions readerOptions = null, params string[] testArchives) - { - ArchiveOpenStreamRead(readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x))); - } - - protected void ArchiveOpenStreamRead(ReaderOptions readerOptions, IEnumerable testArchives) - { - using (var archive = ArchiveFactory.Open(testArchives.Select(f => new FileInfo(f)), null)) - { - try - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - catch (IndexOutOfRangeException) + catch (Exception) { + // Otherwise this will hide the original exception. + stream.ThrowOnDispose = false; throw; } } VerifyFiles(); } - - protected void ArchiveOpenEntryVolumeIndexTest(int[][] results, ReaderOptions readerOptions = null, params string[] testArchives) - { - ArchiveOpenEntryVolumeIndexTest(results, readerOptions, testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x))); - } - - - protected void ArchiveOpenEntryVolumeIndexTest(int[][] results, ReaderOptions readerOptions, IEnumerable testArchives) - { - string[] src = testArchives.ToArray(); - using (var archive = ArchiveFactory.Open(testArchives.Select(f => new FileInfo(f)), null)) - { - try - { - int idx = 0; - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - Assert.Equal(entry.VolumeIndexFirst, results[idx][0]); - Assert.Equal(entry.VolumeIndexLast, results[idx][1]); - Assert.Equal(src[entry.VolumeIndexFirst], archive.Volumes.First(a => a.Index == entry.VolumeIndexFirst).FileName); - Assert.Equal(src[entry.VolumeIndexLast], archive.Volumes.First(a => a.Index == entry.VolumeIndexLast).FileName); - - idx++; - } - } - catch (IndexOutOfRangeException) - { - throw; - } - } - } - - - - protected void ArchiveFileRead(string testArchive, ReaderOptions readerOptions = null) - { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = ArchiveFactory.Open(testArchive, readerOptions)) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - /// - /// Demonstrate the ExtractionOptions.PreserveFileTime and ExtractionOptions.PreserveAttributes extract options - /// - protected void ArchiveFileReadEx(string testArchive) - { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = ArchiveFactory.Open(testArchive)) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true, - PreserveAttributes = true, - PreserveFileTime = true - }); - } - } - VerifyFilesEx(); - } + } + + protected void ArchiveStreamRead(string testArchive, ReaderOptions? readerOptions = null) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + ArchiveStreamRead(readerOptions, testArchive); + } + + protected void ArchiveStreamRead( + ReaderOptions? readerOptions = null, + params string[] testArchives + ) => + ArchiveStreamRead( + readerOptions, + testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)) + ); + + protected void ArchiveStreamRead(ReaderOptions? readerOptions, IEnumerable testArchives) + { + foreach (var path in testArchives) + { + using (var stream = NonDisposingStream.Create(File.OpenRead(path), true)) + using (var archive = ArchiveFactory.Open(stream, readerOptions)) + { + try + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + catch (IndexOutOfRangeException) + { + //SevenZipArchive_BZip2_Split test needs this + stream.ThrowOnDispose = false; + throw; + } + stream.ThrowOnDispose = false; + } + VerifyFiles(); + } + } + + protected void ArchiveStreamMultiRead( + ReaderOptions? readerOptions = null, + params string[] testArchives + ) => + ArchiveStreamMultiRead( + readerOptions, + testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)) + ); + + protected void ArchiveStreamMultiRead( + ReaderOptions? readerOptions, + IEnumerable testArchives + ) + { + using ( + var archive = ArchiveFactory.Open( + testArchives.Select(a => new FileInfo(a)), + readerOptions + ) + ) + { + try + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + catch (IndexOutOfRangeException) + { + throw; + } + } + VerifyFiles(); + } + + protected void ArchiveOpenStreamRead( + ReaderOptions? readerOptions = null, + params string[] testArchives + ) => + ArchiveOpenStreamRead( + readerOptions, + testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)) + ); + + protected void ArchiveOpenStreamRead( + ReaderOptions? readerOptions, + IEnumerable testArchives + ) + { + using ( + var archive = ArchiveFactory.Open( + testArchives.Select(f => new FileInfo(f)), + readerOptions + ) + ) + { + try + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + catch (IndexOutOfRangeException) + { + throw; + } + } + VerifyFiles(); + } + + protected void ArchiveOpenEntryVolumeIndexTest( + int[][] results, + ReaderOptions? readerOptions = null, + params string[] testArchives + ) => + ArchiveOpenEntryVolumeIndexTest( + results, + readerOptions, + testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)) + ); + + protected void ArchiveOpenEntryVolumeIndexTest( + int[][] results, + ReaderOptions? readerOptions, + IEnumerable testArchives + ) + { + var src = testArchives.ToArray(); + using var archive = ArchiveFactory.Open( + testArchives.Select(f => new FileInfo(f)), + readerOptions + ); + try + { + var idx = 0; + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + Assert.Equal(entry.VolumeIndexFirst, results[idx][0]); + Assert.Equal(entry.VolumeIndexLast, results[idx][1]); + Assert.Equal( + src[entry.VolumeIndexFirst], + archive.Volumes.First(a => a.Index == entry.VolumeIndexFirst).FileName + ); + Assert.Equal( + src[entry.VolumeIndexLast], + archive.Volumes.First(a => a.Index == entry.VolumeIndexLast).FileName + ); + + idx++; + } + } + catch (IndexOutOfRangeException) + { + throw; + } + } + + protected void ArchiveFileRead(string testArchive, ReaderOptions? readerOptions = null) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using (var archive = ArchiveFactory.Open(testArchive, readerOptions)) + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + VerifyFiles(); + } + + /// + /// Demonstrate the ExtractionOptions.PreserveFileTime and ExtractionOptions.PreserveAttributes extract options + /// + protected void ArchiveFileReadEx(string testArchive) + { + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + using (var archive = ArchiveFactory.Open(testArchive)) + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true, + PreserveAttributes = true, + PreserveFileTime = true + } + ); + } + } + VerifyFilesEx(); } } diff --git a/tests/SharpCompress.Test/Filters/BranchExecTests.cs b/tests/SharpCompress.Test/Filters/BranchExecTests.cs index 41dea9fd..5c576dcc 100644 --- a/tests/SharpCompress.Test/Filters/BranchExecTests.cs +++ b/tests/SharpCompress.Test/Filters/BranchExecTests.cs @@ -1,157 +1,1244 @@ /* * BranchExecTests.cs -- Test for converters * - * + * * All data is extracted from busybox, where "ip" is the offset within the busybox file. */ using SharpCompress.Compressors.Filters; using Xunit; -namespace SharpCompress.Test.Filters +namespace SharpCompress.Test.Filters; + +public class BranchExecTests { - public class BranchExecTests + private static byte[] x86resultData { get; } = + new byte[] + { + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x0B, + 0x00, + 0x00, + 0xE8, + 0xBD, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x6D, + 0x01, + 0x00, + 0x00, + 0xF0, + 0xCA, + 0x00, + 0x00, + 0x1C, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0xBC, + 0x09, + 0x00, + 0x00, + 0x14, + 0xC2, + 0x00, + 0x00, + 0xE0, + 0x01, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x98, + 0x0B, + 0x00, + 0x00, + 0x60, + 0x75, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0xF1, + 0xFF, + 0x42, + 0x01, + 0x00, + 0x00, + 0x08, + 0xC8, + 0x00, + 0x00, + 0x1C, + 0x00, + }; + + private static byte[] x86Data { get; } = + new byte[] + { + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x0B, + 0x00, + 0x00, + 0xE8, + 0xCA, + 0x20, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x6D, + 0x01, + 0x00, + 0x00, + 0xF0, + 0xCA, + 0x00, + 0x00, + 0x1C, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0xBC, + 0x09, + 0x00, + 0x00, + 0x14, + 0xC2, + 0x00, + 0x00, + 0xE0, + 0x01, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x98, + 0x0B, + 0x00, + 0x00, + 0x60, + 0x75, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0xF1, + 0xFF, + 0x42, + 0x01, + 0x00, + 0x00, + 0x08, + 0xC8, + 0x00, + 0x00, + 0x1C, + 0x00, + }; + + private static byte[] ppcResultData { get; } = + new byte[] + { + 0xF8, + 0x6B, + 0x2E, + 0x8C, + 0x95, + 0xC5, + 0x4B, + 0x1B, + 0x94, + 0x78, + 0x9E, + 0x7C, + 0xBD, + 0x8B, + 0xA8, + 0xAF, + 0x31, + 0x20, + 0xFE, + 0x0F, + 0xB3, + 0x15, + 0x9A, + 0x7C, + 0xD5, + 0x5C, + 0xC2, + 0xC0, + 0xEC, + 0xE9, + 0x43, + 0x2B, + 0xD0, + 0x9F, + 0x2C, + 0xFC, + 0xB8, + 0x2B, + 0x6B, + 0x15, + 0xCD, + 0x3F, + 0x0C, + 0xAF, + 0x8F, + 0x68, + 0xB0, + 0x6E, + 0x6B, + 0x30, + 0x2E, + 0x8C, + 0x3F, + 0x7E, + 0x96, + 0x7C, + 0x93, + 0xB2, + 0xA4, + 0x0E, + 0x43, + 0xEA, + 0x20, + 0x10, + 0x38, + 0x6D, + 0x37, + 0xF8, + 0x87, + 0xFE, + 0xA9, + 0x63, + 0x75, + 0xF5, + 0x56, + 0x34, + 0x4A, + 0xE3, + 0xCF, + 0x89, + 0x18, + 0x08, + 0xC2, + 0x76, + 0x74, + 0x12, + 0xEC, + 0xA7, + 0x6D, + 0xC2, + 0xB7, + 0x1B, + 0x7A, + 0xB2, + 0xD4, + 0xED + }; + + private static byte[] ppcData { get; } = + new byte[] + { + 0xF8, + 0x6B, + 0x2E, + 0x8C, + 0x95, + 0xC5, + 0x4B, + 0x1B, + 0x94, + 0x78, + 0x9E, + 0x7C, + 0xBD, + 0x8B, + 0xA8, + 0xAF, + 0x31, + 0x20, + 0xFE, + 0x0F, + 0xB3, + 0x15, + 0x9A, + 0x7C, + 0xD5, + 0x5C, + 0xC2, + 0xC0, + 0xEC, + 0xE9, + 0x43, + 0x2B, + 0xD0, + 0x9F, + 0x2C, + 0xFC, + 0xB8, + 0x2B, + 0x6B, + 0x15, + 0xCD, + 0x3F, + 0x0C, + 0xAF, + 0x8F, + 0x68, + 0xB0, + 0x6E, + 0x6B, + 0x30, + 0x2E, + 0x8C, + 0x3F, + 0x7E, + 0x96, + 0x7C, + 0x93, + 0xB2, + 0xA4, + 0x0E, + 0x43, + 0xEA, + 0x20, + 0x10, + 0x38, + 0x6D, + 0x37, + 0xF8, + 0x87, + 0xFE, + 0xA9, + 0x63, + 0x75, + 0xF5, + 0x56, + 0x34, + 0x4A, + 0xE3, + 0xD6, + 0x75, + 0x18, + 0x08, + 0xC2, + 0x76, + 0x74, + 0x12, + 0xEC, + 0xA7, + 0x6D, + 0xC2, + 0xB7, + 0x1B, + 0x7A, + 0xB2, + 0xD4, + 0xED + }; + + private static byte[] armResultData { get; } = + new byte[] + { + 0x7C, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0x42, + 0x01, + 0x00, + 0x80, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0xB8, + 0x00, + 0x00, + 0x84, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0x3A, + 0x00, + 0x00, + 0x04, + 0xE0, + 0x2D, + 0xE5, + 0x04, + 0xD0, + 0x4D, + 0xE2, + 0x0E, + 0x04, + 0x00, + 0xEB, + 0x04, + 0xD0, + 0x8D, + 0xE2, + 0x04, + 0xE0, + 0x9D, + 0xE4, + 0x1E, + 0xFF, + 0x2F, + 0xE1, + 0x04, + 0xE0, + 0x2D, + 0xE5, + 0x04, + 0xE0, + 0x9F, + 0xE5, + 0x0E, + 0xE0, + 0x8F, + 0xE0, + 0x08, + 0xF0, + 0xBE, + 0xE5, + 0xF0, + 0x3A, + 0x0A, + 0x00, + 0x00, + 0xC6, + 0x8F, + 0xE2, + 0xA3, + 0xCA, + 0x8C, + 0xE2, + 0xF0, + 0xFA, + 0xBC, + 0xE5, + 0x00, + 0xC6, + 0x8F, + 0xE2, + 0xA3, + 0xCA, + 0x8C, + 0xE2, + 0xE8, + 0xFA, + 0xBC, + 0xE5, + 0x00, + 0xC6, + 0x8F, + 0xE2 + }; + + private static byte[] armData { get; } = + new byte[] + { + 0x7C, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0x42, + 0x01, + 0x00, + 0x80, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0xB8, + 0x00, + 0x00, + 0x84, + 0xFC, + 0x0A, + 0x00, + 0x16, + 0x3A, + 0x00, + 0x00, + 0x04, + 0xE0, + 0x2D, + 0xE5, + 0x04, + 0xD0, + 0x4D, + 0xE2, + 0x18, + 0x13, + 0x00, + 0xEB, + 0x04, + 0xD0, + 0x8D, + 0xE2, + 0x04, + 0xE0, + 0x9D, + 0xE4, + 0x1E, + 0xFF, + 0x2F, + 0xE1, + 0x04, + 0xE0, + 0x2D, + 0xE5, + 0x04, + 0xE0, + 0x9F, + 0xE5, + 0x0E, + 0xE0, + 0x8F, + 0xE0, + 0x08, + 0xF0, + 0xBE, + 0xE5, + 0xF0, + 0x3A, + 0x0A, + 0x00, + 0x00, + 0xC6, + 0x8F, + 0xE2, + 0xA3, + 0xCA, + 0x8C, + 0xE2, + 0xF0, + 0xFA, + 0xBC, + 0xE5, + 0x00, + 0xC6, + 0x8F, + 0xE2, + 0xA3, + 0xCA, + 0x8C, + 0xE2, + 0xE8, + 0xFA, + 0xBC, + 0xE5, + 0x00, + 0xC6, + 0x8F, + 0xE2 + }; + + private static byte[] armtResultData { get; } = + new byte[] + { + 0x95, + 0x23, + 0xB6, + 0xB1, + 0xBE, + 0x60, + 0x79, + 0xF0, + 0xF6, + 0x01, + 0xD9, + 0x7F, + 0x2E, + 0x03, + 0x31, + 0x1C, + 0xFD, + 0xD3, + 0x40, + 0x0F, + 0x21, + 0x3C, + 0x06, + 0x97, + 0xE5, + 0xC3, + 0x57, + 0x11, + 0x76, + 0x6F, + 0xE3, + 0x70, + 0xED, + 0x49, + 0xCB, + 0xB5, + 0xC9, + 0x42, + 0x59, + 0x10, + 0x2F, + 0xBD, + 0xAE, + 0xB1, + 0x40, + 0x4D, + 0x9D, + 0x7C, + 0xE9, + 0xFC, + 0x48, + 0x3E, + 0xBC, + 0x7F, + 0x0B, + 0x23, + 0xB0, + 0x8A, + 0x4D, + 0x02, + 0x39, + 0xC4, + 0xFB, + 0x66, + 0x83, + 0x7F, + 0xA7, + 0xBD, + 0x12, + 0xAC, + 0xED, + 0x31, + 0x34, + 0x93, + 0x4D, + 0x8D, + 0xD7, + 0x94, + 0x93, + 0x1C, + 0x0A, + 0x50, + 0x54, + 0x4B, + 0x03, + 0x55, + 0x27, + 0xFE, + 0xCE, + 0x29, + 0x66, + 0x52, + 0x81, + 0xAE, + 0x69, + 0xA0, + 0x69, + 0xF2, + 0x3D, + 0xFF, + 0xA1, + 0x8A, + 0x5D, + 0x61, + 0x7D, + 0xC5, + 0x94, + 0x0A, + 0x7D, + 0xED, + 0x11, + 0x0F + }; + + private static byte[] armtData { get; } = + new byte[] + { + 0x95, + 0x23, + 0xB6, + 0xB1, + 0xBE, + 0x60, + 0x79, + 0xF0, + 0xF6, + 0x01, + 0xD9, + 0x7F, + 0x2E, + 0x03, + 0x31, + 0x1C, + 0xFD, + 0xD3, + 0x40, + 0x0F, + 0x21, + 0x3C, + 0x06, + 0x97, + 0xE5, + 0xC3, + 0x57, + 0x11, + 0x76, + 0x6F, + 0xE3, + 0x70, + 0xED, + 0x49, + 0xCB, + 0xB5, + 0xC9, + 0x42, + 0x59, + 0x10, + 0x2F, + 0xBD, + 0xAE, + 0xB1, + 0x40, + 0x4D, + 0x9D, + 0x7C, + 0xE9, + 0xFC, + 0x48, + 0x3E, + 0xBC, + 0x7F, + 0x0B, + 0x23, + 0xB0, + 0x8A, + 0x4D, + 0x02, + 0x39, + 0xC4, + 0xFB, + 0x66, + 0x83, + 0x7F, + 0xA7, + 0xBD, + 0x12, + 0xAC, + 0xED, + 0x31, + 0x34, + 0x93, + 0x4D, + 0x8D, + 0xD7, + 0x94, + 0x93, + 0x1C, + 0x0A, + 0x50, + 0x54, + 0x4B, + 0x03, + 0x55, + 0x27, + 0xFE, + 0xCE, + 0x29, + 0x66, + 0x52, + 0x81, + 0xAE, + 0x69, + 0xA0, + 0x6A, + 0xF2, + 0x6F, + 0xFC, + 0xA1, + 0x8A, + 0x5D, + 0x61, + 0x7D, + 0xC5, + 0x94, + 0x0A, + 0x7D, + 0xED, + 0x11, + 0x0F + }; + + private static byte[] ia64ResultData { get; } = + new byte[] + { + 0x4D, + 0xF8, + 0xF2, + 0x0D, + 0x06, + 0x2F, + 0x74, + 0x0F, + 0xF0, + 0x91, + 0x06, + 0x0B, + 0x19, + 0x22, + 0x91, + 0x5A, + 0x66, + 0x56, + 0xA7, + 0x15, + 0x77, + 0x1E, + 0x2F, + 0xA3, + 0xE4, + 0xDE, + 0x93, + 0x1C, + 0xD5, + 0xCE, + 0x6E, + 0x45, + 0x36, + 0x15, + 0x15, + 0x65, + 0x4E, + 0xC5, + 0xA3, + 0x8C, + 0x5A, + 0x8B, + 0x8A, + 0x1C, + 0x12, + 0x5B, + 0x39, + 0x1F, + 0xA0, + 0xF2, + 0x93, + 0x7C, + 0x7F, + 0x5D, + 0xD9, + 0x30, + 0x1F, + 0xF6, + 0x5C, + 0x10, + 0x62, + 0x3E, + 0xB4, + 0x64, + 0x56, + 0x48, + 0xB2, + 0x20, + 0x39, + 0xE8, + 0x44, + 0x10, + 0x87, + 0x9E, + 0x2C, + 0xFC, + 0x29, + 0x0E, + 0x20, + 0x76, + 0xCE, + 0xDA, + 0x93, + 0x1C, + 0xED, + 0x54, + 0x0D, + 0xAF, + 0xEC, + 0xDE, + 0x93, + 0x1C, + 0x2B, + 0x72, + 0xD5, + 0x0D + }; + + private static byte[] ia64Data { get; } = + new byte[] + { + 0x4D, + 0xF8, + 0xF2, + 0x0D, + 0x06, + 0x2F, + 0x74, + 0x0F, + 0xF0, + 0x91, + 0x06, + 0x0B, + 0x19, + 0x22, + 0x91, + 0x5A, + 0x66, + 0x56, + 0xA7, + 0x15, + 0x77, + 0x1E, + 0x2F, + 0xA3, + 0xE4, + 0xDE, + 0x93, + 0x1C, + 0xD5, + 0xCE, + 0x6E, + 0x45, + 0x36, + 0x15, + 0x15, + 0x65, + 0x4E, + 0xC5, + 0xA3, + 0x8C, + 0x5A, + 0x8B, + 0x8A, + 0x1C, + 0x12, + 0x5B, + 0x39, + 0x1F, + 0xA0, + 0xF2, + 0x93, + 0x7C, + 0x7F, + 0x5D, + 0xD9, + 0x30, + 0x1F, + 0xF6, + 0x5C, + 0x10, + 0x62, + 0x3E, + 0xB4, + 0x64, + 0x56, + 0x48, + 0xB2, + 0x20, + 0x39, + 0xE8, + 0x44, + 0x80, + 0x8C, + 0x9E, + 0x2C, + 0xFC, + 0x29, + 0x0E, + 0x20, + 0x76, + 0xCE, + 0xDA, + 0x93, + 0x1C, + 0xED, + 0x54, + 0x0D, + 0xAF, + 0xEC, + 0xDE, + 0x93, + 0x1C, + 0x2B, + 0x72, + 0xD5, + 0x0D + }; + + private static byte[] sparcResultData { get; } = + new byte[] + { + 0x78, + 0x2E, + 0x73, + 0x6F, + 0x2E, + 0x33, + 0x00, + 0x00, + 0x07, + 0x01, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x0B, + 0x00, + 0x00, + 0x00, + 0xA2, + 0x0D, + 0x10, + 0x12, + 0x30, + 0x03, + 0xC9, + 0x37, + 0x40, + 0x1A, + 0x85, + 0xD9, + 0x44, + 0x34, + 0x40, + 0x32, + 0x85, + 0xA0, + 0x30, + 0x40, + 0x00, + 0x70, + 0x00, + 0x40, + 0x84, + 0x80, + 0x04, + 0x00, + 0xE4, + 0xAC, + 0x07, + 0x04, + 0x21, + 0x44, + 0x02, + 0x20, + 0x10, + 0x00, + 0x40, + 0xC2, + 0x89, + 0x98, + 0x85, + 0x00, + 0x58, + 0x6A, + 0x41, + 0x8E, + 0x18, + 0xA1, + 0x91, + 0x00, + 0x10, + 0x00, + }; + + private static byte[] sparcData { get; } = + new byte[] + { + 0x78, + 0x2E, + 0x73, + 0x6F, + 0x2E, + 0x33, + 0x00, + 0x00, + 0x07, + 0x01, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x44, + 0x0B, + 0x00, + 0x00, + 0x00, + 0xA2, + 0x0D, + 0x10, + 0x12, + 0x30, + 0x03, + 0xC9, + 0x37, + 0x40, + 0x1A, + 0x86, + 0x21, + 0x44, + 0x34, + 0x40, + 0x32, + 0x85, + 0xA0, + 0x30, + 0x40, + 0x00, + 0x70, + 0x00, + 0x40, + 0x84, + 0x80, + 0x04, + 0x00, + 0xE4, + 0xAC, + 0x07, + 0x04, + 0x21, + 0x44, + 0x02, + 0x20, + 0x10, + 0x00, + 0x40, + 0xC2, + 0x89, + 0x98, + 0x85, + 0x00, + 0x58, + 0x6A, + 0x41, + 0x8E, + 0x18, + 0xA1, + 0x91, + 0x00, + 0x10, + 0x00, + }; + + private void CompareBuffer(byte[] testBuffer, byte[] targetBuffer) => + Assert.Equal(testBuffer, targetBuffer); + + [Fact] + public void X86ConverterDecodeTest() { - private static byte[] x86resultData { get; } = new byte[] { - 0x12, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x00, 0x00, 0xE8, 0xBD, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x6D, 0x01, 0x00, 0x00, 0xF0, 0xCA, - 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0xBC, 0x09, 0x00, 0x00, 0x14, 0xC2, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x98, 0x0B, 0x00, 0x00, 0x60, 0x75, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xF1, 0xFF, 0x42, 0x01, 0x00, 0x00, 0x08, 0xC8, 0x00, 0x00, 0x1C, 0x00, - }; + uint state = 0; + uint ip = 0x2000; + var testData = x86Data; + BranchExecFilter.X86Converter(testData, ip, ref state); + CompareBuffer(testData, x86resultData); + } - private static byte[] x86Data { get; } = new byte[] { - 0x12, 0x00, 0x00, 0x00, 0x02, 0x0B, 0x00, 0x00, 0xE8, 0xCA, 0x20, 0x00, 0x00, 0x07, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x6D, 0x01, 0x00, 0x00, 0xF0, 0xCA, - 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0xBC, 0x09, 0x00, 0x00, 0x14, 0xC2, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x98, 0x0B, 0x00, 0x00, 0x60, 0x75, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xF1, 0xFF, 0x42, 0x01, 0x00, 0x00, 0x08, 0xC8, 0x00, 0x00, 0x1C, 0x00, - }; + [Fact] + public void PowerPCConverterDecodeTest() + { + uint ip = 0x6A0; + var testData = ppcData; + BranchExecFilter.PowerPCConverter(testData, ip); + CompareBuffer(testData, ppcResultData); + } - private static byte[] ppcResultData { get; } = new byte[] { - 0xF8, 0x6B, 0x2E, 0x8C, 0x95, 0xC5, 0x4B, 0x1B, 0x94, 0x78, 0x9E, 0x7C, 0xBD, 0x8B, 0xA8, 0xAF, 0x31, 0x20, 0xFE, 0x0F, 0xB3, 0x15, 0x9A, 0x7C, 0xD5, 0x5C, - 0xC2, 0xC0, 0xEC, 0xE9, 0x43, 0x2B, 0xD0, 0x9F, 0x2C, 0xFC, 0xB8, 0x2B, 0x6B, 0x15, 0xCD, 0x3F, 0x0C, 0xAF, 0x8F, 0x68, 0xB0, 0x6E, 0x6B, 0x30, 0x2E, 0x8C, - 0x3F, 0x7E, 0x96, 0x7C, 0x93, 0xB2, 0xA4, 0x0E, 0x43, 0xEA, 0x20, 0x10, 0x38, 0x6D, 0x37, 0xF8, 0x87, 0xFE, 0xA9, 0x63, 0x75, 0xF5, 0x56, 0x34, 0x4A, 0xE3, - 0xCF, 0x89, 0x18, 0x08, 0xC2, 0x76, 0x74, 0x12, 0xEC, 0xA7, 0x6D, 0xC2, 0xB7, 0x1B, 0x7A, 0xB2, 0xD4, 0xED - }; + [Fact] + public void ARMConverteDecoderTest() + { + uint ip = 0x3C00; + var testData = armData; + BranchExecFilter.ARMConverter(testData, ip); + CompareBuffer(testData, armResultData); + } - private static byte[] ppcData { get; } = new byte[] { - 0xF8, 0x6B, 0x2E, 0x8C, 0x95, 0xC5, 0x4B, 0x1B, 0x94, 0x78, 0x9E, 0x7C, 0xBD, 0x8B, 0xA8, 0xAF, 0x31, 0x20, 0xFE, 0x0F, 0xB3, 0x15, 0x9A, 0x7C, 0xD5, 0x5C, - 0xC2, 0xC0, 0xEC, 0xE9, 0x43, 0x2B, 0xD0, 0x9F, 0x2C, 0xFC, 0xB8, 0x2B, 0x6B, 0x15, 0xCD, 0x3F, 0x0C, 0xAF, 0x8F, 0x68, 0xB0, 0x6E, 0x6B, 0x30, 0x2E, 0x8C, - 0x3F, 0x7E, 0x96, 0x7C, 0x93, 0xB2, 0xA4, 0x0E, 0x43, 0xEA, 0x20, 0x10, 0x38, 0x6D, 0x37, 0xF8, 0x87, 0xFE, 0xA9, 0x63, 0x75, 0xF5, 0x56, 0x34, 0x4A, 0xE3, - 0xD6, 0x75, 0x18, 0x08, 0xC2, 0x76, 0x74, 0x12, 0xEC, 0xA7, 0x6D, 0xC2, 0xB7, 0x1B, 0x7A, 0xB2, 0xD4, 0xED - }; + [Fact] + public void ARMTConverterDecodeTest() + { + uint ip = 0xA00; + var testData = armtData; + BranchExecFilter.ARMTConverter(testData, ip); + CompareBuffer(testData, armtResultData); + } - private static byte[] armResultData { get; } = new byte[] { - 0x7C, 0xFC, 0x0A, 0x00, 0x16, 0x42, 0x01, 0x00, 0x80, 0xFC, 0x0A, 0x00, 0x16, 0xB8, 0x00, 0x00, 0x84, 0xFC, 0x0A, 0x00, 0x16, 0x3A, 0x00, 0x00, 0x04, 0xE0, - 0x2D, 0xE5, 0x04, 0xD0, 0x4D, 0xE2, 0x0E, 0x04, 0x00, 0xEB, 0x04, 0xD0, 0x8D, 0xE2, 0x04, 0xE0, 0x9D, 0xE4, 0x1E, 0xFF, 0x2F, 0xE1, 0x04, 0xE0, 0x2D, 0xE5, - 0x04, 0xE0, 0x9F, 0xE5, 0x0E, 0xE0, 0x8F, 0xE0, 0x08, 0xF0, 0xBE, 0xE5, 0xF0, 0x3A, 0x0A, 0x00, 0x00, 0xC6, 0x8F, 0xE2, 0xA3, 0xCA, 0x8C, 0xE2, 0xF0, 0xFA, - 0xBC, 0xE5, 0x00, 0xC6, 0x8F, 0xE2, 0xA3, 0xCA, 0x8C, 0xE2, 0xE8, 0xFA, 0xBC, 0xE5, 0x00, 0xC6, 0x8F, 0xE2 - }; + [Fact] + public void IA64ConverterDecodeTest() + { + uint ip = 0xAA0; + var testData = ia64Data; + BranchExecFilter.IA64Converter(testData, ip); + CompareBuffer(testData, ia64ResultData); + } - private static byte[] armData { get; } = new byte[] { - 0x7C, 0xFC, 0x0A, 0x00, 0x16, 0x42, 0x01, 0x00, 0x80, 0xFC, 0x0A, 0x00, 0x16, 0xB8, 0x00, 0x00, 0x84, 0xFC, 0x0A, 0x00, 0x16, 0x3A, 0x00, 0x00, 0x04, 0xE0, - 0x2D, 0xE5, 0x04, 0xD0, 0x4D, 0xE2, 0x18, 0x13, 0x00, 0xEB, 0x04, 0xD0, 0x8D, 0xE2, 0x04, 0xE0, 0x9D, 0xE4, 0x1E, 0xFF, 0x2F, 0xE1, 0x04, 0xE0, 0x2D, 0xE5, - 0x04, 0xE0, 0x9F, 0xE5, 0x0E, 0xE0, 0x8F, 0xE0, 0x08, 0xF0, 0xBE, 0xE5, 0xF0, 0x3A, 0x0A, 0x00, 0x00, 0xC6, 0x8F, 0xE2, 0xA3, 0xCA, 0x8C, 0xE2, 0xF0, 0xFA, - 0xBC, 0xE5, 0x00, 0xC6, 0x8F, 0xE2, 0xA3, 0xCA, 0x8C, 0xE2, 0xE8, 0xFA, 0xBC, 0xE5, 0x00, 0xC6, 0x8F, 0xE2 - }; - - private static byte[] armtResultData { get; } = new byte[] { - 0x95, 0x23, 0xB6, 0xB1, 0xBE, 0x60, 0x79, 0xF0, 0xF6, 0x01, 0xD9, 0x7F, 0x2E, 0x03, 0x31, 0x1C, 0xFD, 0xD3, 0x40, 0x0F, 0x21, 0x3C, 0x06, 0x97, 0xE5, 0xC3, - 0x57, 0x11, 0x76, 0x6F, 0xE3, 0x70, 0xED, 0x49, 0xCB, 0xB5, 0xC9, 0x42, 0x59, 0x10, 0x2F, 0xBD, 0xAE, 0xB1, 0x40, 0x4D, 0x9D, 0x7C, 0xE9, 0xFC, 0x48, 0x3E, - 0xBC, 0x7F, 0x0B, 0x23, 0xB0, 0x8A, 0x4D, 0x02, 0x39, 0xC4, 0xFB, 0x66, 0x83, 0x7F, 0xA7, 0xBD, 0x12, 0xAC, 0xED, 0x31, 0x34, 0x93, 0x4D, 0x8D, 0xD7, 0x94, - 0x93, 0x1C, 0x0A, 0x50, 0x54, 0x4B, 0x03, 0x55, 0x27, 0xFE, 0xCE, 0x29, 0x66, 0x52, 0x81, 0xAE, 0x69, 0xA0, 0x69, 0xF2, 0x3D, 0xFF, 0xA1, 0x8A, 0x5D, 0x61, - 0x7D, 0xC5, 0x94, 0x0A, 0x7D, 0xED, 0x11, 0x0F - }; - - private static byte[] armtData { get; } = new byte[] { - 0x95, 0x23, 0xB6, 0xB1, 0xBE, 0x60, 0x79, 0xF0, 0xF6, 0x01, 0xD9, 0x7F, 0x2E, 0x03, 0x31, 0x1C, 0xFD, 0xD3, 0x40, 0x0F, 0x21, 0x3C, 0x06, 0x97, 0xE5, 0xC3, - 0x57, 0x11, 0x76, 0x6F, 0xE3, 0x70, 0xED, 0x49, 0xCB, 0xB5, 0xC9, 0x42, 0x59, 0x10, 0x2F, 0xBD, 0xAE, 0xB1, 0x40, 0x4D, 0x9D, 0x7C, 0xE9, 0xFC, 0x48, 0x3E, - 0xBC, 0x7F, 0x0B, 0x23, 0xB0, 0x8A, 0x4D, 0x02, 0x39, 0xC4, 0xFB, 0x66, 0x83, 0x7F, 0xA7, 0xBD, 0x12, 0xAC, 0xED, 0x31, 0x34, 0x93, 0x4D, 0x8D, 0xD7, 0x94, - 0x93, 0x1C, 0x0A, 0x50, 0x54, 0x4B, 0x03, 0x55, 0x27, 0xFE, 0xCE, 0x29, 0x66, 0x52, 0x81, 0xAE, 0x69, 0xA0, 0x6A, 0xF2, 0x6F, 0xFC, 0xA1, 0x8A, 0x5D, 0x61, - 0x7D, 0xC5, 0x94, 0x0A, 0x7D, 0xED, 0x11, 0x0F - }; - - private static byte[] ia64ResultData { get; } = new byte[] { - 0x4D, 0xF8, 0xF2, 0x0D, 0x06, 0x2F, 0x74, 0x0F, 0xF0, 0x91, 0x06, 0x0B, 0x19, 0x22, 0x91, 0x5A, 0x66, 0x56, 0xA7, 0x15, 0x77, 0x1E, 0x2F, 0xA3, 0xE4, 0xDE, - 0x93, 0x1C, 0xD5, 0xCE, 0x6E, 0x45, 0x36, 0x15, 0x15, 0x65, 0x4E, 0xC5, 0xA3, 0x8C, 0x5A, 0x8B, 0x8A, 0x1C, 0x12, 0x5B, 0x39, 0x1F, 0xA0, 0xF2, 0x93, 0x7C, - 0x7F, 0x5D, 0xD9, 0x30, 0x1F, 0xF6, 0x5C, 0x10, 0x62, 0x3E, 0xB4, 0x64, 0x56, 0x48, 0xB2, 0x20, 0x39, 0xE8, 0x44, 0x10, 0x87, 0x9E, 0x2C, 0xFC, 0x29, 0x0E, - 0x20, 0x76, 0xCE, 0xDA, 0x93, 0x1C, 0xED, 0x54, 0x0D, 0xAF, 0xEC, 0xDE, 0x93, 0x1C, 0x2B, 0x72, 0xD5, 0x0D - }; - - private static byte[] ia64Data { get; } = new byte[] { - 0x4D, 0xF8, 0xF2, 0x0D, 0x06, 0x2F, 0x74, 0x0F, 0xF0, 0x91, 0x06, 0x0B, 0x19, 0x22, 0x91, 0x5A, 0x66, 0x56, 0xA7, 0x15, 0x77, 0x1E, 0x2F, 0xA3, 0xE4, 0xDE, - 0x93, 0x1C, 0xD5, 0xCE, 0x6E, 0x45, 0x36, 0x15, 0x15, 0x65, 0x4E, 0xC5, 0xA3, 0x8C, 0x5A, 0x8B, 0x8A, 0x1C, 0x12, 0x5B, 0x39, 0x1F, 0xA0, 0xF2, 0x93, 0x7C, - 0x7F, 0x5D, 0xD9, 0x30, 0x1F, 0xF6, 0x5C, 0x10, 0x62, 0x3E, 0xB4, 0x64, 0x56, 0x48, 0xB2, 0x20, 0x39, 0xE8, 0x44, 0x80, 0x8C, 0x9E, 0x2C, 0xFC, 0x29, 0x0E, - 0x20, 0x76, 0xCE, 0xDA, 0x93, 0x1C, 0xED, 0x54, 0x0D, 0xAF, 0xEC, 0xDE, 0x93, 0x1C, 0x2B, 0x72, 0xD5, 0x0D - }; - - private static byte[] sparcResultData { get; } = new byte[] { - 0x78, 0x2E, 0x73, 0x6F, 0x2E, 0x33, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0xA2, 0x0D, - 0x10, 0x12, 0x30, 0x03, 0xC9, 0x37, 0x40, 0x1A, 0x85, 0xD9, 0x44, 0x34, 0x40, 0x32, 0x85, 0xA0, 0x30, 0x40, 0x00, 0x70, 0x00, 0x40, 0x84, 0x80, 0x04, 0x00, - 0xE4, 0xAC, 0x07, 0x04, 0x21, 0x44, 0x02, 0x20, 0x10, 0x00, 0x40, 0xC2, 0x89, 0x98, 0x85, 0x00, 0x58, 0x6A, 0x41, 0x8E, 0x18, 0xA1, 0x91, 0x00, 0x10, 0x00, - }; - - private static byte[] sparcData { get; } = new byte[] { - 0x78, 0x2E, 0x73, 0x6F, 0x2E, 0x33, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x44, 0x0B, 0x00, 0x00, 0x00, 0xA2, 0x0D, - 0x10, 0x12, 0x30, 0x03, 0xC9, 0x37, 0x40, 0x1A, 0x86, 0x21, 0x44, 0x34, 0x40, 0x32, 0x85, 0xA0, 0x30, 0x40, 0x00, 0x70, 0x00, 0x40, 0x84, 0x80, 0x04, 0x00, - 0xE4, 0xAC, 0x07, 0x04, 0x21, 0x44, 0x02, 0x20, 0x10, 0x00, 0x40, 0xC2, 0x89, 0x98, 0x85, 0x00, 0x58, 0x6A, 0x41, 0x8E, 0x18, 0xA1, 0x91, 0x00, 0x10, 0x00, - }; - - private void CompareBuffer(byte[] testBuffer, byte[] targetBuffer) - { - Assert.Equal(testBuffer, targetBuffer); - } - - [Fact] - public void X86ConverterDecodeTest() - { - uint state = 0; - uint ip = 0x2000; - var testData = x86Data; - BranchExecFilter.X86Converter(testData, ip, ref state); - CompareBuffer(testData, x86resultData); - } - - [Fact] - public void PowerPCConverterDecodeTest() - { - uint ip = 0x6A0; - var testData = ppcData; - BranchExecFilter.PowerPCConverter(testData, ip); - CompareBuffer(testData, ppcResultData); - } - - [Fact] - public void ARMConverteDecoderTest() - { - uint ip = 0x3C00; - var testData = armData; - BranchExecFilter.ARMConverter(testData, ip); - CompareBuffer(testData, armResultData); - } - - [Fact] - public void ARMTConverterDecodeTest() - { - uint ip = 0xA00; - var testData = armtData; - BranchExecFilter.ARMTConverter(testData, ip); - CompareBuffer(testData, armtResultData); - } - - [Fact] - public void IA64ConverterDecodeTest() - { - uint ip = 0xAA0; - var testData = ia64Data; - BranchExecFilter.IA64Converter(testData, ip); - CompareBuffer(testData, ia64ResultData); - } - - [Fact] - public void SPARCConverterDecodeTest() - { - uint ip = 0x100; - var testData = sparcData; - BranchExecFilter.SPARCConverter(testData, ip); - CompareBuffer(testData, sparcResultData); - } + [Fact] + public void SPARCConverterDecodeTest() + { + uint ip = 0x100; + var testData = sparcData; + BranchExecFilter.SPARCConverter(testData, ip); + CompareBuffer(testData, sparcResultData); } } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index 15389a4f..e47c339c 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -5,123 +5,113 @@ using SharpCompress.Archives; using SharpCompress.Archives.GZip; using Xunit; -namespace SharpCompress.Test.GZip +namespace SharpCompress.Test.GZip; + +public class GZipArchiveTests : ArchiveTests { - public class GZipArchiveTests : ArchiveTests + public GZipArchiveTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void GZip_Archive_Generic() { - public GZipArchiveTests() + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) + using (var archive = ArchiveFactory.Open(stream)) { - UseExtensionInsteadOfNameToVerify = true; + var entry = archive.Entries.First(); + entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); + + var size = entry.Size; + var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); + var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + + Assert.Equal(size, scratch.Length); + Assert.Equal(size, test.Length); } + CompareArchivesByPath( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), + Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar") + ); + } - [Fact] - public void GZip_Archive_Generic() + [Fact] + public void GZip_Archive() + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) + using (var archive = GZipArchive.Open(stream)) { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = ArchiveFactory.Open(stream)) - { - var entry = archive.Entries.First(); - entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); + var entry = archive.Entries.First(); + entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); - long size = entry.Size; - var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); - var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + var size = entry.Size; + var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); + var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); - Assert.Equal(size, scratch.Length); - Assert.Equal(size, test.Length); - } - CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), - Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + Assert.Equal(size, scratch.Length); + Assert.Equal(size, test.Length); } + CompareArchivesByPath( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), + Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar") + ); + } - [Fact] - public void GZip_Archive() + [Fact] + public void GZip_Archive_NoAdd() + { + var jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + using var archive = GZipArchive.Open(stream); + Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); + archive.SaveTo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + } + + [Fact] + public void GZip_Archive_Multiple_Reads() + { + var inputStream = new MemoryStream(); + using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = GZipArchive.Open(stream)) - { - var entry = archive.Entries.First(); - entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); - - long size = entry.Size; - var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); - var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); - - Assert.Equal(size, scratch.Length); - Assert.Equal(size, test.Length); - } - CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), - Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + fileStream.CopyTo(inputStream); + inputStream.Position = 0; } + using var archive = GZipArchive.Open(inputStream); + var archiveEntry = archive.Entries.First(); - - [Fact] - public void GZip_Archive_NoAdd() + MemoryStream tarStream; + using (var entryStream = archiveEntry.OpenEntryStream()) { - string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = GZipArchive.Open(stream)) - { - Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); - archive.SaveTo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); - } + tarStream = new MemoryStream(); + entryStream.CopyTo(tarStream); } - - - [Fact] - public void GZip_Archive_Multiple_Reads() + var size = tarStream.Length; + using (var entryStream = archiveEntry.OpenEntryStream()) { - var inputStream = new MemoryStream(); - using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - { - fileStream.CopyTo(inputStream); - inputStream.Position = 0; - } - using (var archive = GZipArchive.Open(inputStream)) - { - var archiveEntry = archive.Entries.First(); - - MemoryStream tarStream; - using (var entryStream = archiveEntry.OpenEntryStream()) - { - tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); - } - var size = tarStream.Length; - using (var entryStream = archiveEntry.OpenEntryStream()) - { - tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); - } - Assert.Equal(size, tarStream.Length); - using (var entryStream = archiveEntry.OpenEntryStream()) - { - var result = Archives.Tar.TarArchive.IsTarFile(entryStream); - } - Assert.Equal(size, tarStream.Length); - using (var entryStream = archiveEntry.OpenEntryStream()) - { - tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); - } - Assert.Equal(size, tarStream.Length); - } + tarStream = new MemoryStream(); + entryStream.CopyTo(tarStream); } - - [Fact] - public void TestGzCrcWithMostSignificaltBitNotNegative() + Assert.Equal(size, tarStream.Length); + using (var entryStream = archiveEntry.OpenEntryStream()) { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - { - using (var archive = GZipArchive.Open(stream)) - { - //process all entries in solid archive until the one we want to test - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - Assert.InRange(entry.Crc, 0L, 0xFFFFFFFFL); - } - } - } + var result = Archives.Tar.TarArchive.IsTarFile(entryStream); + } + Assert.Equal(size, tarStream.Length); + using (var entryStream = archiveEntry.OpenEntryStream()) + { + tarStream = new MemoryStream(); + entryStream.CopyTo(tarStream); + } + Assert.Equal(size, tarStream.Length); + } + + [Fact] + public void TestGzCrcWithMostSignificaltBitNotNegative() + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + using var archive = GZipArchive.Open(stream); + //process all entries in solid archive until the one we want to test + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + Assert.InRange(entry.Crc, 0L, 0xFFFFFFFFL); } } } diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index ed308c98..b5a3d501 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -1,37 +1,27 @@ -using System.IO; +using System.IO; using SharpCompress.Common; using SharpCompress.IO; using Xunit; -namespace SharpCompress.Test.GZip +namespace SharpCompress.Test.GZip; + +public class GZipReaderTests : ReaderTests { - public class GZipReaderTests : ReaderTests + public GZipReaderTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void GZip_Reader_Generic() => Read("Tar.tar.gz", CompressionType.GZip); + + [Fact] + public void GZip_Reader_Generic2() { - public GZipReaderTests() + //read only as GZip itme + using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + using var reader = Readers.GZip.GZipReader.Open(new RewindableStream(stream)); + while (reader.MoveToNextEntry()) // Crash here { - UseExtensionInsteadOfNameToVerify = true; - } - - [Fact] - public void GZip_Reader_Generic() - { - Read("Tar.tar.gz", CompressionType.GZip); - } - - - [Fact] - public void GZip_Reader_Generic2() - { - //read only as GZip itme - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var reader = SharpCompress.Readers.GZip.GZipReader.Open(new RewindableStream(stream))) - { - while (reader.MoveToNextEntry()) // Crash here - { - Assert.NotEqual(0, reader.Entry.Size); - Assert.NotEqual(0, reader.Entry.Crc); - } - } + Assert.NotEqual(0, reader.Entry.Size); + Assert.NotEqual(0, reader.Entry.Crc); } } } diff --git a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs index 5c109bd5..7fbf0b5b 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs @@ -4,63 +4,78 @@ using SharpCompress.Writers; using SharpCompress.Writers.GZip; using Xunit; -namespace SharpCompress.Test.GZip +namespace SharpCompress.Test.GZip; + +public class GZipWriterTests : WriterTests { - public class GZipWriterTests : WriterTests + public GZipWriterTests() : base(ArchiveType.GZip) => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void GZip_Writer_Generic() { - public GZipWriterTests() - : base(ArchiveType.GZip) + using ( + Stream stream = File.Open( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + FileMode.OpenOrCreate, + FileAccess.Write + ) + ) + using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.GZip)) { - UseExtensionInsteadOfNameToVerify = true; + writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } + CompareArchivesByPath( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz") + ); + } - [Fact] - public void GZip_Writer_Generic() + [Fact] + public void GZip_Writer() + { + using ( + Stream stream = File.Open( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + FileMode.OpenOrCreate, + FileAccess.Write + ) + ) + using (var writer = new GZipWriter(stream)) { - using (Stream stream = File.Open(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), FileMode.OpenOrCreate, FileAccess.Write)) - using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.GZip)) - { - writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); - } - CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), - Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } + CompareArchivesByPath( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz") + ); + } - [Fact] - public void GZip_Writer() + [Fact] + public void GZip_Writer_Generic_Bad_Compression() => + Assert.Throws(() => { - using (Stream stream = File.Open(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), FileMode.OpenOrCreate, FileAccess.Write)) - using (var writer = new GZipWriter(stream)) - { - writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); - } - CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), - Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - } + using Stream stream = File.OpenWrite(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + using var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.BZip2); + }); - [Fact] - public void GZip_Writer_Generic_Bad_Compression() + [Fact] + public void GZip_Writer_Entry_Path_With_Dir() + { + using ( + Stream stream = File.Open( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + FileMode.OpenOrCreate, + FileAccess.Write + ) + ) + using (var writer = new GZipWriter(stream)) { - Assert.Throws(() => - { - using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"))) - using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.BZip2)) - { - } - }); - } - - [Fact] - public void GZip_Writer_Entry_Path_With_Dir() - { - using (Stream stream = File.Open(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), FileMode.OpenOrCreate, FileAccess.Write)) - using (var writer = new GZipWriter(stream)) - { - var path = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); - writer.Write(path, path); //covers issue #532 - } - CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), - Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); + var path = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); + writer.Write(path, path); //covers issue #532 } + CompareArchivesByPath( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz") + ); } } diff --git a/tests/SharpCompress.Test/Mocks/FlushOnDisposeStream.cs b/tests/SharpCompress.Test/Mocks/FlushOnDisposeStream.cs index 22e84ef9..194e7788 100644 --- a/tests/SharpCompress.Test/Mocks/FlushOnDisposeStream.cs +++ b/tests/SharpCompress.Test/Mocks/FlushOnDisposeStream.cs @@ -1,65 +1,53 @@ using System; using System.IO; -namespace SharpCompress.Test.Mocks +namespace SharpCompress.Test.Mocks; + +// This is a simplified version of CryptoStream that always flushes the inner stream on Dispose to trigger an error in EntryStream +// CryptoStream doesn't always trigger the Flush, so this class is used instead +// See https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/cryptostream.cs,141 + +public class FlushOnDisposeStream : Stream, IDisposable { - // This is a simplified version of CryptoStream that always flushes the inner stream on Dispose to trigger an error in EntryStream - // CryptoStream doesn't always trigger the Flush, so this class is used instead - // See https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/cryptostream.cs,141 + private Stream inner; - public class FlushOnDisposeStream : Stream, IDisposable + public FlushOnDisposeStream(Stream innerStream) => inner = innerStream; + + public override bool CanRead => inner.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => inner.Length; + + public override long Position { - private Stream inner; + get => inner.Position; + set => inner.Position = value; + } - public FlushOnDisposeStream(Stream innerStream) + public override void Flush() => throw new NotImplementedException(); + + public override int Read(byte[] buffer, int offset, int count) => + inner.Read(buffer, offset, 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(); + + protected override void Dispose(bool disposing) + { + if (disposing) { - this.inner = innerStream; + inner.Flush(); + inner.Close(); } - public override bool CanRead => this.inner.CanRead; - - public override bool CanSeek => false; - - public override bool CanWrite => false; - - public override long Length => this.inner.Length; - - public override long Position { get => this.inner.Position; set => this.inner.Position = value; } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return this.inner.Read(buffer, offset, 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(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.inner.Flush(); - this.inner.Close(); - } - - base.Dispose(disposing); - } + base.Dispose(disposing); } } diff --git a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs index aaa52c51..cef93a38 100644 --- a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs +++ b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs @@ -1,68 +1,51 @@ using System; using System.IO; -namespace SharpCompress.Test.Mocks +namespace SharpCompress.Test.Mocks; + +public class ForwardOnlyStream : Stream { - public class ForwardOnlyStream : Stream + private readonly Stream stream; + + public bool IsDisposed { get; private set; } + + public ForwardOnlyStream(Stream stream) => this.stream = stream; + + protected override void Dispose(bool disposing) { - private readonly Stream stream; - - public bool IsDisposed { get; private set; } - - public ForwardOnlyStream(Stream stream) + if (!IsDisposed) { - this.stream = stream; - } - - protected override void Dispose(bool disposing) - { - if (!IsDisposed) + if (disposing) { - if (disposing) - { - stream.Dispose(); - IsDisposed = true; - base.Dispose(disposing); - } + stream.Dispose(); + IsDisposed = true; + base.Dispose(disposing); } } - - public override bool CanRead => true; - - public override bool CanSeek => false; - public override bool CanWrite => false; - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override long Length => throw new NotSupportedException(); - - public override long Position - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return stream.Read(buffer, offset, count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } } -} \ No newline at end of file + + public override bool CanRead => true; + + public override bool CanSeek => false; + public override bool CanWrite => false; + + public override void Flush() => throw new NotSupportedException(); + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) => + stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); +} diff --git a/tests/SharpCompress.Test/Mocks/TestStream.cs b/tests/SharpCompress.Test/Mocks/TestStream.cs index 64bee443..f79c5b9d 100644 --- a/tests/SharpCompress.Test/Mocks/TestStream.cs +++ b/tests/SharpCompress.Test/Mocks/TestStream.cs @@ -1,69 +1,54 @@ using System.IO; -namespace SharpCompress.Test.Mocks +namespace SharpCompress.Test.Mocks; + +public class TestStream : Stream { - public class TestStream : Stream + private readonly Stream stream; + + public TestStream(Stream stream) : this(stream, stream.CanRead, stream.CanWrite, stream.CanSeek) + { } + + public bool IsDisposed { get; private set; } + + public TestStream(Stream stream, bool read, bool write, bool seek) { - private readonly Stream stream; - - public TestStream(Stream stream) : this(stream, stream.CanRead, stream.CanWrite, stream.CanSeek) - { - } - - public bool IsDisposed { get; private set; } - - public TestStream(Stream stream, bool read, bool write, bool seek) - { - this.stream = stream; - CanRead = read; - CanWrite = write; - CanSeek = seek; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - stream.Dispose(); - IsDisposed = true; - } - - public override bool CanRead { get; } - - public override bool CanSeek { get; } - - public override bool CanWrite { get; } - - public override void Flush() - { - stream.Flush(); - } - - public override long Length => stream.Length; - - public override long Position - { - get => stream.Position; - set => stream.Position = value; - } - - public override int Read(byte[] buffer, int offset, int count) - { - return stream.Read(buffer, offset, count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return stream.Seek(offset, origin); - } - - public override void SetLength(long value) - { - stream.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - stream.Write(buffer, offset, count); - } + this.stream = stream; + CanRead = read; + CanWrite = write; + CanSeek = seek; } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + stream.Dispose(); + IsDisposed = true; + } + + public override bool CanRead { get; } + + public override bool CanSeek { get; } + + public override bool CanWrite { get; } + + public override void Flush() => stream.Flush(); + + public override long Length => stream.Length; + + public override long Position + { + get => stream.Position; + set => stream.Position = value; + } + + 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) => + stream.Write(buffer, offset, count); } diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 3580aba3..0936a8ed 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -7,681 +7,579 @@ using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.Readers; using Xunit; -namespace SharpCompress.Test.Rar +namespace SharpCompress.Test.Rar; + +public class RarArchiveTests : ArchiveTests { - public class RarArchiveTests : ArchiveTests + [Fact] + public void Rar_EncryptedFileAndHeader_Archive() => + ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); + + [Fact] + public void Rar_EncryptedFileAndHeader_NoPasswordExceptionTest() => + Assert.Throws( + typeof(CryptographicException), + () => ReadRarPassword("Rar.encrypted_filesAndHeader.rar", null) + ); + + /*[Fact] + public void Rar5_EncryptedFileAndHeader_Archive() { - [Fact] - public void Rar_EncryptedFileAndHeader_Archive() - { - ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); - } + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ - [Fact] - public void Rar_EncryptedFileAndHeader_NoPasswordExceptionTest() - { - Assert.Throws(typeof(CryptographicException), () => ReadRarPassword("Rar.encrypted_filesAndHeader.rar", null)); - } + [Fact] + public void Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest() => + Assert.Throws( + typeof(CryptographicException), + () => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", null) + ); - /*[Fact] - public void Rar5_EncryptedFileAndHeader_Archive() - { - ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - }*/ + [Fact] + public void Rar_EncryptedFileOnly_Archive() => + ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); - [Fact] - public void Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest() - { - Assert.Throws(typeof(CryptographicException), () => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", null)); - } + /*[Fact] + public void Rar5_EncryptedFileOnly_Archive() + { + ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); + }*/ - [Fact] - public void Rar_EncryptedFileOnly_Archive() - { - ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); - } + [Fact] + public void Rar_Encrypted_Archive() => ReadRarPassword("Rar.Encrypted.rar", "test"); - /*[Fact] - public void Rar5_EncryptedFileOnly_Archive() - { - ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); - }*/ + /*[Fact] + public void Rar5_Encrypted_Archive() + { + ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ - [Fact] - public void Rar_Encrypted_Archive() + private void ReadRarPassword(string testArchive, string? password) + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) + using ( + var archive = RarArchive.Open( + stream, + new ReaderOptions() { Password = password, LeaveStreamOpen = true } + ) + ) { - ReadRarPassword("Rar.Encrypted.rar", "test"); - } - - /*[Fact] - public void Rar5_Encrypted_Archive() - { - ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - }*/ - - private void ReadRarPassword(string testArchive, string password) - { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) - using (var archive = RarArchive.Open(stream, new ReaderOptions() + foreach (var entry in archive.Entries) { - Password = password, - LeaveStreamOpen = true - })) - { - foreach (var entry in archive.Entries) + if (!entry.IsDirectory) { - if (!entry.IsDirectory) - { - Assert.Equal(CompressionType.Rar, entry.CompressionType); - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - } - VerifyFiles(); - } - - [Fact] - public void Rar_Multi_Archive_Encrypted() - { - Assert.Throws(() => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test")); - } - - protected void ArchiveFileReadPassword(string archiveName, string password) - { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, archiveName), new ReaderOptions() - { - Password = password, - LeaveStreamOpen = true - })) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - [Fact] - public void Rar_None_ArchiveStreamRead() - { - ArchiveStreamRead("Rar.none.rar"); - } - - [Fact] - public void Rar5_None_ArchiveStreamRead() - { - ArchiveStreamRead("Rar5.none.rar"); - } - - [Fact] - public void Rar_ArchiveStreamRead() - { - ArchiveStreamRead("Rar.rar"); - } - - [Fact] - public void Rar5_ArchiveStreamRead() - { - ArchiveStreamRead("Rar5.rar"); - } - - [Fact] - public void Rar_test_invalid_exttime_ArchiveStreamRead() - { - DoRar_test_invalid_exttime_ArchiveStreamRead("Rar.test_invalid_exttime.rar"); - } - - private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - { - using (var archive = ArchiveFactory.Open(stream)) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } + Assert.Equal(CompressionType.Rar, entry.CompressionType); + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } + VerifyFiles(); + } - [Fact] - public void Rar_Jpg_ArchiveStreamRead() + [Fact] + public void Rar_Multi_Archive_Encrypted() => + Assert.Throws( + () => ArchiveFileReadPassword("Rar.EncryptedParts.part01.rar", "test") + ); + + protected void ArchiveFileReadPassword(string archiveName, string password) + { + using ( + var archive = RarArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, archiveName), + new ReaderOptions() { Password = password, LeaveStreamOpen = true } + ) + ) { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - using (var archive = RarArchive.Open(stream, new ReaderOptions() - { - LookForHeader = true - })) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } + VerifyFiles(); + } - [Fact] - public void Rar_IsSolidArchiveCheck() + [Fact] + public void Rar_None_ArchiveStreamRead() => ArchiveStreamRead("Rar.none.rar"); + + [Fact] + public void Rar5_None_ArchiveStreamRead() => ArchiveStreamRead("Rar5.none.rar"); + + [Fact] + public void Rar_ArchiveStreamRead() => ArchiveStreamRead("Rar.rar"); + + [Fact] + public void Rar5_ArchiveStreamRead() => ArchiveStreamRead("Rar5.rar"); + + [Fact] + public void Rar_test_invalid_exttime_ArchiveStreamRead() => + DoRar_test_invalid_exttime_ArchiveStreamRead("Rar.test_invalid_exttime.rar"); + + private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var archive = ArchiveFactory.Open(stream); + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - DoRar_IsSolidArchiveCheck("Rar.rar"); + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } + } - [Fact] - public void Rar5_IsSolidArchiveCheck() + [Fact] + public void Rar_Jpg_ArchiveStreamRead() + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")); + using (var archive = RarArchive.Open(stream, new ReaderOptions() { LookForHeader = true })) { - DoRar_IsSolidArchiveCheck("Rar5.rar"); - } - - private void DoRar_IsSolidArchiveCheck(string filename) - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - using (var archive = RarArchive.Open(stream)) - { - Assert.False(archive.IsSolid); - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - } - VerifyFiles(); - } - - - [Fact] - public void Rar_IsSolidEntryStreamCheck() - { - DoRar_IsSolidEntryStreamCheck("Rar.solid.rar"); - } - - //Extract the 2nd file in a solid archive to check that the first file is skipped properly - private void DoRar_IsSolidEntryStreamCheck(string filename) - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - { - using (var archive = RarArchive.Open(stream)) - { - Assert.True(archive.IsSolid); - IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray(); - Assert.NotInRange(entries.Length, 0, 1); - Assert.False(entries[0].IsSolid); //first item in a solid archive is not marked solid and is seekable - IArchiveEntry testEntry = entries[1]; - Assert.True(testEntry.IsSolid); //the target. The non seekable entry - - //process all entries in solid archive until the one we want to test - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - using (CrcCheckStream crcStream = new CrcCheckStream((uint)entry.Crc)) //use the 7zip CRC stream for convenience (required a bug fix) - { - using (Stream eStream = entry.OpenEntryStream()) //bug fix in RarStream to report the correct Position - eStream.CopyTo(crcStream); - } //throws if not valid - if (entry == testEntry) - break; - } - } + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } + VerifyFiles(); + } + [Fact] + public void Rar_IsSolidArchiveCheck() => DoRar_IsSolidArchiveCheck("Rar.rar"); - [Fact] - public void Rar_Solid_ArchiveStreamRead() + [Fact] + public void Rar5_IsSolidArchiveCheck() => DoRar_IsSolidArchiveCheck("Rar5.rar"); + + private void DoRar_IsSolidArchiveCheck(string filename) + { + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { - ArchiveStreamRead("Rar.solid.rar"); + using var archive = RarArchive.Open(stream); + Assert.False(archive.IsSolid); + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } } + VerifyFiles(); + } - [Fact] - public void Rar5_Solid_ArchiveStreamRead() + [Fact] + public void Rar_IsSolidEntryStreamCheck() => DoRar_IsSolidEntryStreamCheck("Rar.solid.rar"); + + //Extract the 2nd file in a solid archive to check that the first file is skipped properly + private void DoRar_IsSolidEntryStreamCheck(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var archive = RarArchive.Open(stream); + Assert.True(archive.IsSolid); + IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray(); + Assert.NotInRange(entries.Length, 0, 1); + Assert.False(entries[0].IsSolid); //first item in a solid archive is not marked solid and is seekable + var testEntry = entries[1]; + Assert.True(testEntry.IsSolid); //the target. The non seekable entry + + //process all entries in solid archive until the one we want to test + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - ArchiveStreamRead("Rar5.solid.rar"); + using (var crcStream = new CrcCheckStream((uint)entry.Crc)) //use the 7zip CRC stream for convenience (required a bug fix) + { + using var eStream = entry.OpenEntryStream(); //bug fix in RarStream to report the correct Position + eStream.CopyTo(crcStream); + } //throws if not valid + if (entry == testEntry) + { + break; + } } + } - [Fact] - public void Rar_Solid_StreamRead_Extract_All() - { - ArchiveStreamReadExtractAll("Rar.solid.rar", CompressionType.Rar); - } + [Fact] + public void Rar_Solid_ArchiveStreamRead() => ArchiveStreamRead("Rar.solid.rar"); - [Fact] - public void Rar5_Solid_StreamRead_Extract_All() - { - ArchiveStreamReadExtractAll("Rar5.solid.rar", CompressionType.Rar); - } + [Fact] + public void Rar5_Solid_ArchiveStreamRead() => ArchiveStreamRead("Rar5.solid.rar"); - [Fact] - public void Rar_Multi_ArchiveStreamRead() - { - DoRar_Multi_ArchiveStreamRead(new string[] { + [Fact] + public void Rar_Solid_StreamRead_Extract_All() => + ArchiveStreamReadExtractAll("Rar.solid.rar", CompressionType.Rar); + + [Fact] + public void Rar5_Solid_StreamRead_Extract_All() => + ArchiveStreamReadExtractAll("Rar5.solid.rar", CompressionType.Rar); + + [Fact] + public void Rar_Multi_ArchiveStreamRead() => + DoRar_Multi_ArchiveStreamRead( + new[] + { "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}, false); - } + "Rar.multi.part06.rar" + }, + false + ); - [Fact] - public void Rar5_Multi_ArchiveStreamRead() - { - DoRar_Multi_ArchiveStreamRead(new string[] { + [Fact] + public void Rar5_Multi_ArchiveStreamRead() => + DoRar_Multi_ArchiveStreamRead( + new[] + { "Rar5.multi.part01.rar", "Rar5.multi.part02.rar", "Rar5.multi.part03.rar", "Rar5.multi.part04.rar", "Rar5.multi.part05.rar", - "Rar5.multi.part06.rar"}, false); - } + "Rar5.multi.part06.rar" + }, + false + ); - private void DoRar_Multi_ArchiveStreamRead(string[] archives, bool isSolid) + private void DoRar_Multi_ArchiveStreamRead(string[] archives, bool isSolid) + { + using var archive = RarArchive.Open( + archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) + ); + Assert.Equal(archive.IsSolid, isSolid); + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - using (var archive = RarArchive.Open(archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) - .Select(File.OpenRead))) + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + + [Fact] + public void Rar5_MultiSolid_ArchiveStreamRead() => + DoRar_Multi_ArchiveStreamRead( + new[] { - Assert.Equal(archive.IsSolid, isSolid); - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - } - - [Fact] - public void Rar5_MultiSolid_ArchiveStreamRead() - { - DoRar_Multi_ArchiveStreamRead(new string[] { "Rar.multi.solid.part01.rar", "Rar.multi.solid.part02.rar", "Rar.multi.solid.part03.rar", "Rar.multi.solid.part04.rar", "Rar.multi.solid.part05.rar", - "Rar.multi.solid.part06.rar"}, true); - } + "Rar.multi.solid.part06.rar" + }, + true + ); - [Fact] - public void RarNoneArchiveFileRead() - { - ArchiveFileRead("Rar.none.rar"); - } + [Fact] + public void RarNoneArchiveFileRead() => ArchiveFileRead("Rar.none.rar"); - [Fact] - public void Rar5NoneArchiveFileRead() - { - ArchiveFileRead("Rar5.none.rar"); - } + [Fact] + public void Rar5NoneArchiveFileRead() => ArchiveFileRead("Rar5.none.rar"); - [Fact] - public void Rar_ArchiveFileRead() - { - ArchiveFileRead("Rar.rar"); - } + [Fact] + public void Rar_ArchiveFileRead() => ArchiveFileRead("Rar.rar"); - [Fact] - public void Rar5_ArchiveFileRead() - { - ArchiveFileRead("Rar5.rar"); - } + [Fact] + public void Rar5_ArchiveFileRead() => ArchiveFileRead("Rar5.rar"); - [Fact] - public void Rar_ArchiveFileRead_HasDirectories() - { - DoRar_ArchiveFileRead_HasDirectories("Rar.rar"); - } + [Fact] + public void Rar_ArchiveFileRead_HasDirectories() => + DoRar_ArchiveFileRead_HasDirectories("Rar.rar"); - [Fact] - public void Rar5_ArchiveFileRead_HasDirectories() - { - DoRar_ArchiveFileRead_HasDirectories("Rar5.rar"); - } + [Fact] + public void Rar5_ArchiveFileRead_HasDirectories() => + DoRar_ArchiveFileRead_HasDirectories("Rar5.rar"); - private void DoRar_ArchiveFileRead_HasDirectories(string filename) + private void DoRar_ArchiveFileRead_HasDirectories(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var archive = RarArchive.Open(stream); + Assert.False(archive.IsSolid); + Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory)); + } + + [Fact] + public void Rar_Jpg_ArchiveFileRead() + { + using ( + var archive = RarArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), + new ReaderOptions() { LookForHeader = true } + ) + ) { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - using (var archive = RarArchive.Open(stream)) - { - Assert.False(archive.IsSolid); - Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory)); - } + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } + VerifyFiles(); + } - [Fact] - public void Rar_Jpg_ArchiveFileRead() - { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions() + [Fact] + public void Rar_Solid_ArchiveFileRead() => ArchiveFileRead("Rar.solid.rar"); + + [Fact] + public void Rar5_Solid_ArchiveFileRead() => ArchiveFileRead("Rar5.solid.rar"); + + [Fact] + public void Rar2_Multi_ArchiveStreamRead() => + DoRar_Multi_ArchiveStreamRead( + new[] { - LookForHeader = true - })) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - [Fact] - public void Rar_Solid_ArchiveFileRead() - { - ArchiveFileRead("Rar.solid.rar"); - } - - [Fact] - public void Rar5_Solid_ArchiveFileRead() - { - ArchiveFileRead("Rar5.solid.rar"); - } - - [Fact] - public void Rar2_Multi_ArchiveStreamRead() - { - DoRar_Multi_ArchiveStreamRead(new string[] { "Rar2.multi.rar", "Rar2.multi.r00", "Rar2.multi.r01", "Rar2.multi.r02", "Rar2.multi.r03", "Rar2.multi.r04", - "Rar2.multi.r05"}, false); - } + "Rar2.multi.r05" + }, + false + ); - [Fact] - public void Rar2_Multi_ArchiveFileRead() - { - ArchiveFileRead("Rar2.multi.rar"); //r00, r01... - } + [Fact] + public void Rar2_Multi_ArchiveFileRead() => ArchiveFileRead("Rar2.multi.rar"); //r00, r01... - [Fact] - public void Rar2_ArchiveFileRead() - { - ArchiveFileRead("Rar2.rar"); - } + [Fact] + public void Rar2_ArchiveFileRead() => ArchiveFileRead("Rar2.rar"); - [Fact] - public void Rar2_ArchiveVersionTest() - { - string testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar"); + [Fact] + public void Rar2_ArchiveVersionTest() + { + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar"); - using (var archive = RarArchive.Open(testArchive)) + using var archive = RarArchive.Open(testArchive); + Assert.Equal(2, archive.MinVersion); + Assert.Equal(2, archive.MaxVersion); + } + + [Fact] + public void Rar4_ArchiveVersionTest() + { + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar"); + + using var archive = RarArchive.Open(testArchive); + Assert.Equal(3, archive.MinVersion); + Assert.Equal(4, archive.MaxVersion); + } + + [Fact] + public void Rar5_ArchiveVersionTest() + { + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar"); + + using var archive = RarArchive.Open(testArchive); + Assert.Equal(5, archive.MinVersion); + Assert.Equal(6, archive.MaxVersion); + } + + [Fact] + public void Rar4_Multi_ArchiveFileRead() => ArchiveFileRead("Rar4.multi.part01.rar"); + + [Fact] + public void Rar4_ArchiveFileRead() => ArchiveFileRead("Rar4.rar"); + + [Fact] + public void Rar_GetPartsSplit() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts( + new[] { - Assert.Equal(2, archive.MinVersion); - Assert.Equal(2, archive.MaxVersion); - } - } - - [Fact] - public void Rar4_ArchiveVersionTest() - { - string testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar"); - - using (var archive = RarArchive.Open(testArchive)) - { - Assert.Equal(3, archive.MinVersion); - Assert.Equal(4, archive.MaxVersion); - } - } - - [Fact] - public void Rar5_ArchiveVersionTest() - { - string testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar"); - - using (var archive = RarArchive.Open(testArchive)) - { - Assert.Equal(5, archive.MinVersion); - Assert.Equal(6, archive.MaxVersion); - } - } - - [Fact] - public void Rar4_Multi_ArchiveFileRead() - { - ArchiveFileRead("Rar4.multi.part01.rar"); - } - - [Fact] - public void Rar4_ArchiveFileRead() - { - ArchiveFileRead("Rar4.rar"); - } - - [Fact] - public void Rar_GetPartsSplit() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { "Rar4.split.001", "Rar4.split.002", "Rar4.split.003", "Rar4.split.004", "Rar4.split.005", - "Rar4.split.006"}); - } - [Fact] - public void Rar_GetPartsOld() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { + "Rar4.split.006" + } + ); + + [Fact] + public void Rar_GetPartsOld() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts( + new[] + { "Rar2.multi.rar", "Rar2.multi.r00", "Rar2.multi.r01", "Rar2.multi.r02", "Rar2.multi.r03", "Rar2.multi.r04", - "Rar2.multi.r05"}); - } - [Fact] - public void Rar_GetPartsNew() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { + "Rar2.multi.r05" + } + ); + + [Fact] + public void Rar_GetPartsNew() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts( + new[] + { "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", - "Rar4.multi.part07.rar"}); - } + "Rar4.multi.part07.rar" + } + ); - [Fact] - public void Rar4_Multi_ArchiveStreamRead() - { - DoRar_Multi_ArchiveStreamRead(new string[] { + [Fact] + public void Rar4_Multi_ArchiveStreamRead() => + DoRar_Multi_ArchiveStreamRead( + new[] + { "Rar4.multi.part01.rar", "Rar4.multi.part02.rar", "Rar4.multi.part03.rar", "Rar4.multi.part04.rar", "Rar4.multi.part05.rar", "Rar4.multi.part06.rar", - "Rar4.multi.part07.rar"}, false); - } + "Rar4.multi.part07.rar" + }, + false + ); - //no extension to test the lib identifies the archive by content not ext - [Fact] - public void Rar4_Split_ArchiveStreamRead() - { - ArchiveStreamMultiRead(null, new string[] { + //no extension to test the lib identifies the archive by content not ext + [Fact] + public void Rar4_Split_ArchiveStreamRead() => + ArchiveStreamMultiRead( + null, + new[] + { "Rar4.split.001", "Rar4.split.002", "Rar4.split.003", "Rar4.split.004", "Rar4.split.005", - "Rar4.split.006"}); - } - //will detect and load other files - [Fact] - public void Rar4_Multi_ArchiveFirstFileRead() - { - ArchiveFileRead("Rar4.multi.part01.rar"); - //"Rar4.multi.part02.rar", - //"Rar4.multi.part03.rar", - //"Rar4.multi.part04.rar", - //"Rar4.multi.part05.rar", - //"Rar4.multi.part06.rar", - //"Rar4.multi.part07.rar" - } + "Rar4.split.006" + } + ); + //will detect and load other files [Fact] - public void Rar4_Split_ArchiveFirstFileRead() - { - ArchiveFileRead("Rar4.split.001"); - //"Rar4.split.002", - //"Rar4.split.003", - //"Rar4.split.004", - //"Rar4.split.005", - //"Rar4.split.006" - } - //will detect and load other files - [Fact] - public void Rar4_Split_ArchiveStreamFirstFileRead() - { - ArchiveStreamMultiRead(null, new string[] { + public void Rar4_Multi_ArchiveFirstFileRead() => ArchiveFileRead("Rar4.multi.part01.rar"); + + //"Rar4.multi.part02.rar", + //"Rar4.multi.part03.rar", + //"Rar4.multi.part04.rar", + //"Rar4.multi.part05.rar", + //"Rar4.multi.part06.rar", + //"Rar4.multi.part07.rar" + //will detect and load other files + [Fact] + public void Rar4_Split_ArchiveFirstFileRead() => ArchiveFileRead("Rar4.split.001"); + + //"Rar4.split.002", + //"Rar4.split.003", + //"Rar4.split.004", + //"Rar4.split.005", + //"Rar4.split.006" + //will detect and load other files + [Fact] + public void Rar4_Split_ArchiveStreamFirstFileRead() => + ArchiveStreamMultiRead( + null, + new[] + { "Rar4.split.001", //"Rar4.split.002", //"Rar4.split.003", //"Rar4.split.004", //"Rar4.split.005", //"Rar4.split.006" - }); - } + } + ); - //open with ArchiveFactory.Open and stream - [Fact] - public void Rar4_Split_ArchiveOpen() - { - ArchiveOpenStreamRead(null, - "Rar4.split.001", - "Rar4.split.002", - "Rar4.split.003", - "Rar4.split.004", - "Rar4.split.005", - "Rar4.split.006"); - } + //open with ArchiveFactory.Open and stream + [Fact] + public void Rar4_Split_ArchiveOpen() => + ArchiveOpenStreamRead( + null, + "Rar4.split.001", + "Rar4.split.002", + "Rar4.split.003", + "Rar4.split.004", + "Rar4.split.005", + "Rar4.split.006" + ); - //open with ArchiveFactory.Open and stream - [Fact] - public void Rar4_Multi_ArchiveOpen() - { - ArchiveOpenStreamRead(null, - "Rar4.multi.part01.rar", - "Rar4.multi.part02.rar", - "Rar4.multi.part03.rar", - "Rar4.multi.part04.rar", - "Rar4.multi.part05.rar", - "Rar4.multi.part06.rar", - "Rar4.multi.part07.rar"); - } - - [Fact] - public void Rar4_Multi_ArchiveOpenEntryVolumeIndexTest() - { - ArchiveOpenEntryVolumeIndexTest( - new[] { - new[] { 0, 1 }, //exe - Rar4.multi.part01.rar to Rar4.multi.part02.rar - new[] { 1, 5 }, //jpg - Rar4.multi.part02.rar to Rar4.multi.part06.rar - new[] { 5, 6 } //txt - Rar4.multi.part06.rar to Rar4.multi.part07.rar - }, - null, - "Rar4.multi.part01.rar", - "Rar4.multi.part02.rar", - "Rar4.multi.part03.rar", - "Rar4.multi.part04.rar", - "Rar4.multi.part05.rar", - "Rar4.multi.part06.rar", - "Rar4.multi.part07.rar"); - } + //open with ArchiveFactory.Open and stream + [Fact] + public void Rar4_Multi_ArchiveOpen() => + ArchiveOpenStreamRead( + null, + "Rar4.multi.part01.rar", + "Rar4.multi.part02.rar", + "Rar4.multi.part03.rar", + "Rar4.multi.part04.rar", + "Rar4.multi.part05.rar", + "Rar4.multi.part06.rar", + "Rar4.multi.part07.rar" + ); [Fact] - public void Rar_Multi_ArchiveFileRead() - { - ArchiveFileRead("Rar.multi.part01.rar"); - } - - [Fact] - public void Rar5_Multi_ArchiveFileRead() - { - ArchiveFileRead("Rar5.multi.part01.rar"); - } - - [Fact] - public void Rar_IsFirstVolume_True() - { - DoRar_IsFirstVolume_True("Rar.multi.part01.rar"); - } - - [Fact] - public void Rar5_IsFirstVolume_True() - { - DoRar_IsFirstVolume_True("Rar5.multi.part01.rar"); - } - - private void DoRar_IsFirstVolume_True(string firstFilename) - { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, firstFilename))) + public void Rar4_Multi_ArchiveOpenEntryVolumeIndexTest() => + ArchiveOpenEntryVolumeIndexTest( + new[] { - Assert.True(archive.IsMultipartVolume()); - Assert.True(archive.IsFirstVolume()); - } - } + new[] { 0, 1 }, //exe - Rar4.multi.part01.rar to Rar4.multi.part02.rar + new[] { 1, 5 }, //jpg - Rar4.multi.part02.rar to Rar4.multi.part06.rar + new[] { 5, 6 } //txt - Rar4.multi.part06.rar to Rar4.multi.part07.rar + }, + null, + "Rar4.multi.part01.rar", + "Rar4.multi.part02.rar", + "Rar4.multi.part03.rar", + "Rar4.multi.part04.rar", + "Rar4.multi.part05.rar", + "Rar4.multi.part06.rar", + "Rar4.multi.part07.rar" + ); - [Fact] - public void Rar_IsFirstVolume_False() - { - DoRar_IsFirstVolume_False("Rar.multi.part03.rar"); - } + [Fact] + public void Rar_Multi_ArchiveFileRead() => ArchiveFileRead("Rar.multi.part01.rar"); - [Fact] - public void Rar5_IsFirstVolume_False() - { - DoRar_IsFirstVolume_False("Rar5.multi.part03.rar"); - } + [Fact] + public void Rar5_Multi_ArchiveFileRead() => ArchiveFileRead("Rar5.multi.part01.rar"); - private void DoRar_IsFirstVolume_False(string notFirstFilename) - { - using (var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename))) - { - Assert.True(archive.IsMultipartVolume()); - Assert.False(archive.IsFirstVolume()); - } - } + [Fact] + public void Rar_IsFirstVolume_True() => DoRar_IsFirstVolume_True("Rar.multi.part01.rar"); + + [Fact] + public void Rar5_IsFirstVolume_True() => DoRar_IsFirstVolume_True("Rar5.multi.part01.rar"); + + private void DoRar_IsFirstVolume_True(string firstFilename) + { + using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); + Assert.True(archive.IsMultipartVolume()); + Assert.True(archive.IsFirstVolume()); + } + + [Fact] + public void Rar_IsFirstVolume_False() => DoRar_IsFirstVolume_False("Rar.multi.part03.rar"); + + [Fact] + public void Rar5_IsFirstVolume_False() => DoRar_IsFirstVolume_False("Rar5.multi.part03.rar"); + + private void DoRar_IsFirstVolume_False(string notFirstFilename) + { + using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename)); + Assert.True(archive.IsMultipartVolume()); + Assert.False(archive.IsFirstVolume()); } } diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index a9c4c798..f2b1913c 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -1,61 +1,51 @@ -using System.IO; +using System.IO; using SharpCompress.Common.Rar.Headers; using SharpCompress.IO; using SharpCompress.Readers; using Xunit; -namespace SharpCompress.Test.Rar +namespace SharpCompress.Test.Rar; + +/// +/// Summary description for RarFactoryReaderTest +/// +public class RarHeaderFactoryTest : TestBase { - /// - /// Summary description for RarFactoryReaderTest - /// - public class RarHeaderFactoryTest : TestBase + private readonly RarHeaderFactory rarHeaderFactory; + + public RarHeaderFactoryTest() => + rarHeaderFactory = new RarHeaderFactory( + StreamingMode.Seekable, + new ReaderOptions { LeaveStreamOpen = true } + ); + + [Fact] + public void Rar_ReadHeaders_RecognizeEncryptedFlag() => + ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); + + [Fact] + public void Rar5_ReadHeaders_RecognizeEncryptedFlag() => + ReadEncryptedFlag("Rar5.encrypted_filesAndHeader.rar", true); + + [Fact] + public void Rar_ReadHeaders_RecognizeNoEncryptedFlag() => ReadEncryptedFlag("Rar.rar", false); + + [Fact] + public void Rar5_ReadHeaders_RecognizeNoEncryptedFlag() => ReadEncryptedFlag("Rar5.rar", false); + + private void ReadEncryptedFlag(string testArchive, bool isEncrypted) { - private readonly RarHeaderFactory rarHeaderFactory; - - public RarHeaderFactoryTest() + using var stream = new FileStream( + Path.Combine(TEST_ARCHIVES_PATH, testArchive), + FileMode.Open, + FileAccess.Read + ); + foreach (var header in rarHeaderFactory.ReadHeaders(stream)) { - rarHeaderFactory = new RarHeaderFactory( - StreamingMode.Seekable, - new ReaderOptions { LeaveStreamOpen = true }); - } - - [Fact] - public void Rar_ReadHeaders_RecognizeEncryptedFlag() - { - ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true); - } - - [Fact] - public void Rar5_ReadHeaders_RecognizeEncryptedFlag() - { - ReadEncryptedFlag("Rar5.encrypted_filesAndHeader.rar", true); - } - - [Fact] - public void Rar_ReadHeaders_RecognizeNoEncryptedFlag() - { - ReadEncryptedFlag("Rar.rar", false); - } - - [Fact] - public void Rar5_ReadHeaders_RecognizeNoEncryptedFlag() - { - ReadEncryptedFlag("Rar5.rar", false); - } - - private void ReadEncryptedFlag(string testArchive, bool isEncrypted) - { - using (var stream = new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive), FileMode.Open, FileAccess.Read)) + if (header.HeaderType == HeaderType.Archive || header.HeaderType == HeaderType.Crypt) { - foreach (var header in rarHeaderFactory.ReadHeaders(stream)) - { - if (header.HeaderType == HeaderType.Archive || header.HeaderType == HeaderType.Crypt) - { - Assert.Equal(isEncrypted, rarHeaderFactory.IsEncrypted); - break; - } - } + Assert.Equal(isEncrypted, rarHeaderFactory.IsEncrypted); + break; } } } diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index 6affbc7b..45744270 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -1,380 +1,334 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Readers.Rar; using Xunit; -namespace SharpCompress.Test.Rar +namespace SharpCompress.Test.Rar; + +public class RarReaderTests : ReaderTests { - public class RarReaderTests : ReaderTests - { - [Fact] - public void Rar_Multi_Reader() - { - DoRar_Multi_Reader(new string[] { + [Fact] + public void Rar_Multi_Reader() => + DoRar_Multi_Reader( + new[] + { "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}); - } + "Rar.multi.part06.rar" + } + ); - [Fact] - public void Rar5_Multi_Reader() - { - DoRar_Multi_Reader(new string[] { + [Fact] + public void Rar5_Multi_Reader() => + DoRar_Multi_Reader( + new[] + { "Rar5.multi.part01.rar", "Rar5.multi.part02.rar", "Rar5.multi.part03.rar", "Rar5.multi.part04.rar", "Rar5.multi.part05.rar", - "Rar5.multi.part06.rar"}); - } - - private void DoRar_Multi_Reader(string[] archives) - { - using (var reader = RarReader.Open(archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) - .Select(p => File.OpenRead(p)))) - { - while (reader.MoveToNextEntry()) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } + "Rar5.multi.part06.rar" } - VerifyFiles(); - } + ); - [Fact] - public void Rar_Multi_Reader_Encrypted() + private void DoRar_Multi_Reader(string[] archives) + { + using ( + var reader = RarReader.Open( + archives + .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + .Select(p => File.OpenRead(p)) + ) + ) { - DoRar_Multi_Reader_Encrypted(new string[] { + while (reader.MoveToNextEntry()) + { + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + VerifyFiles(); + } + + [Fact] + public void Rar_Multi_Reader_Encrypted() => + DoRar_Multi_Reader_Encrypted( + new[] + { "Rar.EncryptedParts.part01.rar", "Rar.EncryptedParts.part02.rar", "Rar.EncryptedParts.part03.rar", "Rar.EncryptedParts.part04.rar", "Rar.EncryptedParts.part05.rar", - "Rar.EncryptedParts.part06.rar"}); - } + "Rar.EncryptedParts.part06.rar" + } + ); - private void DoRar_Multi_Reader_Encrypted(string[] archives) + private void DoRar_Multi_Reader_Encrypted(string[] archives) => + Assert.Throws(() => { - Assert.Throws(() => - { - using (var reader = RarReader.Open(archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) - .Select(p => File.OpenRead(p)), - new ReaderOptions() - { - Password = "test" - })) - { - while (reader.MoveToNextEntry()) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - }); - } + using ( + var reader = RarReader.Open( + archives + .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) + .Select(p => File.OpenRead(p)), + new ReaderOptions() { Password = "test" } + ) + ) + { + while (reader.MoveToNextEntry()) + { + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + VerifyFiles(); + }); - [Fact] - public void Rar_Multi_Reader_Delete_Files() - { - DoRar_Multi_Reader_Delete_Files(new string[] { + [Fact] + public void Rar_Multi_Reader_Delete_Files() => + DoRar_Multi_Reader_Delete_Files( + new[] + { "Rar.multi.part01.rar", "Rar.multi.part02.rar", "Rar.multi.part03.rar", "Rar.multi.part04.rar", "Rar.multi.part05.rar", - "Rar.multi.part06.rar"}); - } + "Rar.multi.part06.rar" + } + ); - [Fact] - public void Rar5_Multi_Reader_Delete_Files() - { - DoRar_Multi_Reader_Delete_Files(new string[] { + [Fact] + public void Rar5_Multi_Reader_Delete_Files() => + DoRar_Multi_Reader_Delete_Files( + new[] + { "Rar5.multi.part01.rar", "Rar5.multi.part02.rar", "Rar5.multi.part03.rar", "Rar5.multi.part04.rar", "Rar5.multi.part05.rar", - "Rar5.multi.part06.rar"}); - } + "Rar5.multi.part06.rar" + } + ); - private void DoRar_Multi_Reader_Delete_Files(string[] archives) + private void DoRar_Multi_Reader_Delete_Files(string[] archives) + { + foreach (var file in archives) { - - foreach (var file in archives) + File.Copy( + Path.Combine(TEST_ARCHIVES_PATH, file), + Path.Combine(SCRATCH2_FILES_PATH, file) + ); + } + var streams = archives + .Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)) + .Select(File.OpenRead) + .ToList(); + using (var reader = RarReader.Open(streams)) + { + while (reader.MoveToNextEntry()) { - File.Copy(Path.Combine(TEST_ARCHIVES_PATH, file), Path.Combine(SCRATCH2_FILES_PATH, file)); - } - var streams = archives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)).Select(File.OpenRead).ToList(); - using (var reader = RarReader.Open(streams)) - { - while (reader.MoveToNextEntry()) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - foreach (var stream in streams) - { - stream.Dispose(); - } - VerifyFiles(); - - foreach (var file in archives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))) - { - File.Delete(file); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } - - [Fact] - public void Rar_None_Reader() + foreach (var stream in streams) { - Read("Rar.none.rar", CompressionType.Rar); + stream.Dispose(); } + VerifyFiles(); - [Fact] - public void Rar5_None_Reader() + foreach (var file in archives.Select(s => Path.Combine(SCRATCH2_FILES_PATH, s))) { - Read("Rar5.none.rar", CompressionType.Rar); + File.Delete(file); } + } - [Fact] - public void Rar_Reader() - { - Read("Rar.rar", CompressionType.Rar); - } + [Fact] + public void Rar_None_Reader() => Read("Rar.none.rar", CompressionType.Rar); - [Fact] - public void Rar5_Reader() - { - Read("Rar5.rar", CompressionType.Rar); - } + [Fact] + public void Rar5_None_Reader() => Read("Rar5.none.rar", CompressionType.Rar); - [Fact] - public void Rar_EncryptedFileAndHeader_Reader() - { - ReadRar("Rar.encrypted_filesAndHeader.rar", "test"); - } + [Fact] + public void Rar_Reader() => Read("Rar.rar", CompressionType.Rar); - /*[Fact] - public void Rar5_EncryptedFileAndHeader_Reader() - { - ReadRar("Rar5.encrypted_filesAndHeader.rar", "test"); - }*/ + [Fact] + public void Rar5_Reader() => Read("Rar5.rar", CompressionType.Rar); - [Fact] - public void Rar_EncryptedFileOnly_Reader() - { - ReadRar("Rar.encrypted_filesOnly.rar", "test"); - } + [Fact] + public void Rar_EncryptedFileAndHeader_Reader() => + ReadRar("Rar.encrypted_filesAndHeader.rar", "test"); - /*[Fact] - public void Rar5_EncryptedFileOnly_Reader() - { - ReadRar("Rar5.encrypted_filesOnly.rar", "test"); - }*/ + /*[Fact] + public void Rar5_EncryptedFileAndHeader_Reader() + { + ReadRar("Rar5.encrypted_filesAndHeader.rar", "test"); + }*/ - [Fact] - public void Rar_Encrypted_Reader() - { - ReadRar("Rar.Encrypted.rar", "test"); - } + [Fact] + public void Rar_EncryptedFileOnly_Reader() => ReadRar("Rar.encrypted_filesOnly.rar", "test"); - /*[Fact] - public void Rar5_Encrypted_Reader() - { - ReadRar("Rar5.encrypted_filesOnly.rar", "test"); - }*/ + /*[Fact] + public void Rar5_EncryptedFileOnly_Reader() + { + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); + }*/ - private void ReadRar(string testArchive, string password) - { - Read(testArchive, CompressionType.Rar, new ReaderOptions { Password = password }); - } + [Fact] + public void Rar_Encrypted_Reader() => ReadRar("Rar.Encrypted.rar", "test"); - [Fact] - public void Rar_Entry_Stream() - { - DoRar_Entry_Stream("Rar.rar"); - } + /*[Fact] + public void Rar5_Encrypted_Reader() + { + ReadRar("Rar5.encrypted_filesOnly.rar", "test"); + }*/ - [Fact] - public void Rar5_Entry_Stream() - { - DoRar_Entry_Stream("Rar5.rar"); - } + private void ReadRar(string testArchive, string password) => + Read(testArchive, CompressionType.Rar, new ReaderOptions { Password = password }); - private void DoRar_Entry_Stream(string filename) + [Fact] + public void Rar_Entry_Stream() => DoRar_Entry_Stream("Rar.rar"); + + [Fact] + public void Rar5_Entry_Stream() => DoRar_Entry_Stream("Rar5.rar"); + + private void DoRar_Entry_Stream(string filename) + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) + using (var reader = ReaderFactory.Open(stream)) { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - using (var reader = ReaderFactory.Open(stream)) + while (reader.MoveToNextEntry()) { - while (reader.MoveToNextEntry()) - { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - using (var entryStream = reader.OpenEntryStream()) - { - string file = Path.GetFileName(reader.Entry.Key); - string folder = Path.GetDirectoryName(reader.Entry.Key); - string destdir = Path.Combine(SCRATCH_FILES_PATH, folder); - if (!Directory.Exists(destdir)) - { - Directory.CreateDirectory(destdir); - } - string destinationFileName = Path.Combine(destdir, file); - - using (FileStream fs = File.OpenWrite(destinationFileName)) - { - entryStream.TransferTo(fs); - } - } - } - } - } - VerifyFiles(); - } - - [Fact] - public void Rar_Reader_Audio_program() - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar"))) - using (var reader = ReaderFactory.Open(stream, new ReaderOptions() - { - LookForHeader = true - })) - { - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() + using var entryStream = reader.OpenEntryStream(); + var file = Path.GetFileName(reader.Entry.Key); + var folder = + Path.GetDirectoryName(reader.Entry.Key) + ?? throw new ArgumentNullException(); + var destdir = Path.Combine(SCRATCH_FILES_PATH, folder); + if (!Directory.Exists(destdir)) { - ExtractFullPath = true, - Overwrite = true - }); - } - } - CompareFilesByPath(Path.Combine(SCRATCH_FILES_PATH, "test.dat"), - Path.Combine(MISC_TEST_FILES_PATH, "test.dat")); - } - - [Fact] - public void Rar_Jpg_Reader() - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) - using (var reader = RarReader.Open(stream, new ReaderOptions() - { - LookForHeader = true - })) - { - while (reader.MoveToNextEntry()) - { - Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - [Fact] - public void Rar_Solid_Reader() - { - Read("Rar.solid.rar", CompressionType.Rar); - } - - [Fact] - public void Rar5_Solid_Reader() - { - Read("Rar5.solid.rar", CompressionType.Rar); - } - - [Fact] - public void Rar_Solid_Skip_Reader() - { - DoRar_Solid_Skip_Reader("Rar.solid.rar"); - } - - [Fact] - public void Rar5_Solid_Skip_Reader() - { - DoRar_Solid_Skip_Reader("Rar5.solid.rar"); - } - - private void DoRar_Solid_Skip_Reader(string filename) - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - using (var reader = ReaderFactory.Open(stream, new ReaderOptions() - { - LookForHeader = true - })) - { - while (reader.MoveToNextEntry()) - { - if (reader.Entry.Key.Contains("jpg")) - { - Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); + Directory.CreateDirectory(destdir); } + var destinationFileName = Path.Combine(destdir, file); + + using var fs = File.OpenWrite(destinationFileName); + entryStream.TransferTo(fs); } } } + VerifyFiles(); + } - [Fact] - public void Rar_Reader_Skip() + [Fact] + public void Rar_Reader_Audio_program() + { + using ( + var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar")) + ) + using ( + var reader = ReaderFactory.Open(stream, new ReaderOptions() { LookForHeader = true }) + ) { - DoRar_Reader_Skip("Rar.rar"); - } - - [Fact] - public void Rar5_Reader_Skip() - { - DoRar_Reader_Skip("Rar5.rar"); - } - - private void DoRar_Reader_Skip(string filename) - { - using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - using (var reader = ReaderFactory.Open(stream, new ReaderOptions() + while (reader.MoveToNextEntry()) { - LookForHeader = true - })) + Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + CompareFilesByPath( + Path.Combine(SCRATCH_FILES_PATH, "test.dat"), + Path.Combine(MISC_TEST_FILES_PATH, "test.dat") + ); + } + + [Fact] + public void Rar_Jpg_Reader() + { + using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) + using (var reader = RarReader.Open(stream, new ReaderOptions() { LookForHeader = true })) + { + while (reader.MoveToNextEntry()) { - while (reader.MoveToNextEntry()) - { - if (reader.Entry.Key.Contains("jpg")) - { - Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } + Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + VerifyFiles(); + } + + [Fact] + public void Rar_Solid_Reader() => Read("Rar.solid.rar", CompressionType.Rar); + + [Fact] + public void Rar5_Solid_Reader() => Read("Rar5.solid.rar", CompressionType.Rar); + + [Fact] + public void Rar_Solid_Skip_Reader() => DoRar_Solid_Skip_Reader("Rar.solid.rar"); + + [Fact] + public void Rar5_Solid_Skip_Reader() => DoRar_Solid_Skip_Reader("Rar5.solid.rar"); + + private void DoRar_Solid_Skip_Reader(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var reader = ReaderFactory.Open(stream, new ReaderOptions() { LookForHeader = true }); + while (reader.MoveToNextEntry()) + { + if (reader.Entry.Key.Contains("jpg")) + { + Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + } + + [Fact] + public void Rar_Reader_Skip() => DoRar_Reader_Skip("Rar.rar"); + + [Fact] + public void Rar5_Reader_Skip() => DoRar_Reader_Skip("Rar5.rar"); + + private void DoRar_Reader_Skip(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var reader = ReaderFactory.Open(stream, new ReaderOptions() { LookForHeader = true }); + while (reader.MoveToNextEntry()) + { + if (reader.Entry.Key.Contains("jpg")) + { + Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index d5cfd9dd..be1dbe07 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -5,61 +5,65 @@ using SharpCompress.Readers; using SharpCompress.Test.Mocks; using Xunit; -namespace SharpCompress.Test +namespace SharpCompress.Test; + +public abstract class ReaderTests : TestBase { - public abstract class ReaderTests : TestBase + protected void Read( + string testArchive, + CompressionType expectedCompression, + ReaderOptions? options = null + ) { - protected void Read(string testArchive, CompressionType expectedCompression, ReaderOptions options = null) + testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); + + options ??= new ReaderOptions(); + + options.LeaveStreamOpen = true; + ReadImpl(testArchive, expectedCompression, options); + + options.LeaveStreamOpen = false; + ReadImpl(testArchive, expectedCompression, options); + VerifyFiles(); + } + + private void ReadImpl( + string testArchive, + CompressionType expectedCompression, + ReaderOptions options + ) + { + using var file = File.OpenRead(testArchive); + using var protectedStream = NonDisposingStream.Create( + new ForwardOnlyStream(file), + throwOnDispose: true + ); + using var testStream = new TestStream(protectedStream); + using (var reader = ReaderFactory.Open(testStream, options)) { - testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - - options = options ?? new ReaderOptions(); - - options.LeaveStreamOpen = true; - ReadImpl(testArchive, expectedCompression, options); - - options.LeaveStreamOpen = false; - ReadImpl(testArchive, expectedCompression, options); - VerifyFiles(); + UseReader(reader, expectedCompression); + protectedStream.ThrowOnDispose = false; + Assert.False(testStream.IsDisposed, "{nameof(testStream)} prematurely closed"); } - private void ReadImpl(string testArchive, CompressionType expectedCompression, ReaderOptions options) - { - using (var file = File.OpenRead(testArchive)) - { - using (var protectedStream = NonDisposingStream.Create(new ForwardOnlyStream(file), throwOnDispose: true)) - { - using (var testStream = new TestStream(protectedStream)) - { - using (var reader = ReaderFactory.Open(testStream, options)) - { - UseReader(reader, expectedCompression); - protectedStream.ThrowOnDispose = false; - Assert.False(testStream.IsDisposed, "{nameof(testStream)} prematurely closed"); - } + // Boolean XOR -- If the stream should be left open (true), then the stream should not be diposed (false) + // and if the stream should be closed (false), then the stream should be disposed (true) + var message = + $"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}"; + Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message); + } - // Boolean XOR -- If the stream should be left open (true), then the stream should not be diposed (false) - // and if the stream should be closed (false), then the stream should be disposed (true) - var message = $"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}"; - Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message); - } - } - } - } - - public void UseReader(IReader reader, CompressionType expectedCompression) + public void UseReader(IReader reader, CompressionType expectedCompression) + { + while (reader.MoveToNextEntry()) { - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(expectedCompression, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } + Assert.Equal(expectedCompression, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index ad1c71ed..22d45b2a 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -3,146 +3,110 @@ using SharpCompress.Common; using SharpCompress.Readers; using Xunit; -namespace SharpCompress.Test.SevenZip +namespace SharpCompress.Test.SevenZip; + +public class SevenZipArchiveTests : ArchiveTests { - public class SevenZipArchiveTests : ArchiveTests - { - [Fact] - public void SevenZipArchive_Solid_StreamRead() - { - ArchiveStreamRead("7Zip.solid.7z"); - } - [Fact] - public void SevenZipArchive_NonSolid_StreamRead() - { - ArchiveStreamRead("7Zip.nonsolid.7z"); - } - [Fact] - public void SevenZipArchive_LZMA_StreamRead() - { - ArchiveStreamRead("7Zip.LZMA.7z"); - } + [Fact] + public void SevenZipArchive_Solid_StreamRead() => ArchiveStreamRead("7Zip.solid.7z"); - [Fact] - public void SevenZipArchive_LZMA_PathRead() - { - ArchiveFileRead("7Zip.LZMA.7z"); - } + [Fact] + public void SevenZipArchive_NonSolid_StreamRead() => ArchiveStreamRead("7Zip.nonsolid.7z"); - [Fact] - public void SevenZipArchive_LZMAAES_StreamRead() - { - ArchiveStreamRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = "testpassword" }); - } + [Fact] + public void SevenZipArchive_LZMA_StreamRead() => ArchiveStreamRead("7Zip.LZMA.7z"); - [Fact] - public void SevenZipArchive_LZMAAES_PathRead() - { - ArchiveFileRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = "testpassword" }); - } - [Fact] - public void SevenZipArchive_LZMAAES_NoPasswordExceptionTest() - { - Assert.Throws(typeof(CryptographicException), () => ArchiveFileRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = null })); //was failing with ArgumentNullException not CryptographicException like rar - } - [Fact] - public void SevenZipArchive_PPMd_StreamRead() - { - ArchiveStreamRead("7Zip.PPMd.7z"); - } + [Fact] + public void SevenZipArchive_LZMA_PathRead() => ArchiveFileRead("7Zip.LZMA.7z"); - [Fact] - public void SevenZipArchive_PPMd_StreamRead_Extract_All() - { - ArchiveStreamReadExtractAll("7Zip.PPMd.7z", CompressionType.PPMd); - } + [Fact] + public void SevenZipArchive_LZMAAES_StreamRead() => + ArchiveStreamRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = "testpassword" }); - [Fact] - public void SevenZipArchive_PPMd_PathRead() - { - ArchiveFileRead("7Zip.PPMd.7z"); - } + [Fact] + public void SevenZipArchive_LZMAAES_PathRead() => + ArchiveFileRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = "testpassword" }); - [Fact] - public void SevenZipArchive_LZMA2_StreamRead() - { - ArchiveStreamRead("7Zip.LZMA2.7z"); - } + [Fact] + public void SevenZipArchive_LZMAAES_NoPasswordExceptionTest() => + Assert.Throws( + typeof(CryptographicException), + () => ArchiveFileRead("7Zip.LZMA.Aes.7z", new ReaderOptions() { Password = null }) + ); //was failing with ArgumentNullException not CryptographicException like rar - [Fact] - public void SevenZipArchive_LZMA2_PathRead() - { - ArchiveFileRead("7Zip.LZMA2.7z"); - } + [Fact] + public void SevenZipArchive_PPMd_StreamRead() => ArchiveStreamRead("7Zip.PPMd.7z"); - [Fact] - public void SevenZipArchive_LZMA2AES_StreamRead() - { - ArchiveStreamRead("7Zip.LZMA2.Aes.7z", new ReaderOptions { Password = "testpassword" }); - } + [Fact] + public void SevenZipArchive_PPMd_StreamRead_Extract_All() => + ArchiveStreamReadExtractAll("7Zip.PPMd.7z", CompressionType.PPMd); - [Fact] - public void SevenZipArchive_LZMA2AES_PathRead() - { - ArchiveFileRead("7Zip.LZMA2.Aes.7z", new ReaderOptions { Password = "testpassword" }); - } + [Fact] + public void SevenZipArchive_PPMd_PathRead() => ArchiveFileRead("7Zip.PPMd.7z"); - [Fact] - public void SevenZipArchive_BZip2_StreamRead() - { - ArchiveStreamRead("7Zip.BZip2.7z"); - } + [Fact] + public void SevenZipArchive_LZMA2_StreamRead() => ArchiveStreamRead("7Zip.LZMA2.7z"); - [Fact] - public void SevenZipArchive_BZip2_PathRead() - { - ArchiveFileRead("7Zip.BZip2.7z"); - } + [Fact] + public void SevenZipArchive_LZMA2_PathRead() => ArchiveFileRead("7Zip.LZMA2.7z"); - [Fact] - public void SevenZipArchive_LZMA_Time_Attributes_PathRead() - { - ArchiveFileReadEx("7Zip.LZMA.7z"); - } + [Fact] + public void SevenZipArchive_LZMA2AES_StreamRead() => + ArchiveStreamRead("7Zip.LZMA2.Aes.7z", new ReaderOptions { Password = "testpassword" }); - [Fact] - public void SevenZipArchive_BZip2_Split() - { - Assert.Throws(() => ArchiveStreamRead(null, "Original.7z.001", - "Original.7z.002", - "Original.7z.003", - "Original.7z.004", - "Original.7z.005", - "Original.7z.006", - "Original.7z.007")); - } + [Fact] + public void SevenZipArchive_LZMA2AES_PathRead() => + ArchiveFileRead("7Zip.LZMA2.Aes.7z", new ReaderOptions { Password = "testpassword" }); - //Same as archive as Original.7z.001 ... 007 files without the root directory 'Original\' in the archive - this caused the verify to fail - [Fact] - public void SevenZipArchive_BZip2_Split_Working() - { - ArchiveStreamMultiRead(null, "7Zip.BZip2.split.001", - "7Zip.BZip2.split.002", - "7Zip.BZip2.split.003", - "7Zip.BZip2.split.004", - "7Zip.BZip2.split.005", - "7Zip.BZip2.split.006", - "7Zip.BZip2.split.007"); - } + [Fact] + public void SevenZipArchive_BZip2_StreamRead() => ArchiveStreamRead("7Zip.BZip2.7z"); - //will detect and load other files - [Fact] - public void SevenZipArchive_BZip2_Split_FirstFileRead() - { - ArchiveFileRead("7Zip.BZip2.split.001"); - //"7Zip.BZip2.split.002", - //"7Zip.BZip2.split.003", - //"7Zip.BZip2.split.004", - //"7Zip.BZip2.split.005", - //"7Zip.BZip2.split.006", - //"7Zip.BZip2.split.007" - } + [Fact] + public void SevenZipArchive_BZip2_PathRead() => ArchiveFileRead("7Zip.BZip2.7z"); + [Fact] + public void SevenZipArchive_LZMA_Time_Attributes_PathRead() => + ArchiveFileReadEx("7Zip.LZMA.7z"); - } + [Fact] + public void SevenZipArchive_BZip2_Split() => + Assert.Throws( + () => + ArchiveStreamRead( + null, + "Original.7z.001", + "Original.7z.002", + "Original.7z.003", + "Original.7z.004", + "Original.7z.005", + "Original.7z.006", + "Original.7z.007" + ) + ); + + //Same as archive as Original.7z.001 ... 007 files without the root directory 'Original\' in the archive - this caused the verify to fail + [Fact] + public void SevenZipArchive_BZip2_Split_Working() => + ArchiveStreamMultiRead( + null, + "7Zip.BZip2.split.001", + "7Zip.BZip2.split.002", + "7Zip.BZip2.split.003", + "7Zip.BZip2.split.004", + "7Zip.BZip2.split.005", + "7Zip.BZip2.split.006", + "7Zip.BZip2.split.007" + ); + + //will detect and load other files + [Fact] + public void SevenZipArchive_BZip2_Split_FirstFileRead() => + ArchiveFileRead("7Zip.BZip2.split.001"); + + //"7Zip.BZip2.split.002", + //"7Zip.BZip2.split.003", + //"7Zip.BZip2.split.004", + //"7Zip.BZip2.split.005", + //"7Zip.BZip2.split.006", + //"7Zip.BZip2.split.007" } diff --git a/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj index a182e0a4..cbc76254 100644 --- a/tests/SharpCompress.Test/SharpCompress.Test.csproj +++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj @@ -1,21 +1,20 @@  - net6.0;net461 + net7.0;net462 SharpCompress.Test - ../../SharpCompress.snk - true - true SharpCompress.Test - true - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs index fd14be48..ad97cdd4 100644 --- a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs +++ b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs @@ -1,82 +1,531 @@ - using System; using System.IO; using SharpCompress.Compressors.LZMA; using Xunit; -namespace SharpCompress.Test.Streams +namespace SharpCompress.Test.Streams; + +public class LzmaStreamTests { - public class LzmaStreamTests + [Fact] + public void TestLzma2Decompress1Byte() { - [Fact] - public void TestLzma2Decompress1Byte() + var properties = new byte[] { 0x01 }; + var compressedData = new byte[] { 0x01, 0x00, 0x00, 0x58, 0x00 }; + var lzma2Stream = new MemoryStream(compressedData); + + var decompressor = new LzmaStream(properties, lzma2Stream, 5, 1); + Assert.Equal('X', decompressor.ReadByte()); + } + + private static byte[] lzmaData { get; } = + new byte[] { - var properties = new byte[] { 0x01 }; - var compressedData = new byte[] { 0x01, 0x00, 0x00, 0x58, 0x00 }; - var lzma2Stream = new MemoryStream(compressedData); - - var decompressor = new LzmaStream(properties, lzma2Stream, 5, 1); - Assert.Equal('X', decompressor.ReadByte()); - } - - private static byte[] lzmaData { get; } = new byte[] { - 0x5D, 0x00, 0x20, 0x00, 0x00, 0x48, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x24, 0x18, 0x2F, 0xEB, 0x20, 0x78, 0xBA, 0x78, 0x70, 0xDC, 0x43, 0x2C, 0x32, 0xC9, - 0xC3, 0x97, 0x4D, 0x10, 0x74, 0xE2, 0x20, 0xBF, 0x5A, 0xB4, 0xB3, 0xC4, 0x31, 0x80, 0x26, - 0x3E, 0x6A, 0xEA, 0x51, 0xFC, 0xE4, 0x8D, 0x54, 0x96, 0x05, 0xCC, 0x78, 0x59, 0xAC, 0xD4, - 0x21, 0x65, 0x8F, 0xA9, 0xC8, 0x0D, 0x9B, 0xE2, 0xC2, 0xF9, 0x7C, 0x3C, 0xDD, 0x4D, 0x38, - 0x04, 0x0B, 0xF8, 0x0B, 0x68, 0xA5, 0x93, 0x6C, 0x64, 0xAC, 0xCF, 0x71, 0x68, 0xE8, 0x69, - 0x25, 0xC6, 0x17, 0x28, 0xF1, 0x7C, 0xF1, 0xDC, 0x47, 0x51, 0x4D, 0x1E, 0x0E, 0x0B, 0x80, - 0x37, 0x24, 0x58, 0x80, 0xF7, 0xB4, 0xAC, 0x54, 0xF1, 0x0F, 0x7F, 0x0F, 0x0F, 0xF5, 0x9C, - 0xDE, 0x54, 0x4F, 0xA3, 0x7B, 0x20, 0xC5, 0xA8, 0x18, 0x3B, 0xED, 0xDC, 0x04, 0xF6, 0xFB, - 0x86, 0xE0, 0xAB, 0xB6, 0x87, 0x99, 0x92, 0x43, 0x7B, 0x2C, 0xCC, 0x31, 0x83, 0x90, 0xFF, - 0xF1, 0x76, 0x03, 0x90 + 0x5D, + 0x00, + 0x20, + 0x00, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x24, + 0x18, + 0x2F, + 0xEB, + 0x20, + 0x78, + 0xBA, + 0x78, + 0x70, + 0xDC, + 0x43, + 0x2C, + 0x32, + 0xC9, + 0xC3, + 0x97, + 0x4D, + 0x10, + 0x74, + 0xE2, + 0x20, + 0xBF, + 0x5A, + 0xB4, + 0xB3, + 0xC4, + 0x31, + 0x80, + 0x26, + 0x3E, + 0x6A, + 0xEA, + 0x51, + 0xFC, + 0xE4, + 0x8D, + 0x54, + 0x96, + 0x05, + 0xCC, + 0x78, + 0x59, + 0xAC, + 0xD4, + 0x21, + 0x65, + 0x8F, + 0xA9, + 0xC8, + 0x0D, + 0x9B, + 0xE2, + 0xC2, + 0xF9, + 0x7C, + 0x3C, + 0xDD, + 0x4D, + 0x38, + 0x04, + 0x0B, + 0xF8, + 0x0B, + 0x68, + 0xA5, + 0x93, + 0x6C, + 0x64, + 0xAC, + 0xCF, + 0x71, + 0x68, + 0xE8, + 0x69, + 0x25, + 0xC6, + 0x17, + 0x28, + 0xF1, + 0x7C, + 0xF1, + 0xDC, + 0x47, + 0x51, + 0x4D, + 0x1E, + 0x0E, + 0x0B, + 0x80, + 0x37, + 0x24, + 0x58, + 0x80, + 0xF7, + 0xB4, + 0xAC, + 0x54, + 0xF1, + 0x0F, + 0x7F, + 0x0F, + 0x0F, + 0xF5, + 0x9C, + 0xDE, + 0x54, + 0x4F, + 0xA3, + 0x7B, + 0x20, + 0xC5, + 0xA8, + 0x18, + 0x3B, + 0xED, + 0xDC, + 0x04, + 0xF6, + 0xFB, + 0x86, + 0xE0, + 0xAB, + 0xB6, + 0x87, + 0x99, + 0x92, + 0x43, + 0x7B, + 0x2C, + 0xCC, + 0x31, + 0x83, + 0x90, + 0xFF, + 0xF1, + 0x76, + 0x03, + 0x90 }; - private static byte[] lzmaResultData { get; } = new byte[] { - 0x01, 0x00, 0xFD, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x61, 0x18, 0x5F, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x02, 0x00, 0xB4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x61, 0xE5, 0x5E, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB4, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x61, 0x18, - 0x5F, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, - 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFD, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14, - 0x62, 0x18, 0x5F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB4, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x7F, 0x61, 0xE5, 0x5E, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0xCB, 0x15, 0x00, 0x00, 0x02, 0x00, 0xB4, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x7F, 0x61, 0xE5, 0x5E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0xCB, 0x15, 0x00, 0x00, 0x02, 0x00, 0xB4, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x61, 0xE5, 0x5E, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x02, - 0x00, 0xB4, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x96, 0x40, 0x5C, 0x08, 0x00, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x83, 0x12, - 0x00, 0xD4, 0x99, 0x00, 0x00, 0x43, 0x95, 0x00, 0x00, 0xEB, 0x7A, 0x00, 0x00, 0x40, 0x6F, - 0x00, 0x00, 0xD2, 0x6F, 0x00, 0x00, 0x67, 0x74, 0x00, 0x00, 0x02, 0x69, 0x00, 0x00, 0x76, - 0x79, 0x00, 0x00, 0x98, 0x66, 0x00, 0x00, 0x23, 0x25, 0x00, 0x00, 0x01, 0x00, 0xFD, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x3B, 0x2F, 0xC0, 0x5F, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x00, 0x69, 0x00, 0x3D, 0x00, 0x0A, 0x00, 0x00, 0x00 + private static byte[] lzmaResultData { get; } = + new byte[] + { + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFA, + 0x61, + 0x18, + 0x5F, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3D, + 0x61, + 0xE5, + 0x5E, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xE2, + 0x61, + 0x18, + 0x5F, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x14, + 0x62, + 0x18, + 0x5F, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x61, + 0xE5, + 0x5E, + 0x05, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x00, + 0x00, + 0x00, + 0xCB, + 0x15, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x61, + 0xE5, + 0x5E, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x00, + 0x00, + 0x00, + 0xCB, + 0x15, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3D, + 0x61, + 0xE5, + 0x5E, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x96, + 0x40, + 0x5C, + 0x08, + 0x00, + 0x00, + 0x00, + 0x60, + 0x00, + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x83, + 0x12, + 0x00, + 0xD4, + 0x99, + 0x00, + 0x00, + 0x43, + 0x95, + 0x00, + 0x00, + 0xEB, + 0x7A, + 0x00, + 0x00, + 0x40, + 0x6F, + 0x00, + 0x00, + 0xD2, + 0x6F, + 0x00, + 0x00, + 0x67, + 0x74, + 0x00, + 0x00, + 0x02, + 0x69, + 0x00, + 0x00, + 0x76, + 0x79, + 0x00, + 0x00, + 0x98, + 0x66, + 0x00, + 0x00, + 0x23, + 0x25, + 0x00, + 0x00, + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x2F, + 0xC0, + 0x5F, + 0x09, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x3D, + 0x00, + 0x0A, + 0x00, + 0x00, + 0x00 }; - [Fact] - public void TestLzmaBuffer() - { - var input = new MemoryStream(lzmaData); - using (var output = new MemoryStream()) - { - var properties = new byte[5]; - input.Read(properties, 0, 5); + [Fact] + public void TestLzmaBuffer() + { + var input = new MemoryStream(lzmaData); + using var output = new MemoryStream(); + var properties = new byte[5]; + input.Read(properties, 0, 5); - var fileLengthBytes = new byte[8]; - input.Read(fileLengthBytes, 0, 8); - var fileLength = BitConverter.ToInt64(fileLengthBytes, 0); + var fileLengthBytes = new byte[8]; + input.Read(fileLengthBytes, 0, 8); + var fileLength = BitConverter.ToInt64(fileLengthBytes, 0); - var coder = new Decoder(); - coder.SetDecoderProperties(properties); - coder.Code(input, output, input.Length, fileLength, null); + var coder = new Decoder(); + coder.SetDecoderProperties(properties); + coder.Code(input, output, input.Length, fileLength, null); - Assert.Equal(output.ToArray(), lzmaResultData); - } - } + Assert.Equal(output.ToArray(), lzmaResultData); } } diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs index 14beab4f..ab18da45 100644 --- a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs @@ -1,83 +1,82 @@ -using System.IO; +using System.IO; using SharpCompress.IO; using Xunit; -namespace SharpCompress.Test.Streams -{ - public class RewindableStreamTest - { - [Fact] - public void TestRewind() - { - MemoryStream ms = new MemoryStream(); - BinaryWriter bw = new BinaryWriter(ms); - bw.Write(1); - bw.Write(2); - bw.Write(3); - bw.Write(4); - bw.Write(5); - bw.Write(6); - bw.Write(7); - bw.Flush(); - ms.Position = 0; - RewindableStream stream = new RewindableStream(ms); - stream.StartRecording(); - BinaryReader br = new BinaryReader(stream); - Assert.Equal(1, br.ReadInt32()); - Assert.Equal(2, br.ReadInt32()); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - stream.Rewind(true); - stream.StartRecording(); - Assert.Equal(1, br.ReadInt32()); - Assert.Equal(2, br.ReadInt32()); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - Assert.Equal(5, br.ReadInt32()); - Assert.Equal(6, br.ReadInt32()); - Assert.Equal(7, br.ReadInt32()); - stream.Rewind(true); - stream.StartRecording(); - Assert.Equal(1, br.ReadInt32()); - Assert.Equal(2, br.ReadInt32()); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - } +namespace SharpCompress.Test.Streams; - [Fact] - public void TestIncompleteRewind() - { - MemoryStream ms = new MemoryStream(); - BinaryWriter bw = new BinaryWriter(ms); - bw.Write(1); - bw.Write(2); - bw.Write(3); - bw.Write(4); - bw.Write(5); - bw.Write(6); - bw.Write(7); - bw.Flush(); - ms.Position = 0; - RewindableStream stream = new RewindableStream(ms); - stream.StartRecording(); - BinaryReader br = new BinaryReader(stream); - Assert.Equal(1, br.ReadInt32()); - Assert.Equal(2, br.ReadInt32()); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - stream.Rewind(true); - Assert.Equal(1, br.ReadInt32()); - Assert.Equal(2, br.ReadInt32()); - stream.StartRecording(); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - Assert.Equal(5, br.ReadInt32()); - stream.Rewind(true); - Assert.Equal(3, br.ReadInt32()); - Assert.Equal(4, br.ReadInt32()); - Assert.Equal(5, br.ReadInt32()); - Assert.Equal(6, br.ReadInt32()); - Assert.Equal(7, br.ReadInt32()); - } +public class RewindableStreamTest +{ + [Fact] + public void TestRewind() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + bw.Write(1); + bw.Write(2); + bw.Write(3); + bw.Write(4); + bw.Write(5); + bw.Write(6); + bw.Write(7); + bw.Flush(); + ms.Position = 0; + var stream = new RewindableStream(ms); + stream.StartRecording(); + var br = new BinaryReader(stream); + Assert.Equal(1, br.ReadInt32()); + Assert.Equal(2, br.ReadInt32()); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + stream.Rewind(true); + stream.StartRecording(); + Assert.Equal(1, br.ReadInt32()); + Assert.Equal(2, br.ReadInt32()); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + Assert.Equal(5, br.ReadInt32()); + Assert.Equal(6, br.ReadInt32()); + Assert.Equal(7, br.ReadInt32()); + stream.Rewind(true); + stream.StartRecording(); + Assert.Equal(1, br.ReadInt32()); + Assert.Equal(2, br.ReadInt32()); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + } + + [Fact] + public void TestIncompleteRewind() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + bw.Write(1); + bw.Write(2); + bw.Write(3); + bw.Write(4); + bw.Write(5); + bw.Write(6); + bw.Write(7); + bw.Flush(); + ms.Position = 0; + var stream = new RewindableStream(ms); + stream.StartRecording(); + var br = new BinaryReader(stream); + Assert.Equal(1, br.ReadInt32()); + Assert.Equal(2, br.ReadInt32()); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + stream.Rewind(true); + Assert.Equal(1, br.ReadInt32()); + Assert.Equal(2, br.ReadInt32()); + stream.StartRecording(); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + Assert.Equal(5, br.ReadInt32()); + stream.Rewind(true); + Assert.Equal(3, br.ReadInt32()); + Assert.Equal(4, br.ReadInt32()); + Assert.Equal(5, br.ReadInt32()); + Assert.Equal(6, br.ReadInt32()); + Assert.Equal(7, br.ReadInt32()); } } diff --git a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs index 51d2085b..56f934d6 100644 --- a/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs +++ b/tests/SharpCompress.Test/Streams/ZlibBaseStreamTests.cs @@ -6,83 +6,101 @@ using SharpCompress.Compressors.Deflate; using SharpCompress.IO; using Xunit; -namespace SharpCompress.Test.Streams +namespace SharpCompress.Test.Streams; + +public class ZLibBaseStreamTests { - public class ZLibBaseStreamTests + [Fact] + public void TestChunkedZlibCompressesEverything() { - [Fact] - public void TestChunkedZlibCompressesEverything() + var plainData = new byte[] { - byte[] plainData = new byte[] { 0xf7, 0x1b, 0xda, 0x0f, 0xb6, 0x2b, 0x3d, 0x91, 0xd7, 0xe1, 0xb5, 0x11, 0x34, 0x5a, 0x51, 0x3f, 0x8b, 0xce, 0x49, 0xd2 }; - byte[] buf = new byte[plainData.Length * 2]; + 0xf7, + 0x1b, + 0xda, + 0x0f, + 0xb6, + 0x2b, + 0x3d, + 0x91, + 0xd7, + 0xe1, + 0xb5, + 0x11, + 0x34, + 0x5a, + 0x51, + 0x3f, + 0x8b, + 0xce, + 0x49, + 0xd2 + }; + var buf = new byte[plainData.Length * 2]; - MemoryStream plainStream1 = new MemoryStream(plainData); - DeflateStream compressor1 = new DeflateStream(plainStream1, CompressionMode.Compress); - // This is enough to read the entire data - int realCompressedSize = compressor1.Read(buf, 0, plainData.Length * 2); + var plainStream1 = new MemoryStream(plainData); + var compressor1 = new DeflateStream(plainStream1, CompressionMode.Compress); + // This is enough to read the entire data + var realCompressedSize = compressor1.Read(buf, 0, plainData.Length * 2); - MemoryStream plainStream2 = new MemoryStream(plainData); - DeflateStream compressor2 = new DeflateStream(plainStream2, CompressionMode.Compress); - int total = 0; - int r = -1; // Jumpstart - while (r != 0) - { - // Reading in chunks - r = compressor2.Read(buf, 0, plainData.Length); - total += r; - } - - Assert.Equal(total, realCompressedSize); - } - - [Fact] - public void Zlib_should_read_the_previously_written_message() + var plainStream2 = new MemoryStream(plainData); + var compressor2 = new DeflateStream(plainStream2, CompressionMode.Compress); + var total = 0; + var r = -1; // Jumpstart + while (r != 0) { - var message = new string('a', 131073); // 131073 causes the failure, but 131072 (-1) doesn't - var bytes = Encoding.ASCII.GetBytes(message); - - using (var inputStream = new MemoryStream(bytes)) - { - using (var compressedStream = new MemoryStream()) - using (var byteBufferStream = new BufferedStream(inputStream)) // System.IO - { - Compress(byteBufferStream, compressedStream, compressionLevel: 1); - compressedStream.Position = 0; - - using (var decompressedStream = new MemoryStream()) - { - Decompress(compressedStream, decompressedStream); - - byteBufferStream.Position = 0; - var result = Encoding.ASCII.GetString(GetBytes(byteBufferStream)); - result.Should().Be(message); - } - } - } + // Reading in chunks + r = compressor2.Read(buf, 0, plainData.Length); + total += r; } - private void Compress(Stream input, Stream output, int compressionLevel) - { - using (var zlibStream = new ZlibStream(NonDisposingStream.Create(output), CompressionMode.Compress, (CompressionLevel)compressionLevel)) - { - zlibStream.FlushMode = FlushType.Sync; - input.CopyTo(zlibStream); - } - } + Assert.Equal(total, realCompressedSize); + } - private void Decompress(Stream input, Stream output) - { - using (var zlibStream = new ZlibStream(NonDisposingStream.Create(input), CompressionMode.Decompress)) - { - zlibStream.CopyTo(output); - } - } + [Fact] + public void Zlib_should_read_the_previously_written_message() + { + var message = new string('a', 131073); // 131073 causes the failure, but 131072 (-1) doesn't + var bytes = Encoding.ASCII.GetBytes(message); - byte[] GetBytes(BufferedStream stream) - { - byte[] bytes = new byte[stream.Length]; - stream.Read(bytes, 0, (int)stream.Length); - return bytes; - } + using var inputStream = new MemoryStream(bytes); + using var compressedStream = new MemoryStream(); + using var byteBufferStream = new BufferedStream(inputStream); // System.IO + Compress(byteBufferStream, compressedStream, compressionLevel: 1); + compressedStream.Position = 0; + + using var decompressedStream = new MemoryStream(); + Decompress(compressedStream, decompressedStream); + + byteBufferStream.Position = 0; + var result = Encoding.ASCII.GetString(GetBytes(byteBufferStream)); + result.Should().Be(message); + } + + private void Compress(Stream input, Stream output, int compressionLevel) + { + using var zlibStream = new ZlibStream( + NonDisposingStream.Create(output), + CompressionMode.Compress, + (CompressionLevel)compressionLevel + ); + zlibStream.FlushMode = FlushType.Sync; + input.CopyTo(zlibStream); + } + + private void Decompress(Stream input, Stream output) + { + using var zlibStream = new ZlibStream( + NonDisposingStream.Create(input), + CompressionMode.Decompress + ); + zlibStream.CopyTo(output); + } + + private byte[] GetBytes(BufferedStream stream) + { + var bytes = new byte[stream.Length]; + stream.Read(bytes, 0, (int)stream.Length); + return bytes; } } diff --git a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs index 17da414d..fd05d15e 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -11,283 +11,294 @@ using SharpCompress.Readers; using SharpCompress.Writers.Tar; using SharpCompress.Readers.Tar; -namespace SharpCompress.Test.Tar +namespace SharpCompress.Test.Tar; + +public class TarArchiveTests : ArchiveTests { - public class TarArchiveTests : ArchiveTests + public TarArchiveTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void TarArchiveStreamRead() => ArchiveStreamRead("Tar.tar"); + + [Fact] + public void TarArchivePathRead() => ArchiveFileRead("Tar.tar"); + + [Fact] + public void Tar_FileName_Exactly_100_Characters() { - public TarArchiveTests() + string archive = "Tar_FileName_Exactly_100_Characters.tar"; + + // create the 100 char filename + string filename = + "filename_with_exactly_100_characters_______________________________________________________________X"; + + // Step 1: create a tar file containing a file with the test name + using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) + using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None)) + using (Stream inputStream = new MemoryStream()) { - UseExtensionInsteadOfNameToVerify = true; + StreamWriter sw = new StreamWriter(inputStream); + sw.Write("dummy filecontent"); + sw.Flush(); + + inputStream.Position = 0; + writer.Write(filename, inputStream, null); } - [Fact] - public void TarArchiveStreamRead() + // Step 2: check if the written tar file can be read correctly + string unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); + using (var archive2 = TarArchive.Open(unmodified)) { - ArchiveStreamRead("Tar.tar"); - } + Assert.Equal(1, archive2.Entries.Count); + Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key)); - [Fact] - public void TarArchivePathRead() - { - ArchiveFileRead("Tar.tar"); - } - - [Fact] - public void Tar_FileName_Exactly_100_Characters() - { - string archive = "Tar_FileName_Exactly_100_Characters.tar"; - - - // create the 100 char filename - string filename = "filename_with_exactly_100_characters_______________________________________________________________X"; - - // Step 1: create a tar file containing a file with the test name - using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) - using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None)) - using (Stream inputStream = new MemoryStream()) + foreach (var entry in archive2.Entries) { - StreamWriter sw = new StreamWriter(inputStream); - sw.Write("dummy filecontent"); - sw.Flush(); - - inputStream.Position = 0; - writer.Write(filename, inputStream, null); - } - - // Step 2: check if the written tar file can be read correctly - string unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) - { - Assert.Equal(1, archive2.Entries.Count); - Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key)); - - foreach (var entry in archive2.Entries) - { - Assert.Equal("dummy filecontent", new StreamReader(entry.OpenEntryStream()).ReadLine()); - } - } - } - - [Fact] - public void Tar_NonUstarArchiveWithLongNameDoesNotSkipEntriesAfterTheLongOne() - { - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "very long filename.tar"); - using (var archive = TarArchive.Open(unmodified)) - { - Assert.Equal(5, archive.Entries.Count); - Assert.Contains("very long filename/", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("very long filename/very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename.jpg", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("z_file 1.txt", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("z_file 2.txt", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("z_file 3.txt", archive.Entries.Select(entry => entry.Key)); - } - } - - [Fact] - public void Tar_VeryLongFilepathReadback() - { - string archive = "Tar_VeryLongFilepathReadback.tar"; - - - // create a very long filename - string longFilename = ""; - for (int i = 0; i < 600; i = longFilename.Length) - { - longFilename += i.ToString("D10") + "-"; - } - - longFilename += ".txt"; - - // Step 1: create a tar file containing a file with a long name - using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) - using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None)) - using (Stream inputStream = new MemoryStream()) - { - StreamWriter sw = new StreamWriter(inputStream); - sw.Write("dummy filecontent"); - sw.Flush(); - - inputStream.Position = 0; - writer.Write(longFilename, inputStream, null); - } - - // Step 2: check if the written tar file can be read correctly - string unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) - { - Assert.Equal(1, archive2.Entries.Count); - Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key)); - - foreach (var entry in archive2.Entries) - { - Assert.Equal("dummy filecontent", new StreamReader(entry.OpenEntryStream()).ReadLine()); - } - } - } - - [Fact] - public void Tar_UstarArchivePathReadLongName() - { - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "ustar with long names.tar"); - using (var archive = TarArchive.Open(unmodified)) - { - Assert.Equal(6, archive.Entries.Count); - Assert.Contains("Directory/", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("Directory/Some file with veeeeeeeeeery loooooooooong name", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("Directory/Directory with veeeeeeeeeery loooooooooong name/", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("Directory/Directory with veeeeeeeeeery loooooooooong name/Some file with veeeeeeeeeery loooooooooong name", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("Directory/Directory with veeeeeeeeeery loooooooooong name/Directory with veeeeeeeeeery loooooooooong name/", archive.Entries.Select(entry => entry.Key)); - Assert.Contains("Directory/Directory with veeeeeeeeeery loooooooooong name/Directory with veeeeeeeeeery loooooooooong name/Some file with veeeeeeeeeery loooooooooong name", archive.Entries.Select(entry => entry.Key)); - } - } - - [Fact] - public void Tar_Create_New() - { - string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - - // var aropt = new Ar - - using (var archive = TarArchive.Create()) - { - archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = new ArchiveEncoding() - { - Default = Encoding.GetEncoding(866) - }; - archive.SaveTo(scratchPath, twopt); - } - CompareArchivesByPath(unmodified, scratchPath); - } - [Fact] - public void Tar_Random_Write_Add() - { - string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); - string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); - string modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - - using (var archive = TarArchive.Open(unmodified)) - { - archive.AddEntry("jpg\\test.jpg", jpg); - archive.SaveTo(scratchPath, CompressionType.None); - } - CompareArchivesByPath(modified, scratchPath); - } - - [Fact] - public void Tar_Random_Write_Remove() - { - string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar"); - string modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - - using (var archive = TarArchive.Open(unmodified)) - { - var entry = archive.Entries.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)); - archive.RemoveEntry(entry); - archive.SaveTo(scratchPath, CompressionType.None); - } - CompareArchivesByPath(modified, scratchPath); - } - - [Fact] - public void Tar_Containing_Rar_Archive() - { - string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); - using (Stream stream = File.OpenRead(archiveFullPath)) - using (IArchive archive = ArchiveFactory.Open(stream)) - { - Assert.True(archive.Type == ArchiveType.Tar); - } - } - - [Fact] - public void Tar_Empty_Archive() - { - string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.Empty.tar"); - using (Stream stream = File.OpenRead(archiveFullPath)) - using (IArchive archive = ArchiveFactory.Open(stream)) - { - Assert.True(archive.Type == ArchiveType.Tar); - } - } - [Theory] - [InlineData(10)] - [InlineData(128)] - public void Tar_Japanese_Name(int length) - { - using (var mstm = new MemoryStream()) - { - var enc = new ArchiveEncoding() - { - Default = Encoding.UTF8 - }; - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = enc; - var fname = new string((char)0x3042, length); - using (var tw = new TarWriter(mstm, twopt)) - using (var input = new MemoryStream(new byte[32])) - { - tw.Write(fname, input, null); - } - using (var inputMemory = new MemoryStream(mstm.ToArray())) - { - var tropt = new ReaderOptions() - { - ArchiveEncoding = enc - }; - using (var tr = TarReader.Open(inputMemory, tropt)) - { - while (tr.MoveToNextEntry()) - { - Assert.Equal(fname, tr.Entry.Key); - } - } - } - } - } - - [Fact] - public void Tar_Read_One_At_A_Time() - { - var archiveEncoding = new ArchiveEncoding { Default = Encoding.UTF8, }; - var tarWriterOptions = new TarWriterOptions(CompressionType.None, true) { ArchiveEncoding = archiveEncoding, }; - var testBytes = Encoding.UTF8.GetBytes("This is a test."); - - using (var memoryStream = new MemoryStream()) - { - using (var tarWriter = new TarWriter(memoryStream, tarWriterOptions)) - using (var testFileStream = new MemoryStream(testBytes)) - { - tarWriter.Write("test1.txt", testFileStream); - testFileStream.Position = 0; - tarWriter.Write("test2.txt", testFileStream); - } - - memoryStream.Position = 0; - - var numberOfEntries = 0; - - using (var archiveFactory = TarArchive.Open(memoryStream)) - { - foreach (var entry in archiveFactory.Entries) - { - ++numberOfEntries; - - using (var tarEntryStream = entry.OpenEntryStream()) - using (var testFileStream = new MemoryStream()) - { - tarEntryStream.CopyTo(testFileStream); - Assert.Equal(testBytes.Length, testFileStream.Length); - } - } - } - - Assert.Equal(2, numberOfEntries); + Assert.Equal( + "dummy filecontent", + new StreamReader(entry.OpenEntryStream()).ReadLine() + ); } } } + + [Fact] + public void Tar_NonUstarArchiveWithLongNameDoesNotSkipEntriesAfterTheLongOne() + { + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "very long filename.tar"); + using (var archive = TarArchive.Open(unmodified)) + { + Assert.Equal(5, archive.Entries.Count); + Assert.Contains("very long filename/", archive.Entries.Select(entry => entry.Key)); + Assert.Contains( + "very long filename/very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename.jpg", + archive.Entries.Select(entry => entry.Key) + ); + Assert.Contains("z_file 1.txt", archive.Entries.Select(entry => entry.Key)); + Assert.Contains("z_file 2.txt", archive.Entries.Select(entry => entry.Key)); + Assert.Contains("z_file 3.txt", archive.Entries.Select(entry => entry.Key)); + } + } + + [Fact] + public void Tar_VeryLongFilepathReadback() + { + string archive = "Tar_VeryLongFilepathReadback.tar"; + + // create a very long filename + string longFilename = ""; + for (int i = 0; i < 600; i = longFilename.Length) + { + longFilename += i.ToString("D10") + "-"; + } + + longFilename += ".txt"; + + // Step 1: create a tar file containing a file with a long name + using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) + using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None)) + using (Stream inputStream = new MemoryStream()) + { + StreamWriter sw = new StreamWriter(inputStream); + sw.Write("dummy filecontent"); + sw.Flush(); + + inputStream.Position = 0; + writer.Write(longFilename, inputStream, null); + } + + // Step 2: check if the written tar file can be read correctly + string unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); + using (var archive2 = TarArchive.Open(unmodified)) + { + Assert.Equal(1, archive2.Entries.Count); + Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key)); + + foreach (var entry in archive2.Entries) + { + Assert.Equal( + "dummy filecontent", + new StreamReader(entry.OpenEntryStream()).ReadLine() + ); + } + } + } + + [Fact] + public void Tar_UstarArchivePathReadLongName() + { + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "ustar with long names.tar"); + using (var archive = TarArchive.Open(unmodified)) + { + Assert.Equal(6, archive.Entries.Count); + Assert.Contains("Directory/", archive.Entries.Select(entry => entry.Key)); + Assert.Contains( + "Directory/Some file with veeeeeeeeeery loooooooooong name", + archive.Entries.Select(entry => entry.Key) + ); + Assert.Contains( + "Directory/Directory with veeeeeeeeeery loooooooooong name/", + archive.Entries.Select(entry => entry.Key) + ); + Assert.Contains( + "Directory/Directory with veeeeeeeeeery loooooooooong name/Some file with veeeeeeeeeery loooooooooong name", + archive.Entries.Select(entry => entry.Key) + ); + Assert.Contains( + "Directory/Directory with veeeeeeeeeery loooooooooong name/Directory with veeeeeeeeeery loooooooooong name/", + archive.Entries.Select(entry => entry.Key) + ); + Assert.Contains( + "Directory/Directory with veeeeeeeeeery loooooooooong name/Directory with veeeeeeeeeery loooooooooong name/Some file with veeeeeeeeeery loooooooooong name", + archive.Entries.Select(entry => entry.Key) + ); + } + } + + [Fact] + public void Tar_Create_New() + { + string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); + + // var aropt = new Ar + + using (var archive = TarArchive.Create()) + { + archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); + var twopt = new TarWriterOptions(CompressionType.None, true); + twopt.ArchiveEncoding = new ArchiveEncoding() { Default = Encoding.GetEncoding(866) }; + archive.SaveTo(scratchPath, twopt); + } + CompareArchivesByPath(unmodified, scratchPath); + } + + [Fact] + public void Tar_Random_Write_Add() + { + string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); + string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); + string modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); + + using (var archive = TarArchive.Open(unmodified)) + { + archive.AddEntry("jpg\\test.jpg", jpg); + archive.SaveTo(scratchPath, CompressionType.None); + } + CompareArchivesByPath(modified, scratchPath); + } + + [Fact] + public void Tar_Random_Write_Remove() + { + string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar"); + string modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); + + using (var archive = TarArchive.Open(unmodified)) + { + var entry = archive.Entries.Single( + x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ); + archive.RemoveEntry(entry); + archive.SaveTo(scratchPath, CompressionType.None); + } + CompareArchivesByPath(modified, scratchPath); + } + + [Fact] + public void Tar_Containing_Rar_Archive() + { + string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); + using (Stream stream = File.OpenRead(archiveFullPath)) + using (IArchive archive = ArchiveFactory.Open(stream)) + { + Assert.True(archive.Type == ArchiveType.Tar); + } + } + + [Fact] + public void Tar_Empty_Archive() + { + string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.Empty.tar"); + using (Stream stream = File.OpenRead(archiveFullPath)) + using (IArchive archive = ArchiveFactory.Open(stream)) + { + Assert.True(archive.Type == ArchiveType.Tar); + } + } + + [Theory] + [InlineData(10)] + [InlineData(128)] + public void Tar_Japanese_Name(int length) + { + using (var mstm = new MemoryStream()) + { + var enc = new ArchiveEncoding() { Default = Encoding.UTF8 }; + var twopt = new TarWriterOptions(CompressionType.None, true); + twopt.ArchiveEncoding = enc; + var fname = new string((char)0x3042, length); + using (var tw = new TarWriter(mstm, twopt)) + using (var input = new MemoryStream(new byte[32])) + { + tw.Write(fname, input, null); + } + using (var inputMemory = new MemoryStream(mstm.ToArray())) + { + var tropt = new ReaderOptions() { ArchiveEncoding = enc }; + using (var tr = TarReader.Open(inputMemory, tropt)) + { + while (tr.MoveToNextEntry()) + { + Assert.Equal(fname, tr.Entry.Key); + } + } + } + } + } + + [Fact] + public void Tar_Read_One_At_A_Time() + { + var archiveEncoding = new ArchiveEncoding { Default = Encoding.UTF8, }; + var tarWriterOptions = new TarWriterOptions(CompressionType.None, true) + { + ArchiveEncoding = archiveEncoding, + }; + var testBytes = Encoding.UTF8.GetBytes("This is a test."); + + using (var memoryStream = new MemoryStream()) + { + using (var tarWriter = new TarWriter(memoryStream, tarWriterOptions)) + using (var testFileStream = new MemoryStream(testBytes)) + { + tarWriter.Write("test1.txt", testFileStream); + testFileStream.Position = 0; + tarWriter.Write("test2.txt", testFileStream); + } + + memoryStream.Position = 0; + + var numberOfEntries = 0; + + using (var archiveFactory = TarArchive.Open(memoryStream)) + { + foreach (var entry in archiveFactory.Entries) + { + ++numberOfEntries; + + using (var tarEntryStream = entry.OpenEntryStream()) + using (var testFileStream = new MemoryStream()) + { + tarEntryStream.CopyTo(testFileStream); + Assert.Equal(testBytes.Length, testFileStream.Length); + } + } + } + + Assert.Equal(2, numberOfEntries); + } + } } diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs index 26e7b127..a92a3c0a 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using SharpCompress.Common; using SharpCompress.Readers; @@ -6,251 +7,248 @@ using SharpCompress.Readers.Tar; using SharpCompress.Test.Mocks; using Xunit; -namespace SharpCompress.Test.Tar +namespace SharpCompress.Test.Tar; + +public class TarReaderTests : ReaderTests { - public class TarReaderTests : ReaderTests + public TarReaderTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void Tar_Reader() => Read("Tar.tar", CompressionType.None); + + [Fact] + public void Tar_Skip() { - public TarReaderTests() + using ( + Stream stream = new ForwardOnlyStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")) + ) + ) + using (IReader reader = ReaderFactory.Open(stream)) { - UseExtensionInsteadOfNameToVerify = true; - } - - [Fact] - public void Tar_Reader() - { - Read("Tar.tar", CompressionType.None); - } - - [Fact] - public void Tar_Skip() - { - using (Stream stream = new ForwardOnlyStream(File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")))) - using (IReader reader = ReaderFactory.Open(stream)) + int x = 0; + while (reader.MoveToNextEntry()) { - int x = 0; - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) + x++; + if (x % 2 == 0) { - x++; - if (x % 2 == 0) + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + } + } + } + + [Fact] + public void Tar_BZip2_Reader() => Read("Tar.tar.bz2", CompressionType.BZip2); + + [Fact] + public void Tar_GZip_Reader() => Read("Tar.tar.gz", CompressionType.GZip); + + [Fact] + public void Tar_LZip_Reader() => Read("Tar.tar.lz", CompressionType.LZip); + + [Fact] + public void Tar_Xz_Reader() => Read("Tar.tar.xz", CompressionType.Xz); + + [Fact] + public void Tar_BZip2_Entry_Stream() + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) + using (var reader = TarReader.Open(stream)) + { + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); + using (var entryStream = reader.OpenEntryStream()) + { + string file = Path.GetFileName(reader.Entry.Key); + string folder = + Path.GetDirectoryName(reader.Entry.Key) + ?? throw new ArgumentNullException(); + string destdir = Path.Combine(SCRATCH_FILES_PATH, folder); + if (!Directory.Exists(destdir)) { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); + Directory.CreateDirectory(destdir); + } + string destinationFileName = Path.Combine(destdir, file); + + using (FileStream fs = File.OpenWrite(destinationFileName)) + { + entryStream.TransferTo(fs); } } } } } + VerifyFiles(); + } - [Fact] - public void Tar_BZip2_Reader() - { - Read("Tar.tar.bz2", CompressionType.BZip2); - } + [Fact] + public void Tar_LongNamesWithLongNameExtension() + { + var filePaths = new List(); - [Fact] - public void Tar_GZip_Reader() + using ( + Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "Tar.LongPathsWithLongNameExtension.tar") + ) + ) + using (var reader = TarReader.Open(stream)) { - Read("Tar.tar.gz", CompressionType.GZip); - } - - [Fact] - public void Tar_LZip_Reader() - { - Read("Tar.tar.lz", CompressionType.LZip); - } - - [Fact] - public void Tar_Xz_Reader() - { - Read("Tar.tar.xz", CompressionType.Xz); - } - - [Fact] - public void Tar_BZip2_Entry_Stream() - { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) - using (var reader = TarReader.Open(stream)) + while (reader.MoveToNextEntry()) { - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); - using (var entryStream = reader.OpenEntryStream()) - { - string file = Path.GetFileName(reader.Entry.Key); - string folder = Path.GetDirectoryName(reader.Entry.Key); - string destdir = Path.Combine(SCRATCH_FILES_PATH, folder); - if (!Directory.Exists(destdir)) - { - Directory.CreateDirectory(destdir); - } - string destinationFileName = Path.Combine(destdir, file); + filePaths.Add(reader.Entry.Key); + } + } + } - using (FileStream fs = File.OpenWrite(destinationFileName)) - { - entryStream.TransferTo(fs); - } - } + Assert.Equal(3, filePaths.Count); + Assert.Contains("a.txt", filePaths); + Assert.Contains( + "wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php", + filePaths + ); + Assert.Contains( + "wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Foo.php", + filePaths + ); + } + + [Fact] + public void Tar_BZip2_Skip_Entry_Stream() + { + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) + using (var reader = TarReader.Open(stream)) + { + List names = new List(); + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); + using (var entryStream = reader.OpenEntryStream()) + { + entryStream.SkipEntry(); + names.Add(reader.Entry.Key); } } } - VerifyFiles(); + Assert.Equal(3, names.Count); } + } - [Fact] - public void Tar_LongNamesWithLongNameExtension() + [Fact] + public void Tar_Containing_Rar_Reader() + { + string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); + using (Stream stream = File.OpenRead(archiveFullPath)) + using (IReader reader = ReaderFactory.Open(stream)) { - var filePaths = new List(); + Assert.True(reader.ArchiveType == ArchiveType.Tar); + } + } - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.LongPathsWithLongNameExtension.tar"))) - using (var reader = TarReader.Open(stream)) + [Fact] + public void Tar_With_TarGz_With_Flushed_EntryStream() + { + string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsTarGz.tar"); + using (Stream stream = File.OpenRead(archiveFullPath)) + using (IReader reader = ReaderFactory.Open(stream)) + { + Assert.True(reader.MoveToNextEntry()); + Assert.Equal("inner.tar.gz", reader.Entry.Key); + + using (var entryStream = reader.OpenEntryStream()) { - while (reader.MoveToNextEntry()) + using (FlushOnDisposeStream flushingStream = new FlushOnDisposeStream(entryStream)) { - if (!reader.Entry.IsDirectory) + // Extract inner.tar.gz + using (var innerReader = ReaderFactory.Open(flushingStream)) { - filePaths.Add(reader.Entry.Key); - } - } - } - - Assert.Equal(3, filePaths.Count); - Assert.Contains("a.txt", filePaths); - Assert.Contains("wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php", filePaths); - Assert.Contains("wp-content/plugins/gravityformsextend/lib/Aws/Symfony/Component/ClassLoader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Foo.php", filePaths); - } - - [Fact] - public void Tar_BZip2_Skip_Entry_Stream() - { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) - using (var reader = TarReader.Open(stream)) - { - List names = new List(); - while (reader.MoveToNextEntry()) - { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); - using (var entryStream = reader.OpenEntryStream()) - { - entryStream.SkipEntry(); - names.Add(reader.Entry.Key); - } - } - } - Assert.Equal(3, names.Count); - } - } - - [Fact] - public void Tar_Containing_Rar_Reader() - { - string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); - using (Stream stream = File.OpenRead(archiveFullPath)) - using (IReader reader = ReaderFactory.Open(stream)) - { - Assert.True(reader.ArchiveType == ArchiveType.Tar); - } - } - - [Fact] - public void Tar_With_TarGz_With_Flushed_EntryStream() - { - string archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsTarGz.tar"); - using (Stream stream = File.OpenRead(archiveFullPath)) - using (IReader reader = ReaderFactory.Open(stream)) - { - Assert.True(reader.MoveToNextEntry()); - Assert.Equal("inner.tar.gz", reader.Entry.Key); - - using (var entryStream = reader.OpenEntryStream()) - { - - using (FlushOnDisposeStream flushingStream = new FlushOnDisposeStream(entryStream)) - { - - // Extract inner.tar.gz - using (var innerReader = ReaderFactory.Open(flushingStream)) - { - - Assert.True(innerReader.MoveToNextEntry()); - Assert.Equal("test", innerReader.Entry.Key); - - } + Assert.True(innerReader.MoveToNextEntry()); + Assert.Equal("test", innerReader.Entry.Key); } } } } + } #if !NETFRAMEWORK - [Fact] - public void Tar_GZip_With_Symlink_Entries() + [Fact] + public void Tar_GZip_With_Symlink_Entries() + { + var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows + ); + using ( + Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz")) + ) + using (var reader = TarReader.Open(stream)) { - var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( - System.Runtime.InteropServices.OSPlatform.Windows); - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz"))) - using (var reader = TarReader.Open(stream)) + List names = new List(); + while (reader.MoveToNextEntry()) { - List names = new List(); - while (reader.MoveToNextEntry()) + if (reader.Entry.IsDirectory) { - if (reader.Entry.IsDirectory) + continue; + } + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { - continue; - } - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true, - WriteSymbolicLink = (sourcePath, targetPath) => - { - if (!isWindows) - { - var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); - if (File.Exists(sourcePath)) - { - link.Delete(); // equivalent to ln -s -f - } - link.CreateSymbolicLinkTo(targetPath); - } - } - }); - if (!isWindows) - { - if (reader.Entry.LinkTarget != null) + ExtractFullPath = true, + Overwrite = true, + WriteSymbolicLink = (sourcePath, targetPath) => { - var path = System.IO.Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key); - var link = new Mono.Unix.UnixSymbolicLinkInfo(path); - if (link.HasContents) + if (!isWindows) { - // need to convert the link to an absolute path for comparison - var target = reader.Entry.LinkTarget; - var realTarget = System.IO.Path.GetFullPath( - System.IO.Path.Combine($"{System.IO.Path.GetDirectoryName(path)}", - target) - ); + var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); + if (File.Exists(sourcePath)) + { + link.Delete(); // equivalent to ln -s -f + } + link.CreateSymbolicLinkTo(targetPath); + } + } + } + ); + if (!isWindows) + { + if (reader.Entry.LinkTarget != null) + { + var path = System.IO.Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key); + var link = new Mono.Unix.UnixSymbolicLinkInfo(path); + if (link.HasContents) + { + // need to convert the link to an absolute path for comparison + var target = reader.Entry.LinkTarget; + var realTarget = System.IO.Path.GetFullPath( + System.IO.Path.Combine( + $"{System.IO.Path.GetDirectoryName(path)}", + target + ) + ); - Assert.Equal(realTarget, link.GetContents().ToString()); - } - else - { - Assert.True(false, "Symlink has no target"); - } + Assert.Equal(realTarget, link.GetContents().ToString()); + } + else + { + Assert.True(false, "Symlink has no target"); } } } } } -#endif - } +#endif } diff --git a/tests/SharpCompress.Test/Tar/TarWriterTests.cs b/tests/SharpCompress.Test/Tar/TarWriterTests.cs index e38e61f9..4be9f9d8 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterTests.cs @@ -4,57 +4,66 @@ using SharpCompress.Common; using SharpCompress.Writers.Tar; using Xunit; -namespace SharpCompress.Test.Tar +namespace SharpCompress.Test.Tar; + +public class TarWriterTests : WriterTests { - public class TarWriterTests : WriterTests + public TarWriterTests() : base(ArchiveType.Tar) => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void Tar_Writer() => + Write( + CompressionType.None, + "Tar.noEmptyDirs.tar", + "Tar.noEmptyDirs.tar", + Encoding.GetEncoding(866) + ); + + [Fact] + public void Tar_BZip2_Writer() => + Write( + CompressionType.BZip2, + "Tar.noEmptyDirs.tar.bz2", + "Tar.noEmptyDirs.tar.bz2", + Encoding.GetEncoding(866) + ); + + [Fact] + public void Tar_LZip_Writer() => + Write( + CompressionType.LZip, + "Tar.noEmptyDirs.tar.lz", + "Tar.noEmptyDirs.tar.lz", + Encoding.GetEncoding(866) + ); + + [Fact] + public void Tar_Rar_Write() => + Assert.Throws( + () => Write(CompressionType.Rar, "Zip.ppmd.noEmptyDirs.zip", "Zip.ppmd.noEmptyDirs.zip") + ); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Tar_Finalize_Archive(bool finalizeArchive) { - public TarWriterTests() - : base(ArchiveType.Tar) + using var stream = new MemoryStream(); + using Stream content = File.OpenRead(Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg")); + using ( + var writer = new TarWriter( + stream, + new TarWriterOptions(CompressionType.None, finalizeArchive) + ) + ) { - UseExtensionInsteadOfNameToVerify = true; + writer.Write("doesn't matter", content, null); } - [Fact] - public void Tar_Writer() - { - Write(CompressionType.None, "Tar.noEmptyDirs.tar", "Tar.noEmptyDirs.tar", Encoding.GetEncoding(866)); - } - - [Fact] - public void Tar_BZip2_Writer() - { - Write(CompressionType.BZip2, "Tar.noEmptyDirs.tar.bz2", "Tar.noEmptyDirs.tar.bz2", Encoding.GetEncoding(866)); - } - - [Fact] - public void Tar_LZip_Writer() - { - Write(CompressionType.LZip, "Tar.noEmptyDirs.tar.lz", "Tar.noEmptyDirs.tar.lz", Encoding.GetEncoding(866)); - } - - [Fact] - public void Tar_Rar_Write() - { - Assert.Throws(() => Write(CompressionType.Rar, "Zip.ppmd.noEmptyDirs.zip", "Zip.ppmd.noEmptyDirs.zip")); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Tar_Finalize_Archive(bool finalizeArchive) - { - using (MemoryStream stream = new MemoryStream()) - using (Stream content = File.OpenRead(Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"))) - { - using (TarWriter writer = new TarWriter(stream, new TarWriterOptions(CompressionType.None, finalizeArchive))) - { - writer.Write("doesn't matter", content, null); - } - - var paddedContentWithHeader = content.Length / 512 * 512 + 512 + 512; - var expectedStreamLength = finalizeArchive ? paddedContentWithHeader + 512 * 2 : paddedContentWithHeader; - Assert.Equal(expectedStreamLength, stream.Length); - } - } + var paddedContentWithHeader = (content.Length / 512 * 512) + 512 + 512; + var expectedStreamLength = finalizeArchive + ? paddedContentWithHeader + (512 * 2) + : paddedContentWithHeader; + Assert.Equal(expectedStreamLength, stream.Length); } } diff --git a/tests/SharpCompress.Test/TestBase.cs b/tests/SharpCompress.Test/TestBase.cs index 76a85c49..ddfd0096 100644 --- a/tests/SharpCompress.Test/TestBase.cs +++ b/tests/SharpCompress.Test/TestBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,214 +6,215 @@ using System.Text; using SharpCompress.Readers; using Xunit; -namespace SharpCompress.Test +namespace SharpCompress.Test; + +public class TestBase : IDisposable { - public class TestBase : IDisposable + private string SOLUTION_BASE_PATH; + protected string TEST_ARCHIVES_PATH; + protected string ORIGINAL_FILES_PATH; + protected string MISC_TEST_FILES_PATH; + private string SCRATCH_BASE_PATH; + public string SCRATCH_FILES_PATH; + protected string SCRATCH2_FILES_PATH; + + public TestBase() { - private string SOLUTION_BASE_PATH; - protected string TEST_ARCHIVES_PATH; - protected string ORIGINAL_FILES_PATH; - protected string MISC_TEST_FILES_PATH; - private string SCRATCH_BASE_PATH; - public string SCRATCH_FILES_PATH; - protected string SCRATCH2_FILES_PATH; + var index = AppDomain.CurrentDomain.BaseDirectory.IndexOf( + "SharpCompress.Test", + StringComparison.OrdinalIgnoreCase + ); + var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, index); + SOLUTION_BASE_PATH = Path.GetDirectoryName(path) ?? throw new ArgumentNullException(); - public TestBase() + TEST_ARCHIVES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Archives"); + ORIGINAL_FILES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Original"); + MISC_TEST_FILES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "MiscTest"); + + SCRATCH_BASE_PATH = Path.Combine( + SOLUTION_BASE_PATH, + "TestArchives", + Guid.NewGuid().ToString() + ); + SCRATCH_FILES_PATH = Path.Combine(SCRATCH_BASE_PATH, "Scratch"); + SCRATCH2_FILES_PATH = Path.Combine(SCRATCH_BASE_PATH, "Scratch2"); + + Directory.CreateDirectory(SCRATCH_FILES_PATH); + Directory.CreateDirectory(SCRATCH2_FILES_PATH); + } + + public void Dispose() => Directory.Delete(SCRATCH_BASE_PATH, true); + + public void VerifyFiles() + { + if (UseExtensionInsteadOfNameToVerify) { - var index = AppDomain.CurrentDomain.BaseDirectory.IndexOf("SharpCompress.Test", StringComparison.OrdinalIgnoreCase); - SOLUTION_BASE_PATH = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory.Substring(0, index)); + VerifyFilesByExtension(); + } + else + { + VerifyFilesByName(); + } + } - TEST_ARCHIVES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Archives"); - ORIGINAL_FILES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Original"); - MISC_TEST_FILES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "MiscTest"); + /// + /// Verifies the files also check modified time and attributes. + /// + public void VerifyFilesEx() + { + if (UseExtensionInsteadOfNameToVerify) + { + VerifyFilesByExtensionEx(); + } + else + { + VerifyFilesByNameEx(); + } + } - SCRATCH_BASE_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", Guid.NewGuid().ToString()); - SCRATCH_FILES_PATH = Path.Combine(SCRATCH_BASE_PATH, "Scratch"); - SCRATCH2_FILES_PATH = Path.Combine(SCRATCH_BASE_PATH, "Scratch2"); + protected void VerifyFilesByName() + { + var extracted = Directory + .EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => path.Substring(SCRATCH_FILES_PATH.Length)); + var original = Directory + .EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => path.Substring(ORIGINAL_FILES_PATH.Length)); - Directory.CreateDirectory(SCRATCH_FILES_PATH); - Directory.CreateDirectory(SCRATCH2_FILES_PATH); + Assert.Equal(extracted.Count, original.Count); + + foreach (var orig in original) + { + Assert.True(extracted.Contains(orig.Key)); + + CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); + } + } + + /// + /// Verifies the files by name also check modified time and attributes. + /// + protected void VerifyFilesByNameEx() + { + var extracted = Directory + .EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => path.Substring(SCRATCH_FILES_PATH.Length)); + var original = Directory + .EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => path.Substring(ORIGINAL_FILES_PATH.Length)); + + Assert.Equal(extracted.Count, original.Count); + + foreach (var orig in original) + { + Assert.True(extracted.Contains(orig.Key)); + + CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); + CompareFilesByTimeAndAttribut(orig.Single(), extracted[orig.Key].Single()); + } + } + + /// + /// Verifies the files by extension also check modified time and attributes. + /// + protected void VerifyFilesByExtensionEx() + { + var extracted = Directory + .EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => Path.GetExtension(path)); + var original = Directory + .EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => Path.GetExtension(path)); + + Assert.Equal(extracted.Count, original.Count); + + foreach (var orig in original) + { + Assert.True(extracted.Contains(orig.Key)); + + CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); + CompareFilesByTimeAndAttribut(orig.Single(), extracted[orig.Key].Single()); + } + } + + protected bool UseExtensionInsteadOfNameToVerify { get; set; } + + protected void VerifyFilesByExtension() + { + var extracted = Directory + .EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => Path.GetExtension(path)); + var original = Directory + .EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) + .ToLookup(path => Path.GetExtension(path)); + + Assert.Equal(extracted.Count, original.Count); + + foreach (var orig in original) + { + Assert.True(extracted.Contains(orig.Key)); + + CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); + } + } + + protected void CompareFilesByPath(string file1, string file2) + { + //TODO: fix line ending issues with the text file + if (file1.EndsWith("txt", StringComparison.OrdinalIgnoreCase)) + { + return; } - public void Dispose() + using var file1Stream = File.OpenRead(file1); + using var file2Stream = File.OpenRead(file2); + Assert.Equal(file1Stream.Length, file2Stream.Length); + var byte1 = 0; + var byte2 = 0; + for (var counter = 0; byte1 != -1; counter++) { - Directory.Delete(SCRATCH_BASE_PATH, true); - } - - public void VerifyFiles() - { - if (UseExtensionInsteadOfNameToVerify) + byte1 = file1Stream.ReadByte(); + byte2 = file2Stream.ReadByte(); + if (byte1 != byte2) { - VerifyFilesByExtension(); - } - else - { - VerifyFilesByName(); + //string.Format("Byte {0} differ between {1} and {2}", counter, file1, file2) + Assert.Equal(byte1, byte2); } } + } - /// - /// Verifies the files also check modified time and attributes. - /// - public void VerifyFilesEx() + protected void CompareFilesByTimeAndAttribut(string file1, string file2) + { + var fi1 = new FileInfo(file1); + var fi2 = new FileInfo(file2); + Assert.NotEqual(fi1.LastWriteTime, fi2.LastWriteTime); + Assert.Equal(fi1.Attributes, fi2.Attributes); + } + + protected void CompareArchivesByPath(string file1, string file2, Encoding? encoding = null) + { + var readerOptions = new ReaderOptions { LeaveStreamOpen = false }; + readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; + + //don't compare the order. OS X reads files from the file system in a different order therefore makes the archive ordering different + var archive1Entries = new List(); + var archive2Entries = new List(); + using (var archive1 = ReaderFactory.Open(File.OpenRead(file1), readerOptions)) + using (var archive2 = ReaderFactory.Open(File.OpenRead(file2), readerOptions)) { - if (UseExtensionInsteadOfNameToVerify) + while (archive1.MoveToNextEntry()) { - VerifyFilesByExtensionEx(); - } - else - { - VerifyFilesByNameEx(); + Assert.True(archive2.MoveToNextEntry()); + archive1Entries.Add(archive1.Entry.Key); + archive2Entries.Add(archive2.Entry.Key); } + Assert.False(archive2.MoveToNextEntry()); } - - protected void VerifyFilesByName() + archive1Entries.Sort(); + archive2Entries.Sort(); + for (var i = 0; i < archive1Entries.Count; i++) { - var extracted = - Directory.EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => path.Substring(SCRATCH_FILES_PATH.Length)); - var original = - Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => path.Substring(ORIGINAL_FILES_PATH.Length)); - - Assert.Equal(extracted.Count, original.Count); - - foreach (var orig in original) - { - Assert.True(extracted.Contains(orig.Key)); - - CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); - } + Assert.Equal(archive1Entries[i], archive2Entries[i]); } - - /// - /// Verifies the files by name also check modified time and attributes. - /// - protected void VerifyFilesByNameEx() - { - var extracted = - Directory.EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => path.Substring(SCRATCH_FILES_PATH.Length)); - var original = - Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => path.Substring(ORIGINAL_FILES_PATH.Length)); - - Assert.Equal(extracted.Count, original.Count); - - foreach (var orig in original) - { - Assert.True(extracted.Contains(orig.Key)); - - CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); - CompareFilesByTimeAndAttribut(orig.Single(), extracted[orig.Key].Single()); - } - } - - /// - /// Verifies the files by extension also check modified time and attributes. - /// - protected void VerifyFilesByExtensionEx() - { - var extracted = - Directory.EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => Path.GetExtension(path)); - var original = - Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => Path.GetExtension(path)); - - Assert.Equal(extracted.Count, original.Count); - - foreach (var orig in original) - { - Assert.True(extracted.Contains(orig.Key)); - - CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); - CompareFilesByTimeAndAttribut(orig.Single(), extracted[orig.Key].Single()); - } - } - - protected bool UseExtensionInsteadOfNameToVerify { get; set; } - - protected void VerifyFilesByExtension() - { - var extracted = - Directory.EnumerateFiles(SCRATCH_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => Path.GetExtension(path)); - var original = - Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories) - .ToLookup(path => Path.GetExtension(path)); - - Assert.Equal(extracted.Count, original.Count); - - foreach (var orig in original) - { - Assert.True(extracted.Contains(orig.Key)); - - CompareFilesByPath(orig.Single(), extracted[orig.Key].Single()); - } - } - - protected void CompareFilesByPath(string file1, string file2) - { - //TODO: fix line ending issues with the text file - if (file1.EndsWith("txt", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - using (var file1Stream = File.OpenRead(file1)) - using (var file2Stream = File.OpenRead(file2)) - { - Assert.Equal(file1Stream.Length, file2Stream.Length); - int byte1 = 0; - int byte2 = 0; - for (int counter = 0; byte1 != -1; counter++) - { - byte1 = file1Stream.ReadByte(); - byte2 = file2Stream.ReadByte(); - if (byte1 != byte2) - { - //string.Format("Byte {0} differ between {1} and {2}", counter, file1, file2) - Assert.Equal(byte1, byte2); - } - } - } - } - - protected void CompareFilesByTimeAndAttribut(string file1, string file2) - { - FileInfo fi1 = new FileInfo(file1); - FileInfo fi2 = new FileInfo(file2); - Assert.NotEqual(fi1.LastWriteTime, fi2.LastWriteTime); - Assert.Equal(fi1.Attributes, fi2.Attributes); - } - - protected void CompareArchivesByPath(string file1, string file2, Encoding encoding = null) - { - ReaderOptions readerOptions = new ReaderOptions { LeaveStreamOpen = false }; - readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - - //don't compare the order. OS X reads files from the file system in a different order therefore makes the archive ordering different - var archive1Entries = new List(); - var archive2Entries = new List(); - using (var archive1 = ReaderFactory.Open(File.OpenRead(file1), readerOptions)) - using (var archive2 = ReaderFactory.Open(File.OpenRead(file2), readerOptions)) - { - while (archive1.MoveToNextEntry()) - { - Assert.True(archive2.MoveToNextEntry()); - archive1Entries.Add(archive1.Entry.Key); - archive2Entries.Add(archive2.Entry.Key); - } - Assert.False(archive2.MoveToNextEntry()); - } - archive1Entries.Sort(); - archive2Entries.Sort(); - for (int i = 0; i < archive1Entries.Count; i++) - { - Assert.Equal(archive1Entries[i], archive2Entries[i]); - } - } - } } diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index 53c66307..64ed6d2c 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -5,51 +5,47 @@ using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Writers; -namespace SharpCompress.Test +namespace SharpCompress.Test; + +public class WriterTests : TestBase { - public class WriterTests : TestBase + private readonly ArchiveType type; + + protected WriterTests(ArchiveType type) => this.type = type; + + protected void Write( + CompressionType compressionType, + string archive, + string archiveToVerifyAgainst, + Encoding? encoding = null + ) { - private readonly ArchiveType type; - - protected WriterTests(ArchiveType type) + using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) { - this.type = type; - } + var writerOptions = new WriterOptions(compressionType) { LeaveStreamOpen = true, }; - protected void Write(CompressionType compressionType, string archive, string archiveToVerifyAgainst, Encoding encoding = null) + writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; + + using var writer = WriterFactory.Open(stream, type, writerOptions); + writer.WriteAll(ORIGINAL_FILES_PATH, "*", SearchOption.AllDirectories); + } + CompareArchivesByPath( + Path.Combine(SCRATCH2_FILES_PATH, archive), + Path.Combine(TEST_ARCHIVES_PATH, archiveToVerifyAgainst) + ); + + using (Stream stream = File.OpenRead(Path.Combine(SCRATCH2_FILES_PATH, archive))) { - using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) - { - WriterOptions writerOptions = new WriterOptions(compressionType) - { - LeaveStreamOpen = true, - }; + var readerOptions = new ReaderOptions(); - writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; + readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using (var writer = WriterFactory.Open(stream, type, writerOptions)) - { - writer.WriteAll(ORIGINAL_FILES_PATH, "*", SearchOption.AllDirectories); - } - } - CompareArchivesByPath(Path.Combine(SCRATCH2_FILES_PATH, archive), - Path.Combine(TEST_ARCHIVES_PATH, archiveToVerifyAgainst)); - - using (Stream stream = File.OpenRead(Path.Combine(SCRATCH2_FILES_PATH, archive))) - { - ReaderOptions readerOptions = new ReaderOptions(); - - readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - - using (var reader = ReaderFactory.Open(NonDisposingStream.Create(stream), readerOptions)) - { - reader.WriteAllToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true - }); - } - } - VerifyFiles(); + using var reader = ReaderFactory.Open(NonDisposingStream.Create(stream), readerOptions); + reader.WriteAllToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true } + ); } + VerifyFiles(); } } diff --git a/tests/SharpCompress.Test/Xz/Crc32Tests.cs b/tests/SharpCompress.Test/Xz/Crc32Tests.cs index 95082872..52b71078 100644 --- a/tests/SharpCompress.Test/Xz/Crc32Tests.cs +++ b/tests/SharpCompress.Test/Xz/Crc32Tests.cs @@ -1,32 +1,30 @@ -using SharpCompress.Compressors.Xz; -using System; using System.Text; +using SharpCompress.Compressors.Xz; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class Crc32Tests { - public class Crc32Tests + private const string SimpleString = @"The quick brown fox jumps over the lazy dog."; + private readonly byte[] SimpleBytes = Encoding.ASCII.GetBytes(SimpleString); + private const string SimpleString2 = + @"Life moves pretty fast. If you don't stop and look around once in a while, you could miss it."; + private readonly byte[] SimpleBytes2 = Encoding.ASCII.GetBytes(SimpleString2); + + [Fact] + public void ShortAsciiString() { - private const string SimpleString = @"The quick brown fox jumps over the lazy dog."; - private readonly byte[] SimpleBytes = Encoding.ASCII.GetBytes(SimpleString); - private const string SimpleString2 = @"Life moves pretty fast. If you don't stop and look around once in a while, you could miss it."; - private readonly byte[] SimpleBytes2 = Encoding.ASCII.GetBytes(SimpleString2); + var actual = Crc32.Compute(SimpleBytes); - [Fact] - public void ShortAsciiString() - { - var actual = Crc32.Compute(SimpleBytes); + Assert.Equal((uint)0x519025e9, actual); + } - Assert.Equal((UInt32)0x519025e9, actual); - } - - [Fact] - public void ShortAsciiString2() - { - var actual = Crc32.Compute(SimpleBytes2); - - Assert.Equal((UInt32)0x6ee3ad88, actual); - } + [Fact] + public void ShortAsciiString2() + { + var actual = Crc32.Compute(SimpleBytes2); + Assert.Equal((uint)0x6ee3ad88, actual); } } diff --git a/tests/SharpCompress.Test/Xz/Crc64Tests.cs b/tests/SharpCompress.Test/Xz/Crc64Tests.cs index 3095dba4..79912e48 100644 --- a/tests/SharpCompress.Test/Xz/Crc64Tests.cs +++ b/tests/SharpCompress.Test/Xz/Crc64Tests.cs @@ -1,32 +1,30 @@ -using SharpCompress.Compressors.Xz; -using System; using System.Text; +using SharpCompress.Compressors.Xz; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class Crc64Tests { - public class Crc64Tests + private const string SimpleString = @"The quick brown fox jumps over the lazy dog."; + private readonly byte[] SimpleBytes = Encoding.ASCII.GetBytes(SimpleString); + private const string SimpleString2 = + @"Life moves pretty fast. If you don't stop and look around once in a while, you could miss it."; + private readonly byte[] SimpleBytes2 = Encoding.ASCII.GetBytes(SimpleString2); + + [Fact] + public void ShortAsciiString() { - private const string SimpleString = @"The quick brown fox jumps over the lazy dog."; - private readonly byte[] SimpleBytes = Encoding.ASCII.GetBytes(SimpleString); - private const string SimpleString2 = @"Life moves pretty fast. If you don't stop and look around once in a while, you could miss it."; - private readonly byte[] SimpleBytes2 = Encoding.ASCII.GetBytes(SimpleString2); + var actual = Crc64.Compute(SimpleBytes); - [Fact] - public void ShortAsciiString() - { - var actual = Crc64.Compute(SimpleBytes); + Assert.Equal((ulong)0x7E210EB1B03E5A1D, actual); + } - Assert.Equal((UInt64)0x7E210EB1B03E5A1D, actual); - } - - [Fact] - public void ShortAsciiString2() - { - var actual = Crc64.Compute(SimpleBytes2); - - Assert.Equal((UInt64)0x416B4150508661EE, actual); - } + [Fact] + public void ShortAsciiString2() + { + var actual = Crc64.Compute(SimpleBytes2); + Assert.Equal((ulong)0x416B4150508661EE, actual); } } diff --git a/tests/SharpCompress.Test/Xz/Filters/BCJTests.cs b/tests/SharpCompress.Test/Xz/Filters/BCJTests.cs index c562a43b..427a7b98 100644 --- a/tests/SharpCompress.Test/Xz/Filters/BCJTests.cs +++ b/tests/SharpCompress.Test/Xz/Filters/BCJTests.cs @@ -7,82 +7,82 @@ using System.IO; using SharpCompress.Compressors.Xz.Filters; using Xunit; -namespace SharpCompress.Test.Xz.Filters +namespace SharpCompress.Test.Xz.Filters; + +public class BCJTests : XZTestsBase { - public class BCJTests : XZTestsBase + private readonly ArmFilter armFilter; + private readonly ArmThumbFilter armtFilter; + private readonly IA64Filter ia64Filter; + private readonly PowerPCFilter ppcFilter; + private readonly SparcFilter sparcFilter; + private readonly X86Filter x86Filter; + + public BCJTests() { - private readonly ArmFilter armFilter; - private readonly ArmThumbFilter armtFilter; - private readonly IA64Filter ia64Filter; - private readonly PowerPCFilter ppcFilter; - private readonly SparcFilter sparcFilter; - private readonly X86Filter x86Filter; - public BCJTests() - { - armFilter = new ArmFilter(); - armtFilter = new ArmThumbFilter(); - ia64Filter = new IA64Filter(); - ppcFilter = new PowerPCFilter(); - sparcFilter = new SparcFilter(); - x86Filter = new X86Filter(); - } + armFilter = new ArmFilter(); + armtFilter = new ArmThumbFilter(); + ia64Filter = new IA64Filter(); + ppcFilter = new PowerPCFilter(); + sparcFilter = new SparcFilter(); + x86Filter = new X86Filter(); + } - [Fact] - public void IsOnlyAllowedLast() - { - Assert.False(armFilter.AllowAsLast); - Assert.True(armFilter.AllowAsNonLast); + [Fact] + public void IsOnlyAllowedLast() + { + Assert.False(armFilter.AllowAsLast); + Assert.True(armFilter.AllowAsNonLast); - Assert.False(armtFilter.AllowAsLast); - Assert.True(armtFilter.AllowAsNonLast); + Assert.False(armtFilter.AllowAsLast); + Assert.True(armtFilter.AllowAsNonLast); - Assert.False(ia64Filter.AllowAsLast); - Assert.True(ia64Filter.AllowAsNonLast); + Assert.False(ia64Filter.AllowAsLast); + Assert.True(ia64Filter.AllowAsNonLast); - Assert.False(ppcFilter.AllowAsLast); - Assert.True(ppcFilter.AllowAsNonLast); + Assert.False(ppcFilter.AllowAsLast); + Assert.True(ppcFilter.AllowAsNonLast); - Assert.False(sparcFilter.AllowAsLast); - Assert.True(sparcFilter.AllowAsNonLast); + Assert.False(sparcFilter.AllowAsLast); + Assert.True(sparcFilter.AllowAsNonLast); - Assert.False(x86Filter.AllowAsLast); - Assert.True(x86Filter.AllowAsNonLast); - } + Assert.False(x86Filter.AllowAsLast); + Assert.True(x86Filter.AllowAsNonLast); + } - [Fact] - public void ChangesStreamSize() - { - Assert.False(armFilter.ChangesDataSize); - Assert.False(armtFilter.ChangesDataSize); - Assert.False(ia64Filter.ChangesDataSize); - Assert.False(ppcFilter.ChangesDataSize); - Assert.False(sparcFilter.ChangesDataSize); - Assert.False(x86Filter.ChangesDataSize); - } + [Fact] + public void ChangesStreamSize() + { + Assert.False(armFilter.ChangesDataSize); + Assert.False(armtFilter.ChangesDataSize); + Assert.False(ia64Filter.ChangesDataSize); + Assert.False(ppcFilter.ChangesDataSize); + Assert.False(sparcFilter.ChangesDataSize); + Assert.False(x86Filter.ChangesDataSize); + } - [Theory] - [InlineData(new byte[] { 0 })] - [InlineData(new byte[] { 0, 0, 0, 0, 0 })] - public void OnlyAcceptsOneByte(byte[] bytes) - { - InvalidDataException ex; - ex = Assert.Throws(() => armFilter.Init(bytes)); - Assert.Equal("ARM properties unexpected length", ex.Message); + [Theory] + [InlineData(new byte[] { 0 })] + [InlineData(new byte[] { 0, 0, 0, 0, 0 })] + public void OnlyAcceptsOneByte(byte[] bytes) + { + InvalidDataException ex; + ex = Assert.Throws(() => armFilter.Init(bytes)); + Assert.Equal("ARM properties unexpected length", ex.Message); - ex = Assert.Throws(() => armtFilter.Init(bytes)); - Assert.Equal("ARM Thumb properties unexpected length", ex.Message); + ex = Assert.Throws(() => armtFilter.Init(bytes)); + Assert.Equal("ARM Thumb properties unexpected length", ex.Message); - ex = Assert.Throws(() => ia64Filter.Init(bytes)); - Assert.Equal("IA64 properties unexpected length", ex.Message); + ex = Assert.Throws(() => ia64Filter.Init(bytes)); + Assert.Equal("IA64 properties unexpected length", ex.Message); - ex = Assert.Throws(() => ppcFilter.Init(bytes)); - Assert.Equal("PPC properties unexpected length", ex.Message); + ex = Assert.Throws(() => ppcFilter.Init(bytes)); + Assert.Equal("PPC properties unexpected length", ex.Message); - ex = Assert.Throws(() => sparcFilter.Init(bytes)); - Assert.Equal("SPARC properties unexpected length", ex.Message); + ex = Assert.Throws(() => sparcFilter.Init(bytes)); + Assert.Equal("SPARC properties unexpected length", ex.Message); - ex = Assert.Throws(() => x86Filter.Init(bytes)); - Assert.Equal("X86 properties unexpected length", ex.Message); - } + ex = Assert.Throws(() => x86Filter.Init(bytes)); + Assert.Equal("X86 properties unexpected length", ex.Message); } } diff --git a/tests/SharpCompress.Test/Xz/Filters/Lzma2Tests.cs b/tests/SharpCompress.Test/Xz/Filters/Lzma2Tests.cs index 1f527452..83eed21c 100644 --- a/tests/SharpCompress.Test/Xz/Filters/Lzma2Tests.cs +++ b/tests/SharpCompress.Test/Xz/Filters/Lzma2Tests.cs @@ -1,72 +1,65 @@ -using System; +using System; using Xunit; using System.IO; using SharpCompress.Compressors.Xz.Filters; -namespace SharpCompress.Test.Xz.Filters +namespace SharpCompress.Test.Xz.Filters; + +public class Lzma2Tests : XZTestsBase { - public class Lzma2Tests : XZTestsBase + private readonly Lzma2Filter filter; + + public Lzma2Tests() => filter = new Lzma2Filter(); + + [Fact] + public void IsOnlyAllowedLast() { - private readonly Lzma2Filter filter; + Assert.True(filter.AllowAsLast); + Assert.False(filter.AllowAsNonLast); + } - public Lzma2Tests() - { - filter = new Lzma2Filter(); - } + [Fact] + public void ChangesStreamSize() => Assert.True(filter.ChangesDataSize); - [Fact] - public void IsOnlyAllowedLast() - { - Assert.True(filter.AllowAsLast); - Assert.False(filter.AllowAsNonLast); - } + [Theory] + [InlineData(0, (uint)4 * 1024)] + [InlineData(1, (uint)6 * 1024)] + [InlineData(2, (uint)8 * 1024)] + [InlineData(3, (uint)12 * 1024)] + [InlineData(38, (uint)2 * 1024 * 1024 * 1024)] + [InlineData(39, (uint)3 * 1024 * 1024 * 1024)] + [InlineData(40, (uint)(1024 * 1024 * 1024 - 1) * 4 + 3)] + public void CalculatesDictionarySize(byte inByte, uint dicSize) + { + filter.Init(new[] { inByte }); + Assert.Equal(filter.DictionarySize, dicSize); + } - [Fact] - public void ChangesStreamSize() + [Fact] + public void CalculatesDictionarySizeError() + { + uint temp; + filter.Init(new byte[] { 41 }); + var ex = Assert.Throws(() => { - Assert.True(filter.ChangesDataSize); - } + temp = filter.DictionarySize; + }); + Assert.Equal("Dictionary size greater than UInt32.Max", ex.Message); + } - [Theory] - [InlineData(0, (uint)4 * 1024)] - [InlineData(1, (uint)6 * 1024)] - [InlineData(2, (uint)8 * 1024)] - [InlineData(3, (uint)12 * 1024)] - [InlineData(38, (uint)2 * 1024 * 1024 * 1024)] - [InlineData(39, (uint)3 * 1024 * 1024 * 1024)] - [InlineData(40, (uint)(1024 * 1024 * 1024 - 1) * 4 + 3)] - public void CalculatesDictionarySize(byte inByte, uint dicSize) - { - filter.Init(new[] { inByte }); - Assert.Equal(filter.DictionarySize, dicSize); - } + [Theory] + [InlineData(new byte[] { })] + [InlineData(new byte[] { 0, 0 })] + public void OnlyAcceptsOneByte(byte[] bytes) + { + var ex = Assert.Throws(() => filter.Init(bytes)); + Assert.Equal("LZMA properties unexpected length", ex.Message); + } - [Fact] - public void CalculatesDictionarySizeError() - { - uint temp; - filter.Init(new byte[] { 41 }); - var ex = Assert.Throws(() => - { - temp = filter.DictionarySize; - }); - Assert.Equal("Dictionary size greater than UInt32.Max", ex.Message); - } - - [Theory] - [InlineData(new byte[] { })] - [InlineData(new byte[] { 0, 0 })] - public void OnlyAcceptsOneByte(byte[] bytes) - { - var ex = Assert.Throws(() => filter.Init(bytes)); - Assert.Equal("LZMA properties unexpected length", ex.Message); - } - - [Fact] - public void ReservedBytesThrow() - { - var ex = Assert.Throws(() => filter.Init(new byte[] { 0xC0 })); - Assert.Equal("Reserved bits used in LZMA properties", ex.Message); - } + [Fact] + public void ReservedBytesThrow() + { + var ex = Assert.Throws(() => filter.Init(new byte[] { 0xC0 })); + Assert.Equal("Reserved bits used in LZMA properties", ex.Message); } } diff --git a/tests/SharpCompress.Test/Xz/XZBlockTests.cs b/tests/SharpCompress.Test/Xz/XZBlockTests.cs index 50ac0cb1..87e68d93 100644 --- a/tests/SharpCompress.Test/Xz/XZBlockTests.cs +++ b/tests/SharpCompress.Test/Xz/XZBlockTests.cs @@ -1,101 +1,96 @@ -using System.Text; using System.IO; +using System.Text; using SharpCompress.Compressors.Xz; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class XZBlockTests : XZTestsBase { - public class XZBlockTests : XZTestsBase + protected override void Rewind(Stream stream) => stream.Position = 12; + + protected override void RewindIndexed(Stream stream) => stream.Position = 12; + + private byte[] ReadBytes(XZBlock block, int bytesToRead) { - protected override void Rewind(Stream stream) + var buffer = new byte[bytesToRead]; + var read = block.Read(buffer, 0, bytesToRead); + if (read != bytesToRead) { - stream.Position = 12; + throw new EndOfStreamException(); } - protected override void RewindIndexed(Stream stream) - { - stream.Position = 12; - } + return buffer; + } - private byte[] ReadBytes(XZBlock block, int bytesToRead) + [Fact] + public void OnFindIndexBlockThrow() + { + var bytes = new byte[] { 0 }; + using Stream indexBlockStream = new MemoryStream(bytes); + var XZBlock = new XZBlock(indexBlockStream, CheckType.CRC64, 8); + Assert.Throws(() => { - byte[] buffer = new byte[bytesToRead]; - var read = block.Read(buffer, 0, bytesToRead); - if (read != bytesToRead) - { - throw new EndOfStreamException(); - } + ReadBytes(XZBlock, 1); + }); + } - return buffer; - } - - [Fact] - public void OnFindIndexBlockThrow() + [Fact] + public void CrcIncorrectThrows() + { + var bytes = (byte[])Compressed.Clone(); + bytes[20]++; + using Stream badCrcStream = new MemoryStream(bytes); + Rewind(badCrcStream); + var XZBlock = new XZBlock(badCrcStream, CheckType.CRC64, 8); + var ex = Assert.Throws(() => { - var bytes = new byte[] { 0 }; - using (Stream indexBlockStream = new MemoryStream(bytes)) - { - var XZBlock = new XZBlock(indexBlockStream, CheckType.CRC64, 8); - Assert.Throws(() => { ReadBytes(XZBlock, 1); }); - } - } + ReadBytes(XZBlock, 1); + }); + Assert.Equal("Block header corrupt", ex.Message); + } - [Fact] - public void CrcIncorrectThrows() - { - var bytes = Compressed.Clone() as byte[]; - bytes[20]++; - using (Stream badCrcStream = new MemoryStream(bytes)) - { - Rewind(badCrcStream); - var XZBlock = new XZBlock(badCrcStream, CheckType.CRC64, 8); - var ex = Assert.Throws(() => { ReadBytes(XZBlock, 1); }); - Assert.Equal("Block header corrupt", ex.Message); - } - } + [Fact] + public void CanReadM() + { + var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); + Assert.Equal(Encoding.ASCII.GetBytes("M"), ReadBytes(XZBlock, 1)); + } - [Fact] - public void CanReadM() - { - var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); - Assert.Equal(Encoding.ASCII.GetBytes("M"), ReadBytes(XZBlock, 1)); - } + [Fact] + public void CanReadMary() + { + var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); + Assert.Equal(Encoding.ASCII.GetBytes("M"), ReadBytes(XZBlock, 1)); + Assert.Equal(Encoding.ASCII.GetBytes("a"), ReadBytes(XZBlock, 1)); + Assert.Equal(Encoding.ASCII.GetBytes("ry"), ReadBytes(XZBlock, 2)); + } - [Fact] - public void CanReadMary() - { - var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); - Assert.Equal(Encoding.ASCII.GetBytes("M"), ReadBytes(XZBlock, 1)); - Assert.Equal(Encoding.ASCII.GetBytes("a"), ReadBytes(XZBlock, 1)); - Assert.Equal(Encoding.ASCII.GetBytes("ry"), ReadBytes(XZBlock, 2)); - } + [Fact] + public void CanReadPoemWithStreamReader() + { + var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); + var sr = new StreamReader(XZBlock); + Assert.Equal(sr.ReadToEnd(), Original); + } - [Fact] - public void CanReadPoemWithStreamReader() - { - var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); - var sr = new StreamReader(XZBlock); - Assert.Equal(sr.ReadToEnd(), Original); - } + [Fact] + public void NoopWhenNoPadding() + { + // CompressedStream's only block has no padding. + var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); + var sr = new StreamReader(XZBlock); + sr.ReadToEnd(); + Assert.Equal(0L, CompressedStream.Position % 4L); + } - [Fact] - public void NoopWhenNoPadding() - { - // CompressedStream's only block has no padding. - var XZBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8); - var sr = new StreamReader(XZBlock); - sr.ReadToEnd(); - Assert.Equal(0L, CompressedStream.Position % 4L); - } - - [Fact] - public void SkipsPaddingWhenPresent() - { - // CompressedIndexedStream's first block has 1-byte padding. - var XZBlock = new XZBlock(CompressedIndexedStream, CheckType.CRC64, 8); - var sr = new StreamReader(XZBlock); - sr.ReadToEnd(); - Assert.Equal(0L, CompressedIndexedStream.Position % 4L); - } + [Fact] + public void SkipsPaddingWhenPresent() + { + // CompressedIndexedStream's first block has 1-byte padding. + var XZBlock = new XZBlock(CompressedIndexedStream, CheckType.CRC64, 8); + var sr = new StreamReader(XZBlock); + sr.ReadToEnd(); + Assert.Equal(0L, CompressedIndexedStream.Position % 4L); } } diff --git a/tests/SharpCompress.Test/Xz/XZHeaderTests.cs b/tests/SharpCompress.Test/Xz/XZHeaderTests.cs index 1fa2a8b5..3fa9dc86 100644 --- a/tests/SharpCompress.Test/Xz/XZHeaderTests.cs +++ b/tests/SharpCompress.Test/Xz/XZHeaderTests.cs @@ -1,79 +1,87 @@ -using SharpCompress.Compressors.Xz; +using SharpCompress.Compressors.Xz; using System.IO; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class XZHeaderTests : XZTestsBase { - public class XZHeaderTests : XZTestsBase + [Fact] + public void ChecksMagicNumber() { - [Fact] - public void ChecksMagicNumber() + var bytes = (byte[])Compressed.Clone(); + bytes[3]++; + using (Stream badMagicNumberStream = new MemoryStream(bytes)) { - var bytes = Compressed.Clone() as byte[]; - bytes[3]++; - using (Stream badMagicNumberStream = new MemoryStream(bytes)) - { - BinaryReader br = new BinaryReader(badMagicNumberStream); - var header = new XZHeader(br); - var ex = Assert.Throws(() => { header.Process(); }); - Assert.Equal("Invalid XZ Stream", ex.Message); - } - } - - [Fact] - public void CorruptHeaderThrows() - { - var bytes = Compressed.Clone() as byte[]; - bytes[8]++; - using (Stream badCrcStream = new MemoryStream(bytes)) - { - BinaryReader br = new BinaryReader(badCrcStream); - var header = new XZHeader(br); - var ex = Assert.Throws(() => { header.Process(); }); - Assert.Equal("Stream header corrupt", ex.Message); - } - } - - [Fact] - public void BadVersionIfCrcOkButStreamFlagUnknown() - { - var bytes = Compressed.Clone() as byte[]; - byte[] streamFlags = { 0x00, 0xF4 }; - byte[] crc = Crc32.Compute(streamFlags).ToLittleEndianBytes(); - streamFlags.CopyTo(bytes, 6); - crc.CopyTo(bytes, 8); - using (Stream badFlagStream = new MemoryStream(bytes)) - { - BinaryReader br = new BinaryReader(badFlagStream); - var header = new XZHeader(br); - var ex = Assert.Throws(() => { header.Process(); }); - Assert.Equal("Unknown XZ Stream Version", ex.Message); - } - } - - [Fact] - public void ProcessesBlockCheckType() - { - BinaryReader br = new BinaryReader(CompressedStream); + BinaryReader br = new BinaryReader(badMagicNumberStream); var header = new XZHeader(br); - header.Process(); - Assert.Equal(CheckType.CRC64, header.BlockCheckType); - } - - [Fact] - public void CanCalculateBlockCheckSize() - { - BinaryReader br = new BinaryReader(CompressedStream); - var header = new XZHeader(br); - header.Process(); - Assert.Equal(8, header.BlockCheckSize); - } - - [Fact] - public void ProcessesStreamHeaderFromFactory() - { - var header = XZHeader.FromStream(CompressedStream); - Assert.Equal(CheckType.CRC64, header.BlockCheckType); + var ex = Assert.Throws(() => + { + header.Process(); + }); + Assert.Equal("Invalid XZ Stream", ex.Message); } } + + [Fact] + public void CorruptHeaderThrows() + { + var bytes = (byte[])Compressed.Clone(); + bytes[8]++; + using (Stream badCrcStream = new MemoryStream(bytes)) + { + BinaryReader br = new BinaryReader(badCrcStream); + var header = new XZHeader(br); + var ex = Assert.Throws(() => + { + header.Process(); + }); + Assert.Equal("Stream header corrupt", ex.Message); + } + } + + [Fact] + public void BadVersionIfCrcOkButStreamFlagUnknown() + { + var bytes = (byte[])Compressed.Clone(); + byte[] streamFlags = { 0x00, 0xF4 }; + byte[] crc = Crc32.Compute(streamFlags).ToLittleEndianBytes(); + streamFlags.CopyTo(bytes, 6); + crc.CopyTo(bytes, 8); + using (Stream badFlagStream = new MemoryStream(bytes)) + { + BinaryReader br = new BinaryReader(badFlagStream); + var header = new XZHeader(br); + var ex = Assert.Throws(() => + { + header.Process(); + }); + Assert.Equal("Unknown XZ Stream Version", ex.Message); + } + } + + [Fact] + public void ProcessesBlockCheckType() + { + BinaryReader br = new BinaryReader(CompressedStream); + var header = new XZHeader(br); + header.Process(); + Assert.Equal(CheckType.CRC64, header.BlockCheckType); + } + + [Fact] + public void CanCalculateBlockCheckSize() + { + BinaryReader br = new BinaryReader(CompressedStream); + var header = new XZHeader(br); + header.Process(); + Assert.Equal(8, header.BlockCheckSize); + } + + [Fact] + public void ProcessesStreamHeaderFromFactory() + { + var header = XZHeader.FromStream(CompressedStream); + Assert.Equal(CheckType.CRC64, header.BlockCheckType); + } } diff --git a/tests/SharpCompress.Test/Xz/XZIndexTests.cs b/tests/SharpCompress.Test/Xz/XZIndexTests.cs index 543f984e..9c9d15bf 100644 --- a/tests/SharpCompress.Test/Xz/XZIndexTests.cs +++ b/tests/SharpCompress.Test/Xz/XZIndexTests.cs @@ -2,94 +2,103 @@ using System.IO; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class XZIndexTests : XZTestsBase { - public class XZIndexTests : XZTestsBase + protected override void RewindEmpty(Stream stream) => stream.Position = 12; + + protected override void Rewind(Stream stream) => stream.Position = 356; + + protected override void RewindIndexed(Stream stream) => stream.Position = 612; + + [Fact] + public void RecordsStreamStartOnInit() { - protected override void RewindEmpty(Stream stream) + using (Stream badStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 })) { - stream.Position = 12; + BinaryReader br = new BinaryReader(badStream); + var index = new XZIndex(br, false); + Assert.Equal(0, index.StreamStartPosition); } + } - protected override void Rewind(Stream stream) + [Fact] + public void ThrowsIfHasNoIndexMarker() + { + using (Stream badStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 })) { - stream.Position = 356; + BinaryReader br = new BinaryReader(badStream); + var index = new XZIndex(br, false); + Assert.Throws(() => index.Process()); } + } - protected override void RewindIndexed(Stream stream) - { - stream.Position = 612; - } + [Fact] + public void ReadsNoRecord() + { + BinaryReader br = new BinaryReader(CompressedEmptyStream); + var index = new XZIndex(br, false); + index.Process(); + Assert.Equal((ulong)0, index.NumberOfRecords); + } - [Fact] - public void RecordsStreamStartOnInit() - { - using (Stream badStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 })) - { - BinaryReader br = new BinaryReader(badStream); - var index = new XZIndex(br, false); - Assert.Equal(0, index.StreamStartPosition); - } - } + [Fact] + public void ReadsOneRecord() + { + BinaryReader br = new BinaryReader(CompressedStream); + var index = new XZIndex(br, false); + index.Process(); + Assert.Equal((ulong)1, index.NumberOfRecords); + } - [Fact] - public void ThrowsIfHasNoIndexMarker() - { - using (Stream badStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 })) - { - BinaryReader br = new BinaryReader(badStream); - var index = new XZIndex(br, false); - Assert.Throws(() => index.Process()); - } - } + [Fact] + public void ReadsMultipleRecords() + { + BinaryReader br = new BinaryReader(CompressedIndexedStream); + var index = new XZIndex(br, false); + index.Process(); + Assert.Equal((ulong)2, index.NumberOfRecords); + } - [Fact] - public void ReadsNoRecord() + [Fact] + public void ReadsFirstRecord() + { + BinaryReader br = new BinaryReader(CompressedStream); + var index = new XZIndex(br, false); + index.Process(); + Assert.Equal((ulong)OriginalBytes.Length, index.Records[0].UncompressedSize); + } + + [Fact] + public void SkipsPadding() + { + // Index with 3-byte padding. + using ( + Stream badStream = new MemoryStream( + new byte[] + { + 0x00, + 0x01, + 0x10, + 0x80, + 0x01, + 0x00, + 0x00, + 0x00, + 0xB1, + 0x01, + 0xD9, + 0xC9, + 0xFF + } + ) + ) { - BinaryReader br = new BinaryReader(CompressedEmptyStream); + BinaryReader br = new BinaryReader(badStream); var index = new XZIndex(br, false); index.Process(); - Assert.Equal((ulong)0, index.NumberOfRecords); - } - - [Fact] - public void ReadsOneRecord() - { - BinaryReader br = new BinaryReader(CompressedStream); - var index = new XZIndex(br, false); - index.Process(); - Assert.Equal((ulong)1, index.NumberOfRecords); - } - - [Fact] - public void ReadsMultipleRecords() - { - BinaryReader br = new BinaryReader(CompressedIndexedStream); - var index = new XZIndex(br, false); - index.Process(); - Assert.Equal((ulong)2, index.NumberOfRecords); - } - - [Fact] - public void ReadsFirstRecord() - { - BinaryReader br = new BinaryReader(CompressedStream); - var index = new XZIndex(br, false); - index.Process(); - Assert.Equal((ulong)OriginalBytes.Length, index.Records[0].UncompressedSize); - } - - [Fact] - public void SkipsPadding() - { - // Index with 3-byte padding. - using (Stream badStream = new MemoryStream(new byte[] { 0x00, 0x01, 0x10, 0x80, 0x01, 0x00, 0x00, 0x00, 0xB1, 0x01, 0xD9, 0xC9, 0xFF })) - { - BinaryReader br = new BinaryReader(badStream); - var index = new XZIndex(br, false); - index.Process(); - Assert.Equal(0L, badStream.Position % 4L); - } + Assert.Equal(0L, badStream.Position % 4L); } } } diff --git a/tests/SharpCompress.Test/Xz/XZStreamTests.cs b/tests/SharpCompress.Test/Xz/XZStreamTests.cs index 4b6bbaf6..1f50fb40 100644 --- a/tests/SharpCompress.Test/Xz/XZStreamTests.cs +++ b/tests/SharpCompress.Test/Xz/XZStreamTests.cs @@ -2,41 +2,40 @@ using System.IO; using Xunit; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public class XZStreamTests : XZTestsBase { - public class XZStreamTests : XZTestsBase + [Fact] + public void CanReadEmptyStream() { - [Fact] - public void CanReadEmptyStream() + XZStream xz = new XZStream(CompressedEmptyStream); + using (var sr = new StreamReader(xz)) { - XZStream xz = new XZStream(CompressedEmptyStream); - using (var sr = new StreamReader(xz)) - { - string uncompressed = sr.ReadToEnd(); - Assert.Equal(OriginalEmpty, uncompressed); - } + string uncompressed = sr.ReadToEnd(); + Assert.Equal(OriginalEmpty, uncompressed); } + } - [Fact] - public void CanReadStream() + [Fact] + public void CanReadStream() + { + XZStream xz = new XZStream(CompressedStream); + using (var sr = new StreamReader(xz)) { - XZStream xz = new XZStream(CompressedStream); - using (var sr = new StreamReader(xz)) - { - string uncompressed = sr.ReadToEnd(); - Assert.Equal(Original, uncompressed); - } + string uncompressed = sr.ReadToEnd(); + Assert.Equal(Original, uncompressed); } + } - [Fact] - public void CanReadIndexedStream() + [Fact] + public void CanReadIndexedStream() + { + XZStream xz = new XZStream(CompressedIndexedStream); + using (var sr = new StreamReader(xz)) { - XZStream xz = new XZStream(CompressedIndexedStream); - using (var sr = new StreamReader(xz)) - { - string uncompressed = sr.ReadToEnd(); - Assert.Equal(OriginalIndexed, uncompressed); - } + string uncompressed = sr.ReadToEnd(); + Assert.Equal(OriginalIndexed, uncompressed); } } } diff --git a/tests/SharpCompress.Test/Xz/XZTestsBase.cs b/tests/SharpCompress.Test/Xz/XZTestsBase.cs index a0e3032e..5a1b5cbb 100644 --- a/tests/SharpCompress.Test/Xz/XZTestsBase.cs +++ b/tests/SharpCompress.Test/Xz/XZTestsBase.cs @@ -1,149 +1,1140 @@ using System; -using System.Text; using System.IO; +using System.Text; -namespace SharpCompress.Test.Xz +namespace SharpCompress.Test.Xz; + +public abstract class XZTestsBase : IDisposable { - public abstract class XZTestsBase : IDisposable + public XZTestsBase() { - public XZTestsBase() - { - RewindEmpty(CompressedEmptyStream); - Rewind(CompressedStream); - RewindIndexed(CompressedIndexedStream); - } - - public void Dispose() - { - CompressedEmptyStream?.Dispose(); - CompressedStream?.Dispose(); - CompressedIndexedStream?.Dispose(); - } - - protected virtual void RewindEmpty(Stream stream) - { - stream.Position = 0; - } - - protected virtual void Rewind(Stream stream) - { - stream.Position = 0; - } - - protected virtual void RewindIndexed(Stream stream) - { - stream.Position = 0; - } - - protected Stream CompressedEmptyStream { get; } = new MemoryStream(CompressedEmpty); - - protected static byte[] CompressedEmpty { get; } = new byte[] { - 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36, 0x00, 0x00, 0x00, 0x00, - 0x1c, 0xdf, 0x44, 0x21, 0x90, 0x42, 0x99, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a - }; - - protected static byte[] OriginalEmptyBytes => Encoding.ASCII.GetBytes(OriginalEmpty); - - protected static string OriginalEmpty { get; } = string.Empty; - - protected Stream CompressedStream { get; } = new MemoryStream(Compressed); - - protected static byte[] Compressed { get; } = new byte[] { - 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04, 0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01, - 0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3, 0xe0, 0x01, 0xe4, 0x01, 0x3c, 0x5d, 0x00, 0x26, - 0x98, 0x4a, 0x47, 0xc6, 0x6a, 0x27, 0xd7, 0x36, 0x7a, 0x05, 0xb9, 0x4f, 0xd7, 0xde, 0x52, 0x4c, - 0xca, 0x26, 0x4f, 0x23, 0x60, 0x4d, 0xf3, 0x1f, 0xa3, 0x67, 0x49, 0x53, 0xd0, 0xf5, 0xc7, 0xa9, - 0x3e, 0xd6, 0xb5, 0x3d, 0x2b, 0x02, 0xbe, 0x83, 0x27, 0xe2, 0xa6, 0xc3, 0x13, 0x4a, 0x31, 0x14, - 0x33, 0xed, 0x9a, 0x85, 0x1d, 0x05, 0x6e, 0x7e, 0xa4, 0x91, 0xbf, 0x46, 0x71, 0x7d, 0xa7, 0xfb, - 0x12, 0x10, 0xdf, 0x21, 0x73, 0x75, 0xd8, 0xd9, 0xab, 0x8f, 0x1f, 0x8b, 0xb0, 0xb9, 0x3f, 0x9a, - 0xa5, 0x1e, 0xd4, 0x2f, 0xdf, 0x09, 0xb3, 0xfe, 0x45, 0xef, 0x16, 0xec, 0x95, 0x68, 0x64, 0xbb, - 0x42, 0x0c, 0x8b, 0x96, 0x27, 0x30, 0x62, 0x42, 0x91, 0x7c, 0xf3, 0x6e, 0x4d, 0x03, 0xc5, 0x00, - 0x04, 0x73, 0xdd, 0xee, 0xb0, 0xaa, 0xd6, 0x0b, 0x11, 0x90, 0x81, 0xd4, 0xaa, 0x69, 0x63, 0xfa, - 0x2f, 0xb4, 0x25, 0x0a, 0x7f, 0xf9, 0x47, 0x77, 0xb1, 0x1f, 0xc3, 0xb4, 0x4d, 0x51, 0xf8, 0x23, - 0x3a, 0x7c, 0x44, 0xc8, 0xcc, 0xca, 0x72, 0x09, 0xae, 0xc9, 0x7b, 0x7e, 0x91, 0x5d, 0xff, 0xc4, - 0xeb, 0xfd, 0xa1, 0x9b, 0xd4, 0x8d, 0xd7, 0xd3, 0x57, 0xac, 0x7e, 0x3b, 0x97, 0x2e, 0xe4, 0xc2, - 0x2e, 0x93, 0x3d, 0xb0, 0x16, 0x64, 0x78, 0x45, 0xb1, 0xc9, 0x40, 0x96, 0xcf, 0x5b, 0xc2, 0x2f, - 0xaa, 0xba, 0xcf, 0x98, 0x38, 0x21, 0x3d, 0x1a, 0x13, 0xe8, 0xa6, 0xa6, 0xdf, 0xf4, 0x3d, 0x01, - 0xa1, 0x9d, 0xc1, 0x3e, 0x37, 0xac, 0x20, 0xc4, 0xef, 0x18, 0xb1, 0xeb, 0x35, 0xf4, 0x66, 0x9a, - 0x47, 0x3c, 0xce, 0x7c, 0xad, 0xdb, 0x2e, 0x39, 0xf5, 0x8d, 0x4a, 0x1d, 0x65, 0xc2, 0x0f, 0xa4, - 0x40, 0x7e, 0xe6, 0xa7, 0x17, 0xce, 0x75, 0x7f, 0xd9, 0xa3, 0xf9, 0x27, 0x42, 0xd7, 0x98, 0x54, - 0x17, 0xa7, 0x7a, 0x7c, 0x82, 0xdf, 0xeb, 0x08, 0x28, 0x86, 0xdd, 0x57, 0x77, 0x92, 0x80, 0x5f, - 0x7b, 0x3b, 0xce, 0x77, 0x72, 0xff, 0xa3, 0x85, 0xd8, 0x5c, 0x8a, 0xb7, 0x83, 0x58, 0xfa, 0xbd, - 0x72, 0xe3, 0x66, 0x9d, 0x3b, 0xff, 0x13, 0x5b, 0x0b, 0xf1, 0x6c, 0xa6, 0xb1, 0x3b, 0x85, 0x3b, - 0x47, 0x91, 0xc8, 0x7c, 0x38, 0xe2, 0xe5, 0x54, 0xf8, 0x27, 0xee, 0x00, 0xff, 0xd3, 0x68, 0xf1, - 0xc6, 0xc7, 0xd7, 0x24, 0x00, 0x01, 0xd8, 0x02, 0xe5, 0x03, 0x00, 0x00, 0xac, 0x16, 0x1f, 0xa4, - 0xb1, 0xc4, 0x67, 0xfb, 0x02, 0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a - }; - protected static byte[] OriginalBytes => Encoding.ASCII.GetBytes(Original); - - protected static string Original { get; } = - "Mary had a little lamb,\r\n" + - "His fleece was white as snow,\r\n" + - "And everywhere that Mary went,\r\n" + - "The lamb was sure to go.\r\n" + - "\r\n" + - "He followed her to school one day,\r\n" + - "Which was against the rule,\r\n" + - "It made the children laugh and play\r\n" + - "To see a lamb at school.\r\n" + - "\r\n" + - "And so the teacher turned it out,\r\n" + - "But still it lingered near,\r\n" + - "And waited patiently about,\r\n" + - "Till Mary did appear.\r\n" + - "\r\n" + - "\"Why does the lamb love Mary so?\"\r\n" + - "The eager children cry.\r\n" + - "\"Why, Mary loves the lamb, you know.\"\r\n" + - "The teacher did reply."; - - protected Stream CompressedIndexedStream { get; } = new MemoryStream(CompressedIndexed); - - protected static byte[] CompressedIndexed { get; } = new byte[] { - 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36, 0x03, 0xc0, 0xe3, 0x02, - 0x80, 0x20, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xe5, 0xd7, 0x32, 0xe0, 0x0f, 0xff, 0x01, - 0x5b, 0x5d, 0x00, 0x26, 0x98, 0x4a, 0x47, 0xc6, 0x6a, 0x27, 0xd7, 0x36, 0x7a, 0x05, 0xb9, 0x4f, - 0xd7, 0xde, 0x3a, 0x0e, 0xee, 0x1b, 0xd7, 0x81, 0xe9, 0xf5, 0x90, 0x1e, 0xd5, 0x9e, 0x88, 0x32, - 0x1c, 0x7b, 0x43, 0x84, 0x02, 0x58, 0x92, 0xcf, 0x97, 0xfc, 0xae, 0x01, 0x83, 0x23, 0x48, 0x93, - 0xc6, 0x56, 0xcc, 0x6d, 0xb1, 0x23, 0x10, 0x24, 0x3b, 0x9e, 0x06, 0xaa, 0xc0, 0xce, 0x86, 0x0a, - 0xb7, 0x9f, 0x99, 0x61, 0xbe, 0x3b, 0x6d, 0xfe, 0x60, 0xef, 0x14, 0x35, 0x7f, 0x21, 0xe8, 0x96, - 0x0e, 0xbd, 0x41, 0x7c, 0x65, 0x89, 0x96, 0x28, 0x5e, 0x85, 0xa6, 0x4b, 0xf3, 0xf9, 0xf8, 0x25, - 0x31, 0x4a, 0xbb, 0x72, 0xce, 0xcf, 0x53, 0xdf, 0x13, 0x42, 0x2d, 0xbc, 0x95, 0xa5, 0x6d, 0xc4, - 0x8c, 0x72, 0x99, 0xe8, 0x9a, 0xcf, 0x80, 0xd4, 0xc4, 0x3f, 0x55, 0xc3, 0x9b, 0x00, 0xce, 0x65, - 0x27, 0x6e, 0xbf, 0xb2, 0x88, 0xab, 0xc0, 0x5f, 0xf9, 0xd0, 0xc8, 0xbb, 0xd7, 0x48, 0xd7, 0x2e, - 0x5e, 0xbb, 0x23, 0x35, 0x6e, 0x62, 0xb6, 0x13, 0xd4, 0x06, 0xd1, 0x5b, 0x97, 0xee, 0x5b, 0x89, - 0x78, 0x07, 0x24, 0x74, 0x59, 0x06, 0x1e, 0x7f, 0x8c, 0xb0, 0x48, 0xff, 0x0a, 0x76, 0xb2, 0x07, - 0xa0, 0x99, 0xf5, 0x4b, 0x68, 0xd4, 0x55, 0x32, 0xb3, 0x17, 0x7b, 0xb6, 0x26, 0xdb, 0x1c, 0xc3, - 0x0b, 0xda, 0x3e, 0x46, 0xba, 0x1a, 0x67, 0x23, 0xb7, 0x2a, 0x40, 0xdc, 0xc9, 0xa2, 0xe4, 0xb5, - 0x68, 0x5c, 0x81, 0x60, 0xa7, 0xad, 0xe6, 0xba, 0xbb, 0x0d, 0x82, 0x8a, 0xe2, 0x03, 0xa9, 0x22, - 0x09, 0x5e, 0xd8, 0x69, 0xfa, 0x29, 0xd1, 0x32, 0xa1, 0xf0, 0x9b, 0x3c, 0xc3, 0x0b, 0x9a, 0x53, - 0xf0, 0x3e, 0xf3, 0x1b, 0x77, 0xee, 0x8f, 0xa6, 0x15, 0x02, 0x77, 0x14, 0x54, 0x60, 0xae, 0xbe, - 0x91, 0x9e, 0xe6, 0x8b, 0x87, 0x6e, 0x46, 0x44, 0x64, 0xc7, 0x58, 0x90, 0x62, 0x25, 0x32, 0xf9, - 0xcd, 0xd2, 0x73, 0x2e, 0x3f, 0xd7, 0x5d, 0x3c, 0x86, 0x1c, 0xa8, 0x35, 0xa9, 0xc2, 0xcb, 0x59, - 0xcb, 0xac, 0xb3, 0x03, 0x12, 0xd4, 0x8a, 0xde, 0xd5, 0xc1, 0xd8, 0x0c, 0x32, 0x49, 0x87, 0x97, - 0x62, 0x4f, 0x32, 0x39, 0x63, 0x5b, 0x8b, 0xd1, 0x6c, 0x5c, 0x90, 0xd9, 0x93, 0x13, 0xae, 0x70, - 0xf5, 0x2f, 0x40, 0xaf, 0x01, 0x95, 0x01, 0x0c, 0xc5, 0xfa, 0x82, 0xf8, 0x71, 0x9d, 0x53, 0xe6, - 0x47, 0x6e, 0x99, 0x54, 0x57, 0x41, 0x72, 0xea, 0xf5, 0x78, 0xdd, 0x86, 0xbd, 0x00, 0x00, 0x00, - 0x72, 0x6a, 0xf2, 0x47, 0x03, 0xc0, 0xcb, 0x01, 0x8d, 0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, - 0xfb, 0xa7, 0xf7, 0x94, 0xe0, 0x01, 0x0c, 0x00, 0xc3, 0x5d, 0x00, 0x06, 0x82, 0xca, 0x9b, 0x77, - 0x93, 0x57, 0xb3, 0x76, 0xbd, 0x8b, 0xcb, 0xee, 0xf4, 0x2c, 0xff, 0x7f, 0x95, 0x33, 0x15, 0x10, - 0xa5, 0xf9, 0xfd, 0xa6, 0xbb, 0x9e, 0xf9, 0x75, 0x67, 0xee, 0xec, 0x8b, 0x40, 0xea, 0x32, 0x47, - 0x3d, 0x26, 0xbe, 0x11, 0x9c, 0xa6, 0x40, 0xbe, 0x84, 0x1f, 0x1b, 0x35, 0x1a, 0x66, 0x10, 0x9c, - 0xf4, 0x12, 0x1a, 0x95, 0x81, 0xb5, 0x55, 0x6b, 0xc5, 0x42, 0xfd, 0x37, 0x70, 0xc5, 0x08, 0xa4, - 0x27, 0x67, 0x11, 0x0b, 0x1f, 0xcc, 0xdb, 0x54, 0x9b, 0x5a, 0x5f, 0xee, 0x21, 0x63, 0xdd, 0x4b, - 0xbc, 0x49, 0x95, 0x6d, 0xf4, 0xcb, 0x9a, 0x9a, 0x5e, 0xe4, 0x7d, 0x0f, 0x02, 0x22, 0xa9, 0x42, - 0x46, 0x1a, 0x04, 0x87, 0x43, 0x72, 0x59, 0xa4, 0xd6, 0xeb, 0x69, 0x36, 0xde, 0xea, 0x53, 0x8c, - 0x89, 0xd7, 0x22, 0xa6, 0xf7, 0xa8, 0x4c, 0x72, 0x6c, 0x80, 0x69, 0x01, 0xb2, 0xa7, 0xe8, 0x8b, - 0x94, 0xaf, 0x0e, 0x47, 0x58, 0x1d, 0x0e, 0x5c, 0x7c, 0x33, 0x9f, 0x21, 0x17, 0x2c, 0x4f, 0x3d, - 0x72, 0xff, 0xcf, 0x7a, 0x4f, 0x82, 0x5b, 0x85, 0x28, 0x70, 0xf4, 0x8c, 0x81, 0x41, 0xb8, 0x20, - 0x5c, 0x3e, 0x02, 0x5e, 0x5a, 0x61, 0xbb, 0x2f, 0x64, 0xc5, 0x4e, 0x53, 0xe4, 0xca, 0xe4, 0xd9, - 0x75, 0xaf, 0x15, 0x4d, 0xff, 0x01, 0xec, 0x13, 0x4a, 0x70, 0x00, 0x04, 0xf9, 0xfa, 0x00, 0x00, - 0x99, 0x57, 0xc4, 0x96, 0x00, 0x02, 0xf7, 0x02, 0x80, 0x20, 0xdf, 0x01, 0x8d, 0x02, 0x00, 0x00, - 0x4c, 0x41, 0xe6, 0xa1, 0x9b, 0xe3, 0x51, 0x40, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a - }; - - protected static byte[] OriginalIndexedBytes => Encoding.ASCII.GetBytes(OriginalIndexed); - - protected static string OriginalIndexed { get; } = Original + Original + Original + Original + Original + Original + Original + Original + Original; + RewindEmpty(CompressedEmptyStream); + Rewind(CompressedStream); + RewindIndexed(CompressedIndexedStream); } + + public void Dispose() + { + CompressedEmptyStream?.Dispose(); + CompressedStream?.Dispose(); + CompressedIndexedStream?.Dispose(); + } + + protected virtual void RewindEmpty(Stream stream) => stream.Position = 0; + + protected virtual void Rewind(Stream stream) => stream.Position = 0; + + protected virtual void RewindIndexed(Stream stream) => stream.Position = 0; + + protected Stream CompressedEmptyStream { get; } = new MemoryStream(CompressedEmpty); + + protected static byte[] CompressedEmpty { get; } = + new byte[] + { + 0xfd, + 0x37, + 0x7a, + 0x58, + 0x5a, + 0x00, + 0x00, + 0x01, + 0x69, + 0x22, + 0xde, + 0x36, + 0x00, + 0x00, + 0x00, + 0x00, + 0x1c, + 0xdf, + 0x44, + 0x21, + 0x90, + 0x42, + 0x99, + 0x0d, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x59, + 0x5a + }; + + protected static byte[] OriginalEmptyBytes => Encoding.ASCII.GetBytes(OriginalEmpty); + + protected static string OriginalEmpty { get; } = string.Empty; + + protected Stream CompressedStream { get; } = new MemoryStream(Compressed); + + protected static byte[] Compressed { get; } = + new byte[] + { + 0xfd, + 0x37, + 0x7a, + 0x58, + 0x5a, + 0x00, + 0x00, + 0x04, + 0xe6, + 0xd6, + 0xb4, + 0x46, + 0x02, + 0x00, + 0x21, + 0x01, + 0x16, + 0x00, + 0x00, + 0x00, + 0x74, + 0x2f, + 0xe5, + 0xa3, + 0xe0, + 0x01, + 0xe4, + 0x01, + 0x3c, + 0x5d, + 0x00, + 0x26, + 0x98, + 0x4a, + 0x47, + 0xc6, + 0x6a, + 0x27, + 0xd7, + 0x36, + 0x7a, + 0x05, + 0xb9, + 0x4f, + 0xd7, + 0xde, + 0x52, + 0x4c, + 0xca, + 0x26, + 0x4f, + 0x23, + 0x60, + 0x4d, + 0xf3, + 0x1f, + 0xa3, + 0x67, + 0x49, + 0x53, + 0xd0, + 0xf5, + 0xc7, + 0xa9, + 0x3e, + 0xd6, + 0xb5, + 0x3d, + 0x2b, + 0x02, + 0xbe, + 0x83, + 0x27, + 0xe2, + 0xa6, + 0xc3, + 0x13, + 0x4a, + 0x31, + 0x14, + 0x33, + 0xed, + 0x9a, + 0x85, + 0x1d, + 0x05, + 0x6e, + 0x7e, + 0xa4, + 0x91, + 0xbf, + 0x46, + 0x71, + 0x7d, + 0xa7, + 0xfb, + 0x12, + 0x10, + 0xdf, + 0x21, + 0x73, + 0x75, + 0xd8, + 0xd9, + 0xab, + 0x8f, + 0x1f, + 0x8b, + 0xb0, + 0xb9, + 0x3f, + 0x9a, + 0xa5, + 0x1e, + 0xd4, + 0x2f, + 0xdf, + 0x09, + 0xb3, + 0xfe, + 0x45, + 0xef, + 0x16, + 0xec, + 0x95, + 0x68, + 0x64, + 0xbb, + 0x42, + 0x0c, + 0x8b, + 0x96, + 0x27, + 0x30, + 0x62, + 0x42, + 0x91, + 0x7c, + 0xf3, + 0x6e, + 0x4d, + 0x03, + 0xc5, + 0x00, + 0x04, + 0x73, + 0xdd, + 0xee, + 0xb0, + 0xaa, + 0xd6, + 0x0b, + 0x11, + 0x90, + 0x81, + 0xd4, + 0xaa, + 0x69, + 0x63, + 0xfa, + 0x2f, + 0xb4, + 0x25, + 0x0a, + 0x7f, + 0xf9, + 0x47, + 0x77, + 0xb1, + 0x1f, + 0xc3, + 0xb4, + 0x4d, + 0x51, + 0xf8, + 0x23, + 0x3a, + 0x7c, + 0x44, + 0xc8, + 0xcc, + 0xca, + 0x72, + 0x09, + 0xae, + 0xc9, + 0x7b, + 0x7e, + 0x91, + 0x5d, + 0xff, + 0xc4, + 0xeb, + 0xfd, + 0xa1, + 0x9b, + 0xd4, + 0x8d, + 0xd7, + 0xd3, + 0x57, + 0xac, + 0x7e, + 0x3b, + 0x97, + 0x2e, + 0xe4, + 0xc2, + 0x2e, + 0x93, + 0x3d, + 0xb0, + 0x16, + 0x64, + 0x78, + 0x45, + 0xb1, + 0xc9, + 0x40, + 0x96, + 0xcf, + 0x5b, + 0xc2, + 0x2f, + 0xaa, + 0xba, + 0xcf, + 0x98, + 0x38, + 0x21, + 0x3d, + 0x1a, + 0x13, + 0xe8, + 0xa6, + 0xa6, + 0xdf, + 0xf4, + 0x3d, + 0x01, + 0xa1, + 0x9d, + 0xc1, + 0x3e, + 0x37, + 0xac, + 0x20, + 0xc4, + 0xef, + 0x18, + 0xb1, + 0xeb, + 0x35, + 0xf4, + 0x66, + 0x9a, + 0x47, + 0x3c, + 0xce, + 0x7c, + 0xad, + 0xdb, + 0x2e, + 0x39, + 0xf5, + 0x8d, + 0x4a, + 0x1d, + 0x65, + 0xc2, + 0x0f, + 0xa4, + 0x40, + 0x7e, + 0xe6, + 0xa7, + 0x17, + 0xce, + 0x75, + 0x7f, + 0xd9, + 0xa3, + 0xf9, + 0x27, + 0x42, + 0xd7, + 0x98, + 0x54, + 0x17, + 0xa7, + 0x7a, + 0x7c, + 0x82, + 0xdf, + 0xeb, + 0x08, + 0x28, + 0x86, + 0xdd, + 0x57, + 0x77, + 0x92, + 0x80, + 0x5f, + 0x7b, + 0x3b, + 0xce, + 0x77, + 0x72, + 0xff, + 0xa3, + 0x85, + 0xd8, + 0x5c, + 0x8a, + 0xb7, + 0x83, + 0x58, + 0xfa, + 0xbd, + 0x72, + 0xe3, + 0x66, + 0x9d, + 0x3b, + 0xff, + 0x13, + 0x5b, + 0x0b, + 0xf1, + 0x6c, + 0xa6, + 0xb1, + 0x3b, + 0x85, + 0x3b, + 0x47, + 0x91, + 0xc8, + 0x7c, + 0x38, + 0xe2, + 0xe5, + 0x54, + 0xf8, + 0x27, + 0xee, + 0x00, + 0xff, + 0xd3, + 0x68, + 0xf1, + 0xc6, + 0xc7, + 0xd7, + 0x24, + 0x00, + 0x01, + 0xd8, + 0x02, + 0xe5, + 0x03, + 0x00, + 0x00, + 0xac, + 0x16, + 0x1f, + 0xa4, + 0xb1, + 0xc4, + 0x67, + 0xfb, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0x59, + 0x5a + }; + protected static byte[] OriginalBytes => Encoding.ASCII.GetBytes(Original); + + protected static string Original { get; } = + "Mary had a little lamb,\r\n" + + "His fleece was white as snow,\r\n" + + "And everywhere that Mary went,\r\n" + + "The lamb was sure to go.\r\n" + + "\r\n" + + "He followed her to school one day,\r\n" + + "Which was against the rule,\r\n" + + "It made the children laugh and play\r\n" + + "To see a lamb at school.\r\n" + + "\r\n" + + "And so the teacher turned it out,\r\n" + + "But still it lingered near,\r\n" + + "And waited patiently about,\r\n" + + "Till Mary did appear.\r\n" + + "\r\n" + + "\"Why does the lamb love Mary so?\"\r\n" + + "The eager children cry.\r\n" + + "\"Why, Mary loves the lamb, you know.\"\r\n" + + "The teacher did reply."; + + protected Stream CompressedIndexedStream { get; } = new MemoryStream(CompressedIndexed); + + protected static byte[] CompressedIndexed { get; } = + new byte[] + { + 0xfd, + 0x37, + 0x7a, + 0x58, + 0x5a, + 0x00, + 0x00, + 0x01, + 0x69, + 0x22, + 0xde, + 0x36, + 0x03, + 0xc0, + 0xe3, + 0x02, + 0x80, + 0x20, + 0x21, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7e, + 0xe5, + 0xd7, + 0x32, + 0xe0, + 0x0f, + 0xff, + 0x01, + 0x5b, + 0x5d, + 0x00, + 0x26, + 0x98, + 0x4a, + 0x47, + 0xc6, + 0x6a, + 0x27, + 0xd7, + 0x36, + 0x7a, + 0x05, + 0xb9, + 0x4f, + 0xd7, + 0xde, + 0x3a, + 0x0e, + 0xee, + 0x1b, + 0xd7, + 0x81, + 0xe9, + 0xf5, + 0x90, + 0x1e, + 0xd5, + 0x9e, + 0x88, + 0x32, + 0x1c, + 0x7b, + 0x43, + 0x84, + 0x02, + 0x58, + 0x92, + 0xcf, + 0x97, + 0xfc, + 0xae, + 0x01, + 0x83, + 0x23, + 0x48, + 0x93, + 0xc6, + 0x56, + 0xcc, + 0x6d, + 0xb1, + 0x23, + 0x10, + 0x24, + 0x3b, + 0x9e, + 0x06, + 0xaa, + 0xc0, + 0xce, + 0x86, + 0x0a, + 0xb7, + 0x9f, + 0x99, + 0x61, + 0xbe, + 0x3b, + 0x6d, + 0xfe, + 0x60, + 0xef, + 0x14, + 0x35, + 0x7f, + 0x21, + 0xe8, + 0x96, + 0x0e, + 0xbd, + 0x41, + 0x7c, + 0x65, + 0x89, + 0x96, + 0x28, + 0x5e, + 0x85, + 0xa6, + 0x4b, + 0xf3, + 0xf9, + 0xf8, + 0x25, + 0x31, + 0x4a, + 0xbb, + 0x72, + 0xce, + 0xcf, + 0x53, + 0xdf, + 0x13, + 0x42, + 0x2d, + 0xbc, + 0x95, + 0xa5, + 0x6d, + 0xc4, + 0x8c, + 0x72, + 0x99, + 0xe8, + 0x9a, + 0xcf, + 0x80, + 0xd4, + 0xc4, + 0x3f, + 0x55, + 0xc3, + 0x9b, + 0x00, + 0xce, + 0x65, + 0x27, + 0x6e, + 0xbf, + 0xb2, + 0x88, + 0xab, + 0xc0, + 0x5f, + 0xf9, + 0xd0, + 0xc8, + 0xbb, + 0xd7, + 0x48, + 0xd7, + 0x2e, + 0x5e, + 0xbb, + 0x23, + 0x35, + 0x6e, + 0x62, + 0xb6, + 0x13, + 0xd4, + 0x06, + 0xd1, + 0x5b, + 0x97, + 0xee, + 0x5b, + 0x89, + 0x78, + 0x07, + 0x24, + 0x74, + 0x59, + 0x06, + 0x1e, + 0x7f, + 0x8c, + 0xb0, + 0x48, + 0xff, + 0x0a, + 0x76, + 0xb2, + 0x07, + 0xa0, + 0x99, + 0xf5, + 0x4b, + 0x68, + 0xd4, + 0x55, + 0x32, + 0xb3, + 0x17, + 0x7b, + 0xb6, + 0x26, + 0xdb, + 0x1c, + 0xc3, + 0x0b, + 0xda, + 0x3e, + 0x46, + 0xba, + 0x1a, + 0x67, + 0x23, + 0xb7, + 0x2a, + 0x40, + 0xdc, + 0xc9, + 0xa2, + 0xe4, + 0xb5, + 0x68, + 0x5c, + 0x81, + 0x60, + 0xa7, + 0xad, + 0xe6, + 0xba, + 0xbb, + 0x0d, + 0x82, + 0x8a, + 0xe2, + 0x03, + 0xa9, + 0x22, + 0x09, + 0x5e, + 0xd8, + 0x69, + 0xfa, + 0x29, + 0xd1, + 0x32, + 0xa1, + 0xf0, + 0x9b, + 0x3c, + 0xc3, + 0x0b, + 0x9a, + 0x53, + 0xf0, + 0x3e, + 0xf3, + 0x1b, + 0x77, + 0xee, + 0x8f, + 0xa6, + 0x15, + 0x02, + 0x77, + 0x14, + 0x54, + 0x60, + 0xae, + 0xbe, + 0x91, + 0x9e, + 0xe6, + 0x8b, + 0x87, + 0x6e, + 0x46, + 0x44, + 0x64, + 0xc7, + 0x58, + 0x90, + 0x62, + 0x25, + 0x32, + 0xf9, + 0xcd, + 0xd2, + 0x73, + 0x2e, + 0x3f, + 0xd7, + 0x5d, + 0x3c, + 0x86, + 0x1c, + 0xa8, + 0x35, + 0xa9, + 0xc2, + 0xcb, + 0x59, + 0xcb, + 0xac, + 0xb3, + 0x03, + 0x12, + 0xd4, + 0x8a, + 0xde, + 0xd5, + 0xc1, + 0xd8, + 0x0c, + 0x32, + 0x49, + 0x87, + 0x97, + 0x62, + 0x4f, + 0x32, + 0x39, + 0x63, + 0x5b, + 0x8b, + 0xd1, + 0x6c, + 0x5c, + 0x90, + 0xd9, + 0x93, + 0x13, + 0xae, + 0x70, + 0xf5, + 0x2f, + 0x40, + 0xaf, + 0x01, + 0x95, + 0x01, + 0x0c, + 0xc5, + 0xfa, + 0x82, + 0xf8, + 0x71, + 0x9d, + 0x53, + 0xe6, + 0x47, + 0x6e, + 0x99, + 0x54, + 0x57, + 0x41, + 0x72, + 0xea, + 0xf5, + 0x78, + 0xdd, + 0x86, + 0xbd, + 0x00, + 0x00, + 0x00, + 0x72, + 0x6a, + 0xf2, + 0x47, + 0x03, + 0xc0, + 0xcb, + 0x01, + 0x8d, + 0x02, + 0x21, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfb, + 0xa7, + 0xf7, + 0x94, + 0xe0, + 0x01, + 0x0c, + 0x00, + 0xc3, + 0x5d, + 0x00, + 0x06, + 0x82, + 0xca, + 0x9b, + 0x77, + 0x93, + 0x57, + 0xb3, + 0x76, + 0xbd, + 0x8b, + 0xcb, + 0xee, + 0xf4, + 0x2c, + 0xff, + 0x7f, + 0x95, + 0x33, + 0x15, + 0x10, + 0xa5, + 0xf9, + 0xfd, + 0xa6, + 0xbb, + 0x9e, + 0xf9, + 0x75, + 0x67, + 0xee, + 0xec, + 0x8b, + 0x40, + 0xea, + 0x32, + 0x47, + 0x3d, + 0x26, + 0xbe, + 0x11, + 0x9c, + 0xa6, + 0x40, + 0xbe, + 0x84, + 0x1f, + 0x1b, + 0x35, + 0x1a, + 0x66, + 0x10, + 0x9c, + 0xf4, + 0x12, + 0x1a, + 0x95, + 0x81, + 0xb5, + 0x55, + 0x6b, + 0xc5, + 0x42, + 0xfd, + 0x37, + 0x70, + 0xc5, + 0x08, + 0xa4, + 0x27, + 0x67, + 0x11, + 0x0b, + 0x1f, + 0xcc, + 0xdb, + 0x54, + 0x9b, + 0x5a, + 0x5f, + 0xee, + 0x21, + 0x63, + 0xdd, + 0x4b, + 0xbc, + 0x49, + 0x95, + 0x6d, + 0xf4, + 0xcb, + 0x9a, + 0x9a, + 0x5e, + 0xe4, + 0x7d, + 0x0f, + 0x02, + 0x22, + 0xa9, + 0x42, + 0x46, + 0x1a, + 0x04, + 0x87, + 0x43, + 0x72, + 0x59, + 0xa4, + 0xd6, + 0xeb, + 0x69, + 0x36, + 0xde, + 0xea, + 0x53, + 0x8c, + 0x89, + 0xd7, + 0x22, + 0xa6, + 0xf7, + 0xa8, + 0x4c, + 0x72, + 0x6c, + 0x80, + 0x69, + 0x01, + 0xb2, + 0xa7, + 0xe8, + 0x8b, + 0x94, + 0xaf, + 0x0e, + 0x47, + 0x58, + 0x1d, + 0x0e, + 0x5c, + 0x7c, + 0x33, + 0x9f, + 0x21, + 0x17, + 0x2c, + 0x4f, + 0x3d, + 0x72, + 0xff, + 0xcf, + 0x7a, + 0x4f, + 0x82, + 0x5b, + 0x85, + 0x28, + 0x70, + 0xf4, + 0x8c, + 0x81, + 0x41, + 0xb8, + 0x20, + 0x5c, + 0x3e, + 0x02, + 0x5e, + 0x5a, + 0x61, + 0xbb, + 0x2f, + 0x64, + 0xc5, + 0x4e, + 0x53, + 0xe4, + 0xca, + 0xe4, + 0xd9, + 0x75, + 0xaf, + 0x15, + 0x4d, + 0xff, + 0x01, + 0xec, + 0x13, + 0x4a, + 0x70, + 0x00, + 0x04, + 0xf9, + 0xfa, + 0x00, + 0x00, + 0x99, + 0x57, + 0xc4, + 0x96, + 0x00, + 0x02, + 0xf7, + 0x02, + 0x80, + 0x20, + 0xdf, + 0x01, + 0x8d, + 0x02, + 0x00, + 0x00, + 0x4c, + 0x41, + 0xe6, + 0xa1, + 0x9b, + 0xe3, + 0x51, + 0x40, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x59, + 0x5a + }; + + protected static byte[] OriginalIndexedBytes => Encoding.ASCII.GetBytes(OriginalIndexed); + + protected static string OriginalIndexed { get; } = + Original + + Original + + Original + + Original + + Original + + Original + + Original + + Original + + Original; } diff --git a/tests/SharpCompress.Test/Zip/Zip64Tests.cs b/tests/SharpCompress.Test/Zip/Zip64Tests.cs index 9a5210e5..7e20c3d4 100644 --- a/tests/SharpCompress.Test/Zip/Zip64Tests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64Tests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using SharpCompress.Archives; @@ -10,203 +10,200 @@ using SharpCompress.Writers; using SharpCompress.Writers.Zip; using Xunit; -namespace SharpCompress.Test.Zip +namespace SharpCompress.Test.Zip; + +public class Zip64Tests : WriterTests { - public class Zip64Tests : WriterTests + public Zip64Tests() : base(ArchiveType.Zip) { } + + // 4GiB + 1 + private const long FOUR_GB_LIMIT = ((long)uint.MaxValue) + 1; + + [Trait("format", "zip64")] + public void Zip64_Single_Large_File() => + // One single file, requires zip64 + RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: true, forward_only: false); + + [Trait("format", "zip64")] + public void Zip64_Two_Large_Files() => + // One single file, requires zip64 + RunSingleTest(2, FOUR_GB_LIMIT, set_zip64: true, forward_only: false); + + [Trait("format", "zip64")] + public void Zip64_Two_Small_files() => + // Multiple files, does not require zip64 + RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: false, forward_only: false); + + [Trait("format", "zip64")] + public void Zip64_Two_Small_files_stream() => + // Multiple files, does not require zip64, and works with streams + RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: false, forward_only: true); + + [Trait("format", "zip64")] + public void Zip64_Two_Small_Files_Zip64() => + // Multiple files, use zip64 even though it is not required + RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: true, forward_only: false); + + [Trait("format", "zip64")] + public void Zip64_Single_Large_File_Fail() { - public Zip64Tests() - : base(ArchiveType.Zip) + try { + // One single file, should fail + RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: false, forward_only: false); + throw new InvalidOperationException("Test did not fail?"); + } + catch (NotSupportedException) { } + } + + [Trait("zip64", "true")] + public void Zip64_Single_Large_File_Zip64_Streaming_Fail() + { + try + { + // One single file, should fail (fast) with zip64 + RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: true, forward_only: true); + throw new InvalidOperationException("Test did not fail?"); + } + catch (NotSupportedException) { } + } + + [Trait("zip64", "true")] + public void Zip64_Single_Large_File_Streaming_Fail() + { + try + { + // One single file, should fail once the write discovers the problem + RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: false, forward_only: true); + throw new InvalidOperationException("Test did not fail?"); + } + catch (NotSupportedException) { } + } + + public void RunSingleTest( + long files, + long filesize, + bool set_zip64, + bool forward_only, + long write_chunk_size = 1024 * 1024, + string filename = "zip64-test.zip" + ) + { + filename = Path.Combine(SCRATCH2_FILES_PATH, filename); + + if (File.Exists(filename)) + { + File.Delete(filename); } - // 4GiB + 1 - private const long FOUR_GB_LIMIT = ((long)uint.MaxValue) + 1; - - [Trait("format", "zip64")] - public void Zip64_Single_Large_File() + if (!File.Exists(filename)) { - // One single file, requires zip64 - RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: true, forward_only: false); + CreateZipArchive(filename, files, filesize, write_chunk_size, set_zip64, forward_only); } - [Trait("format", "zip64")] - public void Zip64_Two_Large_Files() + var resForward = ReadForwardOnly(filename); + if (resForward.Item1 != files) { - // One single file, requires zip64 - RunSingleTest(2, FOUR_GB_LIMIT, set_zip64: true, forward_only: false); + throw new InvalidOperationException( + $"Incorrect number of items reported: {resForward.Item1}, should have been {files}" + ); } - [Trait("format", "zip64")] - public void Zip64_Two_Small_files() + if (resForward.Item2 != files * filesize) { - // Multiple files, does not require zip64 - RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: false, forward_only: false); + throw new InvalidOperationException( + $"Incorrect combined size reported: {resForward.Item2}, should have been {files * filesize}" + ); } - [Trait("format", "zip64")] - public void Zip64_Two_Small_files_stream() + var resArchive = ReadArchive(filename); + if (resArchive.Item1 != files) { - // Multiple files, does not require zip64, and works with streams - RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: false, forward_only: true); + throw new InvalidOperationException( + $"Incorrect number of items reported: {resArchive.Item1}, should have been {files}" + ); } - [Trait("format", "zip64")] - public void Zip64_Two_Small_Files_Zip64() + if (resArchive.Item2 != files * filesize) { - // Multiple files, use zip64 even though it is not required - RunSingleTest(2, FOUR_GB_LIMIT / 2, set_zip64: true, forward_only: false); + throw new InvalidOperationException( + $"Incorrect number of items reported: {resArchive.Item2}, should have been {files * filesize}" + ); } + } - [Trait("format", "zip64")] - public void Zip64_Single_Large_File_Fail() + public void CreateZipArchive( + string filename, + long files, + long filesize, + long chunksize, + bool set_zip64, + bool forward_only + ) + { + var data = new byte[chunksize]; + + // Use deflate for speed + var opts = new ZipWriterOptions(CompressionType.Deflate) { UseZip64 = set_zip64 }; + + // Use no compression to ensure we hit the limits (actually inflates a bit, but seems better than using method==Store) + var eo = new ZipWriterEntryOptions() { - try - { - // One single file, should fail - RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: false, forward_only: false); - throw new InvalidOperationException("Test did not fail?"); - } - catch (NotSupportedException) - { - } - } + DeflateCompressionLevel = Compressors.Deflate.CompressionLevel.None + }; - [Trait("zip64", "true")] - public void Zip64_Single_Large_File_Zip64_Streaming_Fail() + using var zip = File.OpenWrite(filename); + using var st = forward_only ? (Stream)new ForwardOnlyStream(zip) : zip; + using var zipWriter = (ZipWriter)WriterFactory.Open(st, ArchiveType.Zip, opts); + for (var i = 0; i < files; i++) { - try + using var str = zipWriter.WriteToStream(i.ToString(), eo); + var left = filesize; + while (left > 0) { - // One single file, should fail (fast) with zip64 - RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: true, forward_only: true); - throw new InvalidOperationException("Test did not fail?"); - } - catch (NotSupportedException) - { - } - } - - [Trait("zip64", "true")] - public void Zip64_Single_Large_File_Streaming_Fail() - { - try - { - // One single file, should fail once the write discovers the problem - RunSingleTest(1, FOUR_GB_LIMIT, set_zip64: false, forward_only: true); - throw new InvalidOperationException("Test did not fail?"); - } - catch (NotSupportedException) - { - } - } - - public void RunSingleTest(long files, long filesize, bool set_zip64, bool forward_only, long write_chunk_size = 1024 * 1024, string filename = "zip64-test.zip") - { - filename = Path.Combine(SCRATCH2_FILES_PATH, filename); - - if (File.Exists(filename)) - { - File.Delete(filename); - } - - if (!File.Exists(filename)) - { - CreateZipArchive(filename, files, filesize, write_chunk_size, set_zip64, forward_only); - } - - var resForward = ReadForwardOnly(filename); - if (resForward.Item1 != files) - { - throw new InvalidOperationException($"Incorrect number of items reported: {resForward.Item1}, should have been {files}"); - } - - if (resForward.Item2 != files * filesize) - { - throw new InvalidOperationException($"Incorrect combined size reported: {resForward.Item2}, should have been {files * filesize}"); - } - - var resArchive = ReadArchive(filename); - if (resArchive.Item1 != files) - { - throw new InvalidOperationException($"Incorrect number of items reported: {resArchive.Item1}, should have been {files}"); - } - - if (resArchive.Item2 != files * filesize) - { - throw new InvalidOperationException($"Incorrect number of items reported: {resArchive.Item2}, should have been {files * filesize}"); - } - } - - public void CreateZipArchive(string filename, long files, long filesize, long chunksize, bool set_zip64, bool forward_only) - { - var data = new byte[chunksize]; - - // Use deflate for speed - var opts = new ZipWriterOptions(CompressionType.Deflate) { UseZip64 = set_zip64 }; - - // Use no compression to ensure we hit the limits (actually inflates a bit, but seems better than using method==Store) - var eo = new ZipWriterEntryOptions() { DeflateCompressionLevel = Compressors.Deflate.CompressionLevel.None }; - - using (var zip = File.OpenWrite(filename)) - using (var st = forward_only ? (Stream)new ForwardOnlyStream(zip) : zip) - using (var zipWriter = (ZipWriter)WriterFactory.Open(st, ArchiveType.Zip, opts)) - { - - for (var i = 0; i < files; i++) - { - using (var str = zipWriter.WriteToStream(i.ToString(), eo)) - { - var left = filesize; - while (left > 0) - { - var b = (int)Math.Min(left, data.Length); - str.Write(data, 0, b); - left -= b; - } - } - } - } - } - - public Tuple ReadForwardOnly(string filename) - { - long count = 0; - long size = 0; - Common.Zip.ZipEntry prev = null; - using (var fs = File.OpenRead(filename)) - using (var rd = ZipReader.Open(fs, new ReaderOptions() { LookForHeader = false })) - { - while (rd.MoveToNextEntry()) - { - using (rd.OpenEntryStream()) - { } - - count++; - if (prev != null) - { - size += prev.Size; - } - - prev = rd.Entry; - } - } - - if (prev != null) - { - size += prev.Size; - } - - return new Tuple(count, size); - } - - public Tuple ReadArchive(string filename) - { - using (var archive = ArchiveFactory.Open(filename)) - { - return new Tuple( - archive.Entries.Count(), - archive.Entries.Select(x => x.Size).Sum() - ); + var b = (int)Math.Min(left, data.Length); + str.Write(data, 0, b); + left -= b; } } } + + public Tuple ReadForwardOnly(string filename) + { + long count = 0; + long size = 0; + Common.Zip.ZipEntry? prev = null; + using (var fs = File.OpenRead(filename)) + using (var rd = ZipReader.Open(fs, new ReaderOptions() { LookForHeader = false })) + { + while (rd.MoveToNextEntry()) + { + using (rd.OpenEntryStream()) { } + + count++; + if (prev != null) + { + size += prev.Size; + } + + prev = rd.Entry; + } + } + + if (prev != null) + { + size += prev.Size; + } + + return new Tuple(count, size); + } + + public Tuple ReadArchive(string filename) + { + using var archive = ArchiveFactory.Open(filename); + return new Tuple( + archive.Entries.Count(), + archive.Entries.Select(x => x.Size).Sum() + ); + } } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 4ad7e7db..cdd24675 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Text; @@ -10,666 +10,621 @@ using SharpCompress.Writers; using SharpCompress.Writers.Zip; using Xunit; -namespace SharpCompress.Test.Zip +namespace SharpCompress.Test.Zip; + +public class ZipArchiveTests : ArchiveTests { - public class ZipArchiveTests : ArchiveTests - { - public ZipArchiveTests() - { - UseExtensionInsteadOfNameToVerify = true; - } + public ZipArchiveTests() => UseExtensionInsteadOfNameToVerify = true; - [Fact] - public void Zip_ZipX_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.zipx"); - } + [Fact] + public void Zip_ZipX_ArchiveStreamRead() => ArchiveStreamRead("Zip.zipx"); - [Fact] - public void Zip_BZip2_Streamed_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.bzip2.dd.zip"); - } - [Fact] - public void Zip_BZip2_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.bzip2.zip"); - } - [Fact] - public void Zip_Deflate_Streamed2_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.deflate.dd-.zip"); - } - [Fact] - public void Zip_Deflate_Streamed_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.deflate.dd.zip"); - } - [Fact] - public void Zip_Deflate_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.deflate.zip"); - } - [Fact] - public void Zip_Deflate64_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.deflate64.zip"); - } + [Fact] + public void Zip_BZip2_Streamed_ArchiveStreamRead() => ArchiveStreamRead("Zip.bzip2.dd.zip"); - [Fact] - public void Zip_LZMA_Streamed_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.lzma.dd.zip"); - } - [Fact] - public void Zip_LZMA_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.lzma.zip"); - } - [Fact] - public void Zip_PPMd_Streamed_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.ppmd.dd.zip"); - } - [Fact] - public void Zip_PPMd_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.ppmd.zip"); - } - [Fact] - public void Zip_None_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.none.zip"); - } + [Fact] + public void Zip_BZip2_ArchiveStreamRead() => ArchiveStreamRead("Zip.bzip2.zip"); - [Fact] - public void Zip_BZip2_Streamed_ArchiveFileRead() - { - ArchiveFileRead("Zip.bzip2.dd.zip"); - } - [Fact] - public void Zip_BZip2_ArchiveFileRead() - { - ArchiveFileRead("Zip.bzip2.zip"); - } - [Fact] - public void WinZip26_ArchiveFileRead() - { - ArchiveFileRead("WinZip26.zip"); - } - [Fact] - public void WinZip26_Multi_ArchiveFileRead() - { - ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zip", - "WinZip26.nocomp.multi.z01"); //min split size is 64k so no compression used - } - [Fact] - public void WinZip26_X_BZip2_ArchiveFileRead() - { - ArchiveFileRead("WinZip26_BZip2.zipx"); - } - [Fact] - public void WinZip26_X_Lzma_ArchiveFileRead() - { - ArchiveFileRead("WinZip26_LZMA.zipx"); - } - [Fact] - public void WinZip26_X_Multi_ArchiveFileRead() - { - ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zipx", - "WinZip26.nocomp.multi.zx01"); //min split size is 64k so no compression used - } - [Fact] - public void Zip_Deflate_Streamed2_ArchiveFileRead() - { - ArchiveFileRead("Zip.deflate.dd-.zip"); - } - [Fact] - public void Zip_Deflate_Streamed_ArchiveFileRead() - { - ArchiveFileRead("Zip.deflate.dd.zip"); - } - [Fact] - public void Zip_Deflate_ArchiveFileRead() - { - ArchiveFileRead("Zip.deflate.zip"); - } - //will detect and load other files - [Fact] - public void Zip_Deflate_Multi_ArchiveFirstFileRead() - { - ArchiveFileRead("WinZip26.nocomp.multi.zip"); - //"WinZip26.nocomp.multi.z01" - } - //will detect and load other files - [Fact] - public void ZipX_Deflate_Multi_ArchiveFirstFileRead() - { - ArchiveFileRead("WinZip26.nocomp.multi.zipx"); - //"WinZip26.nocomp.multi.zx01" - } - [Fact] - public void Zip_GetParts() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { - "Infozip.nocomp.multi.zip", - "Infozip.nocomp.multi.z01"}); - } - [Fact] - public void ZipX_GetParts() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { - "WinZip26.nocomp.multi.zipx", - "WinZip26.nocomp.multi.zx01"}); - } - [Fact] - public void Zip_GetPartsSplit() - { - //uses first part to search for all parts and compares against this array - ArchiveGetParts(new string[] { + [Fact] + public void Zip_Deflate_Streamed2_ArchiveStreamRead() => + ArchiveStreamRead("Zip.deflate.dd-.zip"); + + [Fact] + public void Zip_Deflate_Streamed_ArchiveStreamRead() => ArchiveStreamRead("Zip.deflate.dd.zip"); + + [Fact] + public void Zip_Deflate_ArchiveStreamRead() => ArchiveStreamRead("Zip.deflate.zip"); + + [Fact] + public void Zip_Deflate64_ArchiveStreamRead() => ArchiveStreamRead("Zip.deflate64.zip"); + + [Fact] + public void Zip_LZMA_Streamed_ArchiveStreamRead() => ArchiveStreamRead("Zip.lzma.dd.zip"); + + [Fact] + public void Zip_LZMA_ArchiveStreamRead() => ArchiveStreamRead("Zip.lzma.zip"); + + [Fact] + public void Zip_PPMd_Streamed_ArchiveStreamRead() => ArchiveStreamRead("Zip.ppmd.dd.zip"); + + [Fact] + public void Zip_PPMd_ArchiveStreamRead() => ArchiveStreamRead("Zip.ppmd.zip"); + + [Fact] + public void Zip_None_ArchiveStreamRead() => ArchiveStreamRead("Zip.none.zip"); + + [Fact] + public void Zip_BZip2_Streamed_ArchiveFileRead() => ArchiveFileRead("Zip.bzip2.dd.zip"); + + [Fact] + public void Zip_BZip2_ArchiveFileRead() => ArchiveFileRead("Zip.bzip2.zip"); + + [Fact] + public void WinZip26_ArchiveFileRead() => ArchiveFileRead("WinZip26.zip"); + + [Fact] + public void WinZip26_Multi_ArchiveFileRead() => + ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zip", "WinZip26.nocomp.multi.z01"); //min split size is 64k so no compression used + + [Fact] + public void WinZip26_X_BZip2_ArchiveFileRead() => ArchiveFileRead("WinZip26_BZip2.zipx"); + + [Fact] + public void WinZip26_X_Lzma_ArchiveFileRead() => ArchiveFileRead("WinZip26_LZMA.zipx"); + + [Fact] + public void WinZip26_X_Multi_ArchiveFileRead() => + ArchiveStreamMultiRead(null, "WinZip26.nocomp.multi.zipx", "WinZip26.nocomp.multi.zx01"); //min split size is 64k so no compression used + + [Fact] + public void Zip_Deflate_Streamed2_ArchiveFileRead() => ArchiveFileRead("Zip.deflate.dd-.zip"); + + [Fact] + public void Zip_Deflate_Streamed_ArchiveFileRead() => ArchiveFileRead("Zip.deflate.dd.zip"); + + [Fact] + public void Zip_Deflate_ArchiveFileRead() => ArchiveFileRead("Zip.deflate.zip"); + + //will detect and load other files + [Fact] + public void Zip_Deflate_Multi_ArchiveFirstFileRead() => + ArchiveFileRead("WinZip26.nocomp.multi.zip"); + + //"WinZip26.nocomp.multi.z01" + //will detect and load other files + [Fact] + public void ZipX_Deflate_Multi_ArchiveFirstFileRead() => + ArchiveFileRead("WinZip26.nocomp.multi.zipx"); + + //"WinZip26.nocomp.multi.zx01" + [Fact] + public void Zip_GetParts() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts(new[] { "Infozip.nocomp.multi.zip", "Infozip.nocomp.multi.z01" }); + + [Fact] + public void ZipX_GetParts() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts(new[] { "WinZip26.nocomp.multi.zipx", "WinZip26.nocomp.multi.zx01" }); + + [Fact] + public void Zip_GetPartsSplit() => + //uses first part to search for all parts and compares against this array + ArchiveGetParts( + new[] + { "Zip.deflate.split.001", "Zip.deflate.split.002", "Zip.deflate.split.003", "Zip.deflate.split.004", "Zip.deflate.split.005", - "Zip.deflate.split.006"}); - } - //will detect and load other files - [Fact] - public void Zip_Deflate_Split_ArchiveFirstFileRead() - { - ArchiveFileRead("Zip.deflate.split.001"); - //"Zip.deflate.split.002", - //"Zip.deflate.split.003", - //"Zip.deflate.split.004", - //"Zip.deflate.split.005", - //"Zip.deflate.split.006" - } - [Fact] - public void Zip_Deflate_Split_ArchiveFileRead() - { - ArchiveStreamMultiRead(null, "Zip.deflate.split.001", - "Zip.deflate.split.002", - "Zip.deflate.split.003", - "Zip.deflate.split.004", - "Zip.deflate.split.005", - "Zip.deflate.split.006"); - } - [Fact] - public void Zip_InfoZip_Multi_ArchiveFileRead() - { - ArchiveStreamMultiRead(null, "Infozip.nocomp.multi.zip", - "Infozip.nocomp.multi.z01"); //min split size is 64k so no compression used - } - [Fact] - public void Zip_Deflate64_ArchiveFileRead() - { - ArchiveFileRead("Zip.deflate64.zip"); - } - - [Fact] - public void Zip_LZMA_Streamed_ArchiveFileRead() - { - ArchiveFileRead("Zip.lzma.dd.zip"); - } - [Fact] - public void Zip_LZMA_ArchiveFileRead() - { - ArchiveFileRead("Zip.lzma.zip"); - } - [Fact] - public void Zip_PPMd_Streamed_ArchiveFileRead() - { - ArchiveFileRead("Zip.ppmd.dd.zip"); - } - [Fact] - public void Zip_PPMd_ArchiveFileRead() - { - ArchiveFileRead("Zip.ppmd.zip"); - } - [Fact] - public void Zip_None_ArchiveFileRead() - { - ArchiveFileRead("Zip.none.zip"); - } - - [Fact] - public void Zip_Zip64_ArchiveStreamRead() - { - ArchiveStreamRead("Zip.zip64.zip"); - } - - [Fact] - public void Zip_Zip64_ArchiveFileRead() - { - ArchiveFileRead("Zip.zip64.zip"); - } - - [Fact] - public void Zip_Random_Write_Remove() - { - string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - - using (var archive = ZipArchive.Open(unmodified)) - { - var entry = archive.Entries.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)); - archive.RemoveEntry(entry); - - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); - - archive.SaveTo(scratchPath, writerOptions); + "Zip.deflate.split.006" } - CompareArchivesByPath(modified, scratchPath, Encoding.GetEncoding(866)); + ); + + //will detect and load other files + [Fact] + public void Zip_Deflate_Split_ArchiveFirstFileRead() => + ArchiveFileRead("Zip.deflate.split.001"); + + //"Zip.deflate.split.002", + //"Zip.deflate.split.003", + //"Zip.deflate.split.004", + //"Zip.deflate.split.005", + //"Zip.deflate.split.006" + [Fact] + public void Zip_Deflate_Split_ArchiveFileRead() => + ArchiveStreamMultiRead( + null, + "Zip.deflate.split.001", + "Zip.deflate.split.002", + "Zip.deflate.split.003", + "Zip.deflate.split.004", + "Zip.deflate.split.005", + "Zip.deflate.split.006" + ); + + [Fact] + public void Zip_InfoZip_Multi_ArchiveFileRead() => + ArchiveStreamMultiRead(null, "Infozip.nocomp.multi.zip", "Infozip.nocomp.multi.z01"); //min split size is 64k so no compression used + + [Fact] + public void Zip_Deflate64_ArchiveFileRead() => ArchiveFileRead("Zip.deflate64.zip"); + + [Fact] + public void Zip_LZMA_Streamed_ArchiveFileRead() => ArchiveFileRead("Zip.lzma.dd.zip"); + + [Fact] + public void Zip_LZMA_ArchiveFileRead() => ArchiveFileRead("Zip.lzma.zip"); + + [Fact] + public void Zip_PPMd_Streamed_ArchiveFileRead() => ArchiveFileRead("Zip.ppmd.dd.zip"); + + [Fact] + public void Zip_PPMd_ArchiveFileRead() => ArchiveFileRead("Zip.ppmd.zip"); + + [Fact] + public void Zip_None_ArchiveFileRead() => ArchiveFileRead("Zip.none.zip"); + + [Fact] + public void Zip_Zip64_ArchiveStreamRead() => ArchiveStreamRead("Zip.zip64.zip"); + + [Fact] + public void Zip_Zip64_ArchiveFileRead() => ArchiveFileRead("Zip.zip64.zip"); + + [Fact] + public void Zip_Random_Write_Remove() + { + string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); + string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); + + using (var archive = ZipArchive.Open(unmodified)) + { + var entry = archive.Entries.Single( + x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ); + archive.RemoveEntry(entry); + + WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); + writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + + archive.SaveTo(scratchPath, writerOptions); + } + CompareArchivesByPath(modified, scratchPath, Encoding.GetEncoding(866)); + } + + [Fact] + public void Zip_Random_Write_Add() + { + string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); + string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); + string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod2.zip"); + + using (var archive = ZipArchive.Open(unmodified)) + { + archive.AddEntry("jpg\\test.jpg", jpg); + + WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); + writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + + archive.SaveTo(scratchPath, writerOptions); + } + CompareArchivesByPath(modified, scratchPath, Encoding.GetEncoding(866)); + } + + [Fact] + public void Zip_Save_Twice() + { + string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); + string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); + + using (var arc = ZipArchive.Create()) + { + string str = "test.txt"; + var source = new MemoryStream(Encoding.UTF8.GetBytes(str)); + arc.AddEntry("test.txt", source, true, source.Length); + arc.SaveTo(scratchPath1, CompressionType.Deflate); + arc.SaveTo(scratchPath2, CompressionType.Deflate); } - [Fact] - public void Zip_Random_Write_Add() + Assert.Equal(new FileInfo(scratchPath1).Length, new FileInfo(scratchPath2).Length); + } + + [Fact] + public void Zip_Removal_Poly() + { + string scratchPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); + + using (ZipArchive vfs = (ZipArchive)ArchiveFactory.Open(scratchPath)) { - string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); - string scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod2.zip"); - - using (var archive = ZipArchive.Open(unmodified)) - { - archive.AddEntry("jpg\\test.jpg", jpg); - - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); - - archive.SaveTo(scratchPath, writerOptions); - } - CompareArchivesByPath(modified, scratchPath, Encoding.GetEncoding(866)); + var e = vfs.Entries.First( + v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ); + vfs.RemoveEntry(e); + Assert.Null( + vfs.Entries.FirstOrDefault( + v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ) + ); + Assert.Null( + ((IArchive)vfs).Entries.FirstOrDefault( + v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ) + ); } + } - [Fact] - public void Zip_Save_Twice() + [Fact] + public void Zip_Create_NoDups() + { + using (var arc = ZipArchive.Create()) { - string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); - string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); + arc.AddEntry("1.txt", new MemoryStream()); + Assert.Throws(() => arc.AddEntry("\\1.txt", new MemoryStream())); + } + } - using (var arc = ZipArchive.Create()) + [Fact] + public void Zip_Create_Same_Stream() + { + string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); + string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); + + using (var arc = ZipArchive.Create()) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("qwert"))) { - string str = "test.txt"; - var source = new MemoryStream(Encoding.UTF8.GetBytes(str)); - arc.AddEntry("test.txt", source, true, source.Length); + arc.AddEntry("1.txt", stream, false, stream.Length); + arc.AddEntry("2.txt", stream, false, stream.Length); arc.SaveTo(scratchPath1, CompressionType.Deflate); arc.SaveTo(scratchPath2, CompressionType.Deflate); } - - Assert.Equal(new FileInfo(scratchPath1).Length, new FileInfo(scratchPath2).Length); } - [Fact] - public void Zip_Removal_Poly() + Assert.Equal(new FileInfo(scratchPath1).Length, new FileInfo(scratchPath2).Length); + } + + [Fact] + public void Zip_Create_New() + { + foreach ( + var file in Directory.EnumerateFiles( + ORIGINAL_FILES_PATH, + "*.*", + SearchOption.AllDirectories + ) + ) { - - string scratchPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - - - - using (ZipArchive vfs = (ZipArchive)ArchiveFactory.Open(scratchPath)) + var newFileName = file.Substring(ORIGINAL_FILES_PATH.Length); + if ( + newFileName.StartsWith( + Path.DirectorySeparatorChar.ToString(), + StringComparison.OrdinalIgnoreCase + ) + ) { - var e = vfs.Entries.First(v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)); - vfs.RemoveEntry(e); - Assert.Null(vfs.Entries.FirstOrDefault(v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); - Assert.Null(((IArchive)vfs).Entries.FirstOrDefault(v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); + newFileName = newFileName.Substring(1); + } + newFileName = Path.Combine(SCRATCH_FILES_PATH, newFileName); + var newDir = Path.GetDirectoryName(newFileName) ?? throw new ArgumentNullException(); + if (!Directory.Exists(newDir)) + { + Directory.CreateDirectory(newDir); + } + File.Copy(file, newFileName); + } + string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); + + using (var archive = ZipArchive.Create()) + { + archive.AddAllFromDirectory(SCRATCH_FILES_PATH); + + WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); + writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + + archive.SaveTo(scratchPath, writerOptions); + } + CompareArchivesByPath(unmodified, scratchPath, Encoding.GetEncoding(866)); + Directory.Delete(SCRATCH_FILES_PATH, true); + } + + /// + /// Creates an empty zip file and attempts to read it right afterwards. + /// Ensures that parsing file headers works even in that case + /// + [Fact] + public void Zip_Create_Empty_And_Read() + { + var archive = ZipArchive.Create(); + + var archiveStream = new MemoryStream(); + + archive.SaveTo(archiveStream, CompressionType.LZMA); + + archiveStream.Position = 0; + + var readArchive = ArchiveFactory.Open(archiveStream); + + var count = readArchive.Entries.Count(); + + Assert.Equal(0, count); + } + + [Fact] + public void Zip_Create_New_Add_Remove() + { + foreach ( + var file in Directory.EnumerateFiles( + ORIGINAL_FILES_PATH, + "*.*", + SearchOption.AllDirectories + ) + ) + { + var newFileName = file.Substring(ORIGINAL_FILES_PATH.Length); + if ( + newFileName.StartsWith( + Path.DirectorySeparatorChar.ToString(), + StringComparison.OrdinalIgnoreCase + ) + ) + { + newFileName = newFileName.Substring(1); + } + newFileName = Path.Combine(SCRATCH_FILES_PATH, newFileName); + var newDir = Path.GetDirectoryName(newFileName) ?? throw new ArgumentNullException(); + if (!Directory.Exists(newDir)) + { + Directory.CreateDirectory(newDir); + } + File.Copy(file, newFileName); + } + string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); + + using (var archive = ZipArchive.Create()) + { + archive.AddAllFromDirectory(SCRATCH_FILES_PATH); + archive.RemoveEntry( + archive.Entries.Single( + x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ) + ); + Assert.Null( + archive.Entries.FirstOrDefault( + x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) + ) + ); + } + Directory.Delete(SCRATCH_FILES_PATH, true); + } + + [Fact] + public void Zip_Deflate_WinzipAES_Read() + { + using ( + var reader = ZipArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"), + new ReaderOptions() { Password = "test" } + ) + ) + { + foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } + VerifyFiles(); + } - [Fact] - public void Zip_Create_NoDups() + [Fact] + public void Zip_Deflate_WinzipAES_MultiOpenEntryStream() + { + using ( + var reader = ZipArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES2.zip"), + new ReaderOptions() { Password = "test" } + ) + ) { - using (var arc = ZipArchive.Create()) + foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { - arc.AddEntry("1.txt", new MemoryStream()); - Assert.Throws(() => arc.AddEntry("\\1.txt", new MemoryStream())); - } - } - - [Fact] - public void Zip_Create_Same_Stream() - { - string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); - string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); - - using (var arc = ZipArchive.Create()) - { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("qwert"))) - { - arc.AddEntry("1.txt", stream, false, stream.Length); - arc.AddEntry("2.txt", stream, false, stream.Length); - arc.SaveTo(scratchPath1, CompressionType.Deflate); - arc.SaveTo(scratchPath2, CompressionType.Deflate); - } - } - - Assert.Equal(new FileInfo(scratchPath1).Length, new FileInfo(scratchPath2).Length); - } - - [Fact] - public void Zip_Create_New() - { - foreach (var file in Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories)) - { - var newFileName = file.Substring(ORIGINAL_FILES_PATH.Length); - if (newFileName.StartsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.OrdinalIgnoreCase)) - { - newFileName = newFileName.Substring(1); - } - newFileName = Path.Combine(SCRATCH_FILES_PATH, newFileName); - var newDir = Path.GetDirectoryName(newFileName); - if (!Directory.Exists(newDir)) - { - Directory.CreateDirectory(newDir); - } - File.Copy(file, newFileName); - } - string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - - using (var archive = ZipArchive.Create()) - { - archive.AddAllFromDirectory(SCRATCH_FILES_PATH); - - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); - - archive.SaveTo(scratchPath, writerOptions); - } - CompareArchivesByPath(unmodified, scratchPath, Encoding.GetEncoding(866)); - Directory.Delete(SCRATCH_FILES_PATH, true); - } - - /// - /// Creates an empty zip file and attempts to read it right afterwards. - /// Ensures that parsing file headers works even in that case - /// - [Fact] - public void Zip_Create_Empty_And_Read() - { - var archive = ZipArchive.Create(); - - var archiveStream = new MemoryStream(); - - archive.SaveTo(archiveStream, CompressionType.LZMA); - - archiveStream.Position = 0; - - var readArchive = ArchiveFactory.Open(archiveStream); - - var count = readArchive.Entries.Count(); - - Assert.Equal(0, count); - } - - [Fact] - public void Zip_Create_New_Add_Remove() - { - foreach (var file in Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories)) - { - var newFileName = file.Substring(ORIGINAL_FILES_PATH.Length); - if (newFileName.StartsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.OrdinalIgnoreCase)) - { - newFileName = newFileName.Substring(1); - } - newFileName = Path.Combine(SCRATCH_FILES_PATH, newFileName); - var newDir = Path.GetDirectoryName(newFileName); - if (!Directory.Exists(newDir)) - { - Directory.CreateDirectory(newDir); - } - File.Copy(file, newFileName); - } - string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); - - using (var archive = ZipArchive.Create()) - { - archive.AddAllFromDirectory(SCRATCH_FILES_PATH); - archive.RemoveEntry(archive.Entries.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); - Assert.Null(archive.Entries.FirstOrDefault(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); - } - Directory.Delete(SCRATCH_FILES_PATH, true); - } - - [Fact] - public void Zip_Deflate_WinzipAES_Read() - { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"), new ReaderOptions() - { - Password = "test" - })) - { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - [Fact] - public void Zip_Deflate_WinzipAES_MultiOpenEntryStream() - { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES2.zip"), new ReaderOptions() - { - Password = "test" - })) - { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) - { - var stream = entry.OpenEntryStream(); - Assert.NotNull(stream); - var ex = Record.Exception(() => stream = entry.OpenEntryStream()); - Assert.Null(ex); - } - } - } - - [Fact] - public void Zip_Read_Volume_Comment() - { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.zip"), new ReaderOptions() - { - Password = "test" - })) - { - var isComplete = reader.IsComplete; - Assert.Equal(1, reader.Volumes.Count); - - string expectedComment = "Encoding:utf-8 || Compression:Deflate levelDefault || Encrypt:None || ZIP64:Always\r\nCreated at 2017-Jan-23 14:10:43 || DotNetZip Tool v1.9.1.8\r\nTest zip64 archive"; - Assert.Equal(expectedComment, reader.Volumes.First().Comment); - } - } - - [Fact] - public void Zip_BZip2_Pkware_Read() - { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"), new ReaderOptions() - { - Password = "test" - })) - { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - VerifyFiles(); - } - - [Fact] - public void Zip_Random_Entry_Access() - { - string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - - ZipArchive a = ZipArchive.Open(unmodified); - int count = 0; - foreach (var e in a.Entries) - { - count++; - } - - //Prints 3 - Assert.Equal(3, count); - a.Dispose(); - - a = ZipArchive.Open(unmodified); - int count2 = 0; - - foreach (var e in a.Entries) - { - count2++; - - //Stop at last file - if (count2 == count) - { - var s = e.OpenEntryStream(); - s.ReadByte(); //Actually access stream - s.Dispose(); - break; - } - } - - int count3 = 0; - foreach (var e in a.Entries) - { - count3++; - } - - Assert.Equal(3, count3); - } - - [Fact] - public void Zip_Deflate_PKWear_Multipy_Entry_Access() - { - string zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"); - - using (FileStream fileStream = File.Open(zipFile, FileMode.Open)) - { - using (IArchive archive = ArchiveFactory.Open(fileStream, new ReaderOptions { Password = "12345678" })) - { - var entries = archive.Entries.Where(entry => !entry.IsDirectory); - foreach (IArchiveEntry entry in entries) - { - for (var i = 0; i < 100; i++) - { - using (var memoryStream = new MemoryStream()) - using (Stream entryStream = entry.OpenEntryStream()) - { - entryStream.CopyTo(memoryStream); - } - } - } - } - } - } - - [SkippableFact] - public void Zip_Evil_Throws_Exception() - { - //windows only because of the paths - Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT); - - string zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.Evil.zip"); - - Assert.ThrowsAny(() => - { - using (var archive = ZipArchive.Open(zipFile)) - { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) - { - entry.WriteToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - }); - } - - private class NonSeekableMemoryStream : MemoryStream - { - public override bool CanSeek => false; - } - - [Fact] - public void TestSharpCompressWithEmptyStream() - { - MemoryStream stream = new NonSeekableMemoryStream(); - - using (IWriter zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) - { - zipWriter.Write("foo.txt", new MemoryStream(Array.Empty())); - zipWriter.Write("foo2.txt", new MemoryStream(new byte[10])); - } - - stream = new MemoryStream(stream.ToArray()); - File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), stream.ToArray()); - - using (var zipArchive = ZipArchive.Open(stream)) - { - foreach (var entry in zipArchive.Entries) - { - using (var entryStream = entry.OpenEntryStream()) - { - MemoryStream tempStream = new MemoryStream(); - const int bufSize = 0x1000; - byte[] buf = new byte[bufSize]; - int bytesRead = 0; - while ((bytesRead = entryStream.Read(buf, 0, bufSize)) > 0) - { - tempStream.Write(buf, 0, bytesRead); - } - } - } - } - } - - [Fact] - public void Zip_BadLocalExtra_Read() - { - string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.badlocalextra.zip"); - - using (ZipArchive za = ZipArchive.Open(zipPath)) - { - var ex = Record.Exception(() => - { - var firstEntry = za.Entries.First(x => x.Key == "first.txt"); - var buffer = new byte[4096]; - - using (var memoryStream = new MemoryStream()) - using (var firstStream = firstEntry.OpenEntryStream()) - { - firstStream.CopyTo(memoryStream); - Assert.Equal(199, memoryStream.Length); - } - }); - + var stream = entry.OpenEntryStream(); + Assert.NotNull(stream); + var ex = Record.Exception(() => stream = entry.OpenEntryStream()); Assert.Null(ex); } } + } - [Fact] - public void Zip_NoCompression_DataDescriptors_Read() + [Fact] + public void Zip_Read_Volume_Comment() + { + using ( + var reader = ZipArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.zip"), + new ReaderOptions() { Password = "test" } + ) + ) { - string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.datadescriptors.zip"); + var isComplete = reader.IsComplete; + Assert.Equal(1, reader.Volumes.Count); - using (ZipArchive za = ZipArchive.Open(zipPath)) + string expectedComment = + "Encoding:utf-8 || Compression:Deflate levelDefault || Encrypt:None || ZIP64:Always\r\nCreated at 2017-Jan-23 14:10:43 || DotNetZip Tool v1.9.1.8\r\nTest zip64 archive"; + Assert.Equal(expectedComment, reader.Volumes.First().Comment); + } + } + + [Fact] + public void Zip_BZip2_Pkware_Read() + { + using ( + var reader = ZipArchive.Open( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"), + new ReaderOptions() { Password = "test" } + ) + ) + { + foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + VerifyFiles(); + } + + [Fact] + public void Zip_Random_Entry_Access() + { + string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); + + ZipArchive a = ZipArchive.Open(unmodified); + int count = 0; + foreach (var e in a.Entries) + { + count++; + } + + //Prints 3 + Assert.Equal(3, count); + a.Dispose(); + + a = ZipArchive.Open(unmodified); + int count2 = 0; + + foreach (var e in a.Entries) + { + count2++; + + //Stop at last file + if (count2 == count) + { + var s = e.OpenEntryStream(); + s.ReadByte(); //Actually access stream + s.Dispose(); + break; + } + } + + int count3 = 0; + foreach (var e in a.Entries) + { + count3++; + } + + Assert.Equal(3, count3); + } + + [Fact] + public void Zip_Deflate_PKWear_Multipy_Entry_Access() + { + string zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"); + + using (FileStream fileStream = File.Open(zipFile, FileMode.Open)) + { + using ( + IArchive archive = ArchiveFactory.Open( + fileStream, + new ReaderOptions { Password = "12345678" } + ) + ) + { + var entries = archive.Entries.Where(entry => !entry.IsDirectory); + foreach (IArchiveEntry entry in entries) + { + for (var i = 0; i < 100; i++) + { + using (var memoryStream = new MemoryStream()) + using (Stream entryStream = entry.OpenEntryStream()) + { + entryStream.CopyTo(memoryStream); + } + } + } + } + } + } + + [SkippableFact] + public void Zip_Evil_Throws_Exception() + { + //windows only because of the paths + Skip.IfNot(Environment.OSVersion.Platform == PlatformID.Win32NT); + + string zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.Evil.zip"); + + Assert.ThrowsAny(() => + { + using (var archive = ZipArchive.Open(zipFile)) + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + entry.WriteToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); + } + } + }); + } + + private class NonSeekableMemoryStream : MemoryStream + { + public override bool CanSeek => false; + } + + [Fact] + public void TestSharpCompressWithEmptyStream() + { + MemoryStream stream = new NonSeekableMemoryStream(); + + using ( + IWriter zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate) + ) + { + zipWriter.Write("foo.txt", new MemoryStream(Array.Empty())); + zipWriter.Write("foo2.txt", new MemoryStream(new byte[10])); + } + + stream = new MemoryStream(stream.ToArray()); + File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), stream.ToArray()); + + using (var zipArchive = ZipArchive.Open(stream)) + { + foreach (var entry in zipArchive.Entries) + { + using (var entryStream = entry.OpenEntryStream()) + { + MemoryStream tempStream = new MemoryStream(); + const int bufSize = 0x1000; + byte[] buf = new byte[bufSize]; + int bytesRead = 0; + while ((bytesRead = entryStream.Read(buf, 0, bufSize)) > 0) + { + tempStream.Write(buf, 0, bytesRead); + } + } + } + } + } + + [Fact] + public void Zip_BadLocalExtra_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.badlocalextra.zip"); + + using (ZipArchive za = ZipArchive.Open(zipPath)) + { + var ex = Record.Exception(() => { var firstEntry = za.Entries.First(x => x.Key == "first.txt"); var buffer = new byte[4096]; @@ -680,111 +635,145 @@ namespace SharpCompress.Test.Zip firstStream.CopyTo(memoryStream); Assert.Equal(199, memoryStream.Length); } + }); - var len1 = 0; - var buffer1 = new byte[firstEntry.Size + 256]; + Assert.Null(ex); + } + } - using (var firstStream = firstEntry.OpenEntryStream()) - { - len1 = firstStream.Read(buffer1, 0, buffer.Length); - } + [Fact] + public void Zip_NoCompression_DataDescriptors_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.datadescriptors.zip"); - Assert.Equal(199, len1); + using (ZipArchive za = ZipArchive.Open(zipPath)) + { + var firstEntry = za.Entries.First(x => x.Key == "first.txt"); + var buffer = new byte[4096]; + + using (var memoryStream = new MemoryStream()) + using (var firstStream = firstEntry.OpenEntryStream()) + { + firstStream.CopyTo(memoryStream); + Assert.Equal(199, memoryStream.Length); + } + + var len1 = 0; + var buffer1 = new byte[firstEntry.Size + 256]; + + using (var firstStream = firstEntry.OpenEntryStream()) + { + len1 = firstStream.Read(buffer1, 0, buffer.Length); + } + + Assert.Equal(199, len1); #if !NETFRAMEWORK && !NETSTANDARD2_0 - var len2 = 0; - var buffer2 = new byte[firstEntry.Size + 256]; + var len2 = 0; + var buffer2 = new byte[firstEntry.Size + 256]; - using (var firstStream = firstEntry.OpenEntryStream()) - { - len2 = firstStream.Read(buffer2.AsSpan()); - } - Assert.Equal(len1, len2); - Assert.Equal(buffer1, buffer2); + using (var firstStream = firstEntry.OpenEntryStream()) + { + len2 = firstStream.Read(buffer2.AsSpan()); + } + Assert.Equal(len1, len2); + Assert.Equal(buffer1, buffer2); #endif + } + } + + [Fact] + public void Zip_LongComment_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip"); + + using (ZipArchive za = ZipArchive.Open(zipPath)) + { + var count = za.Entries.Count; + Assert.Equal(1, count); + } + } + + [Fact] + public void Zip_Zip64_CompressedSizeExtraOnly_Read() + { + string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.compressedonly.zip"); + + using (ZipArchive za = ZipArchive.Open(zipPath)) + { + var firstEntry = za.Entries.First(x => x.Key == "test/test.txt"); + + using (var memoryStream = new MemoryStream()) + using (var firstStream = firstEntry.OpenEntryStream()) + { + firstStream.CopyTo(memoryStream); + Assert.Equal(15, memoryStream.Length); } } + } - [Fact] - public void Zip_LongComment_Read() + [Fact] + public void Zip_Uncompressed_Skip_All() + { + var keys = new[] { - string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip"); - - using (ZipArchive za = ZipArchive.Open(zipPath)) + "Folder/File1.txt", + "Folder/File2.rtf", + "Folder2/File1.txt", + "Folder2/File2.txt", + "DEADBEEF" + }; + var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); + using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) + { + IArchive archive = ArchiveFactory.Open(stream); + IReader reader = archive.ExtractAllEntries(); + int x = 0; + while (reader.MoveToNextEntry()) { - var count = za.Entries.Count; - Assert.Equal(1, count); + Assert.Equal(keys[x], reader.Entry.Key); + x++; } + + Assert.Equal(4, x); } + } - [Fact] - public void Zip_Zip64_CompressedSizeExtraOnly_Read() + [Fact] + public void Zip_Forced_Ignores_UnicodePathExtra() + { + var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.UnicodePathExtra.zip"); + using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) { - string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.compressedonly.zip"); - - using (ZipArchive za = ZipArchive.Open(zipPath)) - { - var firstEntry = za.Entries.First(x => x.Key == "test/test.txt"); - - using (var memoryStream = new MemoryStream()) - using (var firstStream = firstEntry.OpenEntryStream()) - { - firstStream.CopyTo(memoryStream); - Assert.Equal(15, memoryStream.Length); - } - } - } - - [Fact] - public void Zip_Uncompressed_Skip_All() - { - var keys = new string[] { "Folder/File1.txt", "Folder/File2.rtf", "Folder2/File1.txt", "Folder2/File2.txt", "DEADBEEF" }; - var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); - using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) - { - IArchive archive = ArchiveFactory.Open(stream); - IReader reader = archive.ExtractAllEntries(); - int x = 0; - while (reader.MoveToNextEntry()) - { - Assert.Equal(keys[x], reader.Entry.Key); - x++; - } - - Assert.Equal(4, x); - } - } - - [Fact] - public void Zip_Forced_Ignores_UnicodePathExtra() - { - var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.UnicodePathExtra.zip"); - using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) - { - IArchive archive = ArchiveFactory.Open(stream, new ReaderOptions + IArchive archive = ArchiveFactory.Open( + stream, + new ReaderOptions { ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding("shift_jis"), } - }); - IReader reader = archive.ExtractAllEntries(); - reader.MoveToNextEntry(); - Assert.Equal("궖귛궖귙귪궖귗귪궖귙_wav.frq", reader.Entry.Key); - } - using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) - { - IArchive archive = ArchiveFactory.Open(stream, new ReaderOptions + } + ); + IReader reader = archive.ExtractAllEntries(); + reader.MoveToNextEntry(); + Assert.Equal("궖귛궖귙귪궖귗귪궖귙_wav.frq", reader.Entry.Key); + } + using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) + { + IArchive archive = ArchiveFactory.Open( + stream, + new ReaderOptions { ArchiveEncoding = new ArchiveEncoding { Forced = Encoding.GetEncoding("shift_jis"), } - }); - IReader reader = archive.ExtractAllEntries(); - reader.MoveToNextEntry(); - Assert.Equal("きょきゅんきゃんきゅ_wav.frq", reader.Entry.Key); - } + } + ); + IReader reader = archive.ExtractAllEntries(); + reader.MoveToNextEntry(); + Assert.Equal("きょきゅんきゃんきゅ_wav.frq", reader.Entry.Key); } } } diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 65ae9326..95d9b93d 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -8,431 +8,359 @@ using SharpCompress.Test.Mocks; using SharpCompress.Writers; using Xunit; -namespace SharpCompress.Test.Zip +namespace SharpCompress.Test.Zip; + +public class ZipReaderTests : ReaderTests { - public class ZipReaderTests : ReaderTests + public ZipReaderTests() => UseExtensionInsteadOfNameToVerify = true; + + [Fact] + public void Issue_269_Double_Skip() { - public ZipReaderTests() + var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip"); + using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); + using var reader = ReaderFactory.Open(stream); + var count = 0; + while (reader.MoveToNextEntry()) { - UseExtensionInsteadOfNameToVerify = true; - } - - [Fact] - public void Issue_269_Double_Skip() - { - var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip"); - using (Stream stream = new ForwardOnlyStream(File.OpenRead(path))) - using (IReader reader = ReaderFactory.Open(stream)) + count++; + if (!reader.Entry.IsDirectory) { - int count = 0; - while (reader.MoveToNextEntry()) + if (count % 2 != 0) { - count++; - if (!reader.Entry.IsDirectory) - { - if (count % 2 != 0) - { - reader.WriteEntryTo(Stream.Null); - } - } + reader.WriteEntryTo(Stream.Null); } } } + } - [Fact] - public void Zip_Zip64_Streamed_Read() - { - Read("Zip.zip64.zip", CompressionType.Deflate); - } + [Fact] + public void Zip_Zip64_Streamed_Read() => Read("Zip.zip64.zip", CompressionType.Deflate); - [Fact] - public void Zip_ZipX_Streamed_Read() - { - Read("Zip.zipx", CompressionType.LZMA); - } + [Fact] + public void Zip_ZipX_Streamed_Read() => Read("Zip.zipx", CompressionType.LZMA); - [Fact] - public void Zip_BZip2_Streamed_Read() + [Fact] + public void Zip_BZip2_Streamed_Read() => Read("Zip.bzip2.dd.zip", CompressionType.BZip2); + + [Fact] + public void Zip_BZip2_Read() => Read("Zip.bzip2.zip", CompressionType.BZip2); + + [Fact] + public void Zip_Deflate_Streamed2_Read() => + Read("Zip.deflate.dd-.zip", CompressionType.Deflate); + + [Fact] + public void Zip_Deflate_Streamed_Read() => Read("Zip.deflate.dd.zip", CompressionType.Deflate); + + [Fact] + public void Zip_Deflate_Streamed_Skip() + { + using Stream stream = new ForwardOnlyStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) + ); + using var reader = ReaderFactory.Open(stream); + var x = 0; + while (reader.MoveToNextEntry()) { - Read("Zip.bzip2.dd.zip", CompressionType.BZip2); - } - [Fact] - public void Zip_BZip2_Read() - { - Read("Zip.bzip2.zip", CompressionType.BZip2); - } - [Fact] - public void Zip_Deflate_Streamed2_Read() - { - Read("Zip.deflate.dd-.zip", CompressionType.Deflate); - } - [Fact] - public void Zip_Deflate_Streamed_Read() - { - Read("Zip.deflate.dd.zip", CompressionType.Deflate); - } - [Fact] - public void Zip_Deflate_Streamed_Skip() - { - using (Stream stream = new ForwardOnlyStream(File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")))) - using (IReader reader = ReaderFactory.Open(stream)) + if (!reader.Entry.IsDirectory) { - int x = 0; - while (reader.MoveToNextEntry()) + x++; + if (x % 2 == 0) { - if (!reader.Entry.IsDirectory) - { - x++; - if (x % 2 == 0) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } - [Fact] - public void Zip_Deflate_Read() - { - Read("Zip.deflate.zip", CompressionType.Deflate); - } - [Fact] - public void Zip_Deflate64_Read() - { - Read("Zip.deflate64.zip", CompressionType.Deflate64); - } + } - [Fact] - public void Zip_LZMA_Streamed_Read() - { - Read("Zip.lzma.dd.zip", CompressionType.LZMA); - } - [Fact] - public void Zip_LZMA_Read() - { - Read("Zip.lzma.zip", CompressionType.LZMA); - } - [Fact] - public void Zip_PPMd_Streamed_Read() - { - Read("Zip.ppmd.dd.zip", CompressionType.PPMd); - } - [Fact] - public void Zip_PPMd_Read() - { - Read("Zip.ppmd.zip", CompressionType.PPMd); - } + [Fact] + public void Zip_Deflate_Read() => Read("Zip.deflate.zip", CompressionType.Deflate); - [Fact] - public void Zip_None_Read() - { - Read("Zip.none.zip", CompressionType.None); - } + [Fact] + public void Zip_Deflate64_Read() => Read("Zip.deflate64.zip", CompressionType.Deflate64); - [Fact] - public void Zip_Deflate_NoEmptyDirs_Read() - { - Read("Zip.deflate.noEmptyDirs.zip", CompressionType.Deflate); - } + [Fact] + public void Zip_LZMA_Streamed_Read() => Read("Zip.lzma.dd.zip", CompressionType.LZMA); - [Fact] - public void Zip_BZip2_PkwareEncryption_Read() + [Fact] + public void Zip_LZMA_Read() => Read("Zip.lzma.zip", CompressionType.LZMA); + + [Fact] + public void Zip_PPMd_Streamed_Read() => Read("Zip.ppmd.dd.zip", CompressionType.PPMd); + + [Fact] + public void Zip_PPMd_Read() => Read("Zip.ppmd.zip", CompressionType.PPMd); + + [Fact] + public void Zip_None_Read() => Read("Zip.none.zip", CompressionType.None); + + [Fact] + public void Zip_Deflate_NoEmptyDirs_Read() => + Read("Zip.deflate.noEmptyDirs.zip", CompressionType.Deflate); + + [Fact] + public void Zip_BZip2_PkwareEncryption_Read() + { + using ( + Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip")) + ) + using (var reader = ZipReader.Open(stream, new ReaderOptions() { Password = "test" })) { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"))) - using (var reader = ZipReader.Open(stream, new ReaderOptions() + while (reader.MoveToNextEntry()) { - Password = "test" - })) - { - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } + Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } - VerifyFiles(); } + VerifyFiles(); + } - [Fact] - public void Zip_Reader_Disposal_Test() + [Fact] + public void Zip_Reader_Disposal_Test() + { + using var stream = new TestStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) + ); + using (var reader = ReaderFactory.Open(stream)) { - using (TestStream stream = new TestStream(File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")))) + while (reader.MoveToNextEntry()) { - using (var reader = ReaderFactory.Open(stream)) + if (!reader.Entry.IsDirectory) { - while (reader.MoveToNextEntry()) - { - if (!reader.Entry.IsDirectory) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } - Assert.True(stream.IsDisposed); } } + Assert.True(stream.IsDisposed); + } - [Fact] - public void Zip_Reader_Disposal_Test2() + [Fact] + public void Zip_Reader_Disposal_Test2() + { + using var stream = new TestStream( + File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) + ); + var reader = ReaderFactory.Open(stream); + while (reader.MoveToNextEntry()) { - using (TestStream stream = new TestStream(File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")))) + if (!reader.Entry.IsDirectory) { - var reader = ReaderFactory.Open(stream); - while (reader.MoveToNextEntry()) - { - if (!reader.Entry.IsDirectory) - { - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - Assert.False(stream.IsDisposed); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } + Assert.False(stream.IsDisposed); + } - [Fact] - public void Zip_LZMA_WinzipAES_Read() + [Fact] + public void Zip_LZMA_WinzipAES_Read() => + Assert.Throws(() => { - Assert.Throws(() => - { - using ( - Stream stream = - File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, - "Zip.lzma.WinzipAES.zip"))) - using (var reader = ZipReader.Open(stream, new ReaderOptions() - { - Password = "test" - })) - { - while (reader.MoveToNextEntry()) - { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - } - } - } - VerifyFiles(); - }); - } - - [Fact] - public void Zip_Deflate_WinzipAES_Read() - { - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"))) - using (var reader = ZipReader.Open(stream, new ReaderOptions() - { - Password = "test" - })) + using ( + Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip") + ) + ) + using (var reader = ZipReader.Open(stream, new ReaderOptions() { Password = "test" })) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } VerifyFiles(); - } + }); - [Fact] - public void Zip_Deflate_ZipCrypto_Read() + [Fact] + public void Zip_Deflate_WinzipAES_Read() + { + using ( + Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip") + ) + ) + using (var reader = ZipReader.Open(stream, new ReaderOptions() { Password = "test" })) { - int count = 0; - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip"))) - using (var reader = ZipReader.Open(stream, new ReaderOptions() + while (reader.MoveToNextEntry()) { - Password = "test" - })) - { - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.None, reader.Entry.CompressionType); - reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, - new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); - count++; - } - } - } - Assert.Equal(8, count); - } - - [Fact] - public void TestSharpCompressWithEmptyStream() - { - var expected = new Tuple[] - { - new Tuple("foo.txt", Array.Empty()), - new Tuple("foo2.txt", new byte[10]) - }; - - using (var memory = new MemoryStream()) - { - Stream stream = new TestStream(memory, read: true, write: true, seek: false); - - using (IWriter zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) - { - zipWriter.Write(expected[0].Item1, new MemoryStream(expected[0].Item2)); - zipWriter.Write(expected[1].Item1, new MemoryStream(expected[1].Item2)); - } - - stream = new MemoryStream(memory.ToArray()); - File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), memory.ToArray()); - - using (IReader zipReader = ZipReader.Open(NonDisposingStream.Create(stream, true))) - { - var i = 0; - while (zipReader.MoveToNextEntry()) - { - using (EntryStream entry = zipReader.OpenEntryStream()) - { - MemoryStream tempStream = new MemoryStream(); - const int bufSize = 0x1000; - byte[] buf = new byte[bufSize]; - int bytesRead = 0; - while ((bytesRead = entry.Read(buf, 0, bufSize)) > 0) - { - tempStream.Write(buf, 0, bytesRead); - } - - Assert.Equal(expected[i].Item1, zipReader.Entry.Key); - Assert.Equal(expected[i].Item2, tempStream.ToArray()); - } - i++; - } + Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); } } } + VerifyFiles(); + } - [Fact] - public void Zip_None_Issue86_Streamed_Read() + [Fact] + public void Zip_Deflate_ZipCrypto_Read() + { + var count = 0; + using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip"))) + using (var reader = ZipReader.Open(stream, new ReaderOptions() { Password = "test" })) { - var keys = new string[] { "Empty1", "Empty2", "Dir1/", "Dir2/", "Fake1", "Fake2", "Internal.zip" }; - - using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.issue86.zip"))) - using (var reader = ZipReader.Open(stream)) + while (reader.MoveToNextEntry()) { - foreach (var key in keys) - { - reader.MoveToNextEntry(); - - Assert.Equal(reader.Entry.Key, key); - - if (!reader.Entry.IsDirectory) - { - Assert.Equal(CompressionType.None, reader.Entry.CompressionType); - } - } - - Assert.False(reader.MoveToNextEntry()); - } - } - - [Fact] - public void Zip_ReaderMoveToNextEntry() - { - var keys = new string[] { "version", "sizehint", "data/0/metadata", "data/0/records" }; - - using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "test_477.zip"))) - using (var reader = ZipReader.Open(fileStream)) - { - foreach( var key in keys) - { - reader.MoveToNextEntry(); - - Assert.Equal(reader.Entry.Key, key); - } - } - } - [Fact] - public void Issue_685() - { - var count = 0; - using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Issue_685.zip"))) - using (var reader = ZipReader.Open(fileStream)) - { - - while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) { + Assert.Equal(CompressionType.None, reader.Entry.CompressionType); + reader.WriteEntryToDirectory( + SCRATCH_FILES_PATH, + new ExtractionOptions() { ExtractFullPath = true, Overwrite = true } + ); count++; - reader.OpenEntryStream().Dispose(); // Uncomment for workaround } - Assert.Equal(4, count); } } + Assert.Equal(8, count); + } - [Fact] - public void Zip_Uncompressed_Skip_All() + [Fact] + public void TestSharpCompressWithEmptyStream() + { + var expected = new[] { - var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); - using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) - { - using (var reader = ReaderFactory.Open(stream)) - { - while (reader.MoveToNextEntry()) { } - } - } - } + new Tuple("foo.txt", Array.Empty()), + new Tuple("foo2.txt", new byte[10]) + }; - [Fact] - public void Zip_Uncompressed_64bit() + using var memory = new MemoryStream(); + Stream stream = new TestStream(memory, read: true, write: true, seek: false); + + using (var zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) { - var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "64bitstream.zip.7z"); - using (var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read)) + zipWriter.Write(expected[0].Item1, new MemoryStream(expected[0].Item2)); + zipWriter.Write(expected[1].Item1, new MemoryStream(expected[1].Item2)); + } + + stream = new MemoryStream(memory.ToArray()); + File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), memory.ToArray()); + + using IReader zipReader = ZipReader.Open(NonDisposingStream.Create(stream, true)); + var i = 0; + while (zipReader.MoveToNextEntry()) + { + using (var entry = zipReader.OpenEntryStream()) { - var archive = Archives.ArchiveFactory.Open(stream); - var reader = archive.ExtractAllEntries(); - reader.MoveToNextEntry(); - var zipReader = ZipReader.Open(reader.OpenEntryStream()); - var x = 0; - while (zipReader.MoveToNextEntry()) + var tempStream = new MemoryStream(); + const int bufSize = 0x1000; + var buf = new byte[bufSize]; + var bytesRead = 0; + while ((bytesRead = entry.Read(buf, 0, bufSize)) > 0) { - x++; + tempStream.Write(buf, 0, bytesRead); } - Assert.Equal(4, x); + Assert.Equal(expected[i].Item1, zipReader.Entry.Key); + Assert.Equal(expected[i].Item2, tempStream.ToArray()); + } + i++; + } + } + + [Fact] + public void Zip_None_Issue86_Streamed_Read() + { + var keys = new[] { "Empty1", "Empty2", "Dir1/", "Dir2/", "Fake1", "Fake2", "Internal.zip" }; + + using Stream stream = File.OpenRead( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.issue86.zip") + ); + using var reader = ZipReader.Open(stream); + foreach (var key in keys) + { + reader.MoveToNextEntry(); + + Assert.Equal(reader.Entry.Key, key); + + if (!reader.Entry.IsDirectory) + { + Assert.Equal(CompressionType.None, reader.Entry.CompressionType); } } + Assert.False(reader.MoveToNextEntry()); + } + + [Fact] + public void Zip_ReaderMoveToNextEntry() + { + var keys = new[] { "version", "sizehint", "data/0/metadata", "data/0/records" }; + + using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "test_477.zip")); + using var reader = ZipReader.Open(fileStream); + foreach (var key in keys) + { + reader.MoveToNextEntry(); + + Assert.Equal(reader.Entry.Key, key); + } + } + + [Fact] + public void Issue_685() + { + var count = 0; + using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Issue_685.zip")); + using var reader = ZipReader.Open(fileStream); + while (reader.MoveToNextEntry()) + { + count++; + reader.OpenEntryStream().Dispose(); // Uncomment for workaround + } + Assert.Equal(4, count); + } + + [Fact] + public void Zip_Uncompressed_Skip_All() + { + var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); + using var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read); + using var reader = ReaderFactory.Open(stream); + while (reader.MoveToNextEntry()) { } + } + + [Fact] + public void Zip_Uncompressed_64bit() + { + var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "64bitstream.zip.7z"); + using var stream = File.Open(zipPath, FileMode.Open, FileAccess.Read); + var archive = Archives.ArchiveFactory.Open(stream); + var reader = archive.ExtractAllEntries(); + reader.MoveToNextEntry(); + var zipReader = ZipReader.Open(reader.OpenEntryStream()); + var x = 0; + while (zipReader.MoveToNextEntry()) + { + x++; + } + + Assert.Equal(4, x); } } diff --git a/tests/SharpCompress.Test/Zip/ZipWriterTests.cs b/tests/SharpCompress.Test/Zip/ZipWriterTests.cs index 8d546cec..d2224000 100644 --- a/tests/SharpCompress.Test/Zip/ZipWriterTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipWriterTests.cs @@ -3,51 +3,60 @@ using System.Text; using SharpCompress.Common; using Xunit; -namespace SharpCompress.Test.Zip +namespace SharpCompress.Test.Zip; + +public class ZipWriterTests : WriterTests { - public class ZipWriterTests : WriterTests - { - public ZipWriterTests() - : base(ArchiveType.Zip) - { - } + public ZipWriterTests() : base(ArchiveType.Zip) { } + [Fact] + public void Zip_Deflate_Write() => + Write( + CompressionType.Deflate, + "Zip.deflate.noEmptyDirs.zip", + "Zip.deflate.noEmptyDirs.zip", + Encoding.UTF8 + ); - [Fact] - public void Zip_Deflate_Write() - { - Write(CompressionType.Deflate, "Zip.deflate.noEmptyDirs.zip", "Zip.deflate.noEmptyDirs.zip", Encoding.UTF8); - } + [Fact] + public void Zip_BZip2_Write() => + Write( + CompressionType.BZip2, + "Zip.bzip2.noEmptyDirs.zip", + "Zip.bzip2.noEmptyDirs.zip", + Encoding.UTF8 + ); - [Fact] - public void Zip_BZip2_Write() - { - Write(CompressionType.BZip2, "Zip.bzip2.noEmptyDirs.zip", "Zip.bzip2.noEmptyDirs.zip", Encoding.UTF8); - } + [Fact] + public void Zip_None_Write() => + Write( + CompressionType.None, + "Zip.none.noEmptyDirs.zip", + "Zip.none.noEmptyDirs.zip", + Encoding.UTF8 + ); - [Fact] - public void Zip_None_Write() - { - Write(CompressionType.None, "Zip.none.noEmptyDirs.zip", "Zip.none.noEmptyDirs.zip", Encoding.UTF8); - } + [Fact] + public void Zip_LZMA_Write() => + Write( + CompressionType.LZMA, + "Zip.lzma.noEmptyDirs.zip", + "Zip.lzma.noEmptyDirs.zip", + Encoding.UTF8 + ); - [Fact] - public void Zip_LZMA_Write() - { - Write(CompressionType.LZMA, "Zip.lzma.noEmptyDirs.zip", "Zip.lzma.noEmptyDirs.zip", Encoding.UTF8); - } + [Fact] + public void Zip_PPMd_Write() => + Write( + CompressionType.PPMd, + "Zip.ppmd.noEmptyDirs.zip", + "Zip.ppmd.noEmptyDirs.zip", + Encoding.UTF8 + ); - [Fact] - public void Zip_PPMd_Write() - { - Write(CompressionType.PPMd, "Zip.ppmd.noEmptyDirs.zip", "Zip.ppmd.noEmptyDirs.zip", Encoding.UTF8); - } - - - [Fact] - public void Zip_Rar_Write() - { - Assert.Throws(() => Write(CompressionType.Rar, "Zip.ppmd.noEmptyDirs.zip", "Zip.ppmd.noEmptyDirs.zip")); - } - } + [Fact] + public void Zip_Rar_Write() => + Assert.Throws( + () => Write(CompressionType.Rar, "Zip.ppmd.noEmptyDirs.zip", "Zip.ppmd.noEmptyDirs.zip") + ); }