Files
SabreTools/SabreTools.Helper/Objects/ZipFIle.cs
Matt Nadareski c69dddbc26 [FileTools, SimpleSort, ZipFile/Entry] TorrentZip works! Mostly!
Writing to torrentzip works but for some reason the compressed streams are not being generated at the correct size. This is odd and seems to only affect newly added files and not ones copied from other archives. Also, found some glaring flaws in the headered output that explains why I was having issues previously. Typos D=
2016-09-16 11:37:55 -07:00

999 lines
28 KiB
C#

using OCRC;
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace SabreTools.Helper
{
/// <remarks>
/// Based on work by GordonJ for RomVault
/// https://github.com/gjefferyes/RomVault/blob/master/ROMVault2/SupportedFiles/Zip/zipFile.cs
/// </remarks>
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<ZipFileEntry> _entries = new List<ZipFileEntry>();
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 List<ZipFileEntry> Entries
{
get { return _entries; }
}
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;
}
public bool Contains(string n)
{
return _entries.Contains(new ZipFileEntry(new MemoryStream(), n, true));
}
#endregion
#region Destructors
~ZipFile()
{
Dispose();
}
public void Dispose()
{
if (_zipstream != null)
{
_zipstream.Close();
_zipstream.Dispose();
}
}
#endregion
/// <summary>
/// Find the end of the central directory signature
/// </summary>
/// <returns>Status of the given stream</returns>
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;
}
/// <summary>
/// Read the end of the central directory
/// </summary>
/// <returns>Status of the given stream</returns>
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;
}
/// <summary>
/// Write the end of the central directory
/// </summary>
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);
}
/// <summary>
/// Read the end of the Zip64 central directory
/// </summary>
/// <returns>Status of the given stream</returns>
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;
}
/// <summary>
/// Write the end of the Zip64 central directory
/// </summary>
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
}
/// <summary>
/// Read the end of the Zip64 central directory locator
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Write the end of the Zip64 central directory locator
/// </summary>
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
}
/// <summary>
/// Open a new file as an archive
/// </summary>
/// <param name="filename">Name of the new file to open</param>
/// <param name="timestamp">Timestamp the file should have</param>
/// <param name="readHeaders">True if file headers should be read, false otherwise</param>
/// <returns>Status of the underlying stream</returns>
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
_zipstream = new FileStream(filename, FileMode.Open, FileAccess.Read);
int read = _zipstream.Read(new byte[1], 0, 1);
if (read != 1)
{
Close();
return ZipReturn.ZipErrorOpeningFile;
}
_zipstream.Position = 0;
}
catch (PathTooLongException)
{
Close();
return ZipReturn.ZipFileNameToLong;
}
catch (IOException)
{
Close();
return ZipReturn.ZipErrorOpeningFile;
}
// If we succeeded, 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;
}
}
/// <summary>
/// Create a new file as an archive
/// </summary>
/// <param name="filename">Name of the new file to create</param>
/// <returns>Status of the underlying stream</returns>
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
_zipstream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
ZipOpen = ZipOpenType.OpenWrite;
return ZipReturn.ZipGood;
}
/// <summary>
/// Close the file that the stream refers to
/// </summary>
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;
}
_centerDirSize = (ulong)_zipstream.Position - _centerDirStart;
// 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
_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;
}
/// <summary>
/// Close a failed stream
/// </summary>
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;
}
/// <summary>
/// Open the read file stream
/// </summary>
/// <param name="index">Index of entry to read</param>
/// <param name="raw">If compression mode is deflate, use the zipstream as is, otherwise decompress</param>
/// <param name="stream">Output stream representing the correctly compressed stream</param>
/// <param name="streamSize">Size of the stream regardless of compression</param>
/// <param name="compressionMethod">Compression method to compare against</param>
/// <returns>Status of the underlying stream</returns>
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);
}
/// <summary>
/// Open the read file stream wihtout verification, if possible
/// </summary>
/// <param name="index">Index of entry to read</param>
/// <param name="raw">If compression mode is deflate, use the zipstream as is, otherwise decompress</param>
/// <param name="stream">Output stream representing the correctly compressed stream</param>
/// <param name="streamSize">Size of the stream regardless of compression</param>
/// <param name="compressionMethod">Compression method to compare against</param>
/// <returns>Status of the underlying stream</returns>
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);
}
/// <summary>
/// Close the read file stream
/// </summary>
/// <returns></returns>
public ZipReturn CloseReadStream()
{
return _entries[_readIndex].CloseReadStream();
}
/// <summary>
/// Open the write file stream
/// </summary>
/// <param name="raw">If compression mode is deflate, use the zipstream as is, otherwise decompress</param>
/// <param name="torrentZip">True if outputted stream should be torrentzipped, false otherwise</param>
/// <param name="uncompressedSize">Uncompressed size of the stream</param>
/// <param name="compressionMethod">Compression method to compare against</param>
/// <param name="stream">Output stream representing the correctly compressed stream</param>
/// <returns>Status of the underlying stream</returns>
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, true);
ZipReturn zr = zfe.OpenWriteStream(raw, torrentZip, uncompressedSize, compressionMethod, out stream);
_entries.Add(zfe);
return zr;
}
/// <summary>
/// Close the write file stream
/// </summary>
/// <param name="crc32">CRC to assign to the current stream</param>
/// <returns>Status of the underlying stream</returns>
public ZipReturn CloseWriteStream(uint crc32)
{
return _entries[_entries.Count - 1].CloseWriteStream(crc32);
}
/// <summary>
/// Remove the last added entry, if possible
/// </summary>
/// <returns>Status of the underlying stream</returns>
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;
}
/// <summary>
/// Add a directory marking to a local file
/// </summary>
public void AddDirectory()
{
_entries[_entries.Count - 1].AddDirectory();
}
/// <summary>
/// Scan every individual entry for validity
/// </summary>
public void DeepScan()
{
foreach (ZipFileEntry zfe in _entries)
{
zfe.Check();
}
}
/// <summary>
/// Get the text associated with a return status
/// </summary>
/// <param name="zr">ZipReturn status to parse</param>
/// <returns>String associated with the ZipReturn</returns>
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;
}
/// <summary>
/// Compare two strings in TorrentZip format
/// </summary>
/// <param name="string1"></param>
/// <param name="string2"></param>
/// <returns></returns>
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;
}
}
}
}
}