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))