107 lines
3.8 KiB
C#
107 lines
3.8 KiB
C#
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Text;
|
|
|
|
namespace AeroMCSetup;
|
|
|
|
/// <summary>
|
|
/// Die App liegt als ZIP hinten an der Setup-.exe angehängt:
|
|
/// [Setup.exe][ZIP][8 Byte: ZIP-Länge][8 Byte: "AEROMCPK"]
|
|
/// Windows ignoriert angehängte Daten beim Starten einer .exe.
|
|
/// </summary>
|
|
public static class Payload
|
|
{
|
|
private static readonly byte[] Magic = Encoding.ASCII.GetBytes("AEROMCPK");
|
|
private const int FooterSize = 16;
|
|
|
|
private static (long Offset, long Length)? _info;
|
|
private static bool _checked;
|
|
|
|
/// <summary>Position und Länge des ZIPs in dieser .exe, oder null (z. B. beim Deinstallationsprogramm).</summary>
|
|
public static (long Offset, long Length)? Info
|
|
{
|
|
get
|
|
{
|
|
if (_checked) return _info;
|
|
_checked = true;
|
|
try
|
|
{
|
|
using var f = File.OpenRead(Installer.SelfPath);
|
|
if (f.Length < FooterSize) return null;
|
|
f.Seek(-FooterSize, SeekOrigin.End);
|
|
var footer = new byte[FooterSize];
|
|
if (f.Read(footer, 0, FooterSize) != FooterSize) return null;
|
|
for (int i = 0; i < Magic.Length; i++)
|
|
if (footer[8 + i] != Magic[i]) return null;
|
|
long length = BitConverter.ToInt64(footer, 0);
|
|
long offset = f.Length - FooterSize - length;
|
|
if (length <= 0 || offset <= 0) return null;
|
|
_info = (offset, length);
|
|
}
|
|
catch { _info = null; }
|
|
return _info;
|
|
}
|
|
}
|
|
|
|
public static bool Exists => Info is not null;
|
|
|
|
/// <summary>Länge des eigentlichen Setup-Programms ohne Anhang.</summary>
|
|
public static long StubLength => Info?.Offset ?? new FileInfo(Installer.SelfPath).Length;
|
|
|
|
public static ZipArchive OpenArchive()
|
|
{
|
|
var info = Info ?? throw new InvalidOperationException("Die Installationsdaten fehlen in dieser Datei. Bitte AeroMC-Setup.exe neu herunterladen.");
|
|
var file = File.OpenRead(Installer.SelfPath);
|
|
return new ZipArchive(new SubStream(file, info.Offset, info.Length), ZipArchiveMode.Read, leaveOpen: false);
|
|
}
|
|
|
|
/// <summary>Nur-Lese-Ausschnitt einer Datei, damit ZipArchive den Anhang wie eine eigene Datei sieht.</summary>
|
|
private sealed class SubStream(Stream inner, long start, long length) : Stream
|
|
{
|
|
private long _pos;
|
|
|
|
public override bool CanRead => true;
|
|
public override bool CanSeek => true;
|
|
public override bool CanWrite => false;
|
|
public override long Length => length;
|
|
|
|
public override long Position
|
|
{
|
|
get => _pos;
|
|
set => _pos = Math.Max(0, Math.Min(value, length));
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
long left = length - _pos;
|
|
if (left <= 0) return 0;
|
|
if (count > left) count = (int)left;
|
|
inner.Position = start + _pos;
|
|
int n = inner.Read(buffer, offset, count);
|
|
_pos += n;
|
|
return n;
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
Position = origin switch
|
|
{
|
|
SeekOrigin.Begin => offset,
|
|
SeekOrigin.Current => _pos + offset,
|
|
_ => length + offset,
|
|
};
|
|
return _pos;
|
|
}
|
|
|
|
public override void Flush() { }
|
|
public override void SetLength(long value) => throw new NotSupportedException();
|
|
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing) inner.Dispose();
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|
|
}
|