Files
InnoPatcher/InnoSetup.cs

323 lines
10 KiB
C#

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<BlocksHeader>()];
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<byte, BlocksHeader>(headerB)[0];
ReadOnlySpan<byte> 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<byte> 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<byte> buffer2 = buffer;
return MemoryMarshal.Cast<byte, int>(buffer2.Slice(0, 4))[0];
}
private void ReadString(Stream src, StringBuilder dst, int byteLen) {
int read;
byte[] buffer = _buffer;
ReadOnlySpan<char> bufferC = MemoryMarshal.Cast<byte, char>(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();
}
}
/// <summary>
/// Reads underlying stream composed of fixed-sized CRC protected blocks.
/// </summary>
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<byte> blockData = _buffer;
uint blockCRC = MemoryMarshal.Cast<byte, uint>(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();
}
}
/// <summary>
/// Reads underlying LZMA compressed stream and decompresses it.
/// </summary>
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();
}
}
}