Inno Header v6 reading almost works
This commit is contained in:
327
InnoSetup.cs
327
InnoSetup.cs
@@ -1,18 +1,27 @@
|
||||
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<Header>()];
|
||||
byte[] headerB = new byte[Marshal.SizeOf<BlocksHeader>()];
|
||||
int read, offset = 0;
|
||||
while ((read = file.Read(headerB, offset, headerB.Length - offset)) != 0)
|
||||
offset += read;
|
||||
@@ -20,56 +29,294 @@ sealed class InnoSetup {
|
||||
return false;
|
||||
|
||||
// Read header
|
||||
Header header = MemoryMarshal.Cast<byte, Header>(headerB)[0];
|
||||
BlocksHeader blocksHeader = MemoryMarshal.Cast<byte, BlocksHeader>(headerB)[0];
|
||||
ReadOnlySpan<byte> headerB2 = headerB;
|
||||
if (!InnoLoader.VerifyCRC(headerB2.Slice(4), header.CRC))
|
||||
if (!InnoLoader.VerifyCRC(headerB2.Slice(4), blocksHeader.CRC))
|
||||
return false;
|
||||
|
||||
if (header.Compressed != 0 && innoVersion < 4105)
|
||||
if (blocksHeader.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;
|
||||
Stream data = new FixedBlockReadStream(file, blocksHeader.StoredSize);
|
||||
if (blocksHeader.Compressed != 0)
|
||||
data = new CompressedReadStream(data);
|
||||
|
||||
ReadOnlySpan<byte> blockData = blockB;
|
||||
uint blockCRC = MemoryMarshal.Cast<byte, uint>(blockData.Slice(0, 4))[0];
|
||||
blockData = blockData.Slice(4);
|
||||
|
||||
if (!InnoLoader.VerifyCRC(blockData, blockCRC))
|
||||
return false;
|
||||
// Parse
|
||||
InnoSerializer ser = new InnoSerializer();
|
||||
IInnoHeader header = CreateHeader(innoVersion);
|
||||
ser.Deserialize(data, header);
|
||||
|
||||
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;
|
||||
result = new InnoSetup(header);
|
||||
return true;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct Header {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user