using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using SevenZip.Compression.LZMA; using StringBuilder = System.Text.StringBuilder; namespace InnoPatcher; sealed class InnoSetup { private const int BlockSize = 4096 + 4; private const int CompressionPropertiesLength = 5; private readonly IInnoHeader _header; private InnoSetup(IInnoHeader header) { this._header = header; } 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 BlocksHeader blocksHeader = MemoryMarshal.Cast(headerB)[0]; ReadOnlySpan headerB2 = headerB; if (!InnoLoader.VerifyCRC(headerB2.Slice(4), blocksHeader.CRC)) return false; if (blocksHeader.Compressed != 0 && innoVersion < 4105) throw new NotImplementedException("Zlib decompression not implemented."); Stream data = new FixedBlockReadStream(file, blocksHeader.StoredSize); if (blocksHeader.Compressed != 0) data = new CompressedReadStream(data); // Parse InnoSerializer ser = new InnoSerializer(); IInnoHeader header = CreateHeader(innoVersion); ser.Deserialize(data, header); result = new InnoSetup(header); return true; } public string? AppName => _header.AppName; public string? AppVerName => _header.AppVerName; public string? AppId => _header.AppId; public string? AppCopyright => _header.AppCopyright; public string? AppPublisher => _header.AppPublisher; public string? AppPublisherURL => _header.AppPublisherURL; public string? AppSupportPhone => _header.AppSupportPhone; public string? AppSupportURL => _header.AppSupportURL; public string? AppUpdatesURL => _header.AppUpdatesURL; public string? AppVersion => _header.AppVersion; private static IInnoHeader CreateHeader(int version) { if (version >= 6000 && version <= 7000) return new InnoHeaderV6(); else throw new NotSupportedException($"Header format for version {version} is not supported."); } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BlocksHeader { internal uint CRC; internal uint StoredSize; // Total bytes written, including the CRCs internal byte Compressed; // True if data is compressed, False if not } sealed class InnoSerializer { private byte[] _buffer = new byte[512]; internal void Deserialize(Stream src, IInnoSerializable dst) { StringBuilder sb = new StringBuilder(); for (int i = 0, j = dst.StringCount; i < j; i++) { int byteLen = ReadInt32(src); sb.Clear(); sb.EnsureCapacity(byteLen / 2); ReadString(src, sb, byteLen); dst.SetString(i, sb.ToString()); } for (int i = 0, j = dst.ByteArrayCount; i < j; i++) { int len = ReadInt32(src); byte[] value = new byte[len]; ReadByteArray(src, value, len); dst.SetByteArray(i, value); } int size = dst.PrimitiveSize; if (_buffer.Length < size) { int doubleSize = _buffer.Length * 2; _buffer = new byte[doubleSize >= size ? doubleSize : size]; } ReadByteArray(src, _buffer, size); ReadOnlySpan buffer = _buffer; dst.SetPrimitive(buffer.Slice(0, size)); } private int ReadInt32(Stream a) { int read, offset = 0; byte[] buffer = _buffer; while (offset < 4 && (read = a.Read(buffer, offset, 4 - offset)) != 0) offset += read; if (offset < 4) throw new EndOfStreamException(); ReadOnlySpan buffer2 = buffer; return MemoryMarshal.Cast(buffer2.Slice(0, 4))[0]; } private void ReadString(Stream src, StringBuilder dst, int byteLen) { int read; byte[] buffer = _buffer; ReadOnlySpan bufferC = MemoryMarshal.Cast(buffer); bool splitChar = false; while (byteLen != 0 && (read = src.Read(buffer, splitChar ? 1 : 0, Math.Min(byteLen, buffer.Length))) != 0) { if (splitChar) read++; dst.Append(bufferC.Slice(0, read / 2)); splitChar = (read & 1) != 0; if (splitChar) { buffer[0] = buffer[read - 1]; read--; } byteLen -= read; } if (byteLen != 0) throw new EndOfStreamException(); } private void ReadByteArray(Stream src, byte[] dst, int len) { int read, offset = 0; while (offset < len && (read = src.Read(dst, offset, len - offset)) != 0) offset += read; if (offset != len) throw new EndOfStreamException(); } } /// /// Reads underlying stream composed of fixed-sized CRC protected blocks. /// sealed class FixedBlockReadStream : Stream { private readonly Stream _inner; private readonly byte[] _buffer; private int _bufferOffset; private int _bufferLeft; private uint _innerLeft; internal FixedBlockReadStream(Stream inner, uint innerLength) { this._inner = inner; this._innerLeft = innerLength; this._buffer = new byte[BlockSize]; } 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(); } public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || count < 0 || buffer.Length - offset < count) throw new ArgumentOutOfRangeException(); int result = 0; while (count > 0) { if (_bufferLeft == 0) { if (_innerLeft == 0) return result; ReadBlock(); } int toCopy = count; if (toCopy > _bufferLeft) toCopy = _bufferLeft; Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy); _bufferOffset += toCopy; _bufferLeft -= toCopy; offset += toCopy; count -= toCopy; result += toCopy; } return result; } public override int ReadByte() { if (_bufferLeft == 0) { if (_innerLeft == 0) return -1; ReadBlock(); } _bufferLeft--; return _buffer[_bufferOffset++]; } private void ReadBlock() { if (_bufferLeft != 0) throw new InvalidOperationException(); int blockSize = _innerLeft > BlockSize ? BlockSize : (int)_innerLeft; int read, offset = 0; while ((read = _inner.Read(_buffer, offset, blockSize - offset)) != 0) offset += read; if (offset < blockSize) throw new IOException(); ReadOnlySpan blockData = _buffer; uint blockCRC = MemoryMarshal.Cast(blockData.Slice(0, 4))[0]; blockData = blockData.Slice(4); if (!InnoLoader.VerifyCRC(blockData, blockCRC)) throw new IOException(); _bufferOffset = 4; // skip CRC _bufferLeft = blockSize - 4; // minus CRC } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void Flush() { throw new NotSupportedException(); } } /// /// Reads underlying LZMA compressed stream and decompresses it. /// sealed class CompressedReadStream : Stream { private readonly Stream _inner; private readonly Decoder _decoder; private byte[]? _singleByteBuffer; private MemoryStream? _singleByteStream; private long _outOffset; internal CompressedReadStream(Stream inner) { this._inner = inner; this._decoder = new Decoder(); byte[] props = new byte[CompressionPropertiesLength]; int read, offset = 0; while ((read = inner.Read(props, offset, props.Length - offset)) != 0) offset += read; if (offset != props.Length) throw new IOException(); _decoder.SetDecoderProperties(props); _decoder.Code(inner, Stream.Null, -1, 0, null, noInit: false, isLast: false); } 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(); } public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || count < 0 || buffer.Length - offset < count) throw new ArgumentOutOfRangeException(); MemoryStream result = new MemoryStream(buffer, offset, count, writable: true); _decoder.Code(_inner, result, -1, _outOffset + count, null, noInit: true, isLast: false); int read = (int)result.Position; _outOffset += read; return read; } public override int ReadByte() { if (_singleByteBuffer == null || _singleByteStream == null) { _singleByteBuffer = new byte[1]; _singleByteStream = new MemoryStream(_singleByteBuffer, 0, 1, writable: true); } _singleByteStream.SetLength(0); _decoder.Code(_inner, _singleByteStream, -1, _outOffset + 1, null, noInit: true, isLast: false); int read = (int)_singleByteStream.Position; _outOffset += read; return read == 0 ? -1 : _singleByteBuffer[0]; } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void Flush() { throw new NotSupportedException(); } } }