diff --git a/Bytes/MicrosoftCabinet.Deserializer.cs b/Bytes/MicrosoftCabinet.Deserializer.cs new file mode 100644 index 00000000..8ac5478d --- /dev/null +++ b/Bytes/MicrosoftCabinet.Deserializer.cs @@ -0,0 +1,29 @@ +using System.IO; +using SabreTools.Models.MicrosoftCabinet; + +namespace SabreTools.Serialization.Bytes +{ + // TODO: Add multi-cabinet reading + public partial class MicrosoftCabinet : IByteSerializer + { + /// +#if NET48 + public Cabinet Deserialize(byte[] data, int offset) +#else + public Cabinet? Deserialize(byte[]? data, int offset) +#endif + { + // 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; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return new Streams.MicrosoftCabinet().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/MicrosoftCabinet.Deserializer.cs b/Streams/MicrosoftCabinet.Deserializer.cs new file mode 100644 index 00000000..515ce4b3 --- /dev/null +++ b/Streams/MicrosoftCabinet.Deserializer.cs @@ -0,0 +1,229 @@ +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.MicrosoftCabinet; +using static SabreTools.Models.MicrosoftCabinet.Constants; + +namespace SabreTools.Serialization.Streams +{ + // TODO: Add multi-cabinet reading + public partial class MicrosoftCabinet : IStreamSerializer + { + /// +#if NET48 + public Cabinet Deserialize(Stream data) +#else + public Cabinet? Deserialize(Stream? data) +#endif + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + 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; + } + + /// + /// Parse a Stream into a cabinet header + /// + /// Stream to parse + /// Filled cabinet header on success, null on error + private static CFHEADER ParseCabinetHeader(Stream data) + { + CFHEADER header = new CFHEADER(); + + byte[] signature = data.ReadBytes(4); + header.Signature = Encoding.ASCII.GetString(signature); + if (header.Signature != SignatureString) + return null; + + header.Reserved1 = data.ReadUInt32(); + header.CabinetSize = data.ReadUInt32(); + header.Reserved2 = data.ReadUInt32(); + header.FilesOffset = data.ReadUInt32(); + header.Reserved3 = data.ReadUInt32(); + header.VersionMinor = data.ReadByteValue(); + header.VersionMajor = data.ReadByteValue(); + header.FolderCount = data.ReadUInt16(); + header.FileCount = data.ReadUInt16(); + 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; + } + + /// + /// Parse a Stream into a folder + /// + /// Stream to parse + /// Cabinet header to get flags and sizes from + /// Filled folder on success, null on error + 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); + + folder.DataBlocks = new CFDATA[folder.DataCount]; + for (int i = 0; i < folder.DataCount; i++) + { + CFDATA dataBlock = ParseDataBlock(data, header.DataReservedSize); + folder.DataBlocks[i] = dataBlock; + } + + data.Seek(currentPosition, SeekOrigin.Begin); + } + + return folder; + } + + /// + /// Parse a Stream into a data block + /// + /// Stream to parse + /// Reserved byte size for data blocks + /// Filled folder on success, null on error + 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 (dataReservedSize > 0) + dataBlock.ReservedData = data.ReadBytes(dataReservedSize); + + if (dataBlock.CompressedSize > 0) + dataBlock.CompressedData = data.ReadBytes(dataBlock.CompressedSize); + + return dataBlock; + } + + /// + /// Parse a Stream into a file + /// + /// Stream to parse + /// Filled file on success, null on error + 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; + } + } +} \ No newline at end of file diff --git a/Streams/MicrosoftCabinet.Serializer.cs b/Streams/MicrosoftCabinet.Serializer.cs new file mode 100644 index 00000000..3bc22456 --- /dev/null +++ b/Streams/MicrosoftCabinet.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.MicrosoftCabinet; + +namespace SabreTools.Serialization.Streams +{ + public partial class MicrosoftCabinet : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Cabinet obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Cabinet? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file