diff --git a/ExtractionTool/Program.cs b/ExtractionTool/Program.cs index 35d8ecc0..7f09e36c 100644 --- a/ExtractionTool/Program.cs +++ b/ExtractionTool/Program.cs @@ -244,6 +244,7 @@ namespace ExtractionTool Console.WriteLine(); // TODO: Add Wise installer handling here + nex.Extract(outputDirectory, includeDebug); } // PAK diff --git a/SabreTools.Serialization/Deserializers/NewExecutable.cs b/SabreTools.Serialization/Deserializers/NewExecutable.cs index 28dbe005..ec9d5582 100644 --- a/SabreTools.Serialization/Deserializers/NewExecutable.cs +++ b/SabreTools.Serialization/Deserializers/NewExecutable.cs @@ -139,7 +139,7 @@ namespace SabreTools.Serialization.Deserializers // If the offset for the imported-name table doesn't exist tableAddress = initialOffset + stub.Header.NewExeHeaderAddr + header.EntryTableOffset; - endOffset = initialOffset + stub.Header.NewExeHeaderAddr + + header.EntryTableOffset + header.EntryTableSize; + endOffset = initialOffset + stub.Header.NewExeHeaderAddr + header.EntryTableOffset + header.EntryTableSize; if (tableAddress >= data.Length) return executable; @@ -267,15 +267,15 @@ namespace SabreTools.Serialization.Deserializers /// Filled imported-name table on success, null on error public static Dictionary ParseImportedNameTable(Stream data, long endOffset) { - var importedNameTable = new Dictionary(); + var obj = new Dictionary(); while (data.Position < endOffset && data.Position < data.Length) { ushort currentOffset = (ushort)data.Position; - importedNameTable[currentOffset] = ParseImportedNameTableEntry(data); + obj[currentOffset] = ParseImportedNameTableEntry(data); } - return importedNameTable; + return obj; } /// @@ -361,15 +361,15 @@ namespace SabreTools.Serialization.Deserializers /// Filled nonresident-name table on success, null on error public static NonResidentNameTableEntry[] ParseNonResidentNameTable(Stream data, long endOffset) { - var residentNameTable = new List(); + var obj = new List(); while (data.Position < endOffset && data.Position < data.Length) { var entry = ParseNonResidentNameTableEntry(data); - residentNameTable.Add(entry); + obj.Add(entry); } - return [.. residentNameTable]; + return [.. obj]; } /// @@ -462,15 +462,15 @@ namespace SabreTools.Serialization.Deserializers /// Filled resident-name table on success, null on error public static ResidentNameTableEntry[] ParseResidentNameTable(Stream data, long endOffset) { - var residentNameTable = new List(); + var obj = new List(); while (data.Position < endOffset && data.Position < data.Length) { var entry = ParseResidentNameTableEntry(data); - residentNameTable.Add(entry); + obj.Add(entry); } - return [.. residentNameTable]; + return [.. obj]; } /// diff --git a/SabreTools.Serialization/Wrappers/NewExecutable.cs b/SabreTools.Serialization/Wrappers/NewExecutable.cs index 6e501c73..da3d8ed1 100644 --- a/SabreTools.Serialization/Wrappers/NewExecutable.cs +++ b/SabreTools.Serialization/Wrappers/NewExecutable.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.IO; +using SabreTools.IO.Extensions; +using SabreTools.Matching; using SabreTools.Models.NewExecutable; +using SabreTools.Serialization.Interfaces; namespace SabreTools.Serialization.Wrappers { - public class NewExecutable : WrapperBase + public class NewExecutable : WrapperBase, IExtractable { #region Descriptive Properties @@ -56,9 +59,16 @@ namespace SabreTools.Serialization.Wrappers long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; // Read and find the end of the relocation data +#if NET20 || NET35 if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0) +#else + if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) +#endif { - // TODO: When the model and deserializer get updated, fix this + _dataSource.Seek(offset, SeekOrigin.Begin); + var relocationData = Deserializers.NewExecutable.ParsePerSegmentData(_dataSource); + + offset = _dataSource.Position; } if (offset > endOfSectionData) @@ -84,10 +94,6 @@ namespace SabreTools.Serialization.Wrappers if (endOfSectionData <= 0) endOfSectionData = -1; - // Adjust the position of the data by 705 bytes - // TODO: Investigate what the byte data is - endOfSectionData += 705; - // Cache and return the position _overlayAddress = endOfSectionData; return _overlayAddress.Value; @@ -119,10 +125,25 @@ namespace SabreTools.Serialization.Wrappers return null; // Search through the segments table to find the furthest - int endOfSectionData = -1; + long endOfSectionData = -1; foreach (var entry in SegmentTable) { - int offset = (entry.Offset << Header.SegmentAlignmentShiftCount) + entry.Length; + // Get end of segment data + long offset = (entry.Offset * (1 << Header.SegmentAlignmentShiftCount)) + entry.Length; + + // Read and find the end of the relocation data +#if NET20 || NET35 + if ((entry.FlagWord & SegmentTableEntryFlag.RELOCINFO) != 0) +#else + if (entry.FlagWord.HasFlag(SegmentTableEntryFlag.RELOCINFO)) +#endif + { + _dataSource.Seek(offset, SeekOrigin.Begin); + var relocationData = Deserializers.NewExecutable.ParsePerSegmentData(_dataSource); + + offset = _dataSource.Position; + } + if (offset > endOfSectionData) endOfSectionData = offset; } @@ -146,10 +167,6 @@ namespace SabreTools.Serialization.Wrappers if (endOfSectionData <= 0) return null; - // Adjust the position of the data by 705 bytes - // TODO: Investigate what the byte data is - endOfSectionData += 705; - // If we're at the end of the file, cache an empty byte array if (endOfSectionData >= dataLength) { @@ -159,7 +176,7 @@ namespace SabreTools.Serialization.Wrappers // Otherwise, cache and return the data long overlayLength = dataLength - endOfSectionData; - _overlayData = ReadFromDataSource(endOfSectionData, (int)overlayLength); + _overlayData = ReadFromDataSource((int)endOfSectionData, (int)overlayLength); return _overlayData; } } @@ -375,6 +392,127 @@ namespace SabreTools.Serialization.Wrappers #endregion + #region Extraction + + /// + /// + /// This extracts the following data: + /// - Archives and executables in the overlay + /// + public bool Extract(string outputDirectory, bool includeDebug) + { + bool overlay = ExtractFromOverlay(outputDirectory, includeDebug); + + return overlay; + } + + /// + /// Extract data from the overlay + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if extraction succeeded, false otherwise + public bool ExtractFromOverlay(string outputDirectory, bool includeDebug) + { + try + { + // Cache the overlay data for easier reading + var overlayData = OverlayData; + if (overlayData == null || overlayData.Length == 0) + return false; + + // Set the output variables + int overlayOffset = 0; + string extension = string.Empty; + + // Only process the overlay if it is recognized + for (; overlayOffset < 0x40 && overlayOffset < overlayData.Length; overlayOffset++) + { + int temp = overlayOffset; + byte[] overlaySample = overlayData.ReadBytes(ref temp, 0x10); + + if (overlaySample.StartsWith([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) + { + extension = "7z"; + break; + } + else if (overlaySample.StartsWith([0x3B, 0x21, 0x40, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6C, 0x6C])) + { + // 7-zip SFX script -- ";!@Install" to ";!@InstallEnd@!" + overlayOffset = overlayData.FirstPosition([0x3B, 0x21, 0x40, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6C, 0x6C, 0x45, 0x6E, 0x64, 0x40, 0x21]); + if (overlayOffset == -1) + return false; + + overlayOffset += 15; + extension = "7z"; + break; + } + else if (overlaySample.StartsWith(Models.PKZIP.Constants.LocalFileHeaderSignatureBytes)) + { + extension = "zip"; + break; + } + else if (overlaySample.StartsWith(Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes)) + { + extension = "zip"; + break; + } + else if (overlaySample.StartsWith(Models.PKZIP.Constants.EndOfCentralDirectoryRecord64SignatureBytes)) + { + extension = "zip"; + break; + } + else if (overlaySample.StartsWith(Models.PKZIP.Constants.DataDescriptorSignatureBytes)) + { + extension = "zip"; + break; + } + else if (overlaySample.StartsWith([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00])) + { + extension = "rar"; + break; + } + else if (overlaySample.StartsWith([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00])) + { + extension = "rar"; + break; + } + else if (overlaySample.StartsWith(Models.MSDOS.Constants.SignatureBytes)) + { + extension = "bin"; // exe/dll + break; + } + } + + // If the extension is unset + if (extension.Length == 0) + return false; + + // Create the temp filename + string tempFile = $"embedded_overlay.{extension}"; + if (Filename != null) + tempFile = $"{Path.GetFileName(Filename)}-{tempFile}"; + + tempFile = Path.Combine(outputDirectory, tempFile); + var directoryName = Path.GetDirectoryName(tempFile); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + // Write the resource data to a temp file + using var tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + tempStream?.Write(overlayData, overlayOffset, overlayData.Length - overlayOffset); + + return true; + } + catch (Exception ex) + { + if (includeDebug) Console.Error.WriteLine(ex); + return false; + } + } + + #endregion + #region Resources ///