From d4d2c5a4cc4476769082e4f3f038c58adc5dc4d6 Mon Sep 17 00:00:00 2001 From: Roman Vanicek Date: Fri, 16 May 2025 10:55:34 +0000 Subject: [PATCH] Initial commit with SevenZip code untouched --- .gitignore | 2 + .vscode/settings.json | 1 + InnoLoader.cs | 275 +++++++++++++++++++++++++++++ InnoPatcher.csproj | 16 ++ InnoPatcher.sln | 24 +++ InnoSetup.cs | 75 ++++++++ LzOutWindow.cs | 108 ++++++++++++ LzmaBase.cs | 74 ++++++++ LzmaBitDecoder.cs | 51 ++++++ LzmaBitTreeDecoder.cs | 59 +++++++ LzmaDecoder.cs | 396 ++++++++++++++++++++++++++++++++++++++++++ LzmaICoder.cs | 155 +++++++++++++++++ LzmaRangeDecoder.cs | 117 +++++++++++++ Program.cs | 12 ++ 14 files changed, 1365 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 InnoLoader.cs create mode 100644 InnoPatcher.csproj create mode 100644 InnoPatcher.sln create mode 100644 InnoSetup.cs create mode 100644 LzOutWindow.cs create mode 100644 LzmaBase.cs create mode 100644 LzmaBitDecoder.cs create mode 100644 LzmaBitTreeDecoder.cs create mode 100644 LzmaDecoder.cs create mode 100644 LzmaICoder.cs create mode 100644 LzmaRangeDecoder.cs create mode 100644 Program.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d4a6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/InnoLoader.cs b/InnoLoader.cs new file mode 100644 index 0000000..bb443ff --- /dev/null +++ b/InnoLoader.cs @@ -0,0 +1,275 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; + +using PeNet; +using PeNet.Header.Pe; + +namespace InnoPatcher; + +sealed class InnoLoader { + // + // Based on innounp repository, file innounp.dpr + // + + private const int HeaderSize = 64; + + private static ReadOnlySpan SignaturePrefix => "Inno Setup Setup Data ("u8; + private static ReadOnlySpan SignatureIsxPrefix => "My Inno Setup Extensions Setup Data ("u8; + private static ReadOnlySpan UnicodeFlagLower => "(u)"u8; + private static ReadOnlySpan UnicodeFlagUpper => "(U)"u8; + private static ReadOnlySpan ISXFlag => "with ISX"u8; + + private static readonly byte[] SignatureOffsetTableId6 = new byte[] { 0x72, 0x44, 0x6C, 0x50, 0x74, 0x53, 0x30, 0x36, 0x87, 0x65, 0x56, 0x78 }; // rDlPtS06 and some hex mess + private static readonly byte[] SignatureOffsetTableId7 = new byte[] { 0x72, 0x44, 0x6C, 0x50, 0x74, 0x53, 0x30, 0x37, 0x87, 0x65, 0x56, 0x78 }; // rDlPtS07 and some hex mess + private static readonly byte[] SignatureOffsetTableId8 = new byte[] { 0x72, 0x44, 0x6C, 0x50, 0x74, 0x53, 0xCD, 0xE6, 0xD7, 0x7B, 0x0B, 0x2A }; // rDlPtS and some hex mess + + private const int RCDATA_Id = 10; + private const int InnoResource_Id = 11111; + + private readonly Stream _file; + private readonly InnoLoaderOffsetTable _offsets; + private readonly Version _version; + private readonly bool _isUnicode; + private readonly bool _isISX; + + private InnoLoader(Stream file, InnoLoaderOffsetTable offsets, Version version, bool isUnicode, bool isISX) { + this._file = file; + this._offsets = offsets; + this._version = version; + this._isUnicode = isUnicode; + this._isISX = isISX; + } + + /// + /// Creates the Inno loader from a file with PE format. + /// + internal static InnoLoader FromPE(Stream peFile) { + if (!peFile.CanSeek) + throw new ArgumentOutOfRangeException(); + + InnoLoaderOffsetTable offsets; + if (!TryGetOffsetTableFromResource(peFile, out offsets) + && !TryGetOffsetTableFromFile(peFile, out offsets)) + throw new FormatException("Failed to find the offsets table."); + + byte[] header = new byte[HeaderSize]; + peFile.Seek(offsets.Offset0, SeekOrigin.Begin); + int read, offset = 0; + while ((read = peFile.Read(header, offset, HeaderSize - offset)) != 0) + offset += read; + if (offset < HeaderSize) + throw new FormatException(); + + if (!TryParseHeader(header, out Version? version, out bool isUnicode, out bool isISX)) + throw new FormatException("Failed to parse header."); + + return new InnoLoader(peFile, offsets, version, isUnicode, isISX); + } + + public Version Version => _version; + + internal int Version2 => _version.Major * 1000 + _version.Minor * 100 + _version.Revision; + + public bool IsUnicode => _isUnicode; + + public bool IsISX => _isISX; + + public InnoSetup GetSetup() { + _file.Seek(_offsets.Offset0 + HeaderSize, SeekOrigin.Begin); + if (!InnoSetup.TryParse(_file, Version2, out InnoSetup result)) + throw new FormatException("Failed to parse Inno Setup"); + return result; + } + + private static bool TryGetOffsetTableFromFile(Stream peFile, out InnoLoaderOffsetTable result) { + throw new NotImplementedException(); + } + + private static bool TryGetOffsetTableFromResource(Stream peFile, out InnoLoaderOffsetTable result) { + if (PeFile.TryParse(peFile, out PeFile? pe) && pe!.ImageResourceDirectory != null) { + foreach (var i in pe!.ImageResourceDirectory!.DirectoryEntries!) + if (i.IsIdEntry && i.ID == RCDATA_Id && i.DataIsDirectory) { + foreach (var j in i.ResourceDirectory!.DirectoryEntries!) + if (j.IsIdEntry && j.ID == InnoResource_Id + && j.DataIsDirectory && j.ResourceDirectory!.NumberOfIdEntries == 1) { + ImageResourceDataEntry a = j.ResourceDirectory!.DirectoryEntries![0]!.ResourceDataEntry!; + if (TryGetOffsetFromRva(a.OffsetToData, pe, out uint tableOffset)) { + int read, offset = 0; + byte[] buffer = new byte[a.Size1]; + peFile.Seek(tableOffset, SeekOrigin.Begin); + while ((read = peFile.Read(buffer, offset, buffer.Length - offset)) != 0) + offset += read; + if (offset == buffer.Length && InnoLoaderOffsetTable.TryParse(buffer, out result)) + return true; + } + break; + } + break; + } + } + result = default; + return false; + } + + private static bool TryParseHeader(ReadOnlySpan header, [NotNullWhen(true)] out Version? ver, out bool isUnicode, out bool isISX) { + ver = null; + isUnicode = false; + isISX = false; + if (header.StartsWith(SignaturePrefix)) + header = header[SignaturePrefix.Length..]; + else if (header.StartsWith(SignatureIsxPrefix)) + header = header[SignatureIsxPrefix.Length..]; + else + return false; + + if (header.Length < 2 || header[0] < '1' || header[0] > '9' || header[1] != '.') + return false; + + int verLen = 2; + while (verLen < header.Length) + if (header[verLen] == ')') + break; + else + verLen++; + if (verLen == header.Length) + return false; + + var versionB = header.Slice(0, verLen); + header = header.Slice(verLen + 1); + if (!Version.TryParse(Encoding.ASCII.GetString(versionB), out ver)) + return false; + + isUnicode = header.IndexOf(UnicodeFlagLower) >= 0 || header.IndexOf(UnicodeFlagUpper) >= 0; + isISX = header.IndexOf(ISXFlag) >= 0; + return true; + } + + /// + /// Converts relative virtual address (RVA) to a file offset. + /// + private static bool TryGetOffsetFromRva(ulong rva, PeFile pe, out uint offset) { + foreach (var k in pe.ImageSectionHeaders!) { + if (k.VirtualAddress > rva) { + break; + } else if (rva < k.VirtualAddress + k.VirtualSize) { + offset = (uint)(rva - k.VirtualAddress) + k.PointerToRawData; + return true; + } + } + offset = 0; + return false; + } + + internal static bool VerifyCRC(ReadOnlySpan data, uint expectedCRC) { + return System.IO.Hashing.Crc32.HashToUInt32(data) == expectedCRC; + } + + struct InnoLoaderOffsetTable { + internal uint Version; // SetupLdrOffsetTableVersion + internal uint TotalSize; // Minimum expected size of setup.exe + internal uint OffsetEXE; // Offset of compressed setup.e32 + internal uint UncompressedSizeEXE; // Size of setup.e32 before compression + internal int CRCEXE; // CRC of setup.e32 before compression + internal uint Offset0; // Offset of embedded setup-0.bin data + internal uint Offset1; // Offset of embedded setup-1.bin data or 0 when DiskSpanning=yes + internal uint TableCRC; // CRC of all prior fields in this record + + internal static bool TryParse(ReadOnlySpan data, [NotNullWhen(true)] out InnoLoaderOffsetTable result) { + const int IdLength = 12; + if (data.Length >= IdLength) { + ReadOnlySpan id = data.Slice(0, IdLength); + ReadOnlySpan payload = data.Slice(IdLength); + + if (id.SequenceEqual(SignatureOffsetTableId8) && payload.Length == Marshal.SizeOf()) { + result = MemoryMarshal.Cast(payload)[0].Unify(); + return VerifyCRC(data.Slice(0, data.Length - 4), result.TableCRC); + } else if (id.SequenceEqual(SignatureOffsetTableId7) && payload.Length == Marshal.SizeOf()) { + result = MemoryMarshal.Cast(payload)[0].Unify(); + return VerifyCRC(data.Slice(0, data.Length - 4), result.TableCRC); + } else if (id.SequenceEqual(SignatureOffsetTableId6) && payload.Length == Marshal.SizeOf()) { + result = MemoryMarshal.Cast(payload)[0].Unify(); + return VerifyCRC(data.Slice(0, data.Length - 4), result.TableCRC); + } + } + + result = default; + return false; + } + } + + [StructLayout(LayoutKind.Sequential)] + struct InnoLoaderOffsetTableV6 { + internal uint TotalSize; // Minimum expected size of setup.exe + internal uint OffsetEXE; // Offset of compressed setup.e32 + internal uint CompressedSizeEXE; + internal uint UncompressedSizeEXE; // Size of setup.e32 before compression + internal int CRCEXE; // CRC of setup.e32 before compression + internal uint Offset0; // Offset of embedded setup-0.bin data + internal uint Offset1; // Offset of embedded setup-1.bin data or 0 when DiskSpanning=yes + internal uint TableCRC; // CRC of all prior fields in this record + + internal InnoLoaderOffsetTable Unify() { + InnoLoaderOffsetTable a; + a.Version = 0; + a.TotalSize = TotalSize; + a.OffsetEXE = OffsetEXE; + a.UncompressedSizeEXE = UncompressedSizeEXE; + a.CRCEXE = CRCEXE; + a.Offset0 = Offset0; + a.Offset1 = Offset1; + a.TableCRC = TableCRC; + return a; + } + } + + [StructLayout(LayoutKind.Sequential)] + struct InnoLoaderOffsetTableV7 { + internal uint TotalSize; // Minimum expected size of setup.exe + internal uint OffsetEXE; // Offset of compressed setup.e32 + internal uint UncompressedSizeEXE; // Size of setup.e32 before compression + internal int CRCEXE; // CRC of setup.e32 before compression + internal uint Offset0; // Offset of embedded setup-0.bin data + internal uint Offset1; // Offset of embedded setup-1.bin data or 0 when DiskSpanning=yes + internal uint TableCRC; // CRC of all prior fields in this record + + internal InnoLoaderOffsetTable Unify() { + InnoLoaderOffsetTable a; + a.Version = 0; + a.TotalSize = TotalSize; + a.OffsetEXE = OffsetEXE; + a.UncompressedSizeEXE = UncompressedSizeEXE; + a.CRCEXE = CRCEXE; + a.Offset0 = Offset0; + a.Offset1 = Offset1; + a.TableCRC = TableCRC; + return a; + } + } + + [StructLayout(LayoutKind.Sequential)] + struct InnoLoaderOffsetTableV8 { + internal uint Version; // SetupLdrOffsetTableVersion + internal uint TotalSize; // Minimum expected size of setup.exe + internal uint OffsetEXE; // Offset of compressed setup.e32 + internal uint UncompressedSizeEXE; // Size of setup.e32 before compression + internal int CRCEXE; // CRC of setup.e32 before compression + internal uint Offset0; // Offset of embedded setup-0.bin data + internal uint Offset1; // Offset of embedded setup-1.bin data or 0 when DiskSpanning=yes + internal uint TableCRC; // CRC of all prior fields in this record + + internal InnoLoaderOffsetTable Unify() { + InnoLoaderOffsetTable a; + a.Version = Version; + a.TotalSize = TotalSize; + a.OffsetEXE = OffsetEXE; + a.UncompressedSizeEXE = UncompressedSizeEXE; + a.CRCEXE = CRCEXE; + a.Offset0 = Offset0; + a.Offset1 = Offset1; + a.TableCRC = TableCRC; + return a; + } + } +} diff --git a/InnoPatcher.csproj b/InnoPatcher.csproj new file mode 100644 index 0000000..738dac0 --- /dev/null +++ b/InnoPatcher.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + diff --git a/InnoPatcher.sln b/InnoPatcher.sln new file mode 100644 index 0000000..39794ef --- /dev/null +++ b/InnoPatcher.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InnoPatcher", "InnoPatcher.csproj", "{BAD9EB7A-A518-EEC8-6A54-63A314E0101D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BAD9EB7A-A518-EEC8-6A54-63A314E0101D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAD9EB7A-A518-EEC8-6A54-63A314E0101D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAD9EB7A-A518-EEC8-6A54-63A314E0101D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAD9EB7A-A518-EEC8-6A54-63A314E0101D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B80F9225-8B51-4C7B-9021-B7A3A367CD77} + EndGlobalSection +EndGlobal diff --git a/InnoSetup.cs b/InnoSetup.cs new file mode 100644 index 0000000..1876730 --- /dev/null +++ b/InnoSetup.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using SevenZip.Compression.LZMA; + +namespace InnoPatcher; + +sealed class InnoSetup { + private const int BlockSize = 4096 + 4; + private const int CompressionPropertiesLength = 5; + + internal static bool TryParse(Stream file, int innoVersion, [NotNullWhen(true)] out InnoSetup? result) { + result = null; + + byte[] headerB = new byte[Marshal.SizeOf
()]; + int read, offset = 0; + while ((read = file.Read(headerB, offset, headerB.Length - offset)) != 0) + offset += read; + if (offset < headerB.Length) + return false; + + // Read header + Header header = MemoryMarshal.Cast(headerB)[0]; + ReadOnlySpan headerB2 = headerB; + if (!InnoLoader.VerifyCRC(headerB2.Slice(4), header.CRC)) + return false; + + if (header.Compressed != 0 && innoVersion < 4105) + throw new NotImplementedException("Zlib decompression not implemented."); + + // Decode + Decoder decoder = new Decoder(); + MemoryStream a = new MemoryStream(); + byte[] blockB = new byte[BlockSize]; + bool decoderInitialized = false; + for (uint toRead = header.StoredSize; toRead > 0;) { + int blockSize = toRead > BlockSize ? BlockSize : (int)toRead; + offset = 0; + while ((read = file.Read(blockB, offset, blockSize - offset)) != 0) + offset += read; + if (offset < blockSize) + return false; + + ReadOnlySpan blockData = blockB; + uint blockCRC = MemoryMarshal.Cast(blockData.Slice(0, 4))[0]; + blockData = blockData.Slice(4); + + if (!InnoLoader.VerifyCRC(blockData, blockCRC)) + return false; + + if (header.Compressed != 0) { + if (!decoderInitialized) { + decoder.SetDecoderProperties(blockData.Slice(0, CompressionPropertiesLength).ToArray()); + blockData = blockData.Slice(CompressionPropertiesLength); + decoderInitialized = true; + } + + decoder.Code(new MemoryStream(blockB, blockSize - blockData.Length, blockData.Length), a, blockData.Length, 84158, null); + } else + a.Write(blockData); + + toRead -= (uint)blockSize; + } + + result = null; + return false; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Header { + internal uint CRC; + internal uint StoredSize; // Total bytes written, including the CRCs + internal byte Compressed; // True if data is compressed, False if not + } +} diff --git a/LzOutWindow.cs b/LzOutWindow.cs new file mode 100644 index 0000000..66a1c34 --- /dev/null +++ b/LzOutWindow.cs @@ -0,0 +1,108 @@ +namespace SevenZip.Compression.LZ +{ + public class OutWindow + { + byte[] _buffer = null; + uint _pos; + uint _windowSize = 0; + uint _streamPos; + System.IO.Stream _stream; + + public uint TrainSize = 0; + + public void Create(uint windowSize) + { + if (_windowSize != windowSize) + { + // System.GC.Collect(); + _buffer = new byte[windowSize]; + } + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + } + + public void Init(System.IO.Stream stream, bool solid) + { + ReleaseStream(); + _stream = stream; + if (!solid) + { + _streamPos = 0; + _pos = 0; + TrainSize = 0; + } + } + + public bool Train(System.IO.Stream stream) + { + long len = stream.Length; + uint size = (len < _windowSize) ? (uint)len : _windowSize; + TrainSize = size; + stream.Position = len - size; + _streamPos = _pos = 0; + while (size > 0) + { + uint curSize = _windowSize - _pos; + if (size < curSize) + curSize = size; + int numReadBytes = stream.Read(_buffer, (int)_pos, (int)curSize); + if (numReadBytes == 0) + return false; + size -= (uint)numReadBytes; + _pos += (uint)numReadBytes; + _streamPos += (uint)numReadBytes; + if (_pos == _windowSize) + _streamPos = _pos = 0; + } + return true; + } + + public void ReleaseStream() + { + Flush(); + _stream = null; + } + + public void Flush() + { + uint size = _pos - _streamPos; + if (size == 0) + return; + _stream.Write(_buffer, (int)_streamPos, (int)size); + if (_pos >= _windowSize) + _pos = 0; + _streamPos = _pos; + } + + public void CopyBlock(uint distance, uint len) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + for (; len > 0; len--) + { + if (pos >= _windowSize) + pos = 0; + _buffer[_pos++] = _buffer[pos++]; + if (_pos >= _windowSize) + Flush(); + } + } + + public void PutByte(byte b) + { + _buffer[_pos++] = b; + if (_pos >= _windowSize) + Flush(); + } + + public byte GetByte(uint distance) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + return _buffer[pos]; + } + } +} \ No newline at end of file diff --git a/LzmaBase.cs b/LzmaBase.cs new file mode 100644 index 0000000..d82567f --- /dev/null +++ b/LzmaBase.cs @@ -0,0 +1,74 @@ +namespace SevenZip.Compression.LZMA +{ + internal abstract class Base + { + public const uint kNumRepDistances = 4; + public const uint kNumStates = 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 uint Index; + public void Init() { Index = 0; } + public void UpdateChar() + { + if (Index < 4) Index = 0; + else if (Index < 10) Index -= 3; + else Index -= 6; + } + public void UpdateMatch() { 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; } + } + + public const int kNumPosSlotBits = 6; + public const int kDicLogSizeMin = 0; + // public const int kDicLogSizeMax = 30; + // public const uint kDistTableSizeMax = kDicLogSizeMax * 2; + + public const int kNumLenToPosStatesBits = 2; // it's for speed optimization + public const uint kNumLenToPosStates = 1 << kNumLenToPosStatesBits; + + public const uint kMatchMinLen = 2; + + public static uint GetLenToPosState(uint len) + { + len -= kMatchMinLen; + if (len < kNumLenToPosStates) + return len; + return (uint)(kNumLenToPosStates - 1); + } + + public const int kNumAlignBits = 4; + public const uint kAlignTableSize = 1 << kNumAlignBits; + public const uint kAlignMask = (kAlignTableSize - 1); + + public const uint kStartPosModelIndex = 4; + public const uint kEndPosModelIndex = 14; + public const uint kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; + + public const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2); + + public const uint kNumLitPosStatesBitsEncodingMax = 4; + public const uint kNumLitContextBitsMax = 8; + + public const int kNumPosStatesBitsMax = 4; + public const uint kNumPosStatesMax = (1 << kNumPosStatesBitsMax); + public const int kNumPosStatesBitsEncodingMax = 4; + public const uint kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax); + + public const int kNumLowLenBits = 3; + public const int kNumMidLenBits = 3; + public const int kNumHighLenBits = 8; + public const uint kNumLowLenSymbols = 1 << kNumLowLenBits; + public const uint kNumMidLenSymbols = 1 << kNumMidLenBits; + public const uint kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + + (1 << kNumHighLenBits); + public const uint kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1; + } +} \ No newline at end of file diff --git a/LzmaBitDecoder.cs b/LzmaBitDecoder.cs new file mode 100644 index 0000000..c58ea0b --- /dev/null +++ b/LzmaBitDecoder.cs @@ -0,0 +1,51 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + struct BitDecoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = (1 << kNumBitModelTotalBits); + const int kNumMoveBits = 5; + + uint Prob; + + public void UpdateModel(int numMoveBits, uint symbol) + { + if (symbol == 0) + Prob += (kBitModelTotal - Prob) >> numMoveBits; + else + Prob -= (Prob) >> numMoveBits; + } + + public void Init() { Prob = kBitModelTotal >> 1; } + + public uint Decode(RangeCoder.Decoder rangeDecoder) + { + uint newBound = (uint)(rangeDecoder.Range >> kNumBitModelTotalBits) * (uint)Prob; + if (rangeDecoder.Code < newBound) + { + rangeDecoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 0; + } + else + { + rangeDecoder.Range -= newBound; + rangeDecoder.Code -= newBound; + Prob -= (Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 1; + } + } + } +} \ No newline at end of file diff --git a/LzmaBitTreeDecoder.cs b/LzmaBitTreeDecoder.cs new file mode 100644 index 0000000..6b9de07 --- /dev/null +++ b/LzmaBitTreeDecoder.cs @@ -0,0 +1,59 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + struct BitTreeDecoder + { + BitDecoder[] Models; + 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(RangeCoder.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(RangeCoder.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, + RangeCoder.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; + } + } +} \ No newline at end of file diff --git a/LzmaDecoder.cs b/LzmaDecoder.cs new file mode 100644 index 0000000..4a14c0e --- /dev/null +++ b/LzmaDecoder.cs @@ -0,0 +1,396 @@ +using System; + +namespace SevenZip.Compression.LZMA +{ + using RangeCoder; + + public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream + { + class LenDecoder + { + BitDecoder m_Choice = new BitDecoder(); + BitDecoder m_Choice2 = new BitDecoder(); + BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder m_HighCoder = new BitTreeDecoder(Base.kNumHighLenBits); + uint m_NumPosStates = 0; + + public void Create(uint numPosStates) + { + for (uint posState = m_NumPosStates; posState < numPosStates; posState++) + { + m_LowCoder[posState] = new BitTreeDecoder(Base.kNumLowLenBits); + m_MidCoder[posState] = new BitTreeDecoder(Base.kNumMidLenBits); + } + m_NumPosStates = numPosStates; + } + + public void Init() + { + m_Choice.Init(); + for (uint posState = 0; posState < m_NumPosStates; posState++) + { + m_LowCoder[posState].Init(); + m_MidCoder[posState].Init(); + } + m_Choice2.Init(); + m_HighCoder.Init(); + } + + public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState) + { + if (m_Choice.Decode(rangeDecoder) == 0) + return m_LowCoder[posState].Decode(rangeDecoder); + else + { + uint symbol = Base.kNumLowLenSymbols; + if (m_Choice2.Decode(rangeDecoder) == 0) + symbol += m_MidCoder[posState].Decode(rangeDecoder); + else + { + symbol += Base.kNumMidLenSymbols; + symbol += m_HighCoder.Decode(rangeDecoder); + } + return symbol; + } + } + } + + class LiteralDecoder + { + struct Decoder2 + { + BitDecoder[] m_Decoders; + public void Create() { m_Decoders = new BitDecoder[0x300]; } + public void Init() { for (int i = 0; i < 0x300; i++) m_Decoders[i].Init(); } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder) + { + uint symbol = 1; + do + symbol = (symbol << 1) | m_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 = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + break; + } + } + while (symbol < 0x100); + return (byte)symbol; + } + } + + Decoder2[] m_Coders; + int m_NumPrevBits; + int m_NumPosBits; + uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && + m_NumPosBits == numPosBits) + return; + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Decoder2[numStates]; + for (uint i = 0; i < numStates; i++) + m_Coders[i].Create(); + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + m_Coders[i].Init(); + } + + uint GetState(uint pos, byte prevByte) + { return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits)); } + + public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) + { return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); } + + public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) + { return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); } + }; + + LZ.OutWindow m_OutWindow = new LZ.OutWindow(); + RangeCoder.Decoder m_RangeDecoder = new RangeCoder.Decoder(); + + BitDecoder[] m_IsMatchDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + BitDecoder[] m_IsRepDecoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG0Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG1Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRepG2Decoders = new BitDecoder[Base.kNumStates]; + BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates]; + BitDecoder[] m_PosDecoders = new BitDecoder[Base.kNumFullDistances - Base.kEndPosModelIndex]; + + BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(Base.kNumAlignBits); + + LenDecoder m_LenDecoder = new LenDecoder(); + LenDecoder m_RepLenDecoder = new LenDecoder(); + + LiteralDecoder m_LiteralDecoder = new LiteralDecoder(); + + uint m_DictionarySize; + uint m_DictionarySizeCheck; + + uint m_PosStateMask; + + public Decoder() + { + m_DictionarySize = 0xFFFFFFFF; + for (int i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits); + } + + void SetDictionarySize(uint dictionarySize) + { + if (m_DictionarySize != dictionarySize) + { + m_DictionarySize = dictionarySize; + m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1); + uint blockSize = Math.Max(m_DictionarySizeCheck, (1 << 12)); + m_OutWindow.Create(blockSize); + } + } + + void SetLiteralProperties(int lp, int lc) + { + if (lp > 8) + throw new InvalidParamException(); + if (lc > 8) + throw new InvalidParamException(); + m_LiteralDecoder.Create(lp, lc); + } + + void SetPosBitsProperties(int pb) + { + if (pb > Base.kNumPosStatesBitsMax) + throw new InvalidParamException(); + uint numPosStates = (uint)1 << pb; + m_LenDecoder.Create(numPosStates); + m_RepLenDecoder.Create(numPosStates); + m_PosStateMask = numPosStates - 1; + } + + bool _solid = false; + void Init(System.IO.Stream inStream, System.IO.Stream outStream) + { + m_RangeDecoder.Init(inStream); + m_OutWindow.Init(outStream, _solid); + + uint i; + for (i = 0; i < Base.kNumStates; i++) + { + for (uint j = 0; j <= m_PosStateMask; j++) + { + uint index = (i << Base.kNumPosStatesBitsMax) + j; + m_IsMatchDecoders[index].Init(); + m_IsRep0LongDecoders[index].Init(); + } + m_IsRepDecoders[i].Init(); + m_IsRepG0Decoders[i].Init(); + m_IsRepG1Decoders[i].Init(); + m_IsRepG2Decoders[i].Init(); + } + + m_LiteralDecoder.Init(); + for (i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i].Init(); + // m_PosSpecDecoder.Init(); + for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++) + m_PosDecoders[i].Init(); + + m_LenDecoder.Init(); + m_RepLenDecoder.Init(); + m_PosAlignDecoder.Init(); + } + + public void Code(System.IO.Stream inStream, System.IO.Stream outStream, + Int64 inSize, Int64 outSize, ICodeProgress progress) + { + Init(inStream, outStream); + + Base.State state = new Base.State(); + state.Init(); + uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; + + UInt64 nowPos64 = 0; + UInt64 outSize64 = (UInt64)outSize; + if (nowPos64 < outSize64) + { + if (m_IsMatchDecoders[state.Index << Base.kNumPosStatesBitsMax].Decode(m_RangeDecoder) != 0) + throw new DataErrorException(); + state.UpdateChar(); + byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0); + m_OutWindow.PutByte(b); + nowPos64++; + } + while (nowPos64 < outSize64) + { + // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); + // while(nowPos64 < next) + { + uint posState = (uint)nowPos64 & m_PosStateMask; + if (m_IsMatchDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + byte b; + byte prevByte = m_OutWindow.GetByte(0); + if (!state.IsCharState()) + b = m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, + (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0)); + else + b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte); + m_OutWindow.PutByte(b); + state.UpdateChar(); + nowPos64++; + } + else + { + uint len; + if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) + { + if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + if (m_IsRep0LongDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + state.UpdateShortRep(); + m_OutWindow.PutByte(m_OutWindow.GetByte(rep0)); + nowPos64++; + continue; + } + } + else + { + UInt32 distance; + if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + distance = rep1; + } + else + { + if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) + distance = rep2; + else + { + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen; + state.UpdateRep(); + } + else + { + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); + state.UpdateMatch(); + uint posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder); + if (posSlot >= Base.kStartPosModelIndex) + { + int numDirectBits = (int)((posSlot >> 1) - 1); + rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < Base.kEndPosModelIndex) + rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, + rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); + else + { + rep0 += (m_RangeDecoder.DecodeDirectBits( + numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits); + rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); + } + } + else + rep0 = posSlot; + } + if (rep0 >= m_OutWindow.TrainSize + nowPos64 || rep0 >= m_DictionarySizeCheck) + { + if (rep0 == 0xFFFFFFFF) + break; + throw new DataErrorException(); + } + m_OutWindow.CopyBlock(rep0, len); + nowPos64 += len; + } + } + } + m_OutWindow.Flush(); + m_OutWindow.ReleaseStream(); + m_RangeDecoder.ReleaseStream(); + } + + public void SetDecoderProperties(byte[] properties) + { + if (properties.Length < 5) + throw new InvalidParamException(); + int lc = properties[0] % 9; + int remainder = properties[0] / 9; + int lp = remainder % 5; + int pb = remainder / 5; + if (pb > Base.kNumPosStatesBitsMax) + throw new InvalidParamException(); + UInt32 dictionarySize = 0; + for (int i = 0; i < 4; i++) + dictionarySize += ((UInt32)(properties[1 + i])) << (i * 8); + SetDictionarySize(dictionarySize); + SetLiteralProperties(lp, lc); + SetPosBitsProperties(pb); + } + + public bool Train(System.IO.Stream stream) + { + _solid = true; + return m_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) {} + */ + } +} \ No newline at end of file diff --git a/LzmaICoder.cs b/LzmaICoder.cs new file mode 100644 index 0000000..6d37f61 --- /dev/null +++ b/LzmaICoder.cs @@ -0,0 +1,155 @@ +using System; + +namespace SevenZip +{ + /// + /// The exception that is thrown when an error in input stream occurs during decoding. + /// + class DataErrorException : ApplicationException + { + public DataErrorException(): base("Data Error") { } + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range. + /// + class InvalidParamException : ApplicationException + { + 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); + }; + + public interface ICoder + { + /// + /// Codes streams. + /// + /// + /// input Stream. + /// + /// + /// output Stream. + /// + /// + /// input Size. -1 if unknown. + /// + /// + /// output Size. -1 if unknown. + /// + /// + /// callback progress reference. + /// + /// + /// if input stream is not valid + /// + void Code(System.IO.Stream inStream, System.IO.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. + /// + public 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 + }; + + + public interface ISetCoderProperties + { + void SetCoderProperties(CoderPropID[] propIDs, object[] properties); + }; + + public interface IWriteCoderProperties + { + void WriteCoderProperties(System.IO.Stream outStream); + } + + public interface ISetDecoderProperties + { + void SetDecoderProperties(byte[] properties); + } +} \ No newline at end of file diff --git a/LzmaRangeDecoder.cs b/LzmaRangeDecoder.cs new file mode 100644 index 0000000..7a4c027 --- /dev/null +++ b/LzmaRangeDecoder.cs @@ -0,0 +1,117 @@ +using System; + +namespace SevenZip.Compression.RangeCoder +{ + class Decoder + { + public const uint kTopValue = (1 << 24); + public uint Range; + public uint Code; + // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16); + public System.IO.Stream Stream; + + public void Init(System.IO.Stream stream) + { + // Stream.Init(stream); + Stream = stream; + + Code = 0; + Range = 0xFFFFFFFF; + for (int i = 0; i < 5; i++) + Code = (Code << 8) | (byte)Stream.ReadByte(); + } + + public void ReleaseStream() + { + // Stream.ReleaseStream(); + Stream = null; + } + + public void CloseStream() + { + Stream.Close(); + } + + public void Normalize() + { + while (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public void Normalize2() + { + if (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public uint GetThreshold(uint total) + { + return Code / (Range /= total); + } + + public void Decode(uint start, uint size, uint total) + { + 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 < kTopValue) + { + code = (code << 8) | (byte)Stream.ReadByte(); + range <<= 8; + } + } + 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; + } + + // ulong GetProcessedSize() {return Stream.GetProcessedSize(); } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..8e91c40 --- /dev/null +++ b/Program.cs @@ -0,0 +1,12 @@ +namespace InnoPatcher; + +class Program { + public static void Main(string[] args) { + using (Stream exe = new FileStream("/home/rv/Downloads/BIMTech_Tools_ZWCAD_2.6.5177_2025_04_25/BIMTech_Tools_ZWCAD_2.6.5177_2025_04_25.exe", FileMode.Open)) { + InnoLoader loader = InnoLoader.FromPE(exe); + Console.WriteLine($"Inno Loader {loader.Version}" + (loader.IsUnicode ? " (Unicode)" : "")); + + loader.GetSetup(); + } + } +} \ No newline at end of file