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;