using System.IO; using System.Text; using SabreTools.Data.Models.AdvancedInstaller; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; using static SabreTools.Data.Models.AdvancedInstaller.Constants; #pragma warning disable IDE0017 // Simplify object initialization namespace SabreTools.Serialization.Readers { public class AdvancedInstaller : BaseBinaryReader { /// public override SFX? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long initialOffset = data.Position; // Create a new SFX to populate var sfx = new SFX(); // Try to parse the footer from the data end var footer = ParseFooter(data, initialOffset); if (footer is null) return null; if (footer.Signature != SignatureString) return null; // Set the footer sfx.Footer = footer; // Get to the entry table offset long tableOffset = initialOffset + footer.TablePointer; if (tableOffset < initialOffset || tableOffset >= data.Length) return null; // Seek to the entry table data.SeekIfPossible(tableOffset, SeekOrigin.Begin); // Try to parse the entry table var table = ParseTable(data, footer.EntryCount); if (table.Length != footer.EntryCount) return null; // Set the entry table sfx.Entries = table; return sfx; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into a Footer /// /// Stream to parse /// Initial offset to use in address comparisons /// Filled Footer on success, null on error public static Footer? ParseFooter(Stream data, long initialOffset) { // Seek to the end of the end of the data data.SeekIfPossible(0, SeekOrigin.End); // Cache the current offset long endOffset = data.Position; // Set a maximum of 10 iterations to find the signature int iterations = 10; // Seek backward to the first point it could be data.SeekIfPossible(-10, SeekOrigin.Current); // Search backward for the signature bool signatureFound = false; do { byte[] tempBytes = data.ReadBytes(10); string tempStr = Encoding.ASCII.GetString(tempBytes); if (tempStr == SignatureString) { signatureFound = true; break; } iterations--; data.SeekIfPossible(-11, SeekOrigin.Current); } while (iterations > 0); // If no signature was found if (!signatureFound) return null; // Seek to the first footer offset field data.SeekIfPossible(-70, SeekOrigin.Current); // Find the actual offset of the start of the footer uint footerStart = data.ReadUInt32LittleEndian(); if (footerStart > endOffset) return null; // If the offset is immediately prior, no name exists bool shortFooter = footerStart == data.Position - 8; // Seek to the start of the footer data.SeekIfPossible(initialOffset + footerStart, SeekOrigin.Begin); var obj = new Footer(); obj.Unknown0 = data.ReadUInt32LittleEndian(); // TODO: Make this block safer if (!shortFooter) { obj.OriginalFilenameSize = data.ReadUInt32LittleEndian(); byte[] filenameBytes = data.ReadBytes((int)obj.OriginalFilenameSize * 2); obj.OriginalFilename = Encoding.Unicode.GetString(filenameBytes); obj.Unknown1 = data.ReadUInt32LittleEndian(); } obj.FooterOffset = data.ReadUInt32LittleEndian(); obj.EntryCount = data.ReadUInt32LittleEndian(); obj.Unknown2 = data.ReadUInt32LittleEndian(); obj.UnknownOffset = data.ReadUInt32LittleEndian(); obj.TablePointer = data.ReadUInt32LittleEndian(); obj.FileDataStart = data.ReadUInt32LittleEndian(); byte[] hexStringBytes = data.ReadBytes(32); obj.HexString = Encoding.ASCII.GetString(hexStringBytes); obj.Unknown3 = data.ReadUInt32LittleEndian(); byte[] signatureBytes = data.ReadBytes(10); obj.Signature = Encoding.ASCII.GetString(signatureBytes); // TODO: Handle padding? return obj; } /// /// Parse a Stream into an entry table /// /// Stream to parse /// Number of entries to parse /// Filled entry table on success, null on error public static FileEntry[] ParseTable(Stream data, uint count) { if (count == 0) return []; var obj = new FileEntry[count]; for (uint i = 0; i < count; i++) { obj[i] = ParseFileEntry(data); } return obj; } /// /// Parse a Stream into a FileEntry /// /// Stream to parse /// Filled FileEntry on success, null on error public static FileEntry ParseFileEntry(Stream data) { var obj = new FileEntry(); obj.Unknown0 = data.ReadUInt32LittleEndian(); obj.Unknown1 = data.ReadUInt32LittleEndian(); obj.Unknown2 = data.ReadUInt32LittleEndian(); obj.FileSize = data.ReadUInt32LittleEndian(); obj.FileOffset = data.ReadUInt32LittleEndian(); obj.NameSize = data.ReadUInt32LittleEndian(); byte[] nameBytes = data.ReadBytes((int)obj.NameSize * 2); obj.Name = Encoding.Unicode.GetString(nameBytes); return obj; } } }