using System.IO; using System.IO.Compression; using System.Text; namespace AeroMCSetup; /// /// 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. /// 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; /// Position und Länge des ZIPs in dieser .exe, oder null (z. B. beim Deinstallationsprogramm). 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; /// Länge des eigentlichen Setup-Programms ohne Anhang. 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); } /// Nur-Lese-Ausschnitt einer Datei, damit ZipArchive den Anhang wie eine eigene Datei sieht. 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); } } }