diff --git a/SabreTools.Helper/Data/Constants.cs b/SabreTools.Helper/Data/Constants.cs
index 854b3521..50c457dc 100644
--- a/SabreTools.Helper/Data/Constants.cs
+++ b/SabreTools.Helper/Data/Constants.cs
@@ -178,7 +178,7 @@ namespace SabreTools.Helper
public const uint EndOfLocalFileHeaderSignature = 0x08074b50;
public const uint CentralDirectoryHeaderSignature = 0x02014b50;
public const uint EndOfCentralDirSignature = 0x06054b50;
- public const uint Zip64EndOfCentralDirSignatue = 0x06064b50;
+ public const uint Zip64EndOfCentralDirSignature = 0x06064b50;
public const uint Zip64EndOfCentralDirectoryLocator = 0x07064b50;
#endregion
diff --git a/SabreTools.Helper/Data/Enums.cs b/SabreTools.Helper/Data/Enums.cs
index 06480e4e..3149761d 100644
--- a/SabreTools.Helper/Data/Enums.cs
+++ b/SabreTools.Helper/Data/Enums.cs
@@ -260,5 +260,16 @@
ZipUntested
}
+ ///
+ /// Zip open type
+ ///
+ /// https://raw.githubusercontent.com/gjefferyes/RomVault/5a93500001f0d068f32cf77a048950717507f733/ROMVault2/SupportedFiles/ZipEnums.cs
+ public enum ZipOpenType
+ {
+ Closed,
+ OpenRead,
+ OpenWrite
+ }
+
#endregion
}
diff --git a/SabreTools.Helper/Data/Flags.cs b/SabreTools.Helper/Data/Flags.cs
index a14fec1b..cfc9ddd1 100644
--- a/SabreTools.Helper/Data/Flags.cs
+++ b/SabreTools.Helper/Data/Flags.cs
@@ -80,4 +80,16 @@ namespace SabreTools.Helper
Bit1 = 0x0002,
Bit2 = 0x0004,
}
+
+ ///
+ /// Zipfile special status
+ ///
+ /// https://github.com/gjefferyes/RomVault/blob/5a93500001f0d068f32cf77a048950717507f733/ROMVault2/SupportedFiles/ZipEnums.cs
+ [Flags]
+ public enum ZipStatus
+ {
+ None = 0x0,
+ TorrentZip = 0x1,
+ ExtraData = 0x2
+ }
}
diff --git a/SabreTools.Helper/Objects/ZipFIle.cs b/SabreTools.Helper/Objects/ZipFIle.cs
index 6feacc3c..688181e3 100644
--- a/SabreTools.Helper/Objects/ZipFIle.cs
+++ b/SabreTools.Helper/Objects/ZipFIle.cs
@@ -1,5 +1,8 @@
-using Ionic.Zlib;
+using Ionic.Crc;
+using Ionic.Zlib;
+using OCRC;
using System;
+using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -11,7 +14,890 @@ namespace SabreTools.Helper
/// Based on work by GordonJ for RomVault
/// https://github.com/gjefferyes/RomVault/blob/master/ROMVault2/SupportedFiles/Zip/zipFile.cs
///
- public class ZipFIle
+ public class ZipFile : IDisposable
{
+ #region Private instance variables
+
+ private FileInfo _zipFileInfo;
+ private ulong _centerDirStart;
+ private ulong _centerDirSize;
+ private ulong _endOfCenterDir64;
+ private byte[] _fileComment;
+ private Stream _zipstream;
+ private uint _entriesCount;
+ private readonly List _entries = new List();
+ private ZipStatus _zipStatus;
+ private bool _zip64;
+ private ZipOpenType _zipOpen;
+ private int _readIndex;
+
+ #endregion
+
+ #region Public facing variables
+
+ public string ZipFilename
+ {
+ get { return (_zipFileInfo != null ? _zipFileInfo.FullName : ""); }
+ }
+ public long TimeStamp
+ {
+ get { return (_zipFileInfo != null ? _zipFileInfo.LastWriteTime.Ticks : 0); }
+ }
+ public ZipOpenType ZipOpen
+ {
+ get { return _zipOpen; }
+ set { _zipOpen = value; }
+ }
+ public ZipStatus ZipStatus
+ {
+ get { return _zipStatus; }
+ }
+ public int EntriesCount
+ {
+ get { return _entries.Count; }
+ }
+ public string Filename(int i)
+ {
+ return _entries[i].FileName;
+ }
+ public ulong UncompressedSize(int i)
+ {
+ return _entries[i].UncompressedSize;
+ }
+ public ulong? LocalHeader(int i)
+ {
+ return ((_entries[i].GeneralPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0
+ ? (ulong?)_entries[i].RelativeOffset
+ : null);
+ }
+ public ZipReturn FileStatus(int i)
+ {
+ return _entries[i].FileStatus;
+ }
+ public byte[] CRC32(int i)
+ {
+ return _entries[i].CRC;
+ }
+ public byte[] MD5(int i)
+ {
+ return _entries[i].MD5;
+ }
+ public byte[] SHA1(int i)
+ {
+ return _entries[i].SHA1;
+ }
+
+ #endregion
+
+ #region Destructors
+
+ ~ZipFile()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ if (_zipstream != null)
+ {
+ _zipstream.Close();
+ _zipstream.Dispose();
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Find the end of the central directory signature
+ ///
+ /// Status of the given stream
+ private ZipReturn FindEndOfCentralDirSignature()
+ {
+ long fileSize = _zipstream.Length;
+ long maxBackSearch = 0xffff;
+
+ if (_zipstream.Length < maxBackSearch)
+ {
+ maxBackSearch = _zipstream.Length;
+ }
+
+ const long buffsize = 0x400;
+ byte[] buffer = new byte[buffsize + 4];
+
+ long backPosition = 4;
+ while (backPosition < maxBackSearch)
+ {
+ backPosition += buffsize;
+ if (backPosition > maxBackSearch) backPosition = maxBackSearch;
+
+ long readSize = backPosition > (buffsize + 4) ? (buffsize + 4) : backPosition;
+
+ _zipstream.Position = fileSize - backPosition;
+
+ _zipstream.Read(buffer, 0, (int)readSize);
+
+
+ for (long i = readSize - 4; i >= 0; i--)
+ {
+ if ((buffer[i] != 0x50) || (buffer[i + 1] != 0x4b) || (buffer[i + 2] != 0x05) || (buffer[i + 3] != 0x06))
+ {
+ continue;
+ }
+
+ _zipstream.Position = (fileSize - backPosition) + i;
+ return ZipReturn.ZipGood;
+ }
+ }
+ return ZipReturn.ZipCentralDirError;
+ }
+
+ ///
+ /// Read the end of the central directory
+ ///
+ /// Status of the given stream
+ private ZipReturn ReadEndOfCentralDir()
+ {
+ // Open the stream for reading
+ BinaryReader br = new BinaryReader(_zipstream);
+
+ // If the stream doesn't start with the correct signature, return
+ uint thisSignature = br.ReadUInt32();
+ if (thisSignature != Constants.EndOfCentralDirSignature)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+
+ // If this is part of a spanned archive, return
+ ushort tushort = br.ReadUInt16(); // NumberOfThisDisk
+ if (tushort != 0)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+ tushort = br.ReadUInt16(); // NumberOfThisDiskCenterDir
+ if (tushort != 0)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+
+ // If the number of entries in the current disk doesn't match up with the total entries, return
+ _entriesCount = br.ReadUInt16(); // TotalNumberOfEntriesDisk
+ tushort = br.ReadUInt16(); // TotalNumber of entries in the central directory
+ if (tushort != _entriesCount)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+
+ _centerDirSize = br.ReadUInt32(); // SizeOfCenteralDir
+ _centerDirStart = br.ReadUInt32(); // Offset
+
+ // Get the file comment
+ ushort zipFileCommentLength = br.ReadUInt16();
+ _fileComment = br.ReadBytes(zipFileCommentLength);
+
+ // If there's extra data past the comment, flag that we have extra data
+ if (_zipstream.Position != _zipstream.Length)
+ {
+ _zipStatus |= ZipStatus.ExtraData;
+ }
+
+ return ZipReturn.ZipGood;
+ }
+
+ ///
+ /// Write the end of the central directory
+ ///
+ private void WriteEndOfCentralDir()
+ {
+ // Open the stream for writing
+ BinaryWriter bw = new BinaryWriter(_zipstream);
+
+ // Now write out all of the data
+ bw.Write(Constants.EndOfCentralDirSignature);
+ bw.Write((ushort)0); // NumberOfThisDisk
+ bw.Write((ushort)0); // NumberOfThisDiskCenterDir
+ bw.Write((ushort)(_entries.Count >= 0xffff ? 0xffff : _entries.Count)); // TotalNumberOfEnteriesDisk
+ bw.Write((ushort)(_entries.Count >= 0xffff ? 0xffff : _entries.Count)); // TotalNumber of enteries in the central directory
+ bw.Write((uint)(_centerDirSize >= 0xffffffff ? 0xffffffff : _centerDirSize));
+ bw.Write((uint)(_centerDirStart >= 0xffffffff ? 0xffffffff : _centerDirStart));
+ bw.Write((ushort)_fileComment.Length);
+ bw.Write(_fileComment, 0, _fileComment.Length);
+ }
+
+ ///
+ /// Read the end of the Zip64 central directory
+ ///
+ /// Status of the given stream
+ private ZipReturn ReadZip64EndOfCentralDir()
+ {
+ // Set the type of the archive to Zip64
+ _zip64 = true;
+
+ // Open the stream for reading
+ BinaryReader br = new BinaryReader(_zipstream);
+
+ // If the signature doesn't match, then return
+ uint thisSignature = br.ReadUInt32();
+ if (thisSignature != Constants.Zip64EndOfCentralDirSignature)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+
+ // If the size of the central dir record isn't right, return
+ ulong tulong = br.ReadUInt64(); // Size of zip64 end of central directory record
+ if (tulong != 44)
+ {
+ return ZipReturn.Zip64EndOfCentralDirError;
+ }
+
+ br.ReadUInt16(); // version made by
+
+ // If the version needed to extract isn't correct, return
+ ushort tushort = br.ReadUInt16(); // version needed to extract
+ if (tushort != (ushort)ArchiveVersion.TorrentZip64)
+ {
+ return ZipReturn.Zip64EndOfCentralDirError;
+ }
+
+ // If this is part of a spanned archive, return
+ uint tuint = br.ReadUInt32(); // number of this disk
+ if (tuint != 0)
+ {
+ return ZipReturn.Zip64EndOfCentralDirError;
+ }
+ tuint = br.ReadUInt32(); // number of the disk with the start of the central directory
+ if (tuint != 0)
+ {
+ return ZipReturn.Zip64EndOfCentralDirError;
+ }
+
+ // If the number of entries in the current disk doesn't match up with the total entries, return
+ _entriesCount = (uint)br.ReadUInt64(); // total number of entries in the central directory on this disk
+ tulong = br.ReadUInt64(); // total number of entries in the central directory
+ if (tulong != _entriesCount)
+ {
+ return ZipReturn.Zip64EndOfCentralDirError;
+ }
+
+ _centerDirSize = br.ReadUInt64(); // size of central directory
+ _centerDirStart = br.ReadUInt64(); // offset of start of central directory with respect to the starting disk number
+
+ return ZipReturn.ZipGood;
+ }
+
+ ///
+ /// Write the end of the Zip64 central directory
+ ///
+ private void WriteZip64EndOfCentralDir()
+ {
+ // Open the stream for writing
+ BinaryWriter bw = new BinaryWriter(_zipstream);
+
+ // Now write out all of the data
+ bw.Write(Constants.Zip64EndOfCentralDirSignature);
+ bw.Write((ulong)44); // Size of zip64 end of central directory record
+ bw.Write((ushort)ArchiveVersion.TorrentZip64); // version made by
+ bw.Write((ushort)ArchiveVersion.TorrentZip64); // version needed to extract
+ bw.Write((uint)0); // number of this disk
+ bw.Write((uint)0); // number of the disk with the start of the central directroy
+ bw.Write((ulong)_entries.Count); // total number of entries in the central directory on this disk
+ bw.Write((ulong)_entries.Count); // total number of entries in the central directory
+ bw.Write(_centerDirSize); // size of central directory
+ bw.Write(_centerDirStart); // offset of start of central directory with respect to the starting disk number
+ }
+
+ ///
+ /// Read the end of the Zip64 central directory locator
+ ///
+ ///
+ private ZipReturn ReadZip64EndOfCentralDirectoryLocator()
+ {
+ // Set the current archive type to Zip64
+ _zip64 = true;
+
+ // Open the stream for reading
+ BinaryReader br = new BinaryReader(_zipstream);
+
+ // If the signature doesn't match, return
+ uint thisSignature = br.ReadUInt32();
+ if (thisSignature != Constants.Zip64EndOfCentralDirectoryLocator)
+ {
+ return ZipReturn.ZipEndOfCentralDirectoryError;
+ }
+
+ // If the disk isn't the first and only, then return
+ uint tuint = br.ReadUInt32(); // number of the disk with the start of the zip64 end of centeral directory
+ if (tuint != 0)
+ {
+ return ZipReturn.Zip64EndOfCentralDirectoryLocatorError;
+ }
+
+ _endOfCenterDir64 = br.ReadUInt64(); // relative offset of the zip64 end of central directory record
+
+ tuint = br.ReadUInt32(); // total number of disks
+ if (tuint != 1)
+ {
+ return ZipReturn.Zip64EndOfCentralDirectoryLocatorError;
+ }
+
+ return ZipReturn.ZipGood;
+ }
+
+ ///
+ /// Write the end of the Zip64 central directory locator
+ ///
+ private void WriteZip64EndOfCentralDirectoryLocator()
+ {
+ // Open the stream for writing
+ BinaryWriter bw = new BinaryWriter(_zipstream);
+
+ // Now write the data
+ bw.Write(Constants.Zip64EndOfCentralDirectoryLocator);
+ bw.Write((uint)0); // number of the disk with the start of the zip64 end of centeral directory
+ bw.Write(_endOfCenterDir64); // relative offset of the zip64 end of central directroy record
+ bw.Write((uint)1); // total number of disks
+ }
+
+ ///
+ /// Open a new file as an archive
+ ///
+ /// Name of the new file to open
+ /// Timestamp the file should have
+ /// True if file headers should be read, false otherwise
+ /// Status of the underlying stream
+ public ZipReturn Open(string filename, long timestamp, bool readHeaders)
+ {
+ // If a stream already exists, close it
+ Close();
+
+ // Now, reset the archive information
+ _zipStatus = ZipStatus.None;
+ _zip64 = false;
+ _centerDirStart = 0;
+ _centerDirSize = 0;
+ _zipFileInfo = null;
+
+ // Then, attempt to open the file and get information from it
+ try
+ {
+ // If the input file doesn't exist, close the stream and return
+ if (!File.Exists(filename))
+ {
+ Close();
+ return ZipReturn.ZipErrorFileNotFound;
+ }
+
+ // Get the fileinfo object
+ _zipFileInfo = new FileInfo(filename);
+
+ // If the timestamps don't match, close the stream and return
+ if (_zipFileInfo.LastWriteTime.Ticks != timestamp)
+ {
+ Close();
+ return ZipReturn.ZipErrorTimeStamp;
+ }
+
+ // Now try to open the file for reading
+ FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
+ int errorcode = fs.Read(new byte[1], 0, 1);
+ if (errorcode != 0)
+ {
+ Close();
+ if (errorcode == 32)
+ {
+ return ZipReturn.ZipFileLocked;
+ }
+ return ZipReturn.ZipErrorOpeningFile;
+ }
+ }
+ catch (PathTooLongException)
+ {
+ Close();
+ return ZipReturn.ZipFileNameToLong;
+ }
+ catch (IOException)
+ {
+ Close();
+ return ZipReturn.ZipErrorOpeningFile;
+ }
+
+ // If we succedded, set the flag for read
+ _zipOpen = ZipOpenType.OpenRead;
+
+ // If we're not reading the headers, return
+ if (!readHeaders)
+ {
+ return ZipReturn.ZipGood;
+ }
+
+ //Otherwise, we want to get all of the archive information
+ try
+ {
+ // First, try to get the end of the central directory
+ ZipReturn zr = FindEndOfCentralDirSignature();
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // Now read the end of the central directory
+ long eocd = _zipstream.Position;
+ zr = ReadEndOfCentralDir();
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // If we have any indicators of Zip64, check for the Zip64 EOCD
+ if (_centerDirStart == 0xffffffff || _centerDirSize == 0xffffffff || _entriesCount == 0xffff)
+ {
+ _zip64 = true;
+
+ // Check for the Zip64 EOCD locator
+ _zipstream.Position = eocd - 20;
+ zr = ReadZip64EndOfCentralDirectoryLocator();
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // If it was found, read the Zip64 EOCD
+ _zipstream.Position = (long)_endOfCenterDir64;
+ zr = ReadZip64EndOfCentralDir();
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+ }
+
+ // Now that we have the rest of the information, check for TorrentZip
+ bool torrentZip = false;
+ if (_fileComment.Length == 22)
+ {
+ if (Encoding.ASCII.GetString(_fileComment).Substring(0, 14) == "TORRENTZIPPED-")
+ {
+ // First get to the right part of the stream
+ OptimizedCRC ocrc = new OptimizedCRC();
+ byte[] buffer = new byte[_centerDirSize];
+ _zipstream.Position = (long)_centerDirStart;
+
+ // Then read in the central directory and hash
+ BinaryReader br = new BinaryReader(_zipstream);
+ buffer = br.ReadBytes((int)_centerDirSize);
+ ocrc.Update(buffer, 0, (int)_centerDirSize);
+ string calculatedCrc = ocrc.Value.ToString("X8");
+
+ // If the hashes match, then we have a torrentzip file
+ string extractedCrc = Encoding.ASCII.GetString(_fileComment).Substring(14, 8);
+ if (String.Equals(calculatedCrc, extractedCrc, StringComparison.Ordinal))
+ {
+ torrentZip = true;
+ }
+ }
+ }
+
+ // With potential torrentzip out of the way, read the central directory
+ _zipstream.Position = (long)_centerDirStart;
+
+ // Remove any entries already listed in the archive
+ _entries.Clear();
+ _entries.Capacity = (int)_entriesCount;
+
+ // Now populate the entries from the central directory
+ for (int i = 0; i < _entriesCount; i++)
+ {
+ ZipFileEntry zfe = new ZipFileEntry(_zipstream);
+ zr = zfe.ReadCentralDirectory();
+
+ // If we get any errors, close and return
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // If we have a Zip64 entry, make sure the archive is
+ _zip64 |= zfe.Zip64;
+
+ // Now add the entry to the archive
+ _entries.Add(zfe);
+ }
+
+ // Now that the entries are populated, verify against the actual headers
+ for (int i = 0; i < _entriesCount; i++)
+ {
+ zr = _entries[i].ReadHeader();
+
+ // If we get any errors, close and return
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // If we have a torrentzipped entry, make sure the archive is
+ torrentZip &= _entries[i].TorrentZip;
+ }
+
+ // If we have a torrentzipped file, check the file order
+ if (torrentZip)
+ {
+ for (int i = 0; i < _entriesCount - 1; i++)
+ {
+ if (TorrentZipStringCompare(_entries[i].FileName, _entries[i + 1].FileName) < 0)
+ {
+ continue;
+ }
+ torrentZip = false;
+ break;
+ }
+ }
+
+ // Now check for torrentzipped directories if we still have a torrentZip file
+ if (torrentZip)
+ {
+ for (int i = 0; i < _entriesCount - 1; i++)
+ {
+ // See if we found a directory
+ string filename0 = _entries[i].FileName;
+ if (filename0.Substring(filename0.Length - 1, 1) != "/")
+ {
+ continue;
+ }
+
+ // See if the next file is in that directory
+ string filename1 = _entries[i + 1].FileName;
+ if (filename1.Length <= filename0.Length)
+ {
+ continue;
+ }
+ if (TorrentZipStringCompare(filename0, filename1.Substring(0, filename0.Length)) = 0)
+ {
+ continue;
+ }
+
+ // If we found a file in the directory, then we don't need the directory entry
+ torrentZip = false;
+ break;
+ }
+ }
+
+ // If we still have torrentzip, say the archive is too
+ if (torrentZip)
+ {
+ _zipStatus |= ZipStatus.TorrentZip;
+ }
+
+ return ZipReturn.ZipGood;
+ }
+ catch
+ {
+ Close();
+ return ZipReturn.ZipErrorReadingFile;
+ }
+ }
+
+ ///
+ /// Create a new file as an archive
+ ///
+ /// Name of the new file to create
+ /// Status of the underlying stream
+ public ZipReturn Create(string filename)
+ {
+ // If the file is already open, return
+ if (_zipOpen != ZipOpenType.Closed)
+ {
+ return ZipReturn.ZipFileAlreadyOpen;
+ }
+
+ // Otherwise, create the directory for the file
+ Directory.CreateDirectory(Path.GetDirectoryName(filename));
+ _zipFileInfo = new FileInfo(filename);
+
+ // Now try to open the file
+ FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
+ int errorcode = fs.Read(new byte[1], 0, 1);
+ if (errorcode != 0)
+ {
+ Close();
+ return ZipReturn.ZipErrorOpeningFile;
+ }
+ ZipOpen = ZipOpenType.OpenWrite;
+ return ZipReturn.ZipGood;
+ }
+
+ ///
+ /// Close the file that the stream refers to
+ ///
+ public void Close()
+ {
+ // If the stream is already closed, then just return
+ if (_zipOpen == ZipOpenType.Closed)
+ {
+ return;
+ }
+
+ // If the stream is opened for read, close it
+ if (_zipOpen == ZipOpenType.OpenRead)
+ {
+ Dispose();
+ _zipOpen = ZipOpenType.Closed;
+ return;
+ }
+
+ // Now, the only other choice is open for writing so we check everything is correct
+ _zip64 = false;
+ bool torrentZip = true;
+
+ // Check the central directory
+ _centerDirStart = (ulong)_zipstream.Position;
+ if (_centerDirStart >= 0xffffffff)
+ {
+ _zip64 = true;
+ }
+
+ // Now loop through and add all of the central directory entries
+ foreach (ZipFileEntry zfe in _entries)
+ {
+ zfe.WriteCentralDirectory(_zipstream);
+ _zip64 |= zfe.Zip64;
+ torrentZip &= zfe.TorrentZip;
+ }
+
+ // Then get the central directory hash
+ OptimizedCRC ocrc = new OptimizedCRC();
+ byte[] buffer = new byte[_centerDirSize];
+ long currentPosition = _zipstream.Position;
+ _zipstream.Position = (long)_centerDirStart;
+
+ // Then read in the central directory and hash
+ BinaryReader br = new BinaryReader(_zipstream);
+ buffer = br.ReadBytes((int)_centerDirSize);
+ ocrc.Update(buffer, 0, (int)_centerDirSize);
+ string calculatedCrc = ocrc.Value.ToString("X8");
+
+ // Finally get back to the original position
+ _zipstream.Position = currentPosition;
+
+ // Now set more of the information
+ _centerDirSize = (ulong)_zipstream.Position - _centerDirStart;
+ _fileComment = (torrentZip ? Encoding.ASCII.GetBytes(("TORRENTZIPPED-" + calculatedCrc).ToCharArray()) : new byte[0]);
+ _zipStatus = (torrentZip ? ZipStatus.TorrentZip : ZipStatus.None);
+
+ // If we have a Zip64 archive, write the correct information
+ if (_zip64)
+ {
+ _endOfCenterDir64 = (ulong)_zipstream.Position;
+ WriteZip64EndOfCentralDir();
+ WriteZip64EndOfCentralDirectoryLocator();
+ }
+
+ // Now write out the end of the central directory
+ WriteEndOfCentralDir();
+
+ // Finally, close and dispose of the stream
+ _zipstream.SetLength(_zipstream.Position);
+ _zipstream.Flush();
+ _zipstream.Close();
+ _zipstream.Dispose();
+
+ // Get the new file information
+ _zipFileInfo = new FileInfo(_zipFileInfo.FullName);
+
+ // And set the stream to closed
+ _zipOpen = ZipOpenType.Closed;
+ }
+
+ ///
+ /// Close a failed stream
+ ///
+ public void CloseFailed()
+ {
+ // If the stream is already closed, return
+ if (_zipOpen == ZipOpenType.Closed)
+ {
+ return;
+ }
+
+ // If we're open for read, close the underlying stream
+ if (_zipOpen == ZipOpenType.OpenRead)
+ {
+ Dispose();
+ _zipOpen = ZipOpenType.Closed;
+ return;
+ }
+
+ // Otherwise, we only have an open for write left
+ _zipstream.Flush();
+ _zipstream.Close();
+ _zipstream.Dispose();
+
+ // Delete the failed file
+ File.Delete(_zipFileInfo.FullName);
+ _zipFileInfo = null;
+ _zipOpen = ZipOpenType.Closed;
+ }
+
+ ///
+ /// Open the read file stream
+ ///
+ /// Index of entry to read
+ /// 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
+ /// Status of the underlying stream
+ public ZipReturn OpenReadStream(int index, bool raw, out Stream stream, out ulong streamSize, out CompressionMethod compressionMethod)
+ {
+ // Set all of the defaults
+ streamSize = 0;
+ compressionMethod = CompressionMethod.Stored;
+ _readIndex = index;
+ stream = null;
+
+ // If the file isn't open for read, return
+ if (_zipOpen != ZipOpenType.OpenRead)
+ {
+ return ZipReturn.ZipReadingFromOutputFile;
+ }
+
+ // Now try to read the local file header
+ ZipReturn zr = _entries[index].ReadHeader();
+ if (zr != ZipReturn.ZipGood)
+ {
+ Close();
+ return zr;
+ }
+
+ // Now return the results of opening the local file
+ return _entries[index].OpenReadStream(raw, out stream, out streamSize, out compressionMethod);
+ }
+
+ ///
+ /// Open the read file stream wihtout verification, if possible
+ ///
+ /// Index of entry to read
+ /// 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
+ /// Status of the underlying stream
+ public ZipReturn OpenReadStreamQuick(ulong pos, bool raw, out Stream stream, out ulong streamSize, out CompressionMethod compressionMethod)
+ {
+ // Get the temporary entry based on the defined position
+ ZipFileEntry tempEntry = new ZipFileEntry(_zipstream);
+ tempEntry.RelativeOffset = pos;
+
+ // Clear the local files and add this file instead
+ _entries.Clear();
+ _entries.Add(tempEntry);
+
+ // Now try to read the header quickly
+ ZipReturn zr = tempEntry.ReadHeaderQuick();
+ if (zr != ZipReturn.ZipGood)
+ {
+ stream = null;
+ streamSize = 0;
+ compressionMethod = CompressionMethod.Stored;
+ return zr;
+ }
+ _readIndex = 0;
+
+ // Return the file stream if it worked
+ return tempEntry.OpenReadStream(raw, out stream, out streamSize, out compressionMethod);
+ }
+
+ ///
+ /// Close the read file stream
+ ///
+ ///
+ public ZipReturn CloseReadStream()
+ {
+ return _entries[_readIndex].CloseReadStream();
+ }
+
+ ///
+ /// 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
+ /// Status of the underlying stream
+ public ZipReturn OpenWriteStream(bool raw, bool torrentZip, string filename, ulong uncompressedSize, CompressionMethod compressionMethod, out Stream stream)
+ {
+ // Check to see if the stream is writable
+ stream = null;
+ if (_zipOpen != ZipOpenType.OpenWrite)
+ {
+ return ZipReturn.ZipWritingToInputFile;
+ }
+
+ // Open the entry stream based on the current position
+ ZipFileEntry zfe = new ZipFileEntry(_zipstream, filename);
+ ZipReturn zr = zfe.OpenWriteStream(raw, torrentZip, uncompressedSize, compressionMethod, out stream);
+ _entries.Add(zfe);
+
+ return zr;
+ }
+
+ ///
+ /// Close the write file stream
+ ///
+ /// CRC to assign to the current stream
+ /// Status of the underlying stream
+ public ZipReturn CloseWriteStream(uint crc32)
+ {
+ return _entries[_entries.Count - 1].CloseWriteStream(crc32);
+ }
+
+ ///
+ /// Remove the last added entry, if possible
+ ///
+ /// Status of the underlying stream
+ public ZipReturn RollBack()
+ {
+ // If the stream isn't writable, return
+ if (_zipOpen != ZipOpenType.OpenWrite)
+ {
+ return ZipReturn.ZipWritingToInputFile;
+ }
+
+ // Otherwise, make sure there are entries to roll back
+ int fileCount = _entries.Count;
+ if (fileCount == 0)
+ {
+ return ZipReturn.ZipErrorRollBackFile;
+ }
+
+ // Get the last added entry and remove
+ ZipFileEntry zfe = _entries[fileCount - 1];
+ _entries.RemoveAt(fileCount - 1);
+ _zipstream.Position = (long)zfe.RelativeOffset;
+ return ZipReturn.ZipGood;
+ }
+
+ ///
+ /// Add a directory marking to a local file
+ ///
+ public void AddDirectory()
+ {
+ _entries[_entries.Count - 1].AddDirectory();
+ }
+
+ ///
+ /// Scan every individual entry for validity
+ ///
+ public void DeepScan()
+ {
+ foreach (ZipFileEntry zfe in _entries)
+ {
+ zfe.Check();
+ }
+ }
}
}
diff --git a/SabreTools.Helper/Objects/ZipFileEntry.cs b/SabreTools.Helper/Objects/ZipFileEntry.cs
index cb048ece..7f638900 100644
--- a/SabreTools.Helper/Objects/ZipFileEntry.cs
+++ b/SabreTools.Helper/Objects/ZipFileEntry.cs
@@ -158,7 +158,7 @@ namespace SabreTools.Helper
///
/// Read the central directory entry from the input stream
///
- /// True if the central directory was read correctly, false otherwise
+ /// Status of the underlying stream
public ZipReturn ReadCentralDirectory()
{
try
@@ -381,8 +381,8 @@ namespace SabreTools.Helper
///
/// Read the local file header from the input stream
///
- /// True if the local file header was read correctly, false otherwise
- public ZipReturn ReadLocalFileHeader()
+ /// Status of the underlying stream
+ public ZipReturn ReadHeader()
{
try
{
@@ -587,8 +587,8 @@ namespace SabreTools.Helper
///
/// 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()
+ /// Status of the underlying stream
+ public ZipReturn ReadHeaderQuick()
{
try
{
@@ -707,7 +707,7 @@ namespace SabreTools.Helper
///
/// Write the local file header entry to the included stream
///
- public void WriteLocalFileHeader()
+ public void WriteHeader()
{
// Open the stream for writing
BinaryWriter bw = new BinaryWriter(_zipstream);
@@ -777,8 +777,8 @@ namespace SabreTools.Helper
/// 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)
+ /// Status of the underlying stream
+ public ZipReturn OpenReadStream(bool raw, out Stream stream, out ulong streamSize, out CompressionMethod compressionMethod)
{
streamSize = 0;
compressionMethod = _compressionMethod;
@@ -806,14 +806,14 @@ namespace SabreTools.Helper
break;
}
stream = _readStream;
- return (stream != null);
+ return (stream == null ? ZipReturn.ZipErrorGettingDataStream : ZipReturn.ZipGood);
}
///
/// Close the read file stream
///
- /// True if the stream could be closed, false otherwise
- public bool LocalFileCloseReadStream()
+ /// Status of the underlying stream
+ public ZipReturn CloseReadStream()
{
DeflateStream dfStream = _readStream as DeflateStream;
if (dfStream != null)
@@ -821,7 +821,7 @@ namespace SabreTools.Helper
dfStream.Close();
dfStream.Dispose();
}
- return true;
+ return ZipReturn.ZipGood;
}
///
@@ -832,13 +832,13 @@ namespace SabreTools.Helper
/// 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)
+ /// Status of the underlying stream
+ public ZipReturn OpenWriteStream(bool raw, bool torrentZip, ulong uncompressedSize, CompressionMethod compressionMethod, out Stream stream)
{
_uncompressedSize = uncompressedSize;
_compressionMethod = compressionMethod;
- WriteLocalFileHeader();
+ WriteHeader();
_dataLocation = (ulong)_zipstream.Position;
if (raw)
@@ -861,15 +861,15 @@ namespace SabreTools.Helper
}
stream = _writeStream;
- return (stream != null);
+ return (stream == null ? ZipReturn.ZipErrorGettingDataStream : ZipReturn.ZipGood);
}
///
/// 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)
+ /// Status of the underlying stream
+ public ZipReturn CloseWriteStream(uint crc32)
{
DeflateStream dfStream = _writeStream as DeflateStream;
if (dfStream != null)
@@ -883,14 +883,14 @@ namespace SabreTools.Helper
if (_compressedSize == 0 && _uncompressedSize == 0)
{
- LocalFileAddDirectory();
+ AddDirectory();
_compressedSize = (ulong)_zipstream.Position - _dataLocation;
}
_crc = crc32;
WriteCompressedSize();
- return true;
+ return ZipReturn.ZipGood;
}
///
@@ -941,7 +941,7 @@ namespace SabreTools.Helper
///
/// Get the data from the current file, if not already checked
///
- public void LocalFileCheck()
+ public void Check()
{
// If the file has been tested or has an error, return
if (_fileStatus != ZipReturn.ZipUntested)
@@ -1012,11 +1012,110 @@ namespace SabreTools.Helper
///
/// Add a directory marking to a local file
///
- public void LocalFileAddDirectory()
+ public void AddDirectory()
{
Stream ds = _zipstream;
ds.WriteByte(03);
ds.WriteByte(00);
}
+
+ ///
+ /// Get the text associated with a return status
+ ///
+ /// ZipReturn status to parse
+ /// String associated with the ZipReturn
+ public static string ZipErrorMessageText(ZipReturn zr)
+ {
+ string ret = "Unknown";
+ switch (zr)
+ {
+ case ZipReturn.ZipGood:
+ ret = "";
+ break;
+ case ZipReturn.ZipFileCountError:
+ ret = "The number of file in the Zip does not mach the number of files in the Zips Centeral Directory";
+ break;
+ case ZipReturn.ZipSignatureError:
+ ret = "An unknown Signature Block was found in the Zip";
+ break;
+ case ZipReturn.ZipExtraDataOnEndOfZip:
+ ret = "Extra Data was found on the end of the Zip";
+ break;
+ case ZipReturn.ZipUnsupportedCompression:
+ ret = "An unsupported Compression method was found in the Zip, if you recompress this zip it will be usable";
+ break;
+ case ZipReturn.ZipLocalFileHeaderError:
+ ret = "Error reading a zipped file header information";
+ break;
+ case ZipReturn.ZipCentralDirError:
+ ret = "There is an error in the Zip Centeral Directory";
+ break;
+ case ZipReturn.ZipReadingFromOutputFile:
+ ret = "Trying to write to a Zip file open for output only";
+ break;
+ case ZipReturn.ZipWritingToInputFile:
+ ret = "Tring to read from a Zip file open for input only";
+ break;
+ case ZipReturn.ZipErrorGettingDataStream:
+ ret = "Error creating Data Stream";
+ break;
+ case ZipReturn.ZipCRCDecodeError:
+ ret = "CRC error";
+ break;
+ case ZipReturn.ZipDecodeError:
+ ret = "Error unzipping a file";
+ break;
+ }
+
+ return ret;
+ }
+
+ ///
+ /// Compare two strings in TorrentZip format
+ ///
+ ///
+ ///
+ ///
+ private static int TorrentZipStringCompare(string string1, string string2)
+ {
+ char[] bytes1 = string1.ToCharArray();
+ char[] bytes2 = string2.ToCharArray();
+
+ int pos1 = 0;
+ int pos2 = 0;
+
+ for (;;)
+ {
+ if (pos1 == bytes1.Length)
+ {
+ return ((pos2 == bytes2.Length) ? 0 : -1);
+ }
+ if (pos2 == bytes2.Length)
+ {
+ return 1;
+ }
+
+ int byte1 = bytes1[pos1++];
+ int byte2 = bytes2[pos2++];
+
+ if (byte1 >= 65 && byte1 <= 90)
+ {
+ byte1 += 0x20;
+ }
+ if (byte2 >= 65 && byte2 <= 90)
+ {
+ byte2 += 0x20;
+ }
+
+ if (byte1 < byte2)
+ {
+ return -1;
+ }
+ if (byte1 > byte2)
+ {
+ return 1;
+ }
+ }
+ }
}
}
diff --git a/SabreTools.Helper/SabreTools.Helper.csproj b/SabreTools.Helper/SabreTools.Helper.csproj
index bfe64023..4d75e236 100644
--- a/SabreTools.Helper/SabreTools.Helper.csproj
+++ b/SabreTools.Helper/SabreTools.Helper.csproj
@@ -108,7 +108,7 @@
-
+
True
True
diff --git a/SabreTools.Helper/Tools/FileTools.cs b/SabreTools.Helper/Tools/FileTools.cs
index 23458d1b..58f6f0ed 100644
--- a/SabreTools.Helper/Tools/FileTools.cs
+++ b/SabreTools.Helper/Tools/FileTools.cs
@@ -49,12 +49,12 @@ namespace SabreTools.Helper
// If the archive doesn't exist, create it
if (!File.Exists(archiveFileName))
{
- outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Create);
+ outarchive = System.IO.Compression.ZipFile.Open(archiveFileName, ZipArchiveMode.Create);
outarchive.Dispose();
}
// Open the archive for writing
- using (outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Update))
+ using (outarchive = System.IO.Compression.ZipFile.Open(archiveFileName, ZipArchiveMode.Update))
{
// If the archive doesn't already contain the entry, add it
if (outarchive.GetEntry(rom.Name) == null)
@@ -63,7 +63,7 @@ namespace SabreTools.Helper
}
// If there's a Date attached to the rom, change the entry to that Date
- if (!String.IsNullOrEmpty(rom.Date))
+ if (!string.IsNullOrEmpty(rom.Date))
{
DateTimeOffset dto = DateTimeOffset.Now;
if (DateTimeOffset.TryParse(rom.Date, out dto))
diff --git a/SabreTools.Helper/Tools/FileToolsHash.cs b/SabreTools.Helper/Tools/FileToolsHash.cs
index efe9de6b..afc594fa 100644
--- a/SabreTools.Helper/Tools/FileToolsHash.cs
+++ b/SabreTools.Helper/Tools/FileToolsHash.cs
@@ -32,11 +32,11 @@ namespace SabreTools.Helper
{
if (!File.Exists(archiveFileName))
{
- outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Create);
+ outarchive = System.IO.Compression.ZipFile.Open(archiveFileName, ZipArchiveMode.Create);
}
else
{
- outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Update);
+ outarchive = System.IO.Compression.ZipFile.Open(archiveFileName, ZipArchiveMode.Update);
}
if (File.Exists(input))