From 27ceb4ed48dbe57b2c1449d2122642d330fc8683 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 13 Dec 2022 23:06:49 -0800 Subject: [PATCH] Streamline MS-CAB reading, add to info printing --- BurnOutSharp/FileType/MicrosoftCAB.MSCAB.cs | 434 +++++++++++++------- BurnOutSharp/Tools/Extensions.cs | 2 +- Test/Program.cs | 164 +++++--- 3 files changed, 383 insertions(+), 217 deletions(-) diff --git a/BurnOutSharp/FileType/MicrosoftCAB.MSCAB.cs b/BurnOutSharp/FileType/MicrosoftCAB.MSCAB.cs index 1ad97d79..d2976478 100644 --- a/BurnOutSharp/FileType/MicrosoftCAB.MSCAB.cs +++ b/BurnOutSharp/FileType/MicrosoftCAB.MSCAB.cs @@ -10,7 +10,7 @@ namespace BurnOutSharp.FileType /// // TODO: Add multi-cabinet reading - internal class MSCABCabinet + public class MSCABCabinet { #region Constants @@ -82,7 +82,7 @@ namespace BurnOutSharp.FileType cabinet.Folders = new CFFOLDER[cabinet.Header.FolderCount]; for (int i = 0; i < cabinet.Header.FolderCount; i++) { - cabinet.Folders[i] = CFFOLDER.Deserialize(data, ref dataPtr, basePtr, cabinet.Header.FolderReservedSize, cabinet.Header.DataReservedSize); + cabinet.Folders[i] = CFFOLDER.Deserialize(data, ref dataPtr, basePtr, cabinet.Header); if (cabinet.Folders[i] == null) return null; } @@ -102,6 +102,45 @@ namespace BurnOutSharp.FileType return cabinet; } + /// + /// Deserialize into a MSCABCabinet object + /// + public static MSCABCabinet Deserialize(Stream data) + { + if (data == null || data.Position < 0) + return null; + + MSCABCabinet cabinet = new MSCABCabinet(); + + // Start with the header + cabinet.Header = CFHEADER.Deserialize(data); + if (cabinet.Header == null) + return null; + + // Then retrieve all folder headers + cabinet.Folders = new CFFOLDER[cabinet.Header.FolderCount]; + for (int i = 0; i < cabinet.Header.FolderCount; i++) + { + cabinet.Folders[i] = CFFOLDER.Deserialize(data, cabinet.Header); + if (cabinet.Folders[i] == null) + return null; + } + + // We need to move to where the file headers are stored + data.Seek((int)cabinet.Header.FilesOffset, SeekOrigin.Begin); + + // Then retrieve all file headers + cabinet.Files = new CFFILE[cabinet.Header.FileCount]; + for (int i = 0; i < cabinet.Header.FileCount; i++) + { + cabinet.Files[i] = CFFILE.Deserialize(data); + if (cabinet.Files[i] == null) + return null; + } + + return cabinet; + } + #endregion #region Public Functionality @@ -131,7 +170,7 @@ namespace BurnOutSharp.FileType foreach (CFFILE file in Files) { // Create the output path - string outputPath = Path.Combine(outputDirectory, file.NameAsString); + string outputPath = Path.Combine(outputDirectory, file.Name); // Get the associated folder, if possible CFFOLDER folder = null; @@ -168,7 +207,7 @@ namespace BurnOutSharp.FileType continue; // Check for a match - if (exact ? tempFile.NameAsString == filePath : tempFile.NameAsString.EndsWith(filePath, StringComparison.OrdinalIgnoreCase)) + if (exact ? tempFile.Name == filePath : tempFile.Name.EndsWith(filePath, StringComparison.OrdinalIgnoreCase)) { fileIndex = i; break; @@ -183,7 +222,7 @@ namespace BurnOutSharp.FileType CFFILE file = Files[fileIndex]; // Create the output path - string outputPath = Path.Combine(outputDirectory, file.NameAsString); + string outputPath = Path.Combine(outputDirectory, file.Name); // Get the associated folder, if possible CFFOLDER folder = null; @@ -278,33 +317,13 @@ namespace BurnOutSharp.FileType } #endregion - - #region Internal Functionality - - /// - /// Get a null-terminated string as a byte array from input data - /// - internal static byte[] GetNullTerminatedString(byte[] data, ref int dataPtr) - { - int nullIndex = Array.IndexOf(data, 0x00, dataPtr, 0xFF); - int stringSize = nullIndex - dataPtr; - if (stringSize < 0 || stringSize > 256) - return null; - - byte[] str = new byte[stringSize]; - Array.Copy(data, dataPtr, str, 0, stringSize); - dataPtr += stringSize + 1; - return str; - } - - #endregion } /// /// The CFHEADER structure shown in the following packet diagram provides information about this /// cabinet (.cab) file. /// - internal class CFHEADER + public class CFHEADER { #region Constants @@ -439,7 +458,7 @@ namespace BurnOutSharp.FileType /// reported to begin in the "previous cabinet," the szCabinetPrev field would indicate the name of the /// cabinet to examine. /// - public byte[] CabinetPrev { get; private set; } + public string CabinetPrev { get; private set; } /// /// If the flags.cfhdrPREV_CABINET field is not set, then this @@ -448,7 +467,7 @@ namespace BurnOutSharp.FileType /// This string can be used when prompting the user to insert a disk. The string can contain up to 255 /// bytes, plus the null byte. /// - public byte[] DiskPrev { get; private set; } + public string DiskPrev { get; private set; } /// /// If the flags.cfhdrNEXT_CABINET field is not set, this @@ -456,7 +475,7 @@ namespace BurnOutSharp.FileType /// cabinet file in a set. The string can contain up to 255 bytes, plus the null byte. Files that extend /// beyond the end of the current cabinet file are continued in the named cabinet file. /// - public byte[] CabinetNext { get; private set; } + public string CabinetNext { get; private set; } /// /// If the flags.cfhdrNEXT_CABINET field is not set, this field is @@ -465,7 +484,7 @@ namespace BurnOutSharp.FileType /// string can contain up to 255 bytes, plus the null byte. This string can be used when prompting the /// user to insert a disk. /// - public byte[] DiskNext { get; private set; } + public string DiskNext { get; private set; } #endregion @@ -481,91 +500,145 @@ namespace BurnOutSharp.FileType CFHEADER header = new CFHEADER(); - header.Signature = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.Signature = data.ReadUInt32(ref dataPtr); if (header.Signature != SignatureValue) return null; - header.Reserved1 = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.Reserved1 = data.ReadUInt32(ref dataPtr); if (header.Reserved1 != 0x00000000) return null; - header.CabinetSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.CabinetSize = data.ReadUInt32(ref dataPtr); if (header.CabinetSize > MSCABCabinet.MaximumCabSize) return null; - header.Reserved2 = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.Reserved2 = data.ReadUInt32(ref dataPtr); if (header.Reserved2 != 0x00000000) return null; - header.FilesOffset = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.FilesOffset = data.ReadUInt32(ref dataPtr); - header.Reserved3 = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; + header.Reserved3 = data.ReadUInt32(ref dataPtr); if (header.Reserved3 != 0x00000000) return null; - header.VersionMinor = data[dataPtr++]; - header.VersionMajor = data[dataPtr++]; + header.VersionMinor = data.ReadByte(ref dataPtr); + header.VersionMajor = data.ReadByte(ref dataPtr); if (header.VersionMajor != 0x00000001 || header.VersionMinor != 0x00000003) return null; - header.FolderCount = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + header.FolderCount = data.ReadUInt16(ref dataPtr); if (header.FolderCount > MSCABCabinet.MaximumFolderCount) return null; - header.FileCount = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + header.FileCount = data.ReadUInt16(ref dataPtr); if (header.FileCount > MSCABCabinet.MaximumFileCount) return null; - header.Flags = (HeaderFlags)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - header.SetID = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - header.CabinetIndex = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + header.Flags = (HeaderFlags)data.ReadUInt16(ref dataPtr); + header.SetID = data.ReadUInt16(ref dataPtr); + header.CabinetIndex = data.ReadUInt16(ref dataPtr); if (header.Flags.HasFlag(HeaderFlags.RESERVE_PRESENT)) { - header.HeaderReservedSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + header.HeaderReservedSize = data.ReadUInt16(ref dataPtr); if (header.HeaderReservedSize > 60_000) return null; - header.FolderReservedSize = data[dataPtr++]; - header.DataReservedSize = data[dataPtr++]; + header.FolderReservedSize = data.ReadByte(ref dataPtr); + header.DataReservedSize = data.ReadByte(ref dataPtr); if (header.HeaderReservedSize > 0) - { - header.ReservedData = new byte[header.HeaderReservedSize]; - Array.Copy(data, dataPtr, header.ReservedData, 0, header.HeaderReservedSize); - dataPtr += header.HeaderReservedSize; - } + header.ReservedData = data.ReadBytes(ref dataPtr, header.HeaderReservedSize); } - // TODO: Make string-finding block a helper method if (header.Flags.HasFlag(HeaderFlags.PREV_CABINET)) { - byte[] cabPrev = MSCABCabinet.GetNullTerminatedString(data, ref dataPtr); - if (cabPrev == null) - return null; - - header.CabinetPrev = cabPrev; - - byte[] diskPrev = MSCABCabinet.GetNullTerminatedString(data, ref dataPtr); - if (diskPrev == null) - return null; - - header.DiskPrev = diskPrev; + header.CabinetPrev = data.ReadString(ref dataPtr, Encoding.ASCII); + header.DiskPrev = data.ReadString(ref dataPtr, Encoding.ASCII); } if (header.Flags.HasFlag(HeaderFlags.NEXT_CABINET)) { - byte[] cabNext = MSCABCabinet.GetNullTerminatedString(data, ref dataPtr); - if (cabNext == null) + header.CabinetNext = data.ReadString(ref dataPtr, Encoding.ASCII); + header.DiskNext = data.ReadString(ref dataPtr, Encoding.ASCII); + } + + return header; + } + + /// + /// Deserialize into a CFHEADER object + /// + public static CFHEADER Deserialize(Stream data) + { + if (data == null || data.Position < 0) + return null; + + CFHEADER header = new CFHEADER(); + + header.Signature = data.ReadUInt32(); + if (header.Signature != SignatureValue) + return null; + + header.Reserved1 = data.ReadUInt32(); + if (header.Reserved1 != 0x00000000) + return null; + + header.CabinetSize = data.ReadUInt32(); + if (header.CabinetSize > MSCABCabinet.MaximumCabSize) + return null; + + header.Reserved2 = data.ReadUInt32(); + if (header.Reserved2 != 0x00000000) + return null; + + header.FilesOffset = data.ReadUInt32(); + + header.Reserved3 = data.ReadUInt32(); + if (header.Reserved3 != 0x00000000) + return null; + + header.VersionMinor = data.ReadByteValue(); + header.VersionMajor = data.ReadByteValue(); + if (header.VersionMajor != 0x00000001 || header.VersionMinor != 0x00000003) + return null; + + header.FolderCount = data.ReadUInt16(); + if (header.FolderCount > MSCABCabinet.MaximumFolderCount) + return null; + + header.FileCount = data.ReadUInt16(); + if (header.FileCount > MSCABCabinet.MaximumFileCount) + return null; + + header.Flags = (HeaderFlags)data.ReadUInt16(); + header.SetID = data.ReadUInt16(); + header.CabinetIndex = data.ReadUInt16(); + + if (header.Flags.HasFlag(HeaderFlags.RESERVE_PRESENT)) + { + header.HeaderReservedSize = data.ReadUInt16(); + if (header.HeaderReservedSize > 60_000) return null; - header.CabinetNext = cabNext; + header.FolderReservedSize = data.ReadByteValue(); + header.DataReservedSize = data.ReadByteValue(); - byte[] diskNext = MSCABCabinet.GetNullTerminatedString(data, ref dataPtr); - if (diskNext == null) - return null; + if (header.HeaderReservedSize > 0) + header.ReservedData = data.ReadBytes(header.HeaderReservedSize); + } - header.DiskNext = diskNext; + if (header.Flags.HasFlag(HeaderFlags.PREV_CABINET)) + { + header.CabinetPrev = data.ReadString(Encoding.ASCII); + header.DiskPrev = data.ReadString(Encoding.ASCII); + } + + if (header.Flags.HasFlag(HeaderFlags.NEXT_CABINET)) + { + header.CabinetNext = data.ReadString(Encoding.ASCII); + header.DiskNext = data.ReadString(Encoding.ASCII); } return header; @@ -605,14 +678,14 @@ namespace BurnOutSharp.FileType if (Flags.HasFlag(HeaderFlags.PREV_CABINET)) { - Console.WriteLine($" CabinetPrev: {Encoding.ASCII.GetString(CabinetPrev).TrimEnd('\0')}"); - Console.WriteLine($" DiskPrev: {Encoding.ASCII.GetString(DiskPrev).TrimEnd('\0')}"); + Console.WriteLine($" CabinetPrev: {CabinetPrev}"); + Console.WriteLine($" DiskPrev: {DiskPrev}"); } if (Flags.HasFlag(HeaderFlags.NEXT_CABINET)) { - Console.WriteLine($" CabinetNext: {Encoding.ASCII.GetString(CabinetNext).TrimEnd('\0')}"); - Console.WriteLine($" DiskNext: {Encoding.ASCII.GetString(DiskNext).TrimEnd('\0')}"); + Console.WriteLine($" CabinetNext: {CabinetNext}"); + Console.WriteLine($" DiskNext: {DiskNext}"); } Console.WriteLine(); @@ -622,7 +695,7 @@ namespace BurnOutSharp.FileType } [Flags] - internal enum HeaderFlags : ushort + public enum HeaderFlags : ushort { /// /// The flag is set if this cabinet file is not the first in a set of cabinet files. @@ -665,7 +738,7 @@ namespace BurnOutSharp.FileType /// The typeCompress field can vary from one folder to the next, unless the folder is continued from a /// previous cabinet file. /// - internal class CFFOLDER + public class CFFOLDER { #region Properties @@ -755,23 +828,19 @@ namespace BurnOutSharp.FileType /// /// Deserialize at into a CFFOLDER object /// - public static CFFOLDER Deserialize(byte[] data, ref int dataPtr, int basePtr, byte folderReservedSize, byte dataReservedSize) + public static CFFOLDER Deserialize(byte[] data, ref int dataPtr, int basePtr, CFHEADER header) { if (data == null || dataPtr < 0) return null; CFFOLDER folder = new CFFOLDER(); - folder.CabStartOffset = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - folder.DataCount = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - folder.CompressionType = (CompressionType)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + folder.CabStartOffset = data.ReadUInt32(ref dataPtr); + folder.DataCount = data.ReadUInt16(ref dataPtr); + folder.CompressionType = (CompressionType)data.ReadUInt16(ref dataPtr); - if (folderReservedSize > 0) - { - folder.ReservedData = new byte[folderReservedSize]; - Array.Copy(data, dataPtr, folder.ReservedData, 0, folderReservedSize); - dataPtr += folderReservedSize; - } + if (header.FolderReservedSize > 0) + folder.ReservedData = data.ReadBytes(ref dataPtr, header.FolderReservedSize); if (folder.CabStartOffset > 0) { @@ -779,7 +848,7 @@ namespace BurnOutSharp.FileType for (int i = 0; i < folder.DataCount; i++) { int offset = blockPtr; - CFDATA dataBlock = CFDATA.Deserialize(data, ref blockPtr, dataReservedSize); + CFDATA dataBlock = CFDATA.Deserialize(data, ref blockPtr, header.DataReservedSize); folder.DataBlocks[offset] = dataBlock; } } @@ -787,6 +856,40 @@ namespace BurnOutSharp.FileType return folder; } + /// + /// Deserialize into a CFFOLDER object + /// + public static CFFOLDER Deserialize(Stream data, CFHEADER header) + { + if (data == null || data.Position < 0) + return null; + + CFFOLDER folder = new CFFOLDER(); + + folder.CabStartOffset = data.ReadUInt32(); + folder.DataCount = data.ReadUInt16(); + folder.CompressionType = (CompressionType)data.ReadUInt16(); + + if (header.FolderReservedSize > 0) + folder.ReservedData = data.ReadBytes(header.FolderReservedSize); + + if (folder.CabStartOffset > 0) + { + long currentPosition = data.Position; + data.Seek(folder.CabStartOffset, SeekOrigin.Begin); + + for (int i = 0; i < folder.DataCount; i++) + { + CFDATA dataBlock = CFDATA.Deserialize(data, header.DataReservedSize); + folder.DataBlocks[(int)folder.CabStartOffset] = dataBlock; + } + + data.Seek(currentPosition, SeekOrigin.Begin); + } + + return folder; + } + #endregion #region Public Functionality @@ -807,7 +910,7 @@ namespace BurnOutSharp.FileType #endregion } - internal enum CompressionType : ushort + public enum CompressionType : ushort { /// /// Mask for compression type. @@ -844,7 +947,7 @@ namespace BurnOutSharp.FileType /// continued from the previous cabinet will be first, and entries for files continued to the next cabinet /// will be last. /// - internal class CFFILE + public class CFFILE { #region Properties @@ -896,38 +999,12 @@ namespace BurnOutSharp.FileType /// CFFILE.szName field, but the _A_NAME_IS_UTF attribute is not set, the characters SHOULD be /// interpreted according to the current location. /// - public byte[] Name { get; private set; } + public string Name { get; private set; } #endregion #region Generated Properties - /// - /// Name value as a string (not null-terminated) - /// - public string NameAsString - { - get - { - // Perform sanity checks - if (Name == null || Name.Length == 0) - return null; - - // Attempt to respect the attribute flag for UTF-8 - if (Attributes.HasFlag(FileAttributes.NAME_IS_UTF)) - { - try - { - return Encoding.UTF8.GetString(Name).TrimEnd('\0'); - } - catch { } - } - - // Default case uses local encoding - return Encoding.Default.GetString(Name).TrimEnd('\0'); - } - } - /// /// Convert the internal values into a DateTime object, if possible /// @@ -935,17 +1012,28 @@ namespace BurnOutSharp.FileType { get { - // Date property - int year = (Date >> 9) + 1980; - int month = (Date >> 5) & 0x0F; - int day = Date & 0x1F; + // If we have an invalid DateTime + if (Date == 0 && Time == 0) + return DateTime.MinValue; - // Time property - int hour = Time >> 11; - int minute = (Time >> 5) & 0x3F; - int second = (Time << 1) & 0x3E; + try + { + // Date property + int year = (Date >> 9) + 1980; + int month = (Date >> 5) & 0x0F; + int day = Date & 0x1F; - return new DateTime(year, month, day, hour, minute, second); + // Time property + int hour = Time >> 11; + int minute = (Time >> 5) & 0x3F; + int second = (Time << 1) & 0x3E; + + return new DateTime(year, month, day, hour, minute, second); + } + catch + { + return DateTime.MinValue; + } } set { @@ -968,18 +1056,42 @@ namespace BurnOutSharp.FileType CFFILE file = new CFFILE(); - file.FileSize = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - file.FolderStartOffset = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - file.FolderIndex = (FolderIndex)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - file.Date = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - file.Time = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - file.Attributes = (FileAttributes)BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + file.FileSize = data.ReadUInt32(ref dataPtr); + file.FolderStartOffset = data.ReadUInt32(ref dataPtr); + file.FolderIndex = (FolderIndex)data.ReadUInt16(ref dataPtr); + file.Date = data.ReadUInt16(ref dataPtr); + file.Time = data.ReadUInt16(ref dataPtr); + file.Attributes = (FileAttributes)data.ReadUInt16(ref dataPtr); - byte[] name = MSCABCabinet.GetNullTerminatedString(data, ref dataPtr); - if (name == null) + if (file.Attributes.HasFlag(FileAttributes.NAME_IS_UTF)) + file.Name = data.ReadString(ref dataPtr, Encoding.Unicode); + else + file.Name = data.ReadString(ref dataPtr, Encoding.ASCII); + + return file; + } + + /// + /// Deserialize into a CFFILE object + /// + public static CFFILE Deserialize(Stream data) + { + if (data == null || data.Position < 0) return null; - file.Name = name; + CFFILE file = new CFFILE(); + + file.FileSize = data.ReadUInt32(); + file.FolderStartOffset = data.ReadUInt32(); + file.FolderIndex = (FolderIndex)data.ReadUInt16(); + file.Date = data.ReadUInt16(); + file.Time = data.ReadUInt16(); + file.Attributes = (FileAttributes)data.ReadUInt16(); + + if (file.Attributes.HasFlag(FileAttributes.NAME_IS_UTF)) + file.Name = data.ReadString(Encoding.Unicode); + else + file.Name = data.ReadString(Encoding.ASCII); return file; } @@ -998,7 +1110,7 @@ namespace BurnOutSharp.FileType Console.WriteLine($" FolderIndex: {FolderIndex} (0x{(ushort)FolderIndex:X4})"); Console.WriteLine($" DateTime: {DateAndTimeAsDateTime} (0x{Date:X4} 0x{Time:X4})"); Console.WriteLine($" Attributes: {Attributes} (0x{(ushort)Attributes:X4})"); - Console.WriteLine($" Name: {NameAsString}"); + Console.WriteLine($" Name: {Name}"); Console.WriteLine(); } @@ -1006,7 +1118,7 @@ namespace BurnOutSharp.FileType #endregion } - internal enum FolderIndex : ushort + public enum FolderIndex : ushort { /// /// A value of zero indicates that this is the @@ -1034,7 +1146,7 @@ namespace BurnOutSharp.FileType } [Flags] - internal enum FileAttributes : ushort + public enum FileAttributes : ushort { /// /// File is read-only. @@ -1073,7 +1185,7 @@ namespace BurnOutSharp.FileType /// field. Subsequent CFDATA structure records for this folder are /// contiguous. /// - internal class CFDATA + public class CFDATA { #region Properties @@ -1128,26 +1240,44 @@ namespace BurnOutSharp.FileType CFDATA dataBlock = new CFDATA(); - dataBlock.Checksum = BitConverter.ToUInt32(data, dataPtr); dataPtr += 4; - dataBlock.CompressedSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; - dataBlock.UncompressedSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + dataBlock.Checksum = data.ReadUInt32(ref dataPtr); + dataBlock.CompressedSize = data.ReadUInt16(ref dataPtr); + dataBlock.UncompressedSize = data.ReadUInt16(ref dataPtr); if (dataBlock.UncompressedSize != 0 && dataBlock.CompressedSize > dataBlock.UncompressedSize) return null; if (dataReservedSize > 0) - { - dataBlock.ReservedData = new byte[dataReservedSize]; - Array.Copy(data, dataPtr, dataBlock.ReservedData, 0, dataReservedSize); - dataPtr += dataReservedSize; - } + dataBlock.ReservedData = data.ReadBytes(ref dataPtr, dataReservedSize); if (dataBlock.CompressedSize > 0) - { - dataBlock.CompressedData = new byte[dataBlock.CompressedSize]; - Array.Copy(data, dataPtr, dataBlock.CompressedData, 0, dataBlock.CompressedSize); - dataPtr += dataBlock.CompressedSize; - } + dataBlock.CompressedData = data.ReadBytes(ref dataPtr, dataBlock.CompressedSize); + + return dataBlock; + } + + /// + /// Deserialize into a CFDATA object + /// + public static CFDATA Deserialize(Stream data, byte dataReservedSize = 0) + { + if (data == null || data.Position < 0) + return null; + + CFDATA dataBlock = new CFDATA(); + + dataBlock.Checksum = data.ReadUInt32(); + dataBlock.CompressedSize = data.ReadUInt16(); + dataBlock.UncompressedSize = data.ReadUInt16(); + + if (dataBlock.UncompressedSize != 0 && dataBlock.CompressedSize > dataBlock.UncompressedSize) + return null; + + if (dataReservedSize > 0) + dataBlock.ReservedData = data.ReadBytes(dataReservedSize); + + if (dataBlock.CompressedSize > 0) + dataBlock.CompressedData = data.ReadBytes(dataBlock.CompressedSize); return dataBlock; } @@ -1161,7 +1291,7 @@ namespace BurnOutSharp.FileType /// not supplied by the cabinet file creating application, the checksum field is set to 0 (zero). Cabinet /// extracting applications do not compute or verify the checksum if the field is set to 0 (zero). /// - internal static class Checksum + public static class Checksum { public static uint ChecksumData(byte[] data) { diff --git a/BurnOutSharp/Tools/Extensions.cs b/BurnOutSharp/Tools/Extensions.cs index ab8563c4..c5261c56 100644 --- a/BurnOutSharp/Tools/Extensions.cs +++ b/BurnOutSharp/Tools/Extensions.cs @@ -336,7 +336,7 @@ namespace BurnOutSharp.Tools List tempBuffer = new List(); byte[] buffer = new byte[charWidth]; - while (stream.Read(buffer, 0, charWidth) != 0 && buffer.SequenceEqual(nullTerminator)) + while (stream.Read(buffer, 0, charWidth) != 0 && !buffer.SequenceEqual(nullTerminator)) { tempBuffer.AddRange(buffer); } diff --git a/Test/Program.cs b/Test/Program.cs index 884dbba3..4edbf1be 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using BurnOutSharp; +using BurnOutSharp.FileType; using BurnOutSharp.Wrappers; using static BurnOutSharp.Builder.Extensions; @@ -80,7 +81,7 @@ namespace Test foreach (string inputPath in inputPaths) { if (info) - PrintExecutableInfo(inputPath); + PrintPathInfo(inputPath); else GetAndWriteProtections(scanner, inputPath); } @@ -177,10 +178,10 @@ namespace Test #region Printing /// - /// Wrapper to print executable information for a single path + /// Wrapper to print information for a single path /// /// File or directory path - private static void PrintExecutableInfo(string path) + private static void PrintPathInfo(string path) { Console.WriteLine($"Checking possible path: {path}"); @@ -210,93 +211,128 @@ namespace Test using (Stream stream = File.OpenRead(file)) { // Read the first 4 bytes - byte[] magic = stream.ReadBytes(2); - - if (!IsMSDOS(magic)) - { - Console.WriteLine("Not a recognized executable format, skipping..."); - Console.WriteLine(); - return; - } - - // Build the executable information - Console.WriteLine("Creating MS-DOS executable builder"); - Console.WriteLine(); - + byte[] magic = stream.ReadBytes(4); stream.Seek(0, SeekOrigin.Begin); - var msdos = MSDOS.Create(stream); - if (msdos == null) + + // MS-DOS executable and decendents + if (IsMSDOS(magic)) { - Console.WriteLine("Something went wrong parsing MS-DOS executable"); + // Build the executable information + Console.WriteLine("Creating MS-DOS executable builder"); Console.WriteLine(); - return; - } - // Print the executable info to screen - msdos.Print(); - - // Check for a valid new executable address - if (msdos.NewExeHeaderAddr >= stream.Length) - { - Console.WriteLine("New EXE header address invalid, skipping additional reading..."); - Console.WriteLine(); - return; - } - - // Try to read the executable info - stream.Seek(msdos.NewExeHeaderAddr, SeekOrigin.Begin); - magic = stream.ReadBytes(4); - - // New Executable - if (IsNE(magic)) - { - stream.Seek(0, SeekOrigin.Begin); - var newExecutable = NewExecutable.Create(stream); - if (newExecutable == null) + var msdos = MSDOS.Create(stream); + if (msdos == null) { - Console.WriteLine("Something went wrong parsing New Executable"); + Console.WriteLine("Something went wrong parsing MS-DOS executable"); Console.WriteLine(); return; } // Print the executable info to screen - newExecutable.Print(); - } + msdos.Print(); - // Linear Executable - else if (IsLE(magic)) - { - Console.WriteLine($"Linear executable found. No parsing currently available."); - Console.WriteLine(); - return; - } - - // Portable Executable - else if (IsPE(magic)) - { - stream.Seek(0, SeekOrigin.Begin); - var portableExecutable = PortableExecutable.Create(stream); - if (portableExecutable == null) + // Check for a valid new executable address + if (msdos.NewExeHeaderAddr >= stream.Length) { - Console.WriteLine("Something went wrong parsing Portable Executable"); + Console.WriteLine("New EXE header address invalid, skipping additional reading..."); Console.WriteLine(); return; } - // Print the executable info to screen - portableExecutable.Print(); + // Try to read the executable info + stream.Seek(msdos.NewExeHeaderAddr, SeekOrigin.Begin); + magic = stream.ReadBytes(4); + + // New Executable + if (IsNE(magic)) + { + stream.Seek(0, SeekOrigin.Begin); + var newExecutable = NewExecutable.Create(stream); + if (newExecutable == null) + { + Console.WriteLine("Something went wrong parsing New Executable"); + Console.WriteLine(); + return; + } + + // Print the executable info to screen + newExecutable.Print(); + } + + // Linear Executable + else if (IsLE(magic)) + { + Console.WriteLine($"Linear executable found. No parsing currently available."); + Console.WriteLine(); + return; + } + + // Portable Executable + else if (IsPE(magic)) + { + stream.Seek(0, SeekOrigin.Begin); + var portableExecutable = PortableExecutable.Create(stream); + if (portableExecutable == null) + { + Console.WriteLine("Something went wrong parsing Portable Executable"); + Console.WriteLine(); + return; + } + + // Print the executable info to screen + portableExecutable.Print(); + } + + // Unknown + else + { + Console.WriteLine($"Unrecognized header signature: {BitConverter.ToString(magic).Replace("-", string.Empty)}"); + Console.WriteLine(); + return; + } } - // Unknown + // MS-CAB archive + else if (IsMSCAB(magic)) + { + // Build the cabinet information + Console.WriteLine("Creating MS-CAB deserializer"); + Console.WriteLine(); + + var cabinet = MSCABCabinet.Deserialize(stream); + if (cabinet == null) + { + Console.WriteLine("Something went wrong parsing MS-CAB archive"); + Console.WriteLine(); + return; + } + + // Print the cabinet info to screen + cabinet.PrintInfo(); + } + + // Everything else else { - Console.WriteLine($"Unrecognized header signature: {BitConverter.ToString(magic).Replace("-", string.Empty)}"); + Console.WriteLine("Not a recognized file format, skipping..."); Console.WriteLine(); return; } } } + /// + /// Determine if the magic bytes indicate an MS-CAB archive + /// + private static bool IsMSCAB(byte[] magic) + { + if (magic == null || magic.Length < 4) + return false; + + return magic[0] == 'M' && magic[1] == 'S' && magic[2] == 'C' && magic[3] == 'F'; + } + /// /// Determine if the magic bytes indicate an MS-DOS executable ///