mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-09-22 06:45:03 +00:00
Streamline MS-CAB reading, add to info printing
This commit is contained in:
@@ -10,7 +10,7 @@ namespace BurnOutSharp.FileType
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> into a MSCABCabinet object
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Get a null-terminated string as a byte array from input data
|
||||
/// </summary>
|
||||
internal static byte[] GetNullTerminatedString(byte[] data, ref int dataPtr)
|
||||
{
|
||||
int nullIndex = Array.IndexOf<byte>(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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CFHEADER structure shown in the following packet diagram provides information about this
|
||||
/// cabinet (.cab) file.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
public byte[] CabinetPrev { get; private set; }
|
||||
public string CabinetPrev { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public byte[] DiskPrev { get; private set; }
|
||||
public string DiskPrev { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public byte[] CabinetNext { get; private set; }
|
||||
public string CabinetNext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> into a CFHEADER object
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal class CFFOLDER
|
||||
public class CFFOLDER
|
||||
{
|
||||
#region Properties
|
||||
|
||||
@@ -755,23 +828,19 @@ namespace BurnOutSharp.FileType
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> at <paramref name="dataPtr"/> into a CFFOLDER object
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> into a CFFOLDER object
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
public byte[] Name { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generated Properties
|
||||
|
||||
/// <summary>
|
||||
/// Name value as a string (not null-terminated)
|
||||
/// </summary>
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the internal values into a DateTime object, if possible
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> into a CFFILE object
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// File is read-only.
|
||||
@@ -1073,7 +1185,7 @@ namespace BurnOutSharp.FileType
|
||||
/// <see cref="CFFOLDER.CabStartOffset"/> field. Subsequent CFDATA structure records for this folder are
|
||||
/// contiguous.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize <paramref name="data"/> into a CFDATA object
|
||||
/// </summary>
|
||||
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).
|
||||
/// </summary>
|
||||
internal static class Checksum
|
||||
public static class Checksum
|
||||
{
|
||||
public static uint ChecksumData(byte[] data)
|
||||
{
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace BurnOutSharp.Tools
|
||||
List<byte> tempBuffer = new List<byte>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
164
Test/Program.cs
164
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
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to print executable information for a single path
|
||||
/// Wrapper to print information for a single path
|
||||
/// </summary>
|
||||
/// <param name="path">File or directory path</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the magic bytes indicate an MS-CAB archive
|
||||
/// </summary>
|
||||
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';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the magic bytes indicate an MS-DOS executable
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user