diff --git a/SabreTools.Helper/Data/Constants.cs b/SabreTools.Helper/Data/Constants.cs index c5be876e..854b3521 100644 --- a/SabreTools.Helper/Data/Constants.cs +++ b/SabreTools.Helper/Data/Constants.cs @@ -83,34 +83,34 @@ namespace SabreTools.Helper #region Byte (1024-based) size comparisons public const long KibiByte = 1024; - public const long MibiByte = (long)Math.Pow(KibiByte, 2); - public const long GibiByte = (long)Math.Pow(KibiByte, 3); - public const long TibiByte = (long)Math.Pow(KibiByte, 4); - public const long PibiByte = (long)Math.Pow(KibiByte, 5); + public static long MibiByte = (long)Math.Pow(KibiByte, 2); + public static long GibiByte = (long)Math.Pow(KibiByte, 3); + public static long TibiByte = (long)Math.Pow(KibiByte, 4); + public static long PibiByte = (long)Math.Pow(KibiByte, 5); #endregion #region Byte (1000-based) size comparisons public const long KiloByte = 1000; - public const long MegaByte = (long)Math.Pow(KiloByte, 2); - public const long GigaByte = (long)Math.Pow(KiloByte, 2); - public const long TeraByte = (long)Math.Pow(KiloByte, 2); - public const long PetaByte = (long)Math.Pow(KiloByte, 2); + public static long MegaByte = (long)Math.Pow(KiloByte, 2); + public static long GigaByte = (long)Math.Pow(KiloByte, 2); + public static long TeraByte = (long)Math.Pow(KiloByte, 2); + public static long PetaByte = (long)Math.Pow(KiloByte, 2); #endregion #region Magic numbers as byte arrays - public const byte[] SevenZipSigBytes = new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c }; - public const byte[] GzSigBytes = new byte[] { 0x1f, 0x8b }; - public const byte[] RarSigBytes = new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }; - public const byte[] RarFiveSigBytes = new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 }; - public const byte[] TarSigBytes = new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 }; - public const byte[] TarZeroSigBytes = new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 }; - public const byte[] ZipSigBytes = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; - public const byte[] ZipSigEmptyBytes = new byte[] { 0x50, 0x4b, 0x05, 0x06 }; - public const byte[] ZipSigSpannedBytes = new byte[] { 0x50, 0x4b, 0x07, 0x08 }; + public static byte[] SevenZipSigBytes = new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c }; + public static byte[] GzSigBytes = new byte[] { 0x1f, 0x8b }; + public static byte[] RarSigBytes = new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }; + public static byte[] RarFiveSigBytes = new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 }; + public static byte[] TarSigBytes = new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 }; + public static byte[] TarZeroSigBytes = new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 }; + public static byte[] ZipSigBytes = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; + public static byte[] ZipSigEmptyBytes = new byte[] { 0x50, 0x4b, 0x05, 0x06 }; + public static byte[] ZipSigSpannedBytes = new byte[] { 0x50, 0x4b, 0x07, 0x08 }; #endregion @@ -141,7 +141,7 @@ namespace SabreTools.Helper 0A-0B Last mod file time (0x00, 0xBC) 0C-0D Last mod file date (0x98, 0x21) */ - public const byte[] TorrentZipHeader = new byte[] { 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0xbc, 0x98, 0x21 }; + public static byte[] TorrentZipHeader = new byte[] { 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0xbc, 0x98, 0x21 }; /* Torrent7z Header Format http://cpansearch.perl.org/src/BJOERN/Compress-Deflate7-1.0/7zip/DOC/7zFormat.txt @@ -150,7 +150,7 @@ namespace SabreTools.Helper 06-07 ArchiveVersion (0x00, 0x03) The rest is unknown */ - public const byte[] Torrent7ZipHeader = new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, 0x00, 0x03 }; + public static byte[] Torrent7ZipHeader = new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, 0x00, 0x03 }; /* (Torrent)GZ Header Format https://tools.ietf.org/html/rfc1952 @@ -168,7 +168,7 @@ namespace SabreTools.Helper 1C-1F CRC hash 20-27 Int64 size (mirrored) */ - public const byte[] TorrentGZHeader = new byte[] { 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00 }; + public static byte[] TorrentGZHeader = new byte[] { 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00 }; #endregion diff --git a/SabreTools.Helper/Data/Enums.cs b/SabreTools.Helper/Data/Enums.cs index 3ad3506c..06480e4e 100644 --- a/SabreTools.Helper/Data/Enums.cs +++ b/SabreTools.Helper/Data/Enums.cs @@ -227,5 +227,38 @@ Type17 = 17, } + /// + /// Zip testing type + /// + /// https://raw.githubusercontent.com/gjefferyes/RomVault/5a93500001f0d068f32cf77a048950717507f733/ROMVault2/SupportedFiles/ZipEnums.cs + public enum ZipReturn + { + ZipGood, + ZipFileLocked, + ZipFileCountError, + ZipSignatureError, + ZipExtraDataOnEndOfZip, + ZipUnsupportedCompression, + ZipLocalFileHeaderError, + ZipCentralDirError, + ZipEndOfCentralDirectoryError, + Zip64EndOfCentralDirError, + Zip64EndOfCentralDirectoryLocatorError, + ZipReadingFromOutputFile, + ZipWritingToInputFile, + ZipErrorGettingDataStream, + ZipCRCDecodeError, + ZipDecodeError, + ZipFileNameToLong, + ZipFileAlreadyOpen, + ZipCannotFastOpen, + ZipErrorOpeningFile, + ZipErrorFileNotFound, + ZipErrorReadingFile, + ZipErrorTimeStamp, + ZipErrorRollBackFile, + ZipUntested + } + #endregion } diff --git a/SabreTools.Helper/External/OptimizedCRC.cs b/SabreTools.Helper/External/OptimizedCRC.cs index 92e42e0d..bbc5d97b 100644 --- a/SabreTools.Helper/External/OptimizedCRC.cs +++ b/SabreTools.Helper/External/OptimizedCRC.cs @@ -26,7 +26,7 @@ using System; using System.IO; -namespace CRC32 +namespace OCRC { public class OptimizedCRC : IDisposable { diff --git a/SabreTools.Helper/Objects/ZipArchiveEntry.cs b/SabreTools.Helper/Objects/ZipArchiveEntry.cs deleted file mode 100644 index e44bdcd7..00000000 --- a/SabreTools.Helper/Objects/ZipArchiveEntry.cs +++ /dev/null @@ -1,605 +0,0 @@ -using Ionic.Crc; -using Ionic.Zlib; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading; - -namespace SabreTools.Helper -{ - /// - /// Based on work by GordonJ for RomVault - /// https://github.com/gjefferyes/RomVault/blob/master/ROMVault2/SupportedFiles/Zip/zipFile.cs - /// - public class ZipFileEntry - { - #region Private instance variables - - private readonly Stream _zipstream; - private string _fileName; - private CompressionMethod _compressionMethod; - private ArchiveVersion _versionMadeBy; - private ArchiveVersion _versionNeeded; - private GeneralPurposeBitFlag _generalPurposeBitFlag; - private ushort _lastModFileTime; - private ushort _lastModFileDate; - private uint _crc; - private ulong _compressedSize; - private ulong _uncompressedSize; - private byte[] _extraField; - private byte[] _comment; - private InternalFileAttributes _internalFileAttributes; - private uint _externalFileAttributes; - private ulong _relativeOffset; - private ulong _crc32Location; - private ulong _extraLocation; - private ulong _dataLocation; - private bool _zip64; - private bool _torrentZip; - private byte[] _md5; - private byte[] _sha1; - - #endregion - - #region Public facing variables - - public string FileName - { - get { return _fileName; } - private set { _fileName = value; } - } - public GeneralPurposeBitFlag GeneralPurposeBitFlag - { - get { return _generalPurposeBitFlag; } - private set { _generalPurposeBitFlag = value; } - } - public ushort LastModFileTime - { - get { return _lastModFileTime; } - set { _lastModFileTime = value; } - } - public ushort LastModFileDate - { - get { return _lastModFileDate; } - set { _lastModFileDate = value; } - } - public byte[] CRC - { - get { return BitConverter.GetBytes(_crc); } - private set { _crc = BitConverter.ToUInt32(value, 0); } - } - public ulong UncompressedSize - { - get { return _uncompressedSize; } - private set { _uncompressedSize = value; } - } - public string ExtraField - { - get { return Encoding.ASCII.GetString(_extraField); } - set { _extraField = Style.StringToByteArray(Style.ConvertAsciiToHex(value)); } - } - public string Comment - { - get { return Encoding.ASCII.GetString(_comment); } - set { _comment = Style.StringToByteArray(Style.ConvertAsciiToHex(value)); } - } - public ulong RelativeOffset - { - get { return _relativeOffset; } - set { _relativeOffset = value; } - } - public bool Zip64 - { - get { return _zip64; } - private set { _zip64 = value; } - } - public bool TorrentZip - { - get { return _torrentZip; } - private set { _torrentZip = value; } - } - public byte[] MD5 - { - get { return _md5; } - private set { _md5 = value; } - } - public byte[] SHA1 - { - get { return _sha1; } - private set { _sha1 = value; } - } - - #endregion - - #region Constructors - - /// - /// Create a new ZipFileEntry using just a stream - /// - /// Stream representing the entry - public ZipFileEntry(Stream zipstream) - { - _zipstream = zipstream; - } - - /// - /// Create a new ZipFileEntry from a stream and a filename - /// - /// Stream representing the entry - /// Internal filename to use - /// True if the file should be set with TorrentZip defaults, false otherwise (default) - public ZipFileEntry(Stream zipstream, string filename, bool torrentZip = false) - { - _zip64 = false; - _zipstream = zipstream; - _generalPurposeBitFlag = GeneralPurposeBitFlag.DeflatingMaximumCompression; - _compressionMethod = CompressionMethod.Deflated; - FileName = filename; - - if (torrentZip) - { - _lastModFileTime = 48128; - _lastModFileDate = 8600; - } - } - - /// - /// Read the central directory entry from the input stream - /// - /// True if the central directory was read correctly, false otherwise - public bool ReadCentralDirectory() - { - try - { - // Open the stream for reading - using (BinaryReader br = new BinaryReader(_zipstream)) - { - // If the first bytes aren't a central directory header, log and return - if (br.ReadUInt32() != Constants.CentralDirectoryHeaderSignature) - { - Console.Write("Error: Central directory entry malformed"); - return false; - } - - // Now read in available information, skipping the unnecessary - _versionMadeBy = (ArchiveVersion)br.ReadUInt16(); - _versionNeeded = (ArchiveVersion)br.ReadUInt16(); - _generalPurposeBitFlag = (GeneralPurposeBitFlag)br.ReadUInt16(); - _compressionMethod = (CompressionMethod)br.ReadUInt16(); - - // If we have an unsupported compression method, log and return - if (_compressionMethod != CompressionMethod.Stored && _compressionMethod != CompressionMethod.Deflated) - { - Console.Write("Error: Unsupported compression method; requires store or deflate"); - return false; - } - - // Keep reading available information, skipping the unnecessary - _lastModFileTime = br.ReadUInt16(); - _lastModFileDate = br.ReadUInt16(); - _crc = br.ReadUInt32(); - _compressedSize = br.ReadUInt32(); - _uncompressedSize = br.ReadUInt32(); - - // Now store some temp vars to find the filename, extra field, and comment - ushort fileNameLength = br.ReadUInt16(); - ushort extraFieldLength = br.ReadUInt16(); - ushort fileCommentLength = br.ReadUInt16(); - - // Even more reading available information, skipping the unnecessary - br.ReadUInt16(); // Disk number start - _internalFileAttributes = (InternalFileAttributes)br.ReadUInt16(); - _externalFileAttributes = br.ReadUInt16(); - _relativeOffset = br.ReadUInt32(); - byte[] fileNameBytes = br.ReadBytes(fileNameLength); - _fileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0 - ? Encoding.ASCII.GetString(fileNameBytes) - : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength)); - _extraField = br.ReadBytes(extraFieldLength); - _comment = br.ReadBytes(fileCommentLength); - - /* - Full disclosure: this next section is in GordonJ's work but I honestly - have no idea everything that it does. It seems to do something to figure - out if it's Zip64, or possibly check for random things but it uses the - extra field for this, which I do not fully understand. It's copied in - its entirety below in the hope that it makes things better... - */ - - int pos = 0; - while (extraFieldLength > pos) - { - ushort type = BitConverter.ToUInt16(_extraField, pos); - pos += 2; - ushort blockLength = BitConverter.ToUInt16(_extraField, pos); - pos += 2; - switch (type) - { - case 0x0001: - Zip64 = true; - if (UncompressedSize == 0xffffffff) - { - UncompressedSize = BitConverter.ToUInt64(_extraField, pos); - pos += 8; - } - if (_compressedSize == 0xffffffff) - { - _compressedSize = BitConverter.ToUInt64(_extraField, pos); - pos += 8; - } - if (_relativeOffset == 0xffffffff) - { - _relativeOffset = BitConverter.ToUInt64(_extraField, pos); - pos += 8; - } - break; - case 0x7075: - //byte version = extraField[pos]; - pos += 1; - uint nameCRC32 = BitConverter.ToUInt32(_extraField, pos); - pos += 4; - - Ionic.Crc.CRC32 crcTest = new Ionic.Crc.CRC32(); - crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength); - uint fCRC = (uint)crcTest.Crc32Result; - - if (nameCRC32 != fCRC) - { - Console.Write("Error: Central directory entry malformed"); - return false; - } - - int charLen = blockLength - 5; - - _fileName = Encoding.UTF8.GetString(_extraField, pos, charLen); - pos += charLen; - - break; - default: - pos += blockLength; - break; - } - } - } - } - catch - { - Console.Write("Error: Central directory entry malformed"); - return false; - } - - return true; - } - - /// - /// Write the central directory entry from the included stream - /// - /// Write out the data from the internal stream to the output stream - public void WriteCentralDirectory(Stream output) - { - // Open the crcStream for writing - using (BinaryWriter bw = new BinaryWriter(output)) - { - // Create an empty extra field to start out with - List extraField = new List(); - - // Now get the uncompressed size (for Zip64 compatibility) - uint uncompressedSize32; - if (_uncompressedSize >= 0xffffffff) - { - _zip64 = true; - uncompressedSize32 = 0xffffffff; - extraField.AddRange(BitConverter.GetBytes(_uncompressedSize)); - } - else - { - uncompressedSize32 = (uint)_uncompressedSize; - } - - // Now get the compressed size (for Zip64 compatibility) - uint compressedSize32; - if (_compressedSize >= 0xffffffff) - { - _zip64 = true; - compressedSize32 = 0xffffffff; - extraField.AddRange(BitConverter.GetBytes(_compressedSize)); - } - else - { - compressedSize32 = (uint)_compressedSize; - } - - // Now get the relative offset (for Zip64 compatibility) - uint relativeOffset32; - if (_relativeOffset >= 0xffffffff) - { - _zip64 = true; - relativeOffset32 = 0xffffffff; - extraField.AddRange(BitConverter.GetBytes(_relativeOffset)); - } - else - { - relativeOffset32 = (uint)_relativeOffset; - } - - // If we wrote anything to the extra field, set the flag and size - if (extraField.Count > 0) - { - ushort extraFieldLengthInternal = (ushort)extraField.Count; - extraField.InsertRange(0, BitConverter.GetBytes((ushort)0x0001)); - extraField.InsertRange(2, BitConverter.GetBytes(extraFieldLengthInternal)); - } - ushort extraFieldLength = (ushort)extraField.Count; - - // Now check for a unicode filename and set the flag accordingly - byte[] fileNameBytes; - if (Style.IsUnicode(_fileName)) - { - _generalPurposeBitFlag |= GeneralPurposeBitFlag.LanguageEncodingFlag; - fileNameBytes = Encoding.UTF8.GetBytes(_fileName); - } - else - { - fileNameBytes = Encoding.ASCII.GetBytes(_fileName); - } - ushort fileNameLength = (ushort)fileNameBytes.Length; - - // Set the version needed to extract according to if it's Zip64 - ushort versionNeededToExtract = (ushort)(_zip64 ? 45 : 20); - - // Now, write all of the data to the stream - bw.Write(Constants.CentralDirectoryHeaderSignature); - bw.Write((ushort)ArchiveVersion.MSDOSandOS2); - bw.Write(versionNeededToExtract); - bw.Write((ushort)_generalPurposeBitFlag); - bw.Write((ushort)_compressionMethod); - bw.Write(_lastModFileTime); - bw.Write(_lastModFileDate); - bw.Write(_crc); - bw.Write(compressedSize32); - bw.Write(uncompressedSize32); - bw.Write(fileNameLength); - bw.Write(extraFieldLength); - bw.Write((ushort)0); // File comment length - bw.Write((ushort)0); // Disk number start - bw.Write((ushort)0); // Internal file attributes - bw.Write((uint)0); // External file attributes - bw.Write(relativeOffset32); - bw.Write(fileNameBytes, 0, fileNameLength); // Only write first bytes if longer than allowed - bw.Write(extraField.ToArray(), 0, extraFieldLength); // Only write the first bytes if longer than allowed - // We have no file comment, so we don't have to write more - } - } - - /// - /// Read the local file header from the input stream - /// - /// True if the local file header was read correctly, false otherwise - public bool ReadLocalFileHeader() - { - try - { - // We assume that the file is torrentzip until proven otherwise - _torrentZip = true; - - // Open the stream for reading - using (BinaryReader br = new BinaryReader(_zipstream)) - { - // Set the position of the writer based on the entry information - br.BaseStream.Seek((long)_relativeOffset, SeekOrigin.Begin); - - // If the first bytes aren't a local file header, log and return - if (br.ReadUInt32() != Constants.LocalFileHeaderSignature) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - // Now read in available information, comparing to the known data - if (br.ReadUInt16() != (ushort)_versionNeeded) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if (br.ReadUInt16() != (ushort)_generalPurposeBitFlag) - { - _torrentZip = false; - } - if (br.ReadUInt16() != (ushort)_compressionMethod) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if (br.ReadUInt16() != _lastModFileTime) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if (br.ReadUInt16() != _lastModFileDate) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && br.ReadUInt32() != _crc) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - uint readCompressedSize = br.ReadUInt32(); - // If we have Zip64, the compressed size should be 0xffffffff - if (_zip64 && readCompressedSize != 0xffffffff && readCompressedSize != _compressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - // If we have the zeroed flag set, then no size should be included - if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize && readCompressedSize != 0) - { - Console.Write("Error: Local file header malformed"); - return false; - } - // If we don't have the zeroed flag set, then the size should match - if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && readCompressedSize != _compressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - uint readUncompressedSize = br.ReadUInt32(); - // If we have Zip64, the uncompressed size should be 0xffffffff - if (_zip64 && readUncompressedSize != 0xffffffff && readUncompressedSize != _compressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - // If we have the zeroed flag set, then no size should be included - if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize && readUncompressedSize != 0) - { - Console.Write("Error: Local file header malformed"); - return false; - } - // If we don't have the zeroed flag set, then the size should match - if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && readUncompressedSize != _uncompressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - ushort fileNameLength = br.ReadUInt16(); - ushort extraFieldLength = br.ReadUInt16(); - - byte[] fileNameBytes = br.ReadBytes(fileNameLength); - string tempFileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0 - ? Encoding.ASCII.GetString(fileNameBytes) - : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength)); - - byte[] extraField = br.ReadBytes(extraFieldLength); - - /* - Full disclosure: this next section is in GordonJ's work but I honestly - have no idea everything that it does. It seems to do something to figure - out if it's Zip64, or possibly check for random things but it uses the - extra field for this, which I do not fully understand. It's copied in - its entirety below in the hope that it makes things better... - */ - - _zip64 = false; - int pos = 0; - while (extraFieldLength > pos) - { - ushort type = BitConverter.ToUInt16(extraField, pos); - pos += 2; - ushort blockLength = BitConverter.ToUInt16(extraField, pos); - pos += 2; - switch (type) - { - case 0x0001: - Zip64 = true; - if (readUncompressedSize == 0xffffffff) - { - ulong tLong = BitConverter.ToUInt64(extraField, pos); - if (tLong != UncompressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - pos += 8; - } - if (readCompressedSize == 0xffffffff) - { - ulong tLong = BitConverter.ToUInt64(extraField, pos); - if (tLong != _compressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - pos += 8; - } - break; - case 0x7075: - //byte version = extraField[pos]; - pos += 1; - uint nameCRC32 = BitConverter.ToUInt32(extraField, pos); - pos += 4; - - Ionic.Crc.CRC32 crcTest = new Ionic.Crc.CRC32(); - crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength); - uint fCRC = (uint)crcTest.Crc32Result; - - if (nameCRC32 != fCRC) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - int charLen = blockLength - 5; - - tempFileName = Encoding.UTF8.GetString(extraField, pos, charLen); - pos += charLen; - - break; - default: - pos += blockLength; - break; - } - } - - // Back to code I understand - if (String.Equals(_fileName, tempFileName, StringComparison.InvariantCulture)) - { - Console.Write("Error: Local file header malformed"); - return false; - } - - // Set the position of the data - _dataLocation = (ulong)_zipstream.Position; - - // Now if no other data should be after the data, return - if((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize) - { - return true; - } - - // Otherwise, compare the data after the file too - _zipstream.Seek((long)_compressedSize, SeekOrigin.Current); - - // If there's no subheader, read the next thing as crc - uint tempCrc = br.ReadUInt32(); - if (tempCrc != Constants.EndOfLocalFileHeaderSignature) - { - tempCrc = br.ReadUInt32(); - } - - if (tempCrc != _crc) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if (br.ReadUInt32() != _compressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - if (br.ReadUInt32() != _uncompressedSize) - { - Console.Write("Error: Local file header malformed"); - return false; - } - } - } - catch - { - Console.Write("Error: Local file header malformed"); - return false; - } - - return true; - } - - #endregion - } -} diff --git a/SabreTools.Helper/Objects/ZipFileEntry.cs b/SabreTools.Helper/Objects/ZipFileEntry.cs new file mode 100644 index 00000000..cb048ece --- /dev/null +++ b/SabreTools.Helper/Objects/ZipFileEntry.cs @@ -0,0 +1,1022 @@ +using OCRC; +using Ionic.Crc; +using Ionic.Zlib; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace SabreTools.Helper +{ + /// + /// Based on work by GordonJ for RomVault + /// https://github.com/gjefferyes/RomVault/blob/master/ROMVault2/SupportedFiles/Zip/zipFile.cs + /// + public class ZipFileEntry + { + #region Private instance variables + + private readonly Stream _zipstream; + private Stream _readStream; + private Stream _writeStream; + private string _fileName; + private CompressionMethod _compressionMethod; + private ArchiveVersion _versionMadeBy; + private ArchiveVersion _versionNeeded; + private GeneralPurposeBitFlag _generalPurposeBitFlag; + private ushort _lastModFileTime; + private ushort _lastModFileDate; + private uint _crc; + private ulong _compressedSize; + private ulong _uncompressedSize; + private byte[] _extraField; + private byte[] _comment; + private InternalFileAttributes _internalFileAttributes; + private uint _externalFileAttributes; + private ulong _relativeOffset; + private ulong _crc32Location; + private ulong _extraLocation; + private ulong _dataLocation; + private bool _zip64; + private bool _torrentZip; + private byte[] _md5; + private byte[] _sha1; + private ZipReturn _fileStatus = ZipReturn.ZipUntested; + + #endregion + + #region Public facing variables + + public string FileName + { + get { return _fileName; } + private set { _fileName = value; } + } + public GeneralPurposeBitFlag GeneralPurposeBitFlag + { + get { return _generalPurposeBitFlag; } + private set { _generalPurposeBitFlag = value; } + } + public ushort LastModFileTime + { + get { return _lastModFileTime; } + set { _lastModFileTime = value; } + } + public ushort LastModFileDate + { + get { return _lastModFileDate; } + set { _lastModFileDate = value; } + } + public byte[] CRC + { + get { return BitConverter.GetBytes(_crc); } + private set { _crc = BitConverter.ToUInt32(value, 0); } + } + public ulong UncompressedSize + { + get { return _uncompressedSize; } + private set { _uncompressedSize = value; } + } + public string ExtraField + { + get { return Encoding.ASCII.GetString(_extraField); } + set { _extraField = Style.StringToByteArray(Style.ConvertAsciiToHex(value)); } + } + public string Comment + { + get { return Encoding.ASCII.GetString(_comment); } + set { _comment = Style.StringToByteArray(Style.ConvertAsciiToHex(value)); } + } + public ulong RelativeOffset + { + get { return _relativeOffset; } + set { _relativeOffset = value; } + } + public bool Zip64 + { + get { return _zip64; } + private set { _zip64 = value; } + } + public bool TorrentZip + { + get { return _torrentZip; } + private set { _torrentZip = value; } + } + public byte[] MD5 + { + get { return _md5; } + private set { _md5 = value; } + } + public byte[] SHA1 + { + get { return _sha1; } + private set { _sha1 = value; } + } + public ZipReturn FileStatus + { + get { return _fileStatus; } + set { _fileStatus = value; } + } + + #endregion + + #region Constructors + + /// + /// Create a new ZipFileEntry using just a stream + /// + /// Stream representing the entry + public ZipFileEntry(Stream zipstream) + { + _zipstream = zipstream; + } + + /// + /// Create a new ZipFileEntry from a stream and a filename + /// + /// Stream representing the entry + /// Internal filename to use + /// True if the file should be set with TorrentZip defaults, false otherwise (default) + public ZipFileEntry(Stream zipstream, string filename, bool torrentZip = false) + { + _zip64 = false; + _zipstream = zipstream; + _generalPurposeBitFlag = GeneralPurposeBitFlag.DeflatingMaximumCompression; + _compressionMethod = CompressionMethod.Deflated; + FileName = filename; + + if (torrentZip) + { + _lastModFileTime = 48128; + _lastModFileDate = 8600; + } + } + + #endregion + + /// + /// Read the central directory entry from the input stream + /// + /// True if the central directory was read correctly, false otherwise + public ZipReturn ReadCentralDirectory() + { + try + { + // Open the stream for reading + using (BinaryReader br = new BinaryReader(_zipstream)) + { + // If the first bytes aren't a central directory header, log and return + if (br.ReadUInt32() != Constants.CentralDirectoryHeaderSignature) + { + return ZipReturn.ZipCentralDirError; + } + + // Now read in available information, skipping the unnecessary + _versionMadeBy = (ArchiveVersion)br.ReadUInt16(); + _versionNeeded = (ArchiveVersion)br.ReadUInt16(); + _generalPurposeBitFlag = (GeneralPurposeBitFlag)br.ReadUInt16(); + _compressionMethod = (CompressionMethod)br.ReadUInt16(); + + // If we have an unsupported compression method, log and return + if (_compressionMethod != CompressionMethod.Stored && _compressionMethod != CompressionMethod.Deflated) + { + return ZipReturn.ZipCentralDirError; + } + + // Keep reading available information, skipping the unnecessary + _lastModFileTime = br.ReadUInt16(); + _lastModFileDate = br.ReadUInt16(); + _crc = br.ReadUInt32(); + _compressedSize = br.ReadUInt32(); + _uncompressedSize = br.ReadUInt32(); + + // Now store some temp vars to find the filename, extra field, and comment + ushort fileNameLength = br.ReadUInt16(); + ushort extraFieldLength = br.ReadUInt16(); + ushort fileCommentLength = br.ReadUInt16(); + + // Even more reading available information, skipping the unnecessary + br.ReadUInt16(); // Disk number start + _internalFileAttributes = (InternalFileAttributes)br.ReadUInt16(); + _externalFileAttributes = br.ReadUInt16(); + _relativeOffset = br.ReadUInt32(); + byte[] fileNameBytes = br.ReadBytes(fileNameLength); + _fileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0 + ? Encoding.ASCII.GetString(fileNameBytes) + : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength)); + _extraField = br.ReadBytes(extraFieldLength); + _comment = br.ReadBytes(fileCommentLength); + + /* + Full disclosure: this next section is in GordonJ's work but I honestly + have no idea everything that it does. It seems to do something to figure + out if it's Zip64, or possibly check for random things but it uses the + extra field for this, which I do not fully understand. It's copied in + its entirety below in the hope that it makes things better... + */ + + int pos = 0; + while (extraFieldLength > pos) + { + ushort type = BitConverter.ToUInt16(_extraField, pos); + pos += 2; + ushort blockLength = BitConverter.ToUInt16(_extraField, pos); + pos += 2; + switch (type) + { + case 0x0001: + Zip64 = true; + if (UncompressedSize == 0xffffffff) + { + UncompressedSize = BitConverter.ToUInt64(_extraField, pos); + pos += 8; + } + if (_compressedSize == 0xffffffff) + { + _compressedSize = BitConverter.ToUInt64(_extraField, pos); + pos += 8; + } + if (_relativeOffset == 0xffffffff) + { + _relativeOffset = BitConverter.ToUInt64(_extraField, pos); + pos += 8; + } + break; + case 0x7075: + //byte version = extraField[pos]; + pos += 1; + uint nameCRC32 = BitConverter.ToUInt32(_extraField, pos); + pos += 4; + + CRC32 crcTest = new CRC32(); + crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength); + uint fCRC = (uint)crcTest.Crc32Result; + + if (nameCRC32 != fCRC) + { + return ZipReturn.ZipCentralDirError; + } + + int charLen = blockLength - 5; + + _fileName = Encoding.UTF8.GetString(_extraField, pos, charLen); + pos += charLen; + + break; + default: + pos += blockLength; + break; + } + } + } + } + catch + { + return ZipReturn.ZipCentralDirError; + } + + return ZipReturn.ZipGood; + } + + /// + /// Write the central directory entry from the included stream + /// + /// Write out the data from the internal stream to the output stream + public void WriteCentralDirectory(Stream output) + { + // Open the output stream for writing + BinaryWriter bw = new BinaryWriter(output); + + // Create an empty extra field to start out with + List extraField = new List(); + + // Now get the uncompressed size (for Zip64 compatibility) + uint uncompressedSize32; + if (_uncompressedSize >= 0xffffffff) + { + _zip64 = true; + uncompressedSize32 = 0xffffffff; + extraField.AddRange(BitConverter.GetBytes(_uncompressedSize)); + } + else + { + uncompressedSize32 = (uint)_uncompressedSize; + } + + // Now get the compressed size (for Zip64 compatibility) + uint compressedSize32; + if (_compressedSize >= 0xffffffff) + { + _zip64 = true; + compressedSize32 = 0xffffffff; + extraField.AddRange(BitConverter.GetBytes(_compressedSize)); + } + else + { + compressedSize32 = (uint)_compressedSize; + } + + // Now get the relative offset (for Zip64 compatibility) + uint relativeOffset32; + if (_relativeOffset >= 0xffffffff) + { + _zip64 = true; + relativeOffset32 = 0xffffffff; + extraField.AddRange(BitConverter.GetBytes(_relativeOffset)); + } + else + { + relativeOffset32 = (uint)_relativeOffset; + } + + // If we wrote anything to the extra field, set the flag and size + if (extraField.Count > 0) + { + ushort extraFieldLengthInternal = (ushort)extraField.Count; + extraField.InsertRange(0, BitConverter.GetBytes((ushort)0x0001)); // id + extraField.InsertRange(2, BitConverter.GetBytes(extraFieldLengthInternal)); // data length + } + ushort extraFieldLength = (ushort)extraField.Count; + + // Now check for a unicode filename and set the flag accordingly + byte[] fileNameBytes; + if (Style.IsUnicode(_fileName)) + { + _generalPurposeBitFlag |= GeneralPurposeBitFlag.LanguageEncodingFlag; + fileNameBytes = Encoding.UTF8.GetBytes(_fileName); + } + else + { + fileNameBytes = Encoding.ASCII.GetBytes(_fileName); + } + ushort fileNameLength = (ushort)fileNameBytes.Length; + + // Set the version needed to extract according to if it's Zip64 + ushort versionNeededToExtract = (ushort)(_zip64 ? ArchiveVersion.TorrentZip64 : ArchiveVersion.TorrentZip); + + // Now, write all of the data to the stream + bw.Write(Constants.CentralDirectoryHeaderSignature); + bw.Write((ushort)ArchiveVersion.MSDOSandOS2); + bw.Write(versionNeededToExtract); + bw.Write((ushort)_generalPurposeBitFlag); + bw.Write((ushort)_compressionMethod); + bw.Write(_lastModFileTime); + bw.Write(_lastModFileDate); + bw.Write(_crc); + bw.Write(compressedSize32); + bw.Write(uncompressedSize32); + bw.Write(fileNameLength); + bw.Write(extraFieldLength); + bw.Write((ushort)0); // File comment length + bw.Write((ushort)0); // Disk number start + bw.Write((ushort)0); // Internal file attributes + bw.Write((uint)0); // External file attributes + bw.Write(relativeOffset32); + bw.Write(fileNameBytes, 0, fileNameLength); // Only write first bytes if longer than allowed + bw.Write(extraField.ToArray(), 0, extraFieldLength); // Only write the first bytes if longer than allowed + // We have no file comment, so we don't have to write more + } + + /// + /// Read the local file header from the input stream + /// + /// True if the local file header was read correctly, false otherwise + public ZipReturn ReadLocalFileHeader() + { + try + { + // We assume that the file is torrentzip until proven otherwise + _torrentZip = true; + + // Open the stream for reading + using (BinaryReader br = new BinaryReader(_zipstream)) + { + // Set the position of the writer based on the entry information + br.BaseStream.Seek((long)_relativeOffset, SeekOrigin.Begin); + + // If the first bytes aren't a local file header, log and return + if (br.ReadUInt32() != Constants.LocalFileHeaderSignature) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + // Now read in available information, comparing to the known data + if (br.ReadUInt16() != (ushort)_versionNeeded) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if (br.ReadUInt16() != (ushort)_generalPurposeBitFlag) + { + _torrentZip = false; + } + if (br.ReadUInt16() != (ushort)_compressionMethod) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if (br.ReadUInt16() != _lastModFileTime) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if (br.ReadUInt16() != _lastModFileDate) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && br.ReadUInt32() != _crc) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + uint readCompressedSize = br.ReadUInt32(); + // If we have Zip64, the compressed size should be 0xffffffff + if (_zip64 && readCompressedSize != 0xffffffff && readCompressedSize != _compressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + // If we have the zeroed flag set, then no size should be included + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize && readCompressedSize != 0) + { + return ZipReturn.ZipLocalFileHeaderError; + } + // If we don't have the zeroed flag set, then the size should match + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && readCompressedSize != _compressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + uint readUncompressedSize = br.ReadUInt32(); + // If we have Zip64, the uncompressed size should be 0xffffffff + if (_zip64 && readUncompressedSize != 0xffffffff && readUncompressedSize != _compressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + // If we have the zeroed flag set, then no size should be included + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize && readUncompressedSize != 0) + { + return ZipReturn.ZipLocalFileHeaderError; + } + // If we don't have the zeroed flag set, then the size should match + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == 0 && readUncompressedSize != _uncompressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + ushort fileNameLength = br.ReadUInt16(); + ushort extraFieldLength = br.ReadUInt16(); + + byte[] fileNameBytes = br.ReadBytes(fileNameLength); + string tempFileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0 + ? Encoding.ASCII.GetString(fileNameBytes) + : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength)); + + byte[] extraField = br.ReadBytes(extraFieldLength); + + /* + Full disclosure: this next section is in GordonJ's work but I honestly + have no idea everything that it does. It seems to do something to figure + out if it's Zip64, or possibly check for random things but it uses the + extra field for this, which I do not fully understand. It's copied in + its entirety below in the hope that it makes things better... + */ + + _zip64 = false; + int pos = 0; + while (extraFieldLength > pos) + { + ushort type = BitConverter.ToUInt16(extraField, pos); + pos += 2; + ushort blockLength = BitConverter.ToUInt16(extraField, pos); + pos += 2; + switch (type) + { + case 0x0001: + Zip64 = true; + if (readUncompressedSize == 0xffffffff) + { + ulong tLong = BitConverter.ToUInt64(extraField, pos); + if (tLong != UncompressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + pos += 8; + } + if (readCompressedSize == 0xffffffff) + { + ulong tLong = BitConverter.ToUInt64(extraField, pos); + if (tLong != _compressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + pos += 8; + } + break; + case 0x7075: + //byte version = extraField[pos]; + pos += 1; + uint nameCRC32 = BitConverter.ToUInt32(extraField, pos); + pos += 4; + + CRC32 crcTest = new CRC32(); + crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength); + uint fCRC = (uint)crcTest.Crc32Result; + + if (nameCRC32 != fCRC) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + int charLen = blockLength - 5; + + tempFileName = Encoding.UTF8.GetString(extraField, pos, charLen); + pos += charLen; + + break; + default: + pos += blockLength; + break; + } + } + + // Back to code I understand + if (String.Equals(_fileName, tempFileName, StringComparison.InvariantCulture)) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + // Set the position of the data + _dataLocation = (ulong)_zipstream.Position; + + // Now if no other data should be after the data, return + if((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize) + { + return ZipReturn.ZipGood; + } + + // Otherwise, compare the data after the file too + _zipstream.Seek((long)_compressedSize, SeekOrigin.Current); + + // If there's no subheader, read the next thing as crc + uint tempCrc = br.ReadUInt32(); + if (tempCrc != Constants.EndOfLocalFileHeaderSignature) + { + tempCrc = br.ReadUInt32(); + } + + if (tempCrc != _crc) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if (br.ReadUInt32() != _compressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + if (br.ReadUInt32() != _uncompressedSize) + { + return ZipReturn.ZipLocalFileHeaderError; + } + } + } + catch + { + return ZipReturn.ZipLocalFileHeaderError; + } + + return ZipReturn.ZipGood; + } + + /// + /// Read the local file header from the input stream, assuming correctness + /// + /// True if the local file header was read correctly, false otherwise + public ZipReturn ReadLocalFileHeaderQuick() + { + try + { + // We assume that the file is torrentzip until proven otherwise + _torrentZip = true; + + // Open the stream for reading + using (BinaryReader br = new BinaryReader(_zipstream)) + { + // Set the position of the writer based on the entry information + br.BaseStream.Seek((long)_relativeOffset, SeekOrigin.Begin); + + // If the first bytes aren't a local file header, log and return + if (br.ReadUInt32() != Constants.LocalFileHeaderSignature) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + // Now read in available information, ignoring unneeded + _versionNeeded = (ArchiveVersion)br.ReadUInt16(); + _generalPurposeBitFlag = (GeneralPurposeBitFlag)br.ReadUInt16(); + + // If the flag says there's no hash data, then we can't use quick mode + if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize) + { + return ZipReturn.ZipCannotFastOpen; + } + + _compressionMethod = (CompressionMethod)br.ReadUInt16(); + _lastModFileTime = br.ReadUInt16(); + _lastModFileDate = br.ReadUInt16(); + _crc = br.ReadUInt32(); + _compressedSize = br.ReadUInt32(); + _uncompressedSize = br.ReadUInt32(); + + ushort fileNameLength = br.ReadUInt16(); + ushort extraFieldLength = br.ReadUInt16(); + + byte[] fileNameBytes = br.ReadBytes(fileNameLength); + _fileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0 + ? Encoding.ASCII.GetString(fileNameBytes) + : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength)); + + byte[] extraField = br.ReadBytes(extraFieldLength); + + /* + Full disclosure: this next section is in GordonJ's work but I honestly + have no idea everything that it does. It seems to do something to figure + out if it's Zip64, or possibly check for random things but it uses the + extra field for this, which I do not fully understand. It's copied in + its entirety below in the hope that it makes things better... + */ + + _zip64 = false; + int pos = 0; + while (extraFieldLength > pos) + { + ushort type = BitConverter.ToUInt16(extraField, pos); + pos += 2; + ushort blockLength = BitConverter.ToUInt16(extraField, pos); + pos += 2; + switch (type) + { + case 0x0001: + Zip64 = true; + if (_uncompressedSize == 0xffffffff) + { + _uncompressedSize = BitConverter.ToUInt64(extraField, pos); + pos += 8; + } + if (_compressedSize == 0xffffffff) + { + _compressedSize = BitConverter.ToUInt64(extraField, pos); + pos += 8; + } + break; + case 0x7075: + pos += 1; + uint nameCRC32 = BitConverter.ToUInt32(extraField, pos); + pos += 4; + + CRC32 crcTest = new CRC32(); + crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength); + uint fCRC = (uint)crcTest.Crc32Result; + + if (nameCRC32 != fCRC) + { + return ZipReturn.ZipLocalFileHeaderError; + } + + int charLen = blockLength - 5; + + FileName = Encoding.UTF8.GetString(extraField, pos, charLen); + + pos += charLen; + + break; + default: + pos += blockLength; + break; + } + } + + // Set the position of the data + _dataLocation = (ulong)_zipstream.Position; + } + } + catch + { + return ZipReturn.ZipLocalFileHeaderError; + } + + return ZipReturn.ZipGood; + } + + /// + /// Write the local file header entry to the included stream + /// + public void WriteLocalFileHeader() + { + // Open the stream for writing + BinaryWriter bw = new BinaryWriter(_zipstream); + + // Create an empty extra field to start out with + List extraField = new List(); + + // Figure out if we're in Zip64 based on the size + _zip64 = _uncompressedSize >= 0xffffffff; + + // Now check for a unicode filename and set the flag accordingly + byte[] fileNameBytes; + if (Style.IsUnicode(_fileName)) + { + _generalPurposeBitFlag |= GeneralPurposeBitFlag.LanguageEncodingFlag; + fileNameBytes = Encoding.UTF8.GetBytes(_fileName); + } + else + { + fileNameBytes = Encoding.ASCII.GetBytes(_fileName); + } + + // Set the version needed to extract according to if it's Zip64 + ushort versionNeededToExtract = (ushort)(_zip64 ? ArchiveVersion.TorrentZip64 : ArchiveVersion.TorrentZip); + + // Now save the relative offset and write + _relativeOffset = (ulong)_zipstream.Position; + bw.Write(Constants.LocalFileHeaderSignature); + bw.Write(versionNeededToExtract); + bw.Write((ushort)_generalPurposeBitFlag); + bw.Write((ushort)_compressionMethod); + bw.Write(_lastModFileTime); + bw.Write(_lastModFileDate); + _crc32Location = (ulong)_zipstream.Position; + + // Now, write dummy bytes for crc, compressed size, and uncompressed size + bw.Write(0xffffffff); + bw.Write(0xffffffff); + bw.Write(0xffffffff); + + // If we have Zip64, add the right things to the extra field + if (_zip64) + { + for (int i = 0; i < 20; i++) + { + extraField.Add(0); + } + } + + // Write out the lengths and their associated fields + ushort fileNameLength = (ushort)fileNameBytes.Length; + bw.Write(fileNameLength); + + ushort extraFieldLength = (ushort)extraField.Count; + bw.Write(extraFieldLength); + + bw.Write(fileNameBytes, 0, fileNameLength); + + _extraLocation = (ulong)_zipstream.Position; + bw.Write(extraField.ToArray(), 0, extraFieldLength); + } + + /// + /// Open the read file stream + /// + /// If compression mode is deflate, use the zipstream as is, otherwise decompress + /// Output stream representing the correctly compressed stream + /// Size of the stream regardless of compression + /// Compression method to compare against + /// True if the output stream was read, false otherwise + public bool LocalFileOpenReadStream(bool raw, out Stream stream, out ulong streamSize, out CompressionMethod compressionMethod) + { + streamSize = 0; + compressionMethod = _compressionMethod; + + _readStream = null; + _zipstream.Seek((long)_dataLocation, SeekOrigin.Begin); + + switch (_compressionMethod) + { + case CompressionMethod.Deflated: + if (raw) + { + _readStream = _zipstream; + streamSize = _compressedSize; + } + else + { + _readStream = new DeflateStream(_zipstream, CompressionMode.Decompress, true); + streamSize = _uncompressedSize; + } + break; + case CompressionMethod.Stored: + _readStream = _zipstream; + streamSize = _compressedSize; + break; + } + stream = _readStream; + return (stream != null); + } + + /// + /// Close the read file stream + /// + /// True if the stream could be closed, false otherwise + public bool LocalFileCloseReadStream() + { + DeflateStream dfStream = _readStream as DeflateStream; + if (dfStream != null) + { + dfStream.Close(); + dfStream.Dispose(); + } + return true; + } + + /// + /// Open the write file stream + /// + /// If compression mode is deflate, use the zipstream as is, otherwise decompress + /// True if outputted stream should be torrentzipped, false otherwise + /// Uncompressed size of the stream + /// Compression method to compare against + /// Output stream representing the correctly compressed stream + /// True if the output stream was written, false otherwise + public bool LocalFileOpenWriteStream(bool raw, bool torrentZip, ulong uncompressedSize, CompressionMethod compressionMethod, out Stream stream) + { + _uncompressedSize = uncompressedSize; + _compressionMethod = compressionMethod; + + WriteLocalFileHeader(); + _dataLocation = (ulong)_zipstream.Position; + + if (raw) + { + _writeStream = _zipstream; + _torrentZip = torrentZip; + } + else + { + if (compressionMethod == CompressionMethod.Stored) + { + _writeStream = _zipstream; + _torrentZip = false; + } + else + { + _writeStream = new DeflateStream(_zipstream, CompressionMode.Compress, CompressionLevel.BestCompression, true); + _torrentZip = true; + } + } + + stream = _writeStream; + return (stream != null); + } + + /// + /// Close the write file stream + /// + /// CRC to assign to the current stream + /// True if the stream could be closed, false otherwise + public bool LocalFileCloseWriteStream(uint crc32) + { + DeflateStream dfStream = _writeStream as DeflateStream; + if (dfStream != null) + { + dfStream.Flush(); + dfStream.Close(); + dfStream.Dispose(); + } + + _compressedSize = (ulong)_zipstream.Position - _dataLocation; + + if (_compressedSize == 0 && _uncompressedSize == 0) + { + LocalFileAddDirectory(); + _compressedSize = (ulong)_zipstream.Position - _dataLocation; + } + + _crc = crc32; + WriteCompressedSize(); + + return true; + } + + /// + /// Write out the compressed size of the stream + /// + private void WriteCompressedSize() + { + // Save the current position before seeking + long posNow = _zipstream.Position; + _zipstream.Seek((long)_crc32Location, SeekOrigin.Begin); + + // Open the stream for writing + BinaryWriter bw = new BinaryWriter(_zipstream); + + // Get the 32-bit compatible sizes + uint compressedSize32; + uint uncompressedSize32; + if (_zip64) + { + compressedSize32 = 0xffffffff; + uncompressedSize32 = 0xffffffff; + } + else + { + compressedSize32 = (uint)_compressedSize; + uncompressedSize32 = (uint)_uncompressedSize; + } + + // Now write the data + bw.Write(_crc); + bw.Write(compressedSize32); + bw.Write(uncompressedSize32); + + // If we have Zip64, write additional data + if (_zip64) + { + _zipstream.Seek((long)_extraLocation, SeekOrigin.Begin); + bw.Write((ushort)0x0001); // id + bw.Write((ushort)16); // data length + bw.Write(_uncompressedSize); + bw.Write(_compressedSize); + } + + // Now seek back to the original position + _zipstream.Seek(posNow, SeekOrigin.Begin); + } + + /// + /// Get the data from the current file, if not already checked + /// + public void LocalFileCheck() + { + // If the file has been tested or has an error, return + if (_fileStatus != ZipReturn.ZipUntested) + { + return; + } + + try + { + Stream stream = null; + _zipstream.Seek((long)_dataLocation, SeekOrigin.Begin); + + switch (_compressionMethod) + { + case CompressionMethod.Deflated: + stream = new DeflateStream(_zipstream, CompressionMode.Decompress, true); + break; + case CompressionMethod.Stored: + stream = _zipstream; + break; + } + + if (stream == null) + { + _fileStatus = ZipReturn.ZipErrorGettingDataStream; + return; + } + + // Now get the hash of the stream + uint tempCrc; + using (OptimizedCRC crc = new OptimizedCRC()) + using (MD5 md5 = System.Security.Cryptography.MD5.Create()) + using (SHA1 sha1 = System.Security.Cryptography.SHA1.Create()) + using (BinaryReader fs = new BinaryReader(stream)) + { + byte[] buffer = new byte[1024]; + int read; + while ((read = fs.Read(buffer, 0, buffer.Length)) > 0) + { + crc.Update(buffer, 0, read); + md5.TransformBlock(buffer, 0, read, buffer, 0); + sha1.TransformBlock(buffer, 0, read, buffer, 0); + } + + crc.Update(buffer, 0, 0); + md5.TransformFinalBlock(buffer, 0, 0); + sha1.TransformFinalBlock(buffer, 0, 0); + + tempCrc = (uint)crc.Value; + _md5 = md5.Hash; + _sha1 = sha1.Hash; + } + + if (_compressionMethod == CompressionMethod.Deflated) + { + stream.Close(); + stream.Dispose(); + } + + _fileStatus = (_crc == tempCrc ? ZipReturn.ZipGood : ZipReturn.ZipCRCDecodeError); + } + catch + { + _fileStatus = ZipReturn.ZipDecodeError; + } + } + + /// + /// Add a directory marking to a local file + /// + public void LocalFileAddDirectory() + { + Stream ds = _zipstream; + ds.WriteByte(03); + ds.WriteByte(00); + } + } +} diff --git a/SabreTools.Helper/SabreTools.Helper.csproj b/SabreTools.Helper/SabreTools.Helper.csproj index df54732d..bfe64023 100644 --- a/SabreTools.Helper/SabreTools.Helper.csproj +++ b/SabreTools.Helper/SabreTools.Helper.csproj @@ -107,7 +107,7 @@ - + True diff --git a/SabreTools.Helper/Tools/FileTools.cs b/SabreTools.Helper/Tools/FileTools.cs index f32098ad..23458d1b 100644 --- a/SabreTools.Helper/Tools/FileTools.cs +++ b/SabreTools.Helper/Tools/FileTools.cs @@ -1,4 +1,4 @@ -using CRC32; +using OCRC; using SharpCompress.Archive; using SharpCompress.Archive.SevenZip; using SharpCompress.Common;