From d13c4965d5d9262f80ced59583ea73abdee25b95 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 11 Dec 2024 07:42:28 -0500 Subject: [PATCH] Add temporary test code for MS-ZIP --- Test/Program.cs | 287 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 1 deletion(-) diff --git a/Test/Program.cs b/Test/Program.cs index 54da700..9652700 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -1,4 +1,10 @@ -using SabreTools.Compression; +using System; +using System.IO; +using System.Text; +using SabreTools.Compression.MSZIP; +using SabreTools.IO.Extensions; +using SabreTools.Models.MicrosoftCabinet; +using static SabreTools.Models.MicrosoftCabinet.Constants; namespace Test { @@ -8,5 +14,284 @@ namespace Test { // No implementation, used for experimentation } + + private static void READMSZIPTEST() + { + using var fs = File.OpenRead("INFILE.cab"); + var cab = Deserialize(fs); + if (cab == null || cab.Folders == null || cab.Files == null) + return; + + for (int f = 0; f < cab!.Folders.Length; f++) + { + var folder = cab.Folders[f]; + if (folder?.DataBlocks == null || folder.DataBlocks.Length == 0) + continue; + + var decomp = Decompressor.Create(); + + var ms = new MemoryStream(); + foreach (var db in folder.DataBlocks) + { + if (db?.CompressedData == null) + continue; + + decomp.CopyTo(db.CompressedData, ms); + } + + if (cab?.Files == null || cab.Files.Length == 0) + continue; + + foreach (var file in cab.Files) + { + if (file?.Name == null || file.FolderIndex != (FolderIndex)f) + continue; + + byte[] fileData = new byte[file.FileSize]; + Array.Copy(ms.ToArray(), file.FolderStartOffset, fileData, 0, file.FileSize); + + using var of = File.OpenWrite(Path.Combine("OUTDIR", file.Name)); + of.Write(fileData); + of.Flush(); + } + } + } + + /// + /// Parse a Stream into a cabinet + /// + /// Stream to parse + /// Filled cabinet on success, null on error + private static Cabinet? Deserialize(Stream? data) + { + // If the data is invalid + if (data == null || !data.CanRead) + return null; + + try + { + // 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; + } + catch + { + // Ignore the actual error + return null; + } + } + + /// + /// Parse a Stream into a cabinet header + /// + /// Stream to parse + /// Filled cabinet header on success, null on error + private static CFHEADER? ParseCabinetHeader(Stream data) + { + var 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 NET20 || NET35 + if ((header.Flags & HeaderFlags.RESERVE_PRESENT) != 0) +#else + if (header.Flags.HasFlag(HeaderFlags.RESERVE_PRESENT)) +#endif + { + 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 NET20 || NET35 + if ((header.Flags & HeaderFlags.PREV_CABINET) != 0) +#else + if (header.Flags.HasFlag(HeaderFlags.PREV_CABINET)) +#endif + { + header.CabinetPrev = data.ReadNullTerminatedAnsiString(); + header.DiskPrev = data.ReadNullTerminatedAnsiString(); + } + +#if NET20 || NET35 + if ((header.Flags & HeaderFlags.NEXT_CABINET) != 0) +#else + if (header.Flags.HasFlag(HeaderFlags.NEXT_CABINET)) +#endif + { + header.CabinetNext = data.ReadNullTerminatedAnsiString(); + header.DiskNext = data.ReadNullTerminatedAnsiString(); + } + + 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) + { + var folder = new CFFOLDER(); + + folder.CabStartOffset = data.ReadUInt32(); + folder.DataCount = data.ReadUInt16(); + folder.CompressionType = (CompressionType)data.ReadUInt16() & CompressionType.MASK_TYPE; + + 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) + { + var 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) + { + var 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 = (SabreTools.Models.MicrosoftCabinet.FileAttributes)data.ReadUInt16(); + +#if NET20 || NET35 + if ((file.Attributes & SabreTools.Models.MicrosoftCabinet.FileAttributes.NAME_IS_UTF) != 0) +#else + if (file.Attributes.HasFlag(SabreTools.Models.MicrosoftCabinet.FileAttributes.NAME_IS_UTF)) +#endif + file.Name = data.ReadNullTerminatedUnicodeString(); + else + file.Name = data.ReadNullTerminatedAnsiString(); + + return file; + } } } \ No newline at end of file