mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-07-19 07:15:10 +00:00
Migrate WIP MS-CAB to model/builder/wrapper
This commit is contained in:
585
BurnOutSharp.Builder/MicrosoftCabinet.cs
Normal file
585
BurnOutSharp.Builder/MicrosoftCabinet.cs
Normal file
@@ -0,0 +1,585 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BurnOutSharp.Models.MicrosoftCabinet;
|
||||
|
||||
namespace BurnOutSharp.Builder
|
||||
{
|
||||
// TODO: Add multi-cabinet reading
|
||||
// TODO: Make Stream Data rely on Byte Data
|
||||
public class MicrosoftCabinet
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable signature
|
||||
/// </summary>
|
||||
public static readonly string SignatureString = "MSCF";
|
||||
|
||||
/// <summary>
|
||||
/// Signature as an unsigned Int32 value
|
||||
/// </summary>
|
||||
public const uint SignatureValue = 0x4643534D;
|
||||
|
||||
/// <summary>
|
||||
/// Signature as a byte array
|
||||
/// </summary>
|
||||
public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x53, 0x43, 0x46 };
|
||||
|
||||
/// <summary>
|
||||
/// A maximum uncompressed size of an input file to store in CAB
|
||||
/// </summary>
|
||||
public const uint MaximumUncompressedFileSize = 0x7FFF8000;
|
||||
|
||||
/// <summary>
|
||||
/// A maximum file COUNT
|
||||
/// </summary>
|
||||
public const ushort MaximumFileCount = 0xFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// A maximum size of a created CAB (compressed)
|
||||
/// </summary>
|
||||
public const uint MaximumCabSize = 0x7FFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// A maximum CAB-folder COUNT
|
||||
/// </summary>
|
||||
public const ushort MaximumFolderCount = 0xFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// A maximum uncompressed data size in a CAB-folder
|
||||
/// </summary>
|
||||
public const uint MaximumUncompressedFolderSize = 0x7FFF8000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Byte Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a Microsoft Cabinet file
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled cabinet on success, null on error</returns>
|
||||
public static Cabinet ParseCabinet(byte[] data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = offset;
|
||||
|
||||
// Create a new cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Cabinet Header
|
||||
|
||||
// Try to parse the cabinet header
|
||||
var cabinetHeader = ParseCabinetHeader(data, ref offset);
|
||||
if (cabinetHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the cabinet header
|
||||
cabinet.Header = cabinetHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folders
|
||||
|
||||
// Set the folder array
|
||||
cabinet.Folders = new CFFOLDER[cabinetHeader.FolderCount];
|
||||
|
||||
// Try to parse each folder, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FolderCount; i++)
|
||||
{
|
||||
var folder = ParseFolder(data, ref offset, cabinetHeader, initialOffset);
|
||||
if (folder == null)
|
||||
return null;
|
||||
|
||||
// Set the folder
|
||||
cabinet.Folders[i] = folder;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// Get the files offset
|
||||
int filesOffset = (int)cabinetHeader.FilesOffset + initialOffset;
|
||||
if (filesOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Set the file array
|
||||
cabinet.Files = new CFFILE[cabinetHeader.FileCount];
|
||||
|
||||
// Try to parse each file, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FileCount; i++)
|
||||
{
|
||||
var file = ParseFile(data, ref filesOffset);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
// Set the file
|
||||
cabinet.Files[i] = file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a cabinet header
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled cabinet header on success, null on error</returns>
|
||||
private static CFHEADER ParseCabinetHeader(byte[] data, ref int offset)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
CFHEADER header = new CFHEADER();
|
||||
|
||||
header.Signature = data.ReadUInt32(ref offset);
|
||||
if (header.Signature != SignatureValue)
|
||||
return null;
|
||||
|
||||
header.Reserved1 = data.ReadUInt32(ref offset);
|
||||
if (header.Reserved1 != 0x00000000)
|
||||
return null;
|
||||
|
||||
header.CabinetSize = data.ReadUInt32(ref offset);
|
||||
if (header.CabinetSize > MaximumCabSize)
|
||||
return null;
|
||||
|
||||
header.Reserved2 = data.ReadUInt32(ref offset);
|
||||
if (header.Reserved2 != 0x00000000)
|
||||
return null;
|
||||
|
||||
header.FilesOffset = data.ReadUInt32(ref offset);
|
||||
|
||||
header.Reserved3 = data.ReadUInt32(ref offset);
|
||||
if (header.Reserved3 != 0x00000000)
|
||||
return null;
|
||||
|
||||
header.VersionMinor = data.ReadByte(ref offset);
|
||||
header.VersionMajor = data.ReadByte(ref offset);
|
||||
if (header.VersionMajor != 0x00000001 || header.VersionMinor != 0x00000003)
|
||||
return null;
|
||||
|
||||
header.FolderCount = data.ReadUInt16(ref offset);
|
||||
if (header.FolderCount > MaximumFolderCount)
|
||||
return null;
|
||||
|
||||
header.FileCount = data.ReadUInt16(ref offset);
|
||||
if (header.FileCount > MaximumFileCount)
|
||||
return null;
|
||||
|
||||
header.Flags = (HeaderFlags)data.ReadUInt16(ref offset);
|
||||
header.SetID = data.ReadUInt16(ref offset);
|
||||
header.CabinetIndex = data.ReadUInt16(ref offset);
|
||||
|
||||
if (header.Flags.HasFlag(HeaderFlags.RESERVE_PRESENT))
|
||||
{
|
||||
header.HeaderReservedSize = data.ReadUInt16(ref offset);
|
||||
if (header.HeaderReservedSize > 60_000)
|
||||
return null;
|
||||
|
||||
header.FolderReservedSize = data.ReadByte(ref offset);
|
||||
header.DataReservedSize = data.ReadByte(ref offset);
|
||||
|
||||
if (header.HeaderReservedSize > 0)
|
||||
header.ReservedData = data.ReadBytes(ref offset, header.HeaderReservedSize);
|
||||
}
|
||||
|
||||
if (header.Flags.HasFlag(HeaderFlags.PREV_CABINET))
|
||||
{
|
||||
header.CabinetPrev = data.ReadString(ref offset, Encoding.ASCII);
|
||||
header.DiskPrev = data.ReadString(ref offset, Encoding.ASCII);
|
||||
}
|
||||
|
||||
if (header.Flags.HasFlag(HeaderFlags.NEXT_CABINET))
|
||||
{
|
||||
header.CabinetNext = data.ReadString(ref offset, Encoding.ASCII);
|
||||
header.DiskNext = data.ReadString(ref offset, Encoding.ASCII);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a folder
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <param name="header">Cabinet header to get flags and sizes from</param>
|
||||
/// <param name="initialOffset">Initial offset for calculations</param>
|
||||
/// <returns>Filled folder on success, null on error</returns>
|
||||
private static CFFOLDER ParseFolder(byte[] data, ref int offset, CFHEADER header, int initialOffset)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
CFFOLDER folder = new CFFOLDER();
|
||||
|
||||
folder.CabStartOffset = data.ReadUInt32(ref offset);
|
||||
folder.DataCount = data.ReadUInt16(ref offset);
|
||||
folder.CompressionType = (CompressionType)data.ReadUInt16(ref offset);
|
||||
|
||||
if (header.FolderReservedSize > 0)
|
||||
folder.ReservedData = data.ReadBytes(ref offset, header.FolderReservedSize);
|
||||
|
||||
if (folder.CabStartOffset > 0)
|
||||
{
|
||||
int blockPtr = initialOffset + (int)folder.CabStartOffset;
|
||||
for (int i = 0; i < folder.DataCount; i++)
|
||||
{
|
||||
int dataStart = blockPtr;
|
||||
CFDATA dataBlock = ParseDataBlock(data, ref blockPtr, header.DataReservedSize);
|
||||
folder.DataBlocks[dataStart] = dataBlock;
|
||||
}
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a data block
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <param name="dataReservedSize">Reserved byte size for data blocks</param>
|
||||
/// <returns>Filled folder on success, null on error</returns>
|
||||
private static CFDATA ParseDataBlock(byte[] data, ref int offset, byte dataReservedSize)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
CFDATA dataBlock = new CFDATA();
|
||||
|
||||
dataBlock.Checksum = data.ReadUInt32(ref offset);
|
||||
dataBlock.CompressedSize = data.ReadUInt16(ref offset);
|
||||
dataBlock.UncompressedSize = data.ReadUInt16(ref offset);
|
||||
|
||||
if (dataBlock.UncompressedSize != 0 && dataBlock.CompressedSize > dataBlock.UncompressedSize)
|
||||
return null;
|
||||
|
||||
if (dataReservedSize > 0)
|
||||
dataBlock.ReservedData = data.ReadBytes(ref offset, dataReservedSize);
|
||||
|
||||
if (dataBlock.CompressedSize > 0)
|
||||
dataBlock.CompressedData = data.ReadBytes(ref offset, dataBlock.CompressedSize);
|
||||
|
||||
return dataBlock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a byte array into a file
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to parse</param>
|
||||
/// <param name="offset">Offset into the byte array</param>
|
||||
/// <returns>Filled file on success, null on error</returns>
|
||||
private static CFFILE ParseFile(byte[] data, ref int offset)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
CFFILE file = new CFFILE();
|
||||
|
||||
file.FileSize = data.ReadUInt32(ref offset);
|
||||
file.FolderStartOffset = data.ReadUInt32(ref offset);
|
||||
file.FolderIndex = (FolderIndex)data.ReadUInt16(ref offset);
|
||||
file.Date = data.ReadUInt16(ref offset);
|
||||
file.Time = data.ReadUInt16(ref offset);
|
||||
file.Attributes = (Models.MicrosoftCabinet.FileAttributes)data.ReadUInt16(ref offset);
|
||||
|
||||
if (file.Attributes.HasFlag(Models.MicrosoftCabinet.FileAttributes.NAME_IS_UTF))
|
||||
file.Name = data.ReadString(ref offset, Encoding.Unicode);
|
||||
else
|
||||
file.Name = data.ReadString(ref offset, Encoding.ASCII);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stream Data
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Microsoft Cabinet file
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cabinet on success, null on error</returns>
|
||||
public static Cabinet ParseCabinet(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Cabinet Header
|
||||
|
||||
// Try to parse the cabinet header
|
||||
var cabinetHeader = ParseCabinetHeader(data);
|
||||
if (cabinetHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the cabinet header
|
||||
cabinet.Header = cabinetHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folders
|
||||
|
||||
// Set the folder array
|
||||
cabinet.Folders = new CFFOLDER[cabinetHeader.FolderCount];
|
||||
|
||||
// Try to parse each folder, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FolderCount; i++)
|
||||
{
|
||||
var folder = ParseFolder(data, cabinetHeader);
|
||||
if (folder == null)
|
||||
return null;
|
||||
|
||||
// Set the folder
|
||||
cabinet.Folders[i] = folder;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// Get the files offset
|
||||
int filesOffset = (int)cabinetHeader.FilesOffset + initialOffset;
|
||||
if (filesOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the offset
|
||||
data.Seek(filesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Set the file array
|
||||
cabinet.Files = new CFFILE[cabinetHeader.FileCount];
|
||||
|
||||
// Try to parse each file, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FileCount; i++)
|
||||
{
|
||||
var file = ParseFile(data);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
// Set the file
|
||||
cabinet.Files[i] = file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a cabinet header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled cabinet header on success, null on error</returns>
|
||||
private static CFHEADER ParseCabinetHeader(Stream data)
|
||||
{
|
||||
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 > 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 > MaximumFolderCount)
|
||||
return null;
|
||||
|
||||
header.FileCount = data.ReadUInt16();
|
||||
if (header.FileCount > 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.FolderReservedSize = data.ReadByteValue();
|
||||
header.DataReservedSize = data.ReadByteValue();
|
||||
|
||||
if (header.HeaderReservedSize > 0)
|
||||
header.ReservedData = data.ReadBytes(header.HeaderReservedSize);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a folder
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="header">Cabinet header to get flags and sizes from</param>
|
||||
/// <returns>Filled folder on success, null on error</returns>
|
||||
private static CFFOLDER ParseFolder(Stream data, CFHEADER header)
|
||||
{
|
||||
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 = ParseDataBlock(data, header.DataReservedSize);
|
||||
folder.DataBlocks[(int)folder.CabStartOffset] = dataBlock;
|
||||
}
|
||||
|
||||
data.Seek(currentPosition, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a data block
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="dataReservedSize">Reserved byte size for data blocks</param>
|
||||
/// <returns>Filled folder on success, null on error</returns>
|
||||
private static CFDATA ParseDataBlock(Stream data, byte dataReservedSize)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a file
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled file on success, null on error</returns>
|
||||
private static CFFILE ParseFile(Stream data)
|
||||
{
|
||||
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 = (Models.MicrosoftCabinet.FileAttributes)data.ReadUInt16();
|
||||
|
||||
if (file.Attributes.HasFlag(Models.MicrosoftCabinet.FileAttributes.NAME_IS_UTF))
|
||||
file.Name = data.ReadString(Encoding.Unicode);
|
||||
else
|
||||
file.Name = data.ReadString(Encoding.ASCII);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// The computation and verification of checksums found in CFDATA structure entries cabinet files is
|
||||
/// done by using a function described by the following mathematical notation. When checksums are
|
||||
/// 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>
|
||||
public static class Checksum
|
||||
{
|
||||
public static uint ChecksumData(byte[] data)
|
||||
{
|
||||
uint[] C = new uint[4]
|
||||
{
|
||||
S(data, 1, data.Length),
|
||||
S(data, 2, data.Length),
|
||||
S(data, 3, data.Length),
|
||||
S(data, 4, data.Length),
|
||||
};
|
||||
|
||||
return C[0] ^ C[1] ^ C[2] ^ C[3];
|
||||
}
|
||||
|
||||
private static uint S(byte[] a, int b, int x)
|
||||
{
|
||||
int n = a.Length;
|
||||
|
||||
if (x < 4 && b > n % 4)
|
||||
return 0;
|
||||
else if (x < 4 && b <= n % 4)
|
||||
return a[n - b + 1];
|
||||
else // if (x >= 4)
|
||||
return a[n - x + b] ^ S(a, b, x - 4);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
52
BurnOutSharp.Models/MicrosoftCabinet/CFDATA.cs
Normal file
52
BurnOutSharp.Models/MicrosoftCabinet/CFDATA.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// Each CFDATA structure describes some amount of compressed data, as shown in the following
|
||||
/// packet diagram. The first CFDATA structure entry for each folder is located by using the
|
||||
/// <see cref="CFFOLDER.CabStartOffset"/> field. Subsequent CFDATA structure records for this folder are
|
||||
/// contiguous.
|
||||
/// </summary>
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class CFDATA
|
||||
{
|
||||
/// <summary>
|
||||
/// Checksum of this CFDATA structure, from the <see cref="CompressedSize"/> through the
|
||||
/// <see cref="CompressedData"/> fields. It can be set to 0 (zero) if the checksum is not supplied.
|
||||
/// </summary>
|
||||
public uint Checksum;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes of compressed data in this CFDATA structure record. When the
|
||||
/// <see cref="UncompressedSize"/> field is zero, this field indicates only the number of bytes that fit into this cabinet file.
|
||||
/// </summary>
|
||||
public ushort CompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// The uncompressed size of the data in this CFDATA structure entry in bytes. When this
|
||||
/// CFDATA structure entry is continued in the next cabinet file, the <see cref="UncompressedSize"/> field will be zero, and
|
||||
/// the <see cref="UncompressedSize"/> field in the first CFDATA structure entry in the next cabinet file will report the total
|
||||
/// uncompressed size of the data from both CFDATA structure blocks.
|
||||
/// </summary>
|
||||
public ushort UncompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="HeaderFlags.RESERVE_PRESENT"/> flag is set
|
||||
/// and the <see cref="CFHEADER.DataReservedSize"/> field value is non-zero, this field contains per-datablock application information.
|
||||
/// This field is defined by the application, and it is used for application-defined purposes.
|
||||
/// </summary>
|
||||
public byte[] ReservedData;
|
||||
|
||||
/// <summary>
|
||||
/// The compressed data bytes, compressed by using the <see cref="CFFOLDER.CompressionType"/>
|
||||
/// method. When the <see cref="UncompressedSize"/> field value is zero, these data bytes MUST be combined with the data
|
||||
/// bytes from the next cabinet's first CFDATA structure entry before decompression. When the
|
||||
///<see cref="CFFOLDER.CompressionType"/> field indicates that the data is not compressed, this field contains the
|
||||
/// uncompressed data bytes. In this case, the <see cref="CompressedSize"/> and <see cref="UncompressedSize"/> field values will be equal unless
|
||||
/// this CFDATA structure entry crosses a cabinet file boundary.
|
||||
/// </summary>
|
||||
public byte[] CompressedData;
|
||||
}
|
||||
}
|
||||
68
BurnOutSharp.Models/MicrosoftCabinet/CFFILE.cs
Normal file
68
BurnOutSharp.Models/MicrosoftCabinet/CFFILE.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// Each CFFILE structure contains information about one of the files stored (or at least partially
|
||||
/// stored) in this cabinet, as shown in the following packet diagram.The first CFFILE structure entry in
|
||||
/// each cabinet is found at the absolute offset CFHEADER.coffFiles field. CFHEADER.cFiles field
|
||||
/// indicates how many of these entries are in the cabinet. The CFFILE structure entries in a cabinet
|
||||
/// are ordered by iFolder field value, and then by the uoffFolderStart field value.Entries for files
|
||||
/// continued from the previous cabinet will be first, and entries for files continued to the next cabinet
|
||||
/// will be last.
|
||||
/// </summary>
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class CFFILE
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the uncompressed size of this file, in bytes.
|
||||
/// </summary>
|
||||
public uint FileSize;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the uncompressed offset, in bytes, of the start of this file's data. For the
|
||||
/// first file in each folder, this value will usually be zero. Subsequent files in the folder will have offsets
|
||||
/// that are typically the running sum of the cbFile field values.
|
||||
/// </summary>
|
||||
public uint FolderStartOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the folder that contains this file's data.
|
||||
/// </summary>
|
||||
public FolderIndex FolderIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Date of this file, in the format ((year–1980) << 9)+(month << 5)+(day), where
|
||||
/// month={1..12} and day = { 1..31 }. This "date" is typically considered the "last modified" date in local
|
||||
/// time, but the actual definition is application-defined.
|
||||
/// </summary>
|
||||
public ushort Date;
|
||||
|
||||
/// <summary>
|
||||
/// Time of this file, in the format (hour << 11)+(minute << 5)+(seconds/2), where
|
||||
/// hour={0..23}. This "time" is typically considered the "last modified" time in local time, but the
|
||||
/// actual definition is application-defined.
|
||||
/// </summary>
|
||||
public ushort Time;
|
||||
|
||||
/// <summary>
|
||||
/// Attributes of this file; can be used in any combination.
|
||||
/// </summary>
|
||||
public FileAttributes Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// The null-terminated name of this file. Note that this string can include path
|
||||
/// separator characters.The string can contain up to 256 bytes, plus the null byte. When the
|
||||
/// _A_NAME_IS_UTF attribute is set, this string can be converted directly to Unicode, avoiding
|
||||
/// locale-specific dependencies. When the _A_NAME_IS_UTF attribute is not set, this string is subject
|
||||
/// to interpretation depending on locale. When a string that contains Unicode characters larger than
|
||||
/// 0x007F is encoded in the szName field, the _A_NAME_IS_UTF attribute SHOULD be included in
|
||||
/// the file's attributes. When no characters larger than 0x007F are in the name, the
|
||||
/// _A_NAME_IS_UTF attribute SHOULD NOT be set. If byte values larger than 0x7F are found in
|
||||
/// 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 string Name;
|
||||
}
|
||||
}
|
||||
60
BurnOutSharp.Models/MicrosoftCabinet/CFFOLDER.cs
Normal file
60
BurnOutSharp.Models/MicrosoftCabinet/CFFOLDER.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// Each CFFOLDER structure contains information about one of the folders or partial folders stored in
|
||||
/// this cabinet file, as shown in the following packet diagram.The first CFFOLDER structure entry
|
||||
/// immediately follows the CFHEADER structure entry. The CFHEADER.cFolders field indicates how
|
||||
/// many CFFOLDER structure entries are present.
|
||||
///
|
||||
/// Folders can start in one cabinet, and continue on to one or more succeeding cabinets. When the
|
||||
/// cabinet file creator detects that a folder has been continued into another cabinet, it will complete
|
||||
/// that folder as soon as the current file has been completely compressed.Any additional files will be
|
||||
/// placed in the next folder.Generally, this means that a folder would span at most two cabinets, but it
|
||||
/// could span more than two cabinets if the file is large enough.
|
||||
///
|
||||
/// CFFOLDER structure entries actually refer to folder fragments, not necessarily complete folders. A
|
||||
/// CFFOLDER structure is the beginning of a folder if the iFolder field value in the first file that
|
||||
/// references the folder does not indicate that the folder is continued from the previous cabinet file.
|
||||
///
|
||||
/// The typeCompress field can vary from one folder to the next, unless the folder is continued from a
|
||||
/// previous cabinet file.
|
||||
/// </summary>
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class CFFOLDER
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the absolute file offset of the first CFDATA field block for the folder.
|
||||
/// </summary>
|
||||
public uint CabStartOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of CFDATA structures for this folder that are actually in this cabinet.
|
||||
/// A folder can continue into another cabinet and have more CFDATA structure blocks in that cabinet
|
||||
/// file.A folder can start in a previous cabinet.This number represents only the CFDATA structures for
|
||||
/// this folder that are at least partially recorded in this cabinet.
|
||||
/// </summary>
|
||||
public ushort DataCount;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the compression method used for all CFDATA structure entries in this
|
||||
/// folder.
|
||||
/// </summary>
|
||||
public CompressionType CompressionType;
|
||||
|
||||
/// <summary>
|
||||
/// If the CFHEADER.flags.cfhdrRESERVE_PRESENT field is set
|
||||
/// and the cbCFFolder field is non-zero, then this field contains per-folder application information.
|
||||
/// This field is defined by the application, and is used for application-defined purposes.
|
||||
/// </summary>
|
||||
public byte[] ReservedData;
|
||||
|
||||
/// <summary>
|
||||
/// Data blocks associated with this folder
|
||||
/// </summary>
|
||||
public Dictionary<int, CFDATA> DataBlocks;
|
||||
}
|
||||
}
|
||||
153
BurnOutSharp.Models/MicrosoftCabinet/CFHEADER.cs
Normal file
153
BurnOutSharp.Models/MicrosoftCabinet/CFHEADER.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// The CFHEADER structure shown in the following packet diagram provides information about this
|
||||
/// cabinet (.cab) file.
|
||||
/// </summary>
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class CFHEADER
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the characters "M", "S", "C", and "F" (bytes 0x4D, 0x53, 0x43,
|
||||
/// 0x46). This field is used to ensure that the file is a cabinet (.cab) file.
|
||||
/// </summary>
|
||||
public uint Signature;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved field; MUST be set to 0 (zero).
|
||||
/// </summary>
|
||||
public uint Reserved1;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the total size of the cabinet file, in bytes.
|
||||
/// </summary>
|
||||
public uint CabinetSize;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved field; MUST be set to 0 (zero).
|
||||
/// </summary>
|
||||
public uint Reserved2;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the absolute file offset, in bytes, of the first CFFILE field entry.
|
||||
/// </summary>
|
||||
public uint FilesOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved field; MUST be set to 0 (zero).
|
||||
/// </summary>
|
||||
public uint Reserved3;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the minor cabinet file format version. This value MUST be set to 3 (three).
|
||||
/// </summary>
|
||||
public byte VersionMinor;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the major cabinet file format version. This value MUST be set to 1 (one).
|
||||
/// </summary>
|
||||
public byte VersionMajor;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of CFFOLDER field entries in this cabinet file.
|
||||
/// </summary>
|
||||
public ushort FolderCount;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of CFFILE field entries in this cabinet file.
|
||||
/// </summary>
|
||||
public ushort FileCount;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies bit-mapped values that indicate the presence of optional data.
|
||||
/// </summary>
|
||||
public HeaderFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an arbitrarily derived (random) value that binds a collection of linked cabinet files
|
||||
/// together.All cabinet files in a set will contain the same setID field value.This field is used by
|
||||
/// cabinet file extractors to ensure that cabinet files are not inadvertently mixed.This value has no
|
||||
/// meaning in a cabinet file that is not in a set.
|
||||
/// </summary>
|
||||
public ushort SetID;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the sequential number of this cabinet in a multicabinet set. The first cabinet has
|
||||
/// iCabinet=0. This field, along with the setID field, is used by cabinet file extractors to ensure that
|
||||
/// this cabinet is the correct continuation cabinet when spanning cabinet files.
|
||||
/// </summary>
|
||||
public ushort CabinetIndex;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrRESERVE_PRESENT field is not set, this field is not
|
||||
/// present, and the value of cbCFHeader field MUST be zero.Indicates the size, in bytes, of the
|
||||
/// abReserve field in this CFHEADER structure.Values for cbCFHeader field MUST be between 0-
|
||||
/// 60,000.
|
||||
/// </summary>
|
||||
public ushort HeaderReservedSize;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrRESERVE_PRESENT field is not set, this field is not
|
||||
/// present, and the value of cbCFFolder field MUST be zero.Indicates the size, in bytes, of the
|
||||
/// abReserve field in each CFFOLDER field entry.Values for fhe cbCFFolder field MUST be between
|
||||
/// 0-255.
|
||||
/// </summary>
|
||||
public byte FolderReservedSize;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrRESERVE_PRESENT field is not set, this field is not
|
||||
/// present, and the value for the cbCFDATA field MUST be zero.The cbCFDATA field indicates the
|
||||
/// size, in bytes, of the abReserve field in each CFDATA field entry. Values for the cbCFDATA field
|
||||
/// MUST be between 0 - 255.
|
||||
/// </summary>
|
||||
public byte DataReservedSize;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrRESERVE_PRESENT field is set and the
|
||||
/// cbCFHeader field is non-zero, this field contains per-cabinet-file application information. This field
|
||||
/// is defined by the application, and is used for application-defined purposes.
|
||||
/// </summary>
|
||||
public byte[] ReservedData;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrPREV_CABINET field is not set, this
|
||||
/// field is not present.This is a null-terminated ASCII string that contains the file name of the
|
||||
/// logically previous cabinet file. The string can contain up to 255 bytes, plus the null byte. Note that
|
||||
/// this gives the name of the most recently preceding cabinet file that contains the initial instance of a
|
||||
/// file entry.This might not be the immediately previous cabinet file, when the most recent file spans
|
||||
/// multiple cabinet files.If searching in reverse for a specific file entry, or trying to extract a file that is
|
||||
/// reported to begin in the "previous cabinet," the szCabinetPrev field would indicate the name of the
|
||||
/// cabinet to examine.
|
||||
/// </summary>
|
||||
public string CabinetPrev;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrPREV_CABINET field is not set, then this
|
||||
/// field is not present.This is a null-terminated ASCII string that contains a descriptive name for the
|
||||
/// media that contains the file named in the szCabinetPrev field, such as the text on the disk label.
|
||||
/// 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 string DiskPrev;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrNEXT_CABINET field is not set, this
|
||||
/// field is not present.This is a null-terminated ASCII string that contains the file name of the next
|
||||
/// 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 string CabinetNext;
|
||||
|
||||
/// <summary>
|
||||
/// If the flags.cfhdrNEXT_CABINET field is not set, this field is
|
||||
/// not present.This is a null-terminated ASCII string that contains a descriptive name for the media
|
||||
/// that contains the file named in the szCabinetNext field, such as the text on the disk label. The
|
||||
/// 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 string DiskNext;
|
||||
}
|
||||
}
|
||||
27
BurnOutSharp.Models/MicrosoftCabinet/Cabinet.cs
Normal file
27
BurnOutSharp.Models/MicrosoftCabinet/Cabinet.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// Cabinet files are compressed packages containing a
|
||||
/// number of related files.The format of a cabinet file is optimized for maximum compression. Cabinet
|
||||
/// files support a number of compression formats, including MSZIP, LZX, or uncompressed. This
|
||||
/// document does not specify these internal compression formats.
|
||||
/// </summary>
|
||||
/// <see href="http://download.microsoft.com/download/5/0/1/501ED102-E53F-4CE0-AA6B-B0F93629DDC6/Exchange/%5BMS-CAB%5D.pdf"/>
|
||||
public class Cabinet
|
||||
{
|
||||
/// <summary>
|
||||
/// Cabinet header
|
||||
/// </summary>
|
||||
public CFHEADER Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// One or more CFFOLDER entries
|
||||
/// </summary>
|
||||
public CFFOLDER[] Folders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A series of one or more cabinet file (CFFILE) entries
|
||||
/// </summary>
|
||||
public CFFILE[] Files { get; set; }
|
||||
}
|
||||
}
|
||||
118
BurnOutSharp.Models/MicrosoftCabinet/Enums.cs
Normal file
118
BurnOutSharp.Models/MicrosoftCabinet/Enums.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
|
||||
namespace BurnOutSharp.Models.MicrosoftCabinet
|
||||
{
|
||||
public enum CompressionType : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Mask for compression type.
|
||||
/// </summary>
|
||||
MASK_TYPE = 0x000F,
|
||||
|
||||
/// <summary>
|
||||
/// No compression.
|
||||
/// </summary>
|
||||
TYPE_NONE = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// MSZIP compression.
|
||||
/// </summary>
|
||||
TYPE_MSZIP = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Quantum compression.
|
||||
/// </summary>
|
||||
TYPE_QUANTUM = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// LZX compression.
|
||||
/// </summary>
|
||||
TYPE_LZX = 0x0003,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FileAttributes : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// File is read-only.
|
||||
/// </summary>
|
||||
RDONLY = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// File is hidden.
|
||||
/// </summary>
|
||||
HIDDEN = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// File is a system file.
|
||||
/// </summary>
|
||||
SYSTEM = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// File has been modified since last backup.
|
||||
/// </summary>
|
||||
ARCH = 0x0040,
|
||||
|
||||
/// <summary>
|
||||
/// File will be run after extraction.
|
||||
/// </summary>
|
||||
EXEC = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// The szName field contains UTF.
|
||||
/// </summary>
|
||||
NAME_IS_UTF = 0x0100,
|
||||
}
|
||||
|
||||
public enum FolderIndex : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// A value of zero indicates that this is the
|
||||
/// first folder in this cabinet file.
|
||||
/// </summary>
|
||||
FIRST_FOLDER = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the folder index is actually zero, but that
|
||||
/// extraction of this file would have to begin with the cabinet named in the
|
||||
/// CFHEADER.szCabinetPrev field.
|
||||
/// </summary>
|
||||
CONTINUED_FROM_PREV = 0xFFFD,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the folder index
|
||||
/// is actually one less than THE CFHEADER.cFolders field value, and that extraction of this file will
|
||||
/// require continuation to the cabinet named in the CFHEADER.szCabinetNext field.
|
||||
/// </summary>
|
||||
CONTINUED_TO_NEXT = 0xFFFE,
|
||||
|
||||
/// <see cref="CONTINUED_FROM_PREV"/>
|
||||
/// <see cref="CONTINUED_TO_NEXT"/>
|
||||
CONTINUED_PREV_AND_NEXT = 0xFFFF,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HeaderFlags : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// The flag is set if this cabinet file is not the first in a set of cabinet files.
|
||||
/// When this bit is set, the szCabinetPrev and szDiskPrev fields are present in this CFHEADER
|
||||
/// structure. The value is 0x0001.
|
||||
/// </summary>
|
||||
PREV_CABINET = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// The flag is set if this cabinet file is not the last in a set of cabinet files.
|
||||
/// When this bit is set, the szCabinetNext and szDiskNext fields are present in this CFHEADER
|
||||
/// structure. The value is 0x0002.
|
||||
/// </summary>
|
||||
NEXT_CABINET = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// The flag is set if if this cabinet file contains any reserved fields. When
|
||||
/// this bit is set, the cbCFHeader, cbCFFolder, and cbCFData fields are present in this CFHEADER
|
||||
/// structure. The value is 0x0004.
|
||||
/// </summary>
|
||||
RESERVE_PRESENT = 0x0004,
|
||||
}
|
||||
}
|
||||
394
BurnOutSharp.Wrappers/MicrosoftCabinet.cs
Normal file
394
BurnOutSharp.Wrappers/MicrosoftCabinet.cs
Normal file
@@ -0,0 +1,394 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BurnOutSharp.Wrappers
|
||||
{
|
||||
public class MicrosoftCabinet : WrapperBase
|
||||
{
|
||||
#region Pass-Through Properties
|
||||
|
||||
#region Header
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.Signature"/>
|
||||
public uint Signature => _cabinet.Header.Signature;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.Reserved1"/>
|
||||
public uint Reserved1 => _cabinet.Header.Reserved1;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.CabinetSize"/>
|
||||
public uint CabinetSize => _cabinet.Header.CabinetSize;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.Reserved2"/>
|
||||
public uint Reserved2 => _cabinet.Header.Reserved2;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.FilesOffset"/>
|
||||
public uint FilesOffset => _cabinet.Header.FilesOffset;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.Reserved3"/>
|
||||
public uint Reserved3 => _cabinet.Header.Reserved3;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.VersionMinor"/>
|
||||
public byte VersionMinor => _cabinet.Header.VersionMinor;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.VersionMajor"/>
|
||||
public byte VersionMajor => _cabinet.Header.VersionMajor;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.FolderCount"/>
|
||||
public ushort FolderCount => _cabinet.Header.FolderCount;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.FileCount"/>
|
||||
public ushort FileCount => _cabinet.Header.FileCount;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.FileCount"/>
|
||||
public Models.MicrosoftCabinet.HeaderFlags Flags => _cabinet.Header.Flags;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.SetID"/>
|
||||
public ushort SetID => _cabinet.Header.SetID;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.CabinetIndex"/>
|
||||
public ushort CabinetIndex => _cabinet.Header.CabinetIndex;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.HeaderReservedSize"/>
|
||||
public ushort HeaderReservedSize => _cabinet.Header.HeaderReservedSize;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.FolderReservedSize"/>
|
||||
public byte FolderReservedSize => _cabinet.Header.FolderReservedSize;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.DataReservedSize"/>
|
||||
public byte DataReservedSize => _cabinet.Header.DataReservedSize;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.ReservedData"/>
|
||||
public byte[] ReservedData => _cabinet.Header.ReservedData;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.CabinetPrev"/>
|
||||
public string CabinetPrev => _cabinet.Header.CabinetPrev;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.DiskPrev"/>
|
||||
public string DiskPrev => _cabinet.Header.DiskPrev;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.CabinetNext"/>
|
||||
public string CabinetNext => _cabinet.Header.CabinetNext;
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.CFHEADER.DiskNext"/>
|
||||
public string DiskNext => _cabinet.Header.DiskNext;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folders
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.Cabinet.Folders"/>
|
||||
public Models.MicrosoftCabinet.CFFOLDER[] Folders => _cabinet.Folders;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
/// <inheritdoc cref="Models.MicrosoftCabinet.Cabinet.Files"/>
|
||||
public Models.MicrosoftCabinet.CFFILE[] Files => _cabinet.Files;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Internal representation of the cabinet
|
||||
/// </summary>
|
||||
private Models.MicrosoftCabinet.Cabinet _cabinet;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor
|
||||
/// </summary>
|
||||
private MicrosoftCabinet() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a Microsoft Cabinet from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the executable</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>A cabinet wrapper on success, null on failure</returns>
|
||||
public static MicrosoftCabinet Create(byte[] data, int offset)
|
||||
{
|
||||
var cabinet = Builder.MicrosoftCabinet.ParseCabinet(data, offset);
|
||||
if (cabinet == null)
|
||||
return null;
|
||||
|
||||
var wrapper = new MicrosoftCabinet
|
||||
{
|
||||
_cabinet = cabinet,
|
||||
_dataSource = DataSource.ByteArray,
|
||||
_byteArrayData = data,
|
||||
_byteArrayOffset = offset,
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Microsoft Cabinet from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the executable</param>
|
||||
/// <returns>A cabinet wrapper on success, null on failure</returns>
|
||||
public static MicrosoftCabinet Create(Stream data)
|
||||
{
|
||||
var cabinet = Builder.MicrosoftCabinet.ParseCabinet(data);
|
||||
if (cabinet == null)
|
||||
return null;
|
||||
|
||||
var wrapper = new MicrosoftCabinet
|
||||
{
|
||||
_cabinet = cabinet,
|
||||
_dataSource = DataSource.Stream,
|
||||
_streamData = data,
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folders
|
||||
|
||||
/// <summary>
|
||||
/// Get the uncompressed data associated with a folder
|
||||
/// </summary>
|
||||
/// <param name="folderIndex">Folder index to check</param>
|
||||
/// <returns>Byte array representing the data, null on error</returns>
|
||||
/// <remarks>All but uncompressed are unimplemented</remarks>
|
||||
public byte[] GetUncompressedData(int folderIndex)
|
||||
{
|
||||
// If we have an invalid folder index
|
||||
if (folderIndex < 0 || folderIndex >= Folders.Length)
|
||||
return null;
|
||||
|
||||
// Get the folder header
|
||||
var folder = Folders[folderIndex];
|
||||
if (folder == null)
|
||||
return null;
|
||||
|
||||
// If we have invalid data blocks
|
||||
if (folder.DataBlocks == null || folder.DataBlocks.Count == 0)
|
||||
return null;
|
||||
|
||||
// Store the last decompressed block for MS-ZIP
|
||||
byte[] lastDecompressed = null;
|
||||
|
||||
List<byte> data = new List<byte>();
|
||||
foreach (var dataBlock in folder.DataBlocks.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
|
||||
{
|
||||
byte[] decompressed = null;
|
||||
switch (folder.CompressionType)
|
||||
{
|
||||
case Models.MicrosoftCabinet.CompressionType.TYPE_NONE:
|
||||
decompressed = dataBlock.CompressedData;
|
||||
break;
|
||||
case Models.MicrosoftCabinet.CompressionType.TYPE_MSZIP:
|
||||
// TODO: UNIMPLEMENTED
|
||||
decompressed = dataBlock.CompressedData;
|
||||
break;
|
||||
case Models.MicrosoftCabinet.CompressionType.TYPE_QUANTUM:
|
||||
// TODO: UNIMPLEMENTED
|
||||
decompressed = dataBlock.CompressedData;
|
||||
break;
|
||||
case Models.MicrosoftCabinet.CompressionType.TYPE_LZX:
|
||||
// TODO: UNIMPLEMENTED
|
||||
decompressed = dataBlock.CompressedData;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
lastDecompressed = decompressed;
|
||||
if (decompressed != null)
|
||||
data.AddRange(decompressed);
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
/// <summary>
|
||||
/// Get the DateTime for a particular file index
|
||||
/// </summary>
|
||||
/// <param name="fileIndex">File index to check</param>
|
||||
/// <returns>DateTime representing the file time, null on error</returns>
|
||||
public DateTime? GetDateTime(int fileIndex)
|
||||
{
|
||||
// If we have an invalid file index
|
||||
if (fileIndex < 0 || fileIndex >= Files.Length)
|
||||
return null;
|
||||
|
||||
// Get the file header
|
||||
var file = Files[fileIndex];
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
// If we have an invalid DateTime
|
||||
if (file.Date == 0 && file.Time == 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Date property
|
||||
int year = (file.Date >> 9) + 1980;
|
||||
int month = (file.Date >> 5) & 0x0F;
|
||||
int day = file.Date & 0x1F;
|
||||
|
||||
// Time property
|
||||
int hour = file.Time >> 11;
|
||||
int minute = (file.Time >> 5) & 0x3F;
|
||||
int second = (file.Time << 1) & 0x3E;
|
||||
|
||||
return new DateTime(year, month, day, hour, minute, second);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Printing
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Print()
|
||||
{
|
||||
Console.WriteLine("Microsoft Cabinet Information:");
|
||||
Console.WriteLine("-------------------------");
|
||||
Console.WriteLine();
|
||||
|
||||
PrintHeader();
|
||||
PrintFolders();
|
||||
PrintFiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print header information
|
||||
/// </summary>
|
||||
private void PrintHeader()
|
||||
{
|
||||
Console.WriteLine(" Header Information:");
|
||||
Console.WriteLine(" -------------------------");
|
||||
Console.WriteLine($" Signature: {Signature}");
|
||||
Console.WriteLine($" Reserved 1: {Reserved1}");
|
||||
Console.WriteLine($" Cabinet size: {CabinetSize}");
|
||||
Console.WriteLine($" Reserved 2: {Reserved2}");
|
||||
Console.WriteLine($" Files offset: {FilesOffset}");
|
||||
Console.WriteLine($" Reserved 3: {Reserved3}");
|
||||
Console.WriteLine($" Minor version: {VersionMinor}");
|
||||
Console.WriteLine($" Major version: {VersionMajor}");
|
||||
Console.WriteLine($" Folder count: {FolderCount}");
|
||||
Console.WriteLine($" File count: {FileCount}");
|
||||
Console.WriteLine($" Flags: {Flags}");
|
||||
Console.WriteLine($" Set ID: {SetID}");
|
||||
Console.WriteLine($" Cabinet index: {CabinetIndex}");
|
||||
|
||||
if (Flags.HasFlag(Models.MicrosoftCabinet.HeaderFlags.RESERVE_PRESENT))
|
||||
{
|
||||
Console.WriteLine($" Header reserved size: {HeaderReservedSize}");
|
||||
Console.WriteLine($" Folder reserved size: {FolderReservedSize}");
|
||||
Console.WriteLine($" Data reserved size: {DataReservedSize}");
|
||||
Console.WriteLine($" Reserved data = {BitConverter.ToString(ReservedData).Replace("-", " ")}");
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(Models.MicrosoftCabinet.HeaderFlags.PREV_CABINET))
|
||||
{
|
||||
Console.WriteLine($" Previous cabinet: {CabinetPrev}");
|
||||
Console.WriteLine($" Previous disk: {DiskPrev}");
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(Models.MicrosoftCabinet.HeaderFlags.NEXT_CABINET))
|
||||
{
|
||||
Console.WriteLine($" Next cabinet: {CabinetNext}");
|
||||
Console.WriteLine($" Next disk: {DiskNext}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print folders information
|
||||
/// </summary>
|
||||
private void PrintFolders()
|
||||
{
|
||||
Console.WriteLine(" Folders:");
|
||||
Console.WriteLine(" -------------------------");
|
||||
if (FolderCount == 0 || Folders == null || Folders.Length == 0)
|
||||
{
|
||||
Console.WriteLine(" No folders");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Folders.Length; i++)
|
||||
{
|
||||
var entry = Folders[i];
|
||||
Console.WriteLine($" Folder {i}");
|
||||
Console.WriteLine($" Cab start offset = {entry.CabStartOffset}");
|
||||
Console.WriteLine($" Data count = {entry.DataCount}");
|
||||
Console.WriteLine($" Compression type = {entry.CompressionType}");
|
||||
Console.WriteLine($" Reserved data = {BitConverter.ToString(entry.ReservedData).Replace("-", " ")}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" Data Blocks");
|
||||
Console.WriteLine(" -------------------------");
|
||||
if (entry.DataBlocks == null || entry.DataBlocks.Count == 0)
|
||||
{
|
||||
Console.WriteLine(" No data blocks");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var block in entry.DataBlocks)
|
||||
{
|
||||
Console.WriteLine($" Data Block at offset {block.Key}");
|
||||
Console.WriteLine($" Checksum = {block.Value.Checksum}");
|
||||
Console.WriteLine($" Compressed size = {block.Value.CompressedSize}");
|
||||
Console.WriteLine($" Uncompressed size = {block.Value.UncompressedSize}");
|
||||
Console.WriteLine($" Reserved data = {BitConverter.ToString(block.Value.ReservedData).Replace("-", " ")}");
|
||||
//Console.WriteLine($" Compressed data = {BitConverter.ToString(block.Value.CompressedData).Replace("-", " ")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print files information
|
||||
/// </summary>
|
||||
private void PrintFiles()
|
||||
{
|
||||
Console.WriteLine(" Files:");
|
||||
Console.WriteLine(" -------------------------");
|
||||
if (FileCount == 0 || Files == null || Files.Length == 0)
|
||||
{
|
||||
Console.WriteLine(" No files");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Files.Length; i++)
|
||||
{
|
||||
var entry = Files[i];
|
||||
Console.WriteLine($" File {i}");
|
||||
Console.WriteLine($" File size = {entry.FileSize}");
|
||||
Console.WriteLine($" Folder start offset = {entry.FolderStartOffset}");
|
||||
Console.WriteLine($" Folder index = {entry.FolderIndex}");
|
||||
Console.WriteLine($" Date = {entry.Date}");
|
||||
Console.WriteLine($" Time = {entry.Time}");
|
||||
Console.WriteLine($" Attributes = {entry.Attributes}");
|
||||
Console.WriteLine($" Name = {entry.Name}");
|
||||
}
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BurnOutSharp;
|
||||
using BurnOutSharp.FileType;
|
||||
using BurnOutSharp.Wrappers;
|
||||
using static BurnOutSharp.Builder.Extensions;
|
||||
|
||||
@@ -300,7 +299,7 @@ namespace Test
|
||||
Console.WriteLine("Creating MS-CAB deserializer");
|
||||
Console.WriteLine();
|
||||
|
||||
var cabinet = MSCABCabinet.Deserialize(stream);
|
||||
var cabinet = MicrosoftCabinet.Create(stream);
|
||||
if (cabinet == null)
|
||||
{
|
||||
Console.WriteLine("Something went wrong parsing MS-CAB archive");
|
||||
@@ -309,7 +308,7 @@ namespace Test
|
||||
}
|
||||
|
||||
// Print the cabinet info to screen
|
||||
cabinet.PrintInfo();
|
||||
cabinet.Print();
|
||||
}
|
||||
|
||||
// Everything else
|
||||
|
||||
Reference in New Issue
Block a user