diff --git a/SabreTools.Library/FileTypes/BaseArchive.cs b/SabreTools.Library/FileTypes/BaseArchive.cs
index ba76c42c..8ba4d0c3 100644
--- a/SabreTools.Library/FileTypes/BaseArchive.cs
+++ b/SabreTools.Library/FileTypes/BaseArchive.cs
@@ -12,123 +12,123 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- public abstract class BaseArchive : Folder
- {
- #region Protected instance variables
+ public abstract class BaseArchive : Folder
+ {
+ #region Protected instance variables
- // Buffer size used by archives
- protected const int _bufferSize = 4096 * 128;
+ // Buffer size used by archives
+ protected const int _bufferSize = 4096 * 128;
- #endregion
+ #endregion
- #region Construtors
+ #region Construtors
- ///
- /// Create a new Archive with no base file
- ///
- public BaseArchive()
- {
- }
+ ///
+ /// Create a new Archive with no base file
+ ///
+ public BaseArchive()
+ {
+ }
- ///
- /// Create a new Archive from the given file
- ///
- /// Name of the file to use as an archive
- /// True if hashes for this file should be calculated, false otherwise (default)
- public BaseArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- }
+ ///
+ /// Create a new Archive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public BaseArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ }
- #endregion
+ #endregion
- #region Extraction
+ #region Extraction
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override abstract bool CopyAll(string outDir);
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override abstract bool CopyAll(string outDir);
- ///
- /// Attempt to extract an entry from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override abstract string CopyToFile(string entryName, string outDir);
+ ///
+ /// Attempt to extract an entry from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override abstract string CopyToFile(string entryName, string outDir);
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override abstract (MemoryStream, string) CopyToStream(string entryName);
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override abstract (MemoryStream, string) CopyToStream(string entryName);
- #endregion
+ #endregion
- #region Information
+ #region Information
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override abstract List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false);
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override abstract List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false);
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override abstract List GetEmptyFolders();
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override abstract List GetEmptyFolders();
- ///
- /// Check whether the input file is a standardized format
- ///
- public abstract bool IsTorrent();
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public abstract bool IsTorrent();
- #endregion
+ #endregion
- #region Writing
+ #region Writing
- ///
- /// Write an input file to an archive
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override abstract bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false);
+ ///
+ /// Write an input file to an archive
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override abstract bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false);
- ///
- /// Write an input stream to an archive
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override abstract bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false);
+ ///
+ /// Write an input stream to an archive
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override abstract bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false);
- ///
- /// Write a set of input files to an archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override abstract bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false);
+ ///
+ /// Write a set of input files to an archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override abstract bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false);
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/BaseFile.cs b/SabreTools.Library/FileTypes/BaseFile.cs
index d625639d..f3e01481 100644
--- a/SabreTools.Library/FileTypes/BaseFile.cs
+++ b/SabreTools.Library/FileTypes/BaseFile.cs
@@ -9,151 +9,115 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- public class BaseFile
- {
- #region Protected instance variables
+ public class BaseFile
+ {
+ #region Publicly facing variables
- protected FileType _fileType;
- protected string _filename;
- protected string _parent;
- protected string _date;
+ // TODO: Get all of these values automatically so there is no public "set"
+ public FileType Type { get; protected set; }
+ public string Filename { get; set; }
+ public string Parent { get; set; }
+ public string Date { get; set; }
+ public long? Size { get; set; }
+ public byte[] CRC { get; set; }
+ public byte[] MD5 { get; set; }
+ public byte[] SHA1 { get; set; }
+ public byte[] SHA256 { get; set; }
+ public byte[] SHA384 { get; set; }
+ public byte[] SHA512 { get; set; }
- // External hash values for the file
- protected long? _size;
- protected byte[] _crc;
- protected byte[] _md5;
- protected byte[] _sha1;
- protected byte[] _sha256;
- protected byte[] _sha384;
- protected byte[] _sha512;
+ #endregion
- #endregion
+ #region Construtors
- #region Publicly facing variables
+ ///
+ /// Create a new BaseFile with no base file
+ ///
+ public BaseFile()
+ {
+ }
- // TODO: Get all of these values automatically so there is no public "set"
- public FileType Type
- {
- get { return _fileType; }
- }
- public string Filename
- {
- get { return _filename; }
- set { _filename = value; }
- }
- public string Parent
- {
- get { return _parent; }
- set { _parent = value; }
- }
- public string Date
- {
- get { return _date; }
- set { _date = value; }
- }
- public long? Size
- {
- get { return _size; }
- set { _size = value; }
- }
- public byte[] CRC
- {
- get { return _crc; }
- set { _crc = value; }
- }
- public byte[] MD5
- {
- get { return _md5; }
- set { _md5 = value; }
- }
- public byte[] SHA1
- {
- get { return _sha1; }
- set { _sha1 = value; }
- }
- public byte[] SHA256
- {
- get { return _sha256; }
- set { _sha256 = value; }
- }
- public byte[] SHA384
- {
- get { return _sha384; }
- set { _sha384 = value; }
- }
- public byte[] SHA512
- {
- get { return _sha512; }
- set { _sha512 = value; }
- }
+ ///
+ /// Create a new BaseFile from the given file
+ ///
+ /// Name of the file to use
+ /// True if hashes for this file should be calculated (default), false otherwise
+ public BaseFile(string filename, bool getHashes = true)
+ {
+ this.Filename = filename;
- #endregion
+ if (getHashes)
+ {
+ BaseFile temp = Utilities.GetFileInfo(this.Filename);
- #region Construtors
+ if (temp != null)
+ {
+ this.Parent = temp.Parent;
+ this.Date = temp.Date;
+ this.CRC = temp.CRC;
+ this.MD5 = temp.MD5;
+ this.SHA1 = temp.SHA1;
+ this.SHA256 = temp.SHA256;
+ this.SHA384 = temp.SHA384;
+ this.SHA512 = temp.SHA512;
+ }
+ }
+ }
- ///
- /// Create a new BaseFile with no base file
- ///
- public BaseFile()
- {
- }
+ ///
+ /// Create a new BaseFile from the given file
+ ///
+ /// Name of the file to use
+ /// Stream to populate information from
+ /// True if hashes for this file should be calculated (default), false otherwise
+ public BaseFile(string filename, Stream stream, bool getHashes = true)
+ {
+ this.Filename = filename;
- ///
- /// Create a new BaseFile from the given file
- ///
- /// Name of the file to use
- /// True if hashes for this file should be calculated (default), false otherwise
- public BaseFile(string filename, bool getHashes = true)
- {
- this._filename = filename;
+ if (getHashes)
+ {
+ BaseFile temp = Utilities.GetStreamInfo(stream, stream.Length);
- if (getHashes)
- {
- BaseFile temp = Utilities.GetFileInfo(_filename);
+ if(temp != null)
+ {
+ this.Parent = temp.Parent;
+ this.Date = temp.Date;
+ this.CRC = temp.CRC;
+ this.MD5 = temp.MD5;
+ this.SHA1 = temp.SHA1;
+ this.SHA256 = temp.SHA256;
+ this.SHA384 = temp.SHA384;
+ this.SHA512 = temp.SHA512;
+ }
+ }
+
+ }
- if (temp != null)
- {
- this._parent = temp.Parent;
- this._date = temp.Date;
- this._crc = temp.CRC;
- this._md5 = temp.MD5;
- this._sha1 = temp.SHA1;
- this._sha256 = temp.SHA256;
- this._sha384 = temp.SHA384;
- this._sha512 = temp.SHA512;
- }
- }
- }
+ ///
+ /// Create a new BaseFile from the given metadata
+ ///
+ /// Name of the file to use
+ /// Parent folder or archive
+ /// File date
+ /// CRC hash as a byte array
+ /// MD5 hash as a byte array
+ /// SHA-1 hash as a byte array
+ /// SHA-256 hash as a byte array
+ /// SHA-384 hash as a byte array
+ /// SHA-512 hash as a byte array
+ public BaseFile(string filename, string parent, string date, byte[] crc, byte[] md5, byte[] sha1, byte[] sha256, byte[] sha384, byte[] sha512)
+ {
+ this.Filename = filename;
+ this.Parent = parent;
+ this.Date = date;
+ this.CRC = crc;
+ this.MD5 = md5;
+ this.SHA1 = sha1;
+ this.SHA256 = sha256;
+ this.SHA384 = sha384;
+ this.SHA512 = sha512;
+ }
- ///
- /// Create a new BaseFile from the given file
- ///
- /// Name of the file to use
- /// Stream to populate information from
- /// True if hashes for this file should be calculated (default), false otherwise
- public BaseFile(string filename, Stream stream, bool getHashes = true)
- {
- this._filename = filename;
-
- if (getHashes)
- {
- BaseFile temp = Utilities.GetStreamInfo(stream, stream.Length);
-
- if(temp != null)
- {
- this._parent = temp.Parent;
- this._date = temp.Date;
- this._crc = temp.CRC;
- this._md5 = temp.MD5;
- this._sha1 = temp.SHA1;
- this._sha256 = temp.SHA256;
- this._sha384 = temp.SHA384;
- this._sha512 = temp.SHA512;
- }
- }
-
- }
-
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/CHDFile.cs b/SabreTools.Library/FileTypes/CHDFile.cs
index 7fee31a2..a08ec7e9 100644
--- a/SabreTools.Library/FileTypes/CHDFile.cs
+++ b/SabreTools.Library/FileTypes/CHDFile.cs
@@ -11,491 +11,491 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- ///
- /// This is code adapted from chd.h and chd.cpp in MAME
- /// Additional archival code from https://github.com/rtissera/libchdr/blob/master/src/chd.h
- ///
- ///
- /// ----------------------------------------------
- /// Common CHD Header:
- /// 0x00-0x07 - CHD signature
- /// 0x08-0x0B - Header size
- /// 0x0C-0x0F - CHD version
- /// ----------------------------------------------
- /// CHD v1 header layout:
- /// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
- /// 0x14-0x17 - Compression
- /// 0x18-0x1B - 512-byte sectors per hunk
- /// 0x1C-0x1F - Hunk count
- /// 0x20-0x23 - Hard disk cylinder count
- /// 0x24-0x27 - Hard disk head count
- /// 0x28-0x2B - Hard disk sector count
- /// 0x2C-0x3B - MD5
- /// 0x3C-0x4B - Parent MD5
- /// ----------------------------------------------
- /// CHD v2 header layout:
- /// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
- /// 0x14-0x17 - Compression
- /// 0x18-0x1B - seclen-byte sectors per hunk
- /// 0x1C-0x1F - Hunk count
- /// 0x20-0x23 - Hard disk cylinder count
- /// 0x24-0x27 - Hard disk head count
- /// 0x28-0x2B - Hard disk sector count
- /// 0x2C-0x3B - MD5
- /// 0x3C-0x4B - Parent MD5
- /// 0x4C-0x4F - Number of bytes per sector (seclen)
- /// ----------------------------------------------
- /// CHD v3 header layout:
- /// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
- /// 0x14-0x17 - Compression
- /// 0x18-0x1B - Hunk count
- /// 0x1C-0x23 - Logical Bytes
- /// 0x24-0x2C - Metadata Offset
- /// ...
- /// 0x4C-0x4F - Hunk Bytes
- /// 0x50-0x63 - SHA-1
- /// 0x64-0x77 - Parent SHA-1
- /// 0x78-0x87 - Map
- /// ----------------------------------------------
- /// CHD v4 header layout:
- /// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
- /// 0x14-0x17 - Compression
- /// 0x18-0x1B - Hunk count
- /// 0x1C-0x23 - Logical Bytes
- /// 0x24-0x2C - Metadata Offset
- /// ...
- /// 0x2C-0x2F - Hunk Bytes
- /// 0x30-0x43 - SHA-1
- /// 0x44-0x57 - Parent SHA-1
- /// 0x58-0x6b - Raw SHA-1
- /// 0x6c-0x7b - Map
- /// ----------------------------------------------
- /// CHD v5 header layout:
- /// 0x10-0x13 - Compression format 1
- /// 0x14-0x17 - Compression format 2
- /// 0x18-0x1B - Compression format 3
- /// 0x1C-0x1F - Compression format 4
- /// 0x20-0x27 - Logical Bytes
- /// 0x28-0x2F - Map Offset
- /// 0x30-0x37 - Metadata Offset
- /// 0x38-0x3B - Hunk Bytes
- /// 0x3C-0x3F - Unit Bytes
- /// 0x40-0x53 - Raw SHA-1
- /// 0x54-0x67 - SHA-1
- /// 0x68-0x7b - Parent SHA-1
- /// ----------------------------------------------
- ///
- public class CHDFile : BaseFile
- {
- #region Private instance variables
-
- // Core parameters from the header
- private byte[] m_signature; // signature
- private uint m_headersize; // size of the header
- private uint m_version; // version of the header
- private ulong m_logicalbytes; // logical size of the raw CHD data in bytes
- private ulong m_mapoffset; // offset of map
- private ulong m_metaoffset; // offset to first metadata bit
- private uint m_sectorsperhunk; // number of sectors per hunk
- private uint m_hunkbytes; // size of each raw hunk in bytes
- private ulong m_hunkcount; // number of hunks represented
- private uint m_unitbytes; // size of each unit in bytes
- private ulong m_unitcount; // number of units represented
- private CHD_CODEC[] m_compression = new CHD_CODEC[4]; // array of compression types used
-
- // map information
- private uint m_mapentrybytes; // length of each entry in a map
-
- // additional required vars
- private uint? _headerVersion;
- private BinaryReader m_br; // Binary reader representing the CHD stream
-
- #endregion
-
- #region Pubically facing variables
-
- public uint? Version
- {
- get
- {
- if (_headerVersion == null)
- {
- _headerVersion = ValidateHeaderVersion();
- }
-
- return _headerVersion;
- }
- }
-
- #endregion
-
- #region Constructors
-
- ///
- /// Create a new, blank CHDFile
- ///
- public CHDFile()
- {
- this._fileType = FileType.CHD;
- }
-
- ///
- /// Create a new CHDFile from an input file
- ///
- ///
- public CHDFile(string filename)
- : this(Utilities.TryOpenRead(filename))
- {
- }
-
- ///
- /// Create a new CHDFile from an input stream
- ///
- /// Stream representing the CHD file
- public CHDFile(Stream chdstream)
- {
- _fileType = FileType.CHD;
- m_br = new BinaryReader(chdstream);
-
- _headerVersion = ValidateHeaderVersion();
-
- if (_headerVersion != null)
- {
- byte[] hash = GetHashFromHeader();
-
- if (hash != null)
- {
- if (hash.Length == Constants.MD5Length)
- {
- _md5 = hash;
- }
- else if (hash.Length == Constants.SHA1Length)
- {
- _sha1 = hash;
- }
- }
- }
- }
-
- #endregion
-
- #region Header Parsing
-
- ///
- /// Validate the initial signature, version, and header size
- ///
- /// Unsigned int containing the version number, null if invalid
- private uint? ValidateHeaderVersion()
- {
- try
- {
- // Seek to the beginning to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(0, SeekOrigin.Begin);
-
- // Read and verify the CHD signature
- m_signature = m_br.ReadBytes(8);
-
- // If no signature could be read, return null
- if (m_signature == null || m_signature.Length == 0)
- {
- return null;
- }
-
- if (!m_signature.StartsWith(Constants.CHDSignature, exact: true))
- {
- // throw CHDERR_INVALID_FILE;
- return null;
- }
-
- // Get the header size and version
- m_headersize = m_br.ReadUInt32Reverse();
- m_version = m_br.ReadUInt32Reverse();
-
- // If we have an invalid combination of size and version
- if ((m_version == 1 && m_headersize != Constants.CHD_V1_HEADER_SIZE)
- || (m_version == 2 && m_headersize != Constants.CHD_V2_HEADER_SIZE)
- || (m_version == 3 && m_headersize != Constants.CHD_V3_HEADER_SIZE)
- || (m_version == 4 && m_headersize != Constants.CHD_V4_HEADER_SIZE)
- || (m_version == 5 && m_headersize != Constants.CHD_V5_HEADER_SIZE)
- || (m_version < 1 || m_version > 5))
- {
- // throw CHDERR_UNSUPPORTED_VERSION;
- return null;
- }
-
- return m_version;
- }
- catch
- {
- return null;
- }
- }
-
- ///
- /// Get the internal MD5 (v1, v2) or SHA-1 (v3, v4, v5) from the CHD
- ///
- /// MD5 as a byte array, null on error
- private byte[] GetHashFromHeader()
- {
- // Validate the header by default just in case
- uint? version = ValidateHeaderVersion();
-
- // Now get the hash, if possible
- byte[] hash;
-
- // Now parse the rest of the header according to the version
- try
- {
- switch (version)
- {
- case 1:
- hash = ParseCHDv1Header();
- break;
- case 2:
- hash = ParseCHDv2Header();
- break;
- case 3:
- hash = ParseCHDv3Header();
- break;
- case 4:
- hash = ParseCHDv4Header();
- break;
- case 5:
- hash = ParseCHDv5Header();
- break;
- case null:
- default:
- // throw CHDERR_INVALID_FILE;
- return null;
- }
- }
- catch
- {
- // throw CHDERR_INVALID_FILE;
- return null;
- }
-
- return hash;
- }
-
- ///
- /// Parse a CHD v1 header
- ///
- /// The extracted MD5 on success, null otherwise
- private byte[] ParseCHDv1Header()
- {
- // Seek to after the signature to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(16, SeekOrigin.Begin);
-
- // Set the blank MD5 hash
- byte[] md5 = new byte[16];
-
- // Set offsets and defaults
- m_mapoffset = 0;
- m_mapentrybytes = 0;
-
- // Read the CHD flags
- uint flags = m_br.ReadUInt32Reverse();
-
- // Determine compression
- switch (m_br.ReadUInt32())
- {
- case 0: m_compression[0] = CHD_CODEC.NONE; break;
- case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
- default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
- }
-
- m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
-
- m_sectorsperhunk = m_br.ReadUInt32Reverse();
- m_hunkcount = m_br.ReadUInt32Reverse();
- m_br.ReadUInt32Reverse(); // Cylinder count
- m_br.ReadUInt32Reverse(); // Head count
- m_br.ReadUInt32Reverse(); // Sector count
-
- md5 = m_br.ReadBytes(16);
- m_br.ReadBytes(16); // Parent MD5
-
- return md5;
- }
-
- ///
- /// Parse a CHD v2 header
- ///
- /// The extracted MD5 on success, null otherwise
- private byte[] ParseCHDv2Header()
- {
- // Seek to after the signature to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(16, SeekOrigin.Begin);
-
- // Set the blank MD5 hash
- byte[] md5 = new byte[16];
-
- // Set offsets and defaults
- m_mapoffset = 0;
- m_mapentrybytes = 0;
-
- // Read the CHD flags
- uint flags = m_br.ReadUInt32Reverse();
-
- // Determine compression
- switch (m_br.ReadUInt32())
- {
- case 0: m_compression[0] = CHD_CODEC.NONE; break;
- case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
- default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
- }
-
- m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
-
- m_sectorsperhunk = m_br.ReadUInt32Reverse();
- m_hunkcount = m_br.ReadUInt32Reverse();
- m_br.ReadUInt32Reverse(); // Cylinder count
- m_br.ReadUInt32Reverse(); // Head count
- m_br.ReadUInt32Reverse(); // Sector count
-
- md5 = m_br.ReadBytes(16);
- m_br.ReadBytes(16); // Parent MD5
- m_br.ReadUInt32Reverse(); // Sector size
-
- return md5;
- }
-
- ///
- /// Parse a CHD v3 header
- ///
- /// The extracted SHA-1 on success, null otherwise
- private byte[] ParseCHDv3Header()
- {
- // Seek to after the signature to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(16, SeekOrigin.Begin);
-
- // Set the blank SHA-1 hash
- byte[] sha1 = new byte[20];
-
- // Set offsets and defaults
- m_mapoffset = 120;
- m_mapentrybytes = 16;
-
- // Read the CHD flags
- uint flags = m_br.ReadUInt32Reverse();
-
- // Determine compression
- switch (m_br.ReadUInt32())
- {
- case 0: m_compression[0] = CHD_CODEC.NONE; break;
- case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
- default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
- }
-
- m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
-
- m_hunkcount = m_br.ReadUInt32Reverse();
- m_logicalbytes = m_br.ReadUInt64Reverse();
- m_metaoffset = m_br.ReadUInt32Reverse();
-
- m_br.BaseStream.Seek(76, SeekOrigin.Begin);
- m_hunkbytes = m_br.ReadUInt32Reverse();
-
- m_br.BaseStream.Seek(Constants.CHDv3SHA1Offset, SeekOrigin.Begin);
- sha1 = m_br.ReadBytes(20);
-
- // guess at the units based on snooping the metadata
- // m_unitbytes = guess_unitbytes();
- m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
-
- return sha1;
- }
-
- ///
- /// Parse a CHD v4 header
- ///
- /// The extracted SHA-1 on success, null otherwise
- private byte[] ParseCHDv4Header()
- {
- // Seek to after the signature to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(16, SeekOrigin.Begin);
-
- // Set the blank SHA-1 hash
- byte[] sha1 = new byte[20];
-
- // Set offsets and defaults
- m_mapoffset = 108;
- m_mapentrybytes = 16;
-
- // Read the CHD flags
- uint flags = m_br.ReadUInt32Reverse();
-
- // Determine compression
- switch (m_br.ReadUInt32())
- {
- case 0: m_compression[0] = CHD_CODEC.NONE; break;
- case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
- case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
- default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
- }
-
- m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
-
- m_hunkcount = m_br.ReadUInt32Reverse();
- m_logicalbytes = m_br.ReadUInt64Reverse();
- m_metaoffset = m_br.ReadUInt32Reverse();
-
- m_br.BaseStream.Seek(44, SeekOrigin.Begin);
- m_hunkbytes = m_br.ReadUInt32Reverse();
-
- m_br.BaseStream.Seek(Constants.CHDv4SHA1Offset, SeekOrigin.Begin);
- sha1 = m_br.ReadBytes(20);
-
- // guess at the units based on snooping the metadata
- // m_unitbytes = guess_unitbytes();
- m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
- return sha1;
- }
-
- ///
- /// Parse a CHD v5 header
- ///
- /// The extracted SHA-1 on success, null otherwise
- private byte[] ParseCHDv5Header()
- {
- // Seek to after the signature to make sure we're reading the correct bytes
- m_br.BaseStream.Seek(16, SeekOrigin.Begin);
-
- // Set the blank SHA-1 hash
- byte[] sha1 = new byte[20];
-
- // Determine compression
- m_compression[0] = (CHD_CODEC)m_br.ReadUInt32Reverse();
- m_compression[1] = (CHD_CODEC)m_br.ReadUInt32Reverse();
- m_compression[2] = (CHD_CODEC)m_br.ReadUInt32Reverse();
- m_compression[3] = (CHD_CODEC)m_br.ReadUInt32Reverse();
-
- m_logicalbytes = m_br.ReadUInt64Reverse();
- m_mapoffset = m_br.ReadUInt64Reverse();
- m_metaoffset = m_br.ReadUInt64Reverse();
- m_hunkbytes = m_br.ReadUInt32Reverse();
- m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
- m_unitbytes = m_br.ReadUInt32Reverse();
- m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
-
- // m_allow_writes = !compressed();
-
- // determine properties of map entries
- // m_mapentrybytes = compressed() ? 12 : 4;
-
- m_br.BaseStream.Seek(Constants.CHDv5SHA1Offset, SeekOrigin.Begin);
- sha1 = m_br.ReadBytes(20);
- return sha1;
- }
-
- #endregion
- }
+ ///
+ /// This is code adapted from chd.h and chd.cpp in MAME
+ /// Additional archival code from https://github.com/rtissera/libchdr/blob/master/src/chd.h
+ ///
+ ///
+ /// ----------------------------------------------
+ /// Common CHD Header:
+ /// 0x00-0x07 - CHD signature
+ /// 0x08-0x0B - Header size
+ /// 0x0C-0x0F - CHD version
+ /// ----------------------------------------------
+ /// CHD v1 header layout:
+ /// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
+ /// 0x14-0x17 - Compression
+ /// 0x18-0x1B - 512-byte sectors per hunk
+ /// 0x1C-0x1F - Hunk count
+ /// 0x20-0x23 - Hard disk cylinder count
+ /// 0x24-0x27 - Hard disk head count
+ /// 0x28-0x2B - Hard disk sector count
+ /// 0x2C-0x3B - MD5
+ /// 0x3C-0x4B - Parent MD5
+ /// ----------------------------------------------
+ /// CHD v2 header layout:
+ /// 0x10-0x13 - Flags (1: Has parent MD5, 2: Disallow writes)
+ /// 0x14-0x17 - Compression
+ /// 0x18-0x1B - seclen-byte sectors per hunk
+ /// 0x1C-0x1F - Hunk count
+ /// 0x20-0x23 - Hard disk cylinder count
+ /// 0x24-0x27 - Hard disk head count
+ /// 0x28-0x2B - Hard disk sector count
+ /// 0x2C-0x3B - MD5
+ /// 0x3C-0x4B - Parent MD5
+ /// 0x4C-0x4F - Number of bytes per sector (seclen)
+ /// ----------------------------------------------
+ /// CHD v3 header layout:
+ /// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
+ /// 0x14-0x17 - Compression
+ /// 0x18-0x1B - Hunk count
+ /// 0x1C-0x23 - Logical Bytes
+ /// 0x24-0x2C - Metadata Offset
+ /// ...
+ /// 0x4C-0x4F - Hunk Bytes
+ /// 0x50-0x63 - SHA-1
+ /// 0x64-0x77 - Parent SHA-1
+ /// 0x78-0x87 - Map
+ /// ----------------------------------------------
+ /// CHD v4 header layout:
+ /// 0x10-0x13 - Flags (1: Has parent SHA-1, 2: Disallow writes)
+ /// 0x14-0x17 - Compression
+ /// 0x18-0x1B - Hunk count
+ /// 0x1C-0x23 - Logical Bytes
+ /// 0x24-0x2C - Metadata Offset
+ /// ...
+ /// 0x2C-0x2F - Hunk Bytes
+ /// 0x30-0x43 - SHA-1
+ /// 0x44-0x57 - Parent SHA-1
+ /// 0x58-0x6b - Raw SHA-1
+ /// 0x6c-0x7b - Map
+ /// ----------------------------------------------
+ /// CHD v5 header layout:
+ /// 0x10-0x13 - Compression format 1
+ /// 0x14-0x17 - Compression format 2
+ /// 0x18-0x1B - Compression format 3
+ /// 0x1C-0x1F - Compression format 4
+ /// 0x20-0x27 - Logical Bytes
+ /// 0x28-0x2F - Map Offset
+ /// 0x30-0x37 - Metadata Offset
+ /// 0x38-0x3B - Hunk Bytes
+ /// 0x3C-0x3F - Unit Bytes
+ /// 0x40-0x53 - Raw SHA-1
+ /// 0x54-0x67 - SHA-1
+ /// 0x68-0x7b - Parent SHA-1
+ /// ----------------------------------------------
+ ///
+ public class CHDFile : BaseFile
+ {
+ #region Private instance variables
+
+ // Core parameters from the header
+ private byte[] m_signature; // signature
+ private uint m_headersize; // size of the header
+ private uint m_version; // version of the header
+ private ulong m_logicalbytes; // logical size of the raw CHD data in bytes
+ private ulong m_mapoffset; // offset of map
+ private ulong m_metaoffset; // offset to first metadata bit
+ private uint m_sectorsperhunk; // number of sectors per hunk
+ private uint m_hunkbytes; // size of each raw hunk in bytes
+ private ulong m_hunkcount; // number of hunks represented
+ private uint m_unitbytes; // size of each unit in bytes
+ private ulong m_unitcount; // number of units represented
+ private CHD_CODEC[] m_compression = new CHD_CODEC[4]; // array of compression types used
+
+ // map information
+ private uint m_mapentrybytes; // length of each entry in a map
+
+ // additional required vars
+ private uint? _headerVersion;
+ private BinaryReader m_br; // Binary reader representing the CHD stream
+
+ #endregion
+
+ #region Pubically facing variables
+
+ public uint? Version
+ {
+ get
+ {
+ if (_headerVersion == null)
+ {
+ _headerVersion = ValidateHeaderVersion();
+ }
+
+ return _headerVersion;
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Create a new, blank CHDFile
+ ///
+ public CHDFile()
+ {
+ this.Type = FileType.CHD;
+ }
+
+ ///
+ /// Create a new CHDFile from an input file
+ ///
+ ///
+ public CHDFile(string filename)
+ : this(Utilities.TryOpenRead(filename))
+ {
+ }
+
+ ///
+ /// Create a new CHDFile from an input stream
+ ///
+ /// Stream representing the CHD file
+ public CHDFile(Stream chdstream)
+ {
+ this.Type = FileType.CHD;
+ m_br = new BinaryReader(chdstream);
+
+ _headerVersion = ValidateHeaderVersion();
+
+ if (_headerVersion != null)
+ {
+ byte[] hash = GetHashFromHeader();
+
+ if (hash != null)
+ {
+ if (hash.Length == Constants.MD5Length)
+ {
+ this.MD5 = hash;
+ }
+ else if (hash.Length == Constants.SHA1Length)
+ {
+ this.SHA1 = hash;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Header Parsing
+
+ ///
+ /// Validate the initial signature, version, and header size
+ ///
+ /// Unsigned int containing the version number, null if invalid
+ private uint? ValidateHeaderVersion()
+ {
+ try
+ {
+ // Seek to the beginning to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(0, SeekOrigin.Begin);
+
+ // Read and verify the CHD signature
+ m_signature = m_br.ReadBytes(8);
+
+ // If no signature could be read, return null
+ if (m_signature == null || m_signature.Length == 0)
+ {
+ return null;
+ }
+
+ if (!m_signature.StartsWith(Constants.CHDSignature, exact: true))
+ {
+ // throw CHDERR_INVALID_FILE;
+ return null;
+ }
+
+ // Get the header size and version
+ m_headersize = m_br.ReadUInt32Reverse();
+ m_version = m_br.ReadUInt32Reverse();
+
+ // If we have an invalid combination of size and version
+ if ((m_version == 1 && m_headersize != Constants.CHD_V1_HEADER_SIZE)
+ || (m_version == 2 && m_headersize != Constants.CHD_V2_HEADER_SIZE)
+ || (m_version == 3 && m_headersize != Constants.CHD_V3_HEADER_SIZE)
+ || (m_version == 4 && m_headersize != Constants.CHD_V4_HEADER_SIZE)
+ || (m_version == 5 && m_headersize != Constants.CHD_V5_HEADER_SIZE)
+ || (m_version < 1 || m_version > 5))
+ {
+ // throw CHDERR_UNSUPPORTED_VERSION;
+ return null;
+ }
+
+ return m_version;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Get the internal MD5 (v1, v2) or SHA-1 (v3, v4, v5) from the CHD
+ ///
+ /// MD5 as a byte array, null on error
+ private byte[] GetHashFromHeader()
+ {
+ // Validate the header by default just in case
+ uint? version = ValidateHeaderVersion();
+
+ // Now get the hash, if possible
+ byte[] hash;
+
+ // Now parse the rest of the header according to the version
+ try
+ {
+ switch (version)
+ {
+ case 1:
+ hash = ParseCHDv1Header();
+ break;
+ case 2:
+ hash = ParseCHDv2Header();
+ break;
+ case 3:
+ hash = ParseCHDv3Header();
+ break;
+ case 4:
+ hash = ParseCHDv4Header();
+ break;
+ case 5:
+ hash = ParseCHDv5Header();
+ break;
+ case null:
+ default:
+ // throw CHDERR_INVALID_FILE;
+ return null;
+ }
+ }
+ catch
+ {
+ // throw CHDERR_INVALID_FILE;
+ return null;
+ }
+
+ return hash;
+ }
+
+ ///
+ /// Parse a CHD v1 header
+ ///
+ /// The extracted MD5 on success, null otherwise
+ private byte[] ParseCHDv1Header()
+ {
+ // Seek to after the signature to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(16, SeekOrigin.Begin);
+
+ // Set the blank MD5 hash
+ byte[] md5 = new byte[16];
+
+ // Set offsets and defaults
+ m_mapoffset = 0;
+ m_mapentrybytes = 0;
+
+ // Read the CHD flags
+ uint flags = m_br.ReadUInt32Reverse();
+
+ // Determine compression
+ switch (m_br.ReadUInt32())
+ {
+ case 0: m_compression[0] = CHD_CODEC.NONE; break;
+ case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
+ default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
+ }
+
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
+
+ m_sectorsperhunk = m_br.ReadUInt32Reverse();
+ m_hunkcount = m_br.ReadUInt32Reverse();
+ m_br.ReadUInt32Reverse(); // Cylinder count
+ m_br.ReadUInt32Reverse(); // Head count
+ m_br.ReadUInt32Reverse(); // Sector count
+
+ md5 = m_br.ReadBytes(16);
+ m_br.ReadBytes(16); // Parent MD5
+
+ return md5;
+ }
+
+ ///
+ /// Parse a CHD v2 header
+ ///
+ /// The extracted MD5 on success, null otherwise
+ private byte[] ParseCHDv2Header()
+ {
+ // Seek to after the signature to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(16, SeekOrigin.Begin);
+
+ // Set the blank MD5 hash
+ byte[] md5 = new byte[16];
+
+ // Set offsets and defaults
+ m_mapoffset = 0;
+ m_mapentrybytes = 0;
+
+ // Read the CHD flags
+ uint flags = m_br.ReadUInt32Reverse();
+
+ // Determine compression
+ switch (m_br.ReadUInt32())
+ {
+ case 0: m_compression[0] = CHD_CODEC.NONE; break;
+ case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
+ default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
+ }
+
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
+
+ m_sectorsperhunk = m_br.ReadUInt32Reverse();
+ m_hunkcount = m_br.ReadUInt32Reverse();
+ m_br.ReadUInt32Reverse(); // Cylinder count
+ m_br.ReadUInt32Reverse(); // Head count
+ m_br.ReadUInt32Reverse(); // Sector count
+
+ md5 = m_br.ReadBytes(16);
+ m_br.ReadBytes(16); // Parent MD5
+ m_br.ReadUInt32Reverse(); // Sector size
+
+ return md5;
+ }
+
+ ///
+ /// Parse a CHD v3 header
+ ///
+ /// The extracted SHA-1 on success, null otherwise
+ private byte[] ParseCHDv3Header()
+ {
+ // Seek to after the signature to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(16, SeekOrigin.Begin);
+
+ // Set the blank SHA-1 hash
+ byte[] sha1 = new byte[20];
+
+ // Set offsets and defaults
+ m_mapoffset = 120;
+ m_mapentrybytes = 16;
+
+ // Read the CHD flags
+ uint flags = m_br.ReadUInt32Reverse();
+
+ // Determine compression
+ switch (m_br.ReadUInt32())
+ {
+ case 0: m_compression[0] = CHD_CODEC.NONE; break;
+ case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
+ default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
+ }
+
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
+
+ m_hunkcount = m_br.ReadUInt32Reverse();
+ m_logicalbytes = m_br.ReadUInt64Reverse();
+ m_metaoffset = m_br.ReadUInt32Reverse();
+
+ m_br.BaseStream.Seek(76, SeekOrigin.Begin);
+ m_hunkbytes = m_br.ReadUInt32Reverse();
+
+ m_br.BaseStream.Seek(Constants.CHDv3SHA1Offset, SeekOrigin.Begin);
+ sha1 = m_br.ReadBytes(20);
+
+ // guess at the units based on snooping the metadata
+ // m_unitbytes = guess_unitbytes();
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+
+ return sha1;
+ }
+
+ ///
+ /// Parse a CHD v4 header
+ ///
+ /// The extracted SHA-1 on success, null otherwise
+ private byte[] ParseCHDv4Header()
+ {
+ // Seek to after the signature to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(16, SeekOrigin.Begin);
+
+ // Set the blank SHA-1 hash
+ byte[] sha1 = new byte[20];
+
+ // Set offsets and defaults
+ m_mapoffset = 108;
+ m_mapentrybytes = 16;
+
+ // Read the CHD flags
+ uint flags = m_br.ReadUInt32Reverse();
+
+ // Determine compression
+ switch (m_br.ReadUInt32())
+ {
+ case 0: m_compression[0] = CHD_CODEC.NONE; break;
+ case 1: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 2: m_compression[0] = CHD_CODEC.ZLIB; break;
+ case 3: m_compression[0] = CHD_CODEC.AVHUFF; break;
+ default: /* throw CHDERR_UNKNOWN_COMPRESSION; */ return null;
+ }
+
+ m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC.NONE;
+
+ m_hunkcount = m_br.ReadUInt32Reverse();
+ m_logicalbytes = m_br.ReadUInt64Reverse();
+ m_metaoffset = m_br.ReadUInt32Reverse();
+
+ m_br.BaseStream.Seek(44, SeekOrigin.Begin);
+ m_hunkbytes = m_br.ReadUInt32Reverse();
+
+ m_br.BaseStream.Seek(Constants.CHDv4SHA1Offset, SeekOrigin.Begin);
+ sha1 = m_br.ReadBytes(20);
+
+ // guess at the units based on snooping the metadata
+ // m_unitbytes = guess_unitbytes();
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+ return sha1;
+ }
+
+ ///
+ /// Parse a CHD v5 header
+ ///
+ /// The extracted SHA-1 on success, null otherwise
+ private byte[] ParseCHDv5Header()
+ {
+ // Seek to after the signature to make sure we're reading the correct bytes
+ m_br.BaseStream.Seek(16, SeekOrigin.Begin);
+
+ // Set the blank SHA-1 hash
+ byte[] sha1 = new byte[20];
+
+ // Determine compression
+ m_compression[0] = (CHD_CODEC)m_br.ReadUInt32Reverse();
+ m_compression[1] = (CHD_CODEC)m_br.ReadUInt32Reverse();
+ m_compression[2] = (CHD_CODEC)m_br.ReadUInt32Reverse();
+ m_compression[3] = (CHD_CODEC)m_br.ReadUInt32Reverse();
+
+ m_logicalbytes = m_br.ReadUInt64Reverse();
+ m_mapoffset = m_br.ReadUInt64Reverse();
+ m_metaoffset = m_br.ReadUInt64Reverse();
+ m_hunkbytes = m_br.ReadUInt32Reverse();
+ m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
+ m_unitbytes = m_br.ReadUInt32Reverse();
+ m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
+
+ // m_allow_writes = !compressed();
+
+ // determine properties of map entries
+ // m_mapentrybytes = compressed() ? 12 : 4;
+
+ m_br.BaseStream.Seek(Constants.CHDv5SHA1Offset, SeekOrigin.Begin);
+ sha1 = m_br.ReadBytes(20);
+ return sha1;
+ }
+
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/CoreRarArchive.cs b/SabreTools.Library/FileTypes/CoreRarArchive.cs
index 8081878f..47ccbb2e 100644
--- a/SabreTools.Library/FileTypes/CoreRarArchive.cs
+++ b/SabreTools.Library/FileTypes/CoreRarArchive.cs
@@ -117,223 +117,223 @@ using SabreTools.Library.DatItems;
///
namespace SabreTools.Library.FileTypes
{
- public class CoreRarArchive : BaseArchive
- {
- // SFX Module Information
- public byte[] SFX;
+ public class CoreRarArchive : BaseArchive
+ {
+ // SFX Module Information
+ public byte[] SFX;
- // Standard Header Information
- public uint HeaderCRC32;
- public uint HeaderSize; // vint
- public RarHeaderFlags HeaderFlags; // vint
- public uint ExtraAreaSize; // vint
- public RarArchiveFlags ArchiveFlags; // vint
- public uint VolumeNumber; // vint
- public byte[] ExtraArea;
+ // Standard Header Information
+ public uint HeaderCRC32;
+ public uint HeaderSize; // vint
+ public RarHeaderFlags HeaderFlags; // vint
+ public uint ExtraAreaSize; // vint
+ public RarArchiveFlags ArchiveFlags; // vint
+ public uint VolumeNumber; // vint
+ public byte[] ExtraArea;
- // Encryption Header Information
- public uint EncryptionHeaderCRC32;
- public uint EncryptionHeaderSize; // vint
- public RarHeaderFlags EncryptionHeaderFlags; // vint
- public uint EncryptionVersion; // vint
- public uint EncryptionFlags; // vint
- public byte KDFCount;
- public byte[] Salt = new byte[16];
- public byte[] CheckValue = new byte[12];
+ // Encryption Header Information
+ public uint EncryptionHeaderCRC32;
+ public uint EncryptionHeaderSize; // vint
+ public RarHeaderFlags EncryptionHeaderFlags; // vint
+ public uint EncryptionVersion; // vint
+ public uint EncryptionFlags; // vint
+ public byte KDFCount;
+ public byte[] Salt = new byte[16];
+ public byte[] CheckValue = new byte[12];
- // Locator Information
- public uint LocatorSize; // vint
- public uint LocatorFlags; // vint
- public uint QuickOpenOffset; // vint
- public uint RecoveryRecordOffset; // vint
+ // Locator Information
+ public uint LocatorSize; // vint
+ public uint LocatorFlags; // vint
+ public uint QuickOpenOffset; // vint
+ public uint RecoveryRecordOffset; // vint
- // Entry Information
- public List Entries = new List();
+ // Entry Information
+ public List Entries = new List();
- #region Unimplemented methods
+ #region Unimplemented methods
- public override bool CopyAll(string outDir)
- {
- throw new NotImplementedException();
- }
+ public override bool CopyAll(string outDir)
+ {
+ throw new NotImplementedException();
+ }
- public override string CopyToFile(string entryName, string outDir)
- {
- throw new NotImplementedException();
- }
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ throw new NotImplementedException();
+ }
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- throw new NotImplementedException();
- }
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ throw new NotImplementedException();
+ }
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- throw new NotImplementedException();
- }
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ throw new NotImplementedException();
+ }
- public override List GetEmptyFolders()
- {
- throw new NotImplementedException();
- }
+ public override List GetEmptyFolders()
+ {
+ throw new NotImplementedException();
+ }
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
- }
+ #endregion
+ }
- public class CoreRarArchiveEntry
- {
- // Standard Entry Information
- public uint HeaderCRC32;
- public uint HeaderSize; // vint
- public RarHeaderType HeaderType; // vint
- public RarHeaderFlags HeaderFlags; // vint
- public uint ExtraAreaSize; // vint
- public uint DataAreaSize; // vint
- public RarFileFlags FileFlags; // vint
- public uint UnpackedSize; // vint
- public uint Attributes; // vint
- public uint mtime;
- public uint DataCRC32;
- public uint CompressionInformation; // vint
- public uint HostOS; // vint
- public uint NameLength; // vint
- public byte[] Name;
- public byte[] DataArea;
+ public class CoreRarArchiveEntry
+ {
+ // Standard Entry Information
+ public uint HeaderCRC32;
+ public uint HeaderSize; // vint
+ public RarHeaderType HeaderType; // vint
+ public RarHeaderFlags HeaderFlags; // vint
+ public uint ExtraAreaSize; // vint
+ public uint DataAreaSize; // vint
+ public RarFileFlags FileFlags; // vint
+ public uint UnpackedSize; // vint
+ public uint Attributes; // vint
+ public uint mtime;
+ public uint DataCRC32;
+ public uint CompressionInformation; // vint
+ public uint HostOS; // vint
+ public uint NameLength; // vint
+ public byte[] Name;
+ public byte[] DataArea;
- // File Encryption Information
- public uint EncryptionSize; // vint
- public RarEncryptionFlags EncryptionFlags; // vint
- public byte KDFCount;
- public byte[] Salt = new byte[16];
- public byte[] IV = new byte[16];
- public byte[] CheckValue = new byte[12];
+ // File Encryption Information
+ public uint EncryptionSize; // vint
+ public RarEncryptionFlags EncryptionFlags; // vint
+ public byte KDFCount;
+ public byte[] Salt = new byte[16];
+ public byte[] IV = new byte[16];
+ public byte[] CheckValue = new byte[12];
- // File Hash Information
- public uint HashSize; // vint
- public uint HashType; // vint
- public byte[] HashData = new byte[32];
+ // File Hash Information
+ public uint HashSize; // vint
+ public uint HashType; // vint
+ public byte[] HashData = new byte[32];
- // File Time Information
- public uint TimeSize; // vint
- public RarTimeFlags TimeFlags; // vint
- public uint TimeMtime;
- public ulong TimeMtime64;
- public uint TimeCtime;
- public ulong TimeCtime64;
- public uint TimeLtime;
- public ulong TimeLtime64;
+ // File Time Information
+ public uint TimeSize; // vint
+ public RarTimeFlags TimeFlags; // vint
+ public uint TimeMtime;
+ public ulong TimeMtime64;
+ public uint TimeCtime;
+ public ulong TimeCtime64;
+ public uint TimeLtime;
+ public ulong TimeLtime64;
- // File Version Information
- public uint VersionSize; // vint
- public const uint VersionFlags = 0; // vint
- public uint VersionNumber; // vint
+ // File Version Information
+ public uint VersionSize; // vint
+ public const uint VersionFlags = 0; // vint
+ public uint VersionNumber; // vint
- // File System Redirection Record
- public uint RedirectionSize; // vint
- public RarRedirectionType RedirectionType; // vint
- public uint RedirectionFlags; // vint
- public uint RedirectionNameLength; // vint
- public byte[] RedirectionName;
+ // File System Redirection Record
+ public uint RedirectionSize; // vint
+ public RarRedirectionType RedirectionType; // vint
+ public uint RedirectionFlags; // vint
+ public uint RedirectionNameLength; // vint
+ public byte[] RedirectionName;
- // Unix Owner Record
- public uint UnixOwnerSize; // vint
- public RarUnixOwnerRecordFlags UnixOwnerFlags; // vint
- public uint UnixOwnerUserNameLength; // vint
- public byte[] UnixOwnerUserName;
- public uint UnixOwnerGroupNameLength; // vint
- public byte[] UnixOwnerGroupName;
- public uint UnixOwnerUserId; // vint
- public uint UnixOwnerGroupId; // vint
+ // Unix Owner Record
+ public uint UnixOwnerSize; // vint
+ public RarUnixOwnerRecordFlags UnixOwnerFlags; // vint
+ public uint UnixOwnerUserNameLength; // vint
+ public byte[] UnixOwnerUserName;
+ public uint UnixOwnerGroupNameLength; // vint
+ public byte[] UnixOwnerGroupName;
+ public uint UnixOwnerUserId; // vint
+ public uint UnixOwnerGroupId; // vint
- // Service Data Information
- public uint ServiceSize; // vint
- public byte[] ServiceData;
- }
+ // Service Data Information
+ public uint ServiceSize; // vint
+ public byte[] ServiceData;
+ }
- // BELOW ARE CONCRETE IMPLEMENTATIONS OF HEADER DETAILS
+ // BELOW ARE CONCRETE IMPLEMENTATIONS OF HEADER DETAILS
- ///
- /// General archive block format used by all RAR block types
- ///
- public class GeneralArchiveBlockFormat
- {
- public uint HeaderCRC32;
- public uint HeaderSize; // vint
- public HeaderType HeaderType;
- public HeaderFlags HeaderFlags;
- public ulong ExtraAreaSize; // vint
- public ulong DataAreaSize; // vint
- public byte[] ExtraArea;
- public byte[] DataArea;
- }
+ ///
+ /// General archive block format used by all RAR block types
+ ///
+ public class GeneralArchiveBlockFormat
+ {
+ public uint HeaderCRC32;
+ public uint HeaderSize; // vint
+ public HeaderType HeaderType;
+ public HeaderFlags HeaderFlags;
+ public ulong ExtraAreaSize; // vint
+ public ulong DataAreaSize; // vint
+ public byte[] ExtraArea;
+ public byte[] DataArea;
+ }
- ///
- /// General extra area format used by all RAR extra area records
- ///
- public class GeneralExtraAreaFormat
- {
- public ulong Size; // vint
- public ulong Type; // vint
- public byte[] Data;
- }
+ ///
+ /// General extra area format used by all RAR extra area records
+ ///
+ public class GeneralExtraAreaFormat
+ {
+ public ulong Size; // vint
+ public ulong Type; // vint
+ public byte[] Data;
+ }
- ///
- /// Encryption header only present in encrypted archives
- ///
- /// Every proceeding header is started from 16 byte AES-256
- /// initialization vectors followed by encrypted header data
- ///
- public class ArchiveEncryptionHeader : GeneralArchiveBlockFormat
- {
- public new HeaderType HeaderType = HeaderType.ArchiveEncryptionHeader;
- public ulong EncryptionVersion; // vint
- public ulong EncryptionFlags; // vint
- }
+ ///
+ /// Encryption header only present in encrypted archives
+ ///
+ /// Every proceeding header is started from 16 byte AES-256
+ /// initialization vectors followed by encrypted header data
+ ///
+ public class ArchiveEncryptionHeader : GeneralArchiveBlockFormat
+ {
+ public new HeaderType HeaderType = HeaderType.ArchiveEncryptionHeader;
+ public ulong EncryptionVersion; // vint
+ public ulong EncryptionFlags; // vint
+ }
- ///
- /// Types of archive header
- ///
- public enum HeaderType : ulong // vint
- {
- MainArchiveHeader = 1,
- FileHeader = 2,
- ServiceHeader = 3,
- ArchiveEncryptionHeader = 4,
- EndOfArchiveHeader = 5,
- }
+ ///
+ /// Types of archive header
+ ///
+ public enum HeaderType : ulong // vint
+ {
+ MainArchiveHeader = 1,
+ FileHeader = 2,
+ ServiceHeader = 3,
+ ArchiveEncryptionHeader = 4,
+ EndOfArchiveHeader = 5,
+ }
- ///
- /// Flags common for all headers
- ///
- [Flags]
- public enum HeaderFlags : ulong // vint
- {
- ExtraAreaIsPresentInEndOfHeader = 0x0001,
- DataAreaIsPresentInEndOfHeader = 0x0002,
- BlocksWithUnknownType = 0x0004, // this flag must be skipped when updating an archive
- DataAreaIsContinuingFromPreviousVolume = 0x0008,
- DataAreaIsContinuingInNextVolume = 0x0010,
- BlockDependsOnPrecedingFileBlock = 0x0020,
- PreserveChildBlockIfHostBlockIsModified = 0x0040,
- }
+ ///
+ /// Flags common for all headers
+ ///
+ [Flags]
+ public enum HeaderFlags : ulong // vint
+ {
+ ExtraAreaIsPresentInEndOfHeader = 0x0001,
+ DataAreaIsPresentInEndOfHeader = 0x0002,
+ BlocksWithUnknownType = 0x0004, // this flag must be skipped when updating an archive
+ DataAreaIsContinuingFromPreviousVolume = 0x0008,
+ DataAreaIsContinuingInNextVolume = 0x0010,
+ BlockDependsOnPrecedingFileBlock = 0x0020,
+ PreserveChildBlockIfHostBlockIsModified = 0x0040,
+ }
}
diff --git a/SabreTools.Library/FileTypes/Folder.cs b/SabreTools.Library/FileTypes/Folder.cs
index 973c3719..ba0f0cd3 100644
--- a/SabreTools.Library/FileTypes/Folder.cs
+++ b/SabreTools.Library/FileTypes/Folder.cs
@@ -23,305 +23,305 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a folder for reading and writing
- ///
- public class Folder : BaseFile
- {
- #region Protected instance variables
+ ///
+ /// Represents a folder for reading and writing
+ ///
+ public class Folder : BaseFile
+ {
+ #region Protected instance variables
- protected List _children;
+ protected List _children;
- #endregion
+ #endregion
- #region Constructors
+ #region Constructors
- ///
- /// Create a new folder with no base file
- ///
- public Folder()
- : base()
- {
- _fileType = FileType.Folder;
- }
+ ///
+ /// Create a new folder with no base file
+ ///
+ public Folder()
+ : base()
+ {
+ this.Type = FileType.Folder;
+ }
- ///
- /// Create a new folder from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public Folder(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.Folder;
- }
+ ///
+ /// Create a new folder from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public Folder(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.Folder;
+ }
- #endregion
+ #endregion
- #region Extraction
+ #region Extraction
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public virtual bool CopyAll(string outDir)
- {
- // Copy all files from the current folder to the output directory recursively
- try
- {
- // Make sure the folders exist
- Directory.CreateDirectory(_filename);
- Directory.CreateDirectory(outDir);
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public virtual bool CopyAll(string outDir)
+ {
+ // Copy all files from the current folder to the output directory recursively
+ try
+ {
+ // Make sure the folders exist
+ Directory.CreateDirectory(this.Filename);
+ Directory.CreateDirectory(outDir);
- Directory.Copy(_filename, outDir, true, PathFormat.FullPath);
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return false;
- }
+ Directory.Copy(this.Filename, outDir, true, PathFormat.FullPath);
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return false;
+ }
- return true;
- }
+ return true;
+ }
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public virtual string CopyToFile(string entryName, string outDir)
- {
- string realentry = null;
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public virtual string CopyToFile(string entryName, string outDir)
+ {
+ string realentry = null;
- // Copy single file from the current folder to the output directory, if exists
- try
- {
- // Make sure the folders exist
- Directory.CreateDirectory(_filename);
- Directory.CreateDirectory(outDir);
+ // Copy single file from the current folder to the output directory, if exists
+ try
+ {
+ // Make sure the folders exist
+ Directory.CreateDirectory(this.Filename);
+ Directory.CreateDirectory(outDir);
- // Get all files from the input directory
- List files = Utilities.RetrieveFiles(_filename, new List());
+ // Get all files from the input directory
+ List files = Utilities.RetrieveFiles(this.Filename, new List());
- // Now sort through to find the first file that matches
- string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
+ // Now sort through to find the first file that matches
+ string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
- // If we had a file, copy that over to the new name
- if (!String.IsNullOrWhiteSpace(match))
- {
- realentry = match;
- File.Copy(match, Path.Combine(outDir, entryName));
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return realentry;
- }
+ // If we had a file, copy that over to the new name
+ if (!String.IsNullOrWhiteSpace(match))
+ {
+ realentry = match;
+ File.Copy(match, Path.Combine(outDir, entryName));
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return realentry;
+ }
- return realentry;
- }
+ return realentry;
+ }
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public virtual (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realentry = null;
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public virtual (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realentry = null;
- // Copy single file from the current folder to the output directory, if exists
- try
- {
- // Make sure the folders exist
- Directory.CreateDirectory(_filename);
+ // Copy single file from the current folder to the output directory, if exists
+ try
+ {
+ // Make sure the folders exist
+ Directory.CreateDirectory(this.Filename);
- // Get all files from the input directory
- List files = Utilities.RetrieveFiles(_filename, new List());
+ // Get all files from the input directory
+ List files = Utilities.RetrieveFiles(this.Filename, new List());
- // Now sort through to find the first file that matches
- string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
+ // Now sort through to find the first file that matches
+ string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
- // If we had a file, copy that over to the new name
- if (!String.IsNullOrWhiteSpace(match))
- {
- Utilities.TryOpenRead(match).CopyTo(ms);
- realentry = match;
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return (ms, realentry);
- }
+ // If we had a file, copy that over to the new name
+ if (!String.IsNullOrWhiteSpace(match))
+ {
+ Utilities.TryOpenRead(match).CopyTo(ms);
+ realentry = match;
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return (ms, realentry);
+ }
- return (ms, realentry);
- }
+ return (ms, realentry);
+ }
- #endregion
+ #endregion
- #region Information
+ #region Information
- ///
- /// Generate a list of immediate children from the current folder
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of BaseFile objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public virtual List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- if (_children == null || _children.Count == 0)
- {
- _children = new List();
- foreach (string file in Directory.EnumerateFiles(_filename, "*", SearchOption.TopDirectoryOnly))
- {
- BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date);
- _children.Add(nf);
- }
- foreach (string dir in Directory.EnumerateDirectories(_filename, "*", SearchOption.TopDirectoryOnly))
- {
- Folder fl = new Folder(dir);
- _children.Add(fl);
- }
- }
+ ///
+ /// Generate a list of immediate children from the current folder
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of BaseFile objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public virtual List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ if (_children == null || _children.Count == 0)
+ {
+ _children = new List();
+ foreach (string file in Directory.EnumerateFiles(this.Filename, "*", SearchOption.TopDirectoryOnly))
+ {
+ BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date);
+ _children.Add(nf);
+ }
+ foreach (string dir in Directory.EnumerateDirectories(this.Filename, "*", SearchOption.TopDirectoryOnly))
+ {
+ Folder fl = new Folder(dir);
+ _children.Add(fl);
+ }
+ }
- return _children;
- }
+ return _children;
+ }
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the folder
- public virtual List GetEmptyFolders()
- {
- return Utilities.GetEmptyDirectories(_filename).ToList();
- }
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the folder
+ public virtual List GetEmptyFolders()
+ {
+ return Utilities.GetEmptyDirectories(this.Filename).ToList();
+ }
- #endregion
+ #endregion
- #region Writing
+ #region Writing
- ///
- /// Write an input file to an output folder
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public virtual bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- FileStream fs = Utilities.TryOpenRead(inputFile);
- return Write(fs, outDir, rom, date, romba);
- }
+ ///
+ /// Write an input file to an output folder
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public virtual bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ FileStream fs = Utilities.TryOpenRead(inputFile);
+ return Write(fs, outDir, rom, date, romba);
+ }
- ///
- /// Write an input stream to an output folder
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public virtual bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- bool success = false;
+ ///
+ /// Write an input stream to an output folder
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public virtual bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ bool success = false;
- // If either input is null or empty, return
- if (inputStream == null || rom == null || rom.Name == null)
- {
- return success;
- }
+ // If either input is null or empty, return
+ if (inputStream == null || rom == null || rom.Name == null)
+ {
+ return success;
+ }
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
- // Set internal variables
- FileStream outputStream = null;
+ // Set internal variables
+ FileStream outputStream = null;
- // Get the output folder name from the first rebuild rom
- string fileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName), Utilities.RemovePathUnsafeCharacters(rom.Name));
+ // Get the output folder name from the first rebuild rom
+ string fileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName), Utilities.RemovePathUnsafeCharacters(rom.Name));
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(fileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(fileName));
- }
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(fileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(fileName));
+ }
- // Overwrite output files by default
- outputStream = Utilities.TryCreate(fileName);
+ // Overwrite output files by default
+ outputStream = Utilities.TryCreate(fileName);
- // If the output stream isn't null
- if (outputStream != null)
- {
- // Copy the input stream to the output
- inputStream.Seek(0, SeekOrigin.Begin);
- int bufferSize = 4096 * 128;
- byte[] ibuffer = new byte[bufferSize];
- int ilen;
- while ((ilen = inputStream.Read(ibuffer, 0, bufferSize)) > 0)
- {
- outputStream.Write(ibuffer, 0, ilen);
- outputStream.Flush();
- }
- outputStream.Dispose();
+ // If the output stream isn't null
+ if (outputStream != null)
+ {
+ // Copy the input stream to the output
+ inputStream.Seek(0, SeekOrigin.Begin);
+ int bufferSize = 4096 * 128;
+ byte[] ibuffer = new byte[bufferSize];
+ int ilen;
+ while ((ilen = inputStream.Read(ibuffer, 0, bufferSize)) > 0)
+ {
+ outputStream.Write(ibuffer, 0, ilen);
+ outputStream.Flush();
+ }
+ outputStream.Dispose();
- if (rom.ItemType == ItemType.Rom)
- {
- if (date && !String.IsNullOrWhiteSpace(((Rom)rom).Date))
- {
- File.SetCreationTime(fileName, DateTime.Parse(((Rom)rom).Date));
- }
- }
+ if (rom.ItemType == ItemType.Rom)
+ {
+ if (date && !String.IsNullOrWhiteSpace(((Rom)rom).Date))
+ {
+ File.SetCreationTime(fileName, DateTime.Parse(((Rom)rom).Date));
+ }
+ }
- success = true;
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- inputStream.Dispose();
- outputStream?.Dispose();
- }
+ success = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ inputStream.Dispose();
+ outputStream?.Dispose();
+ }
- return success;
- }
+ return success;
+ }
- ///
- /// Write a set of input files to an output folder (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public virtual bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write a set of input files to an output folder (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public virtual bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/GZipArchive.cs b/SabreTools.Library/FileTypes/GZipArchive.cs
index cbd47bf7..0314ab23 100644
--- a/SabreTools.Library/FileTypes/GZipArchive.cs
+++ b/SabreTools.Library/FileTypes/GZipArchive.cs
@@ -24,528 +24,528 @@ using Ionic.Zlib;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a TorrentGZip archive for reading and writing
- ///
- public class GZipArchive : BaseArchive
- {
- #region Constructors
-
- ///
- /// Create a new TorrentGZipArchive with no base file
- ///
- public GZipArchive()
- : base()
- {
- _fileType = FileType.GZipArchive;
- }
-
- ///
- /// Create a new TorrentGZipArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public GZipArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.GZipArchive;
- }
-
- #endregion
-
- #region Extraction
-
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- bool encounteredErrors = true;
-
- try
- {
- // Create the temp directory
- Directory.CreateDirectory(outDir);
-
- // Decompress the _filename stream
- FileStream outstream = Utilities.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(_filename)));
- GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(_filename), Ionic.Zlib.CompressionMode.Decompress);
- gzstream.CopyTo(outstream);
-
- // Dispose of the streams
- outstream.Dispose();
- gzstream.Dispose();
-
- encounteredErrors = false;
- }
- catch (EndOfStreamException)
- {
- // Catch this but don't count it as an error because SharpCompress is unsafe
- }
- catch (InvalidOperationException)
- {
- encounteredErrors = true;
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- encounteredErrors = true;
- }
-
- return encounteredErrors;
- }
-
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- // Try to extract a stream using the given information
- (MemoryStream ms, string realEntry) = CopyToStream(entryName);
-
- // If the memory stream and the entry name are both non-null, we write to file
- if (ms != null && realEntry != null)
- {
- realEntry = Path.Combine(outDir, realEntry);
-
- // Create the output subfolder now
- Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
-
- // Now open and write the file if possible
- FileStream fs = Utilities.TryCreate(realEntry);
- if (fs != null)
- {
- ms.Seek(0, SeekOrigin.Begin);
- byte[] zbuffer = new byte[_bufferSize];
- int zlen;
- while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
- {
- fs.Write(zbuffer, 0, zlen);
- fs.Flush();
- }
-
- ms?.Dispose();
- fs?.Dispose();
- }
- else
- {
- ms?.Dispose();
- fs?.Dispose();
- realEntry = null;
- }
- }
-
- return realEntry;
- }
-
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realEntry = null;
-
- try
- {
- // Decompress the _filename stream
- realEntry = Path.GetFileNameWithoutExtension(_filename);
- GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(_filename), Ionic.Zlib.CompressionMode.Decompress);
-
- // Write the file out
- byte[] gbuffer = new byte[_bufferSize];
- int glen;
- while ((glen = gzstream.Read(gbuffer, 0, _bufferSize)) > 0)
- {
-
- ms.Write(gbuffer, 0, glen);
- ms.Flush();
- }
-
- // Dispose of the streams
- gzstream.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- ms = null;
- realEntry = null;
- }
-
- return (ms, realEntry);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- if (_children == null || _children.Count == 0)
- {
- _children = new List();
-
- string gamename = Path.GetFileNameWithoutExtension(_filename);
-
- BaseFile possibleTgz = GetTorrentGZFileInfo();
-
- // If it was, then add it to the outputs and continue
- if (possibleTgz != null && possibleTgz.Filename != null)
- {
- _children.Add(possibleTgz);
- }
- else
- {
- try
- {
- // If secure hashes are disabled, do a quickscan
- if (omitFromScan == Hash.SecureHashes)
- {
- BaseFile tempRom = new BaseFile()
- {
- Filename = gamename,
- };
- BinaryReader br = new BinaryReader(Utilities.TryOpenRead(_filename));
- br.BaseStream.Seek(-8, SeekOrigin.End);
- byte[] headercrc = br.ReadBytesReverse(4);
- tempRom.CRC = headercrc;
- tempRom.Size = br.ReadInt32Reverse();
- br.Dispose();
-
- _children.Add(tempRom);
- }
- // Otherwise, use the stream directly
- else
- {
- GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(_filename), Ionic.Zlib.CompressionMode.Decompress);
- BaseFile gzipEntryRom = Utilities.GetStreamInfo(gzstream, gzstream.Length, omitFromScan: omitFromScan);
- gzipEntryRom.Filename = gzstream.FileName;
- gzipEntryRom.Parent = gamename;
- gzipEntryRom.Date = (date && gzstream.LastModified != null ? gzstream.LastModified?.ToString("yyyy/MM/dd hh:mm:ss") : null);
- _children.Add(gzipEntryRom);
- gzstream.Dispose();
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return null;
- }
- }
- }
-
- return _children;
- }
-
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- // GZip files don't contain directories
- return new List();
- }
-
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- // Check for the file existing first
- if (!File.Exists(_filename))
- {
- return false;
- }
-
- string datum = Path.GetFileName(_filename).ToLowerInvariant();
- long filesize = new FileInfo(_filename).Length;
-
- // If we have the romba depot files, just skip them gracefully
- if (datum == ".romba_size" || datum == ".romba_size.backup")
- {
- Globals.Logger.Verbose("Romba depot file found, skipping: {0}", _filename);
- return false;
- }
-
- // Check if the name is the right length
- if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
- {
- Globals.Logger.Warning("Non SHA-1 filename found, skipping: '{0}'", Path.GetFullPath(_filename));
- return false;
- }
-
- // Check if the file is at least the minimum length
- if (filesize < 40 /* bytes */)
- {
- Globals.Logger.Warning("Possibly corrupt file '{0}' with size {1}", Path.GetFullPath(_filename), Utilities.GetBytesReadable(filesize));
- return false;
- }
-
- // Get the Romba-specific header data
- byte[] header; // Get preamble header for checking
- byte[] headermd5; // MD5
- byte[] headercrc; // CRC
- ulong headersz; // Int64 size
- BinaryReader br = new BinaryReader(Utilities.TryOpenRead(_filename));
- header = br.ReadBytes(12);
- headermd5 = br.ReadBytes(16);
- headercrc = br.ReadBytes(4);
- headersz = br.ReadUInt64();
- br.Dispose();
-
- // If the header is not correct, return a blank rom
- bool correct = true;
- for (int i = 0; i < header.Length; i++)
- {
- // This is a temp fix to ignore the modification time and OS until romba can be fixed
- if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9)
- {
- continue;
- }
- correct &= (header[i] == Constants.TorrentGZHeader[i]);
- }
- if (!correct)
- {
- return false;
- }
-
- return true;
- }
-
- ///
- /// Retrieve file information for a single torrent GZ file
- ///
- /// Populated DatItem object if success, empty one on error
- public BaseFile GetTorrentGZFileInfo()
- {
- // Check for the file existing first
- if (!File.Exists(_filename))
- {
- return null;
- }
-
- string datum = Path.GetFileName(_filename).ToLowerInvariant();
- long filesize = new FileInfo(_filename).Length;
-
- // If we have the romba depot files, just skip them gracefully
- if (datum == ".romba_size" || datum == ".romba_size.backup")
- {
- Globals.Logger.Verbose("Romba depot file found, skipping: {0}", _filename);
- return null;
- }
-
- // Check if the name is the right length
- if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
- {
- Globals.Logger.Warning("Non SHA-1 filename found, skipping: '{0}'", Path.GetFullPath(_filename));
- return null;
- }
-
- // Check if the file is at least the minimum length
- if (filesize < 40 /* bytes */)
- {
- Globals.Logger.Warning("Possibly corrupt file '{0}' with size {1}", Path.GetFullPath(_filename), Utilities.GetBytesReadable(filesize));
- return null;
- }
-
- // Get the Romba-specific header data
- byte[] header; // Get preamble header for checking
- byte[] headermd5; // MD5
- byte[] headercrc; // CRC
- ulong headersz; // Int64 size
- BinaryReader br = new BinaryReader(Utilities.TryOpenRead(_filename));
- header = br.ReadBytes(12);
- headermd5 = br.ReadBytes(16);
- headercrc = br.ReadBytes(4);
- headersz = br.ReadUInt64();
- br.Dispose();
-
- // If the header is not correct, return a blank rom
- bool correct = true;
- for (int i = 0; i < header.Length; i++)
- {
- // This is a temp fix to ignore the modification time and OS until romba can be fixed
- if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9)
- {
- continue;
- }
- correct &= (header[i] == Constants.TorrentGZHeader[i]);
- }
- if (!correct)
- {
- return null;
- }
-
- // Now convert the data and get the right position
- long extractedsize = (long)headersz;
-
- BaseFile baseFile = new BaseFile
- {
- Filename = Path.GetFileNameWithoutExtension(_filename).ToLowerInvariant(),
- Size = extractedsize,
- CRC = headercrc,
- MD5 = headermd5,
- SHA1 = Utilities.StringToByteArray(Path.GetFileNameWithoutExtension(_filename)), // TODO: When updating to SHA-256, this needs to update to SHA256
-
- Parent = Path.GetFileNameWithoutExtension(_filename).ToLowerInvariant(),
- };
-
- return baseFile;
- }
-
- #endregion
-
- #region Writing
-
- ///
- /// Write an input file to a torrent GZ file
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(string inputFile, string outDir, Rom rom = null, bool date = false, bool romba = false)
- {
- // Check that the input file exists
- if (!File.Exists(inputFile))
- {
- Globals.Logger.Warning("File '{0}' does not exist!", inputFile);
- return false;
- }
- inputFile = Path.GetFullPath(inputFile);
-
- // Get the file stream for the file and write out
- return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date, romba);
- }
-
- ///
- /// Write an input stream to a torrent GZ file
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(Stream inputStream, string outDir, Rom rom = null, bool date = false, bool romba = false)
- {
- bool success = false;
-
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
-
- // Make sure the output directory exists
- if (!Directory.Exists(outDir))
- {
- Directory.CreateDirectory(outDir);
- }
- outDir = Path.GetFullPath(outDir);
-
- // Now get the Rom info for the file so we have hashes and size
- rom = new Rom(Utilities.GetStreamInfo(inputStream, inputStream.Length, keepReadOpen: true));
-
- // Get the output file name
- string outfile = null;
-
- // If we have a romba output, add the romba path
- if (romba)
- {
- outfile = Path.Combine(outDir, Utilities.GetRombaPath(rom.SHA1)); // TODO: When updating to SHA-256, this needs to update to SHA256
-
- // Check to see if the folder needs to be created
- if (!Directory.Exists(Path.GetDirectoryName(outfile)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(outfile));
- }
- }
- // Otherwise, we're just rebuilding to the main directory
- else
- {
- outfile = Path.Combine(outDir, rom.SHA1 + ".gz"); // TODO: When updating to SHA-256, this needs to update to SHA256
- }
-
- // If the output file exists, don't try to write again
- if (!File.Exists(outfile))
- {
- // Compress the input stream
- FileStream outputStream = Utilities.TryCreate(outfile);
-
- // Open the output file for writing
- BinaryWriter sw = new BinaryWriter(outputStream);
-
- // Write standard header and TGZ info
- byte[] data = Constants.TorrentGZHeader
- .Concat(Utilities.StringToByteArray(rom.MD5)) // MD5
- .Concat(Utilities.StringToByteArray(rom.CRC)) // CRC
- .ToArray();
- sw.Write(data);
- sw.Write((ulong)rom.Size); // Long size (Unsigned, Mirrored)
-
- // Now create a deflatestream from the input file
- DeflateStream ds = new DeflateStream(outputStream, Ionic.Zlib.CompressionMode.Compress, Ionic.Zlib.CompressionLevel.BestCompression, true);
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- ds.Write(ibuffer, 0, ilen);
- ds.Flush();
- }
- ds.Dispose();
-
- // Now write the standard footer
- sw.Write(Utilities.StringToByteArray(rom.CRC).Reverse().ToArray());
- sw.Write((uint)rom.Size);
-
- // Dispose of everything
- sw.Dispose();
- outputStream.Dispose();
- inputStream.Dispose();
- }
-
- return true;
- }
-
- ///
- /// Write a set of input files to a torrent GZ archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
-
- #endregion
- }
+ ///
+ /// Represents a TorrentGZip archive for reading and writing
+ ///
+ public class GZipArchive : BaseArchive
+ {
+ #region Constructors
+
+ ///
+ /// Create a new TorrentGZipArchive with no base file
+ ///
+ public GZipArchive()
+ : base()
+ {
+ this.Type = FileType.GZipArchive;
+ }
+
+ ///
+ /// Create a new TorrentGZipArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public GZipArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.GZipArchive;
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ bool encounteredErrors = true;
+
+ try
+ {
+ // Create the temp directory
+ Directory.CreateDirectory(outDir);
+
+ // Decompress the _filename stream
+ FileStream outstream = Utilities.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
+ GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(this.Filename), Ionic.Zlib.CompressionMode.Decompress);
+ gzstream.CopyTo(outstream);
+
+ // Dispose of the streams
+ outstream.Dispose();
+ gzstream.Dispose();
+
+ encounteredErrors = false;
+ }
+ catch (EndOfStreamException)
+ {
+ // Catch this but don't count it as an error because SharpCompress is unsafe
+ }
+ catch (InvalidOperationException)
+ {
+ encounteredErrors = true;
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ encounteredErrors = true;
+ }
+
+ return encounteredErrors;
+ }
+
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ // Try to extract a stream using the given information
+ (MemoryStream ms, string realEntry) = CopyToStream(entryName);
+
+ // If the memory stream and the entry name are both non-null, we write to file
+ if (ms != null && realEntry != null)
+ {
+ realEntry = Path.Combine(outDir, realEntry);
+
+ // Create the output subfolder now
+ Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
+
+ // Now open and write the file if possible
+ FileStream fs = Utilities.TryCreate(realEntry);
+ if (fs != null)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ byte[] zbuffer = new byte[_bufferSize];
+ int zlen;
+ while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
+ {
+ fs.Write(zbuffer, 0, zlen);
+ fs.Flush();
+ }
+
+ ms?.Dispose();
+ fs?.Dispose();
+ }
+ else
+ {
+ ms?.Dispose();
+ fs?.Dispose();
+ realEntry = null;
+ }
+ }
+
+ return realEntry;
+ }
+
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realEntry = null;
+
+ try
+ {
+ // Decompress the _filename stream
+ realEntry = Path.GetFileNameWithoutExtension(this.Filename);
+ GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(this.Filename), Ionic.Zlib.CompressionMode.Decompress);
+
+ // Write the file out
+ byte[] gbuffer = new byte[_bufferSize];
+ int glen;
+ while ((glen = gzstream.Read(gbuffer, 0, _bufferSize)) > 0)
+ {
+
+ ms.Write(gbuffer, 0, glen);
+ ms.Flush();
+ }
+
+ // Dispose of the streams
+ gzstream.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ ms = null;
+ realEntry = null;
+ }
+
+ return (ms, realEntry);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ if (_children == null || _children.Count == 0)
+ {
+ _children = new List();
+
+ string gamename = Path.GetFileNameWithoutExtension(this.Filename);
+
+ BaseFile possibleTgz = GetTorrentGZFileInfo();
+
+ // If it was, then add it to the outputs and continue
+ if (possibleTgz != null && possibleTgz.Filename != null)
+ {
+ _children.Add(possibleTgz);
+ }
+ else
+ {
+ try
+ {
+ // If secure hashes are disabled, do a quickscan
+ if (omitFromScan == Hash.SecureHashes)
+ {
+ BaseFile tempRom = new BaseFile()
+ {
+ Filename = gamename,
+ };
+ BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
+ br.BaseStream.Seek(-8, SeekOrigin.End);
+ byte[] headercrc = br.ReadBytesReverse(4);
+ tempRom.CRC = headercrc;
+ tempRom.Size = br.ReadInt32Reverse();
+ br.Dispose();
+
+ _children.Add(tempRom);
+ }
+ // Otherwise, use the stream directly
+ else
+ {
+ GZipStream gzstream = new GZipStream(Utilities.TryOpenRead(this.Filename), Ionic.Zlib.CompressionMode.Decompress);
+ BaseFile gzipEntryRom = Utilities.GetStreamInfo(gzstream, gzstream.Length, omitFromScan: omitFromScan);
+ gzipEntryRom.Filename = gzstream.FileName;
+ gzipEntryRom.Parent = gamename;
+ gzipEntryRom.Date = (date && gzstream.LastModified != null ? gzstream.LastModified?.ToString("yyyy/MM/dd hh:mm:ss") : null);
+ _children.Add(gzipEntryRom);
+ gzstream.Dispose();
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return null;
+ }
+ }
+ }
+
+ return _children;
+ }
+
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ // GZip files don't contain directories
+ return new List();
+ }
+
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ // Check for the file existing first
+ if (!File.Exists(this.Filename))
+ {
+ return false;
+ }
+
+ string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
+ long filesize = new FileInfo(this.Filename).Length;
+
+ // If we have the romba depot files, just skip them gracefully
+ if (datum == ".romba_size" || datum == ".romba_size.backup")
+ {
+ Globals.Logger.Verbose("Romba depot file found, skipping: {0}", this.Filename);
+ return false;
+ }
+
+ // Check if the name is the right length
+ if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
+ {
+ Globals.Logger.Warning("Non SHA-1 filename found, skipping: '{0}'", Path.GetFullPath(this.Filename));
+ return false;
+ }
+
+ // Check if the file is at least the minimum length
+ if (filesize < 40 /* bytes */)
+ {
+ Globals.Logger.Warning("Possibly corrupt file '{0}' with size {1}", Path.GetFullPath(this.Filename), Utilities.GetBytesReadable(filesize));
+ return false;
+ }
+
+ // Get the Romba-specific header data
+ byte[] header; // Get preamble header for checking
+ byte[] headermd5; // MD5
+ byte[] headercrc; // CRC
+ ulong headersz; // Int64 size
+ BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
+ header = br.ReadBytes(12);
+ headermd5 = br.ReadBytes(16);
+ headercrc = br.ReadBytes(4);
+ headersz = br.ReadUInt64();
+ br.Dispose();
+
+ // If the header is not correct, return a blank rom
+ bool correct = true;
+ for (int i = 0; i < header.Length; i++)
+ {
+ // This is a temp fix to ignore the modification time and OS until romba can be fixed
+ if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9)
+ {
+ continue;
+ }
+ correct &= (header[i] == Constants.TorrentGZHeader[i]);
+ }
+ if (!correct)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Retrieve file information for a single torrent GZ file
+ ///
+ /// Populated DatItem object if success, empty one on error
+ public BaseFile GetTorrentGZFileInfo()
+ {
+ // Check for the file existing first
+ if (!File.Exists(this.Filename))
+ {
+ return null;
+ }
+
+ string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
+ long filesize = new FileInfo(this.Filename).Length;
+
+ // If we have the romba depot files, just skip them gracefully
+ if (datum == ".romba_size" || datum == ".romba_size.backup")
+ {
+ Globals.Logger.Verbose("Romba depot file found, skipping: {0}", this.Filename);
+ return null;
+ }
+
+ // Check if the name is the right length
+ if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
+ {
+ Globals.Logger.Warning("Non SHA-1 filename found, skipping: '{0}'", Path.GetFullPath(this.Filename));
+ return null;
+ }
+
+ // Check if the file is at least the minimum length
+ if (filesize < 40 /* bytes */)
+ {
+ Globals.Logger.Warning("Possibly corrupt file '{0}' with size {1}", Path.GetFullPath(this.Filename), Utilities.GetBytesReadable(filesize));
+ return null;
+ }
+
+ // Get the Romba-specific header data
+ byte[] header; // Get preamble header for checking
+ byte[] headermd5; // MD5
+ byte[] headercrc; // CRC
+ ulong headersz; // Int64 size
+ BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
+ header = br.ReadBytes(12);
+ headermd5 = br.ReadBytes(16);
+ headercrc = br.ReadBytes(4);
+ headersz = br.ReadUInt64();
+ br.Dispose();
+
+ // If the header is not correct, return a blank rom
+ bool correct = true;
+ for (int i = 0; i < header.Length; i++)
+ {
+ // This is a temp fix to ignore the modification time and OS until romba can be fixed
+ if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9)
+ {
+ continue;
+ }
+ correct &= (header[i] == Constants.TorrentGZHeader[i]);
+ }
+ if (!correct)
+ {
+ return null;
+ }
+
+ // Now convert the data and get the right position
+ long extractedsize = (long)headersz;
+
+ BaseFile baseFile = new BaseFile
+ {
+ Filename = Path.GetFileNameWithoutExtension(this.Filename).ToLowerInvariant(),
+ Size = extractedsize,
+ CRC = headercrc,
+ MD5 = headermd5,
+ SHA1 = Utilities.StringToByteArray(Path.GetFileNameWithoutExtension(this.Filename)), // TODO: When updating to SHA-256, this needs to update to SHA256
+
+ Parent = Path.GetFileNameWithoutExtension(this.Filename).ToLowerInvariant(),
+ };
+
+ return baseFile;
+ }
+
+ #endregion
+
+ #region Writing
+
+ ///
+ /// Write an input file to a torrent GZ file
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(string inputFile, string outDir, Rom rom = null, bool date = false, bool romba = false)
+ {
+ // Check that the input file exists
+ if (!File.Exists(inputFile))
+ {
+ Globals.Logger.Warning("File '{0}' does not exist!", inputFile);
+ return false;
+ }
+ inputFile = Path.GetFullPath(inputFile);
+
+ // Get the file stream for the file and write out
+ return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date, romba);
+ }
+
+ ///
+ /// Write an input stream to a torrent GZ file
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(Stream inputStream, string outDir, Rom rom = null, bool date = false, bool romba = false)
+ {
+ bool success = false;
+
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
+
+ // Make sure the output directory exists
+ if (!Directory.Exists(outDir))
+ {
+ Directory.CreateDirectory(outDir);
+ }
+ outDir = Path.GetFullPath(outDir);
+
+ // Now get the Rom info for the file so we have hashes and size
+ rom = new Rom(Utilities.GetStreamInfo(inputStream, inputStream.Length, keepReadOpen: true));
+
+ // Get the output file name
+ string outfile = null;
+
+ // If we have a romba output, add the romba path
+ if (romba)
+ {
+ outfile = Path.Combine(outDir, Utilities.GetRombaPath(rom.SHA1)); // TODO: When updating to SHA-256, this needs to update to SHA256
+
+ // Check to see if the folder needs to be created
+ if (!Directory.Exists(Path.GetDirectoryName(outfile)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(outfile));
+ }
+ }
+ // Otherwise, we're just rebuilding to the main directory
+ else
+ {
+ outfile = Path.Combine(outDir, rom.SHA1 + ".gz"); // TODO: When updating to SHA-256, this needs to update to SHA256
+ }
+
+ // If the output file exists, don't try to write again
+ if (!File.Exists(outfile))
+ {
+ // Compress the input stream
+ FileStream outputStream = Utilities.TryCreate(outfile);
+
+ // Open the output file for writing
+ BinaryWriter sw = new BinaryWriter(outputStream);
+
+ // Write standard header and TGZ info
+ byte[] data = Constants.TorrentGZHeader
+ .Concat(Utilities.StringToByteArray(rom.MD5)) // MD5
+ .Concat(Utilities.StringToByteArray(rom.CRC)) // CRC
+ .ToArray();
+ sw.Write(data);
+ sw.Write((ulong)rom.Size); // Long size (Unsigned, Mirrored)
+
+ // Now create a deflatestream from the input file
+ DeflateStream ds = new DeflateStream(outputStream, Ionic.Zlib.CompressionMode.Compress, Ionic.Zlib.CompressionLevel.BestCompression, true);
+
+ // Copy the input stream to the output
+ byte[] ibuffer = new byte[_bufferSize];
+ int ilen;
+ while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
+ {
+ ds.Write(ibuffer, 0, ilen);
+ ds.Flush();
+ }
+ ds.Dispose();
+
+ // Now write the standard footer
+ sw.Write(Utilities.StringToByteArray(rom.CRC).Reverse().ToArray());
+ sw.Write((uint)rom.Size);
+
+ // Dispose of everything
+ sw.Dispose();
+ outputStream.Dispose();
+ inputStream.Dispose();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Write a set of input files to a torrent GZ archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/LRZipArchive.cs b/SabreTools.Library/FileTypes/LRZipArchive.cs
index 4ffb5c5b..7a611f55 100644
--- a/SabreTools.Library/FileTypes/LRZipArchive.cs
+++ b/SabreTools.Library/FileTypes/LRZipArchive.cs
@@ -13,152 +13,152 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a TorrentLRZip archive for reading and writing
- ///
- /// TODO: Implement from source at https://github.com/ckolivas/lrzip
- public class LRZipArchive : BaseArchive
- {
- #region Constructors
+ ///
+ /// Represents a TorrentLRZip archive for reading and writing
+ ///
+ /// TODO: Implement from source at https://github.com/ckolivas/lrzip
+ public class LRZipArchive : BaseArchive
+ {
+ #region Constructors
- ///
- /// Create a new LRZipArchive with no base file
- ///
- public LRZipArchive()
- : base()
- {
- _fileType = FileType.LRZipArchive;
- }
+ ///
+ /// Create a new LRZipArchive with no base file
+ ///
+ public LRZipArchive()
+ : base()
+ {
+ this.Type = FileType.LRZipArchive;
+ }
- ///
- /// Create a new LRZipArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True if hashes for this file should be calculated, false otherwise (default)
- public LRZipArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.LRZipArchive;
- }
+ ///
+ /// Create a new LRZipArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public LRZipArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.LRZipArchive;
+ }
- #endregion
+ #endregion
- #region Extraction
+ #region Extraction
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Information
+ #region Information
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Writing
+ #region Writing
- ///
- /// Write an input file to a torrent LRZip file
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input file to a torrent LRZip file
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write an input stream to a torrent LRZip file
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input stream to a torrent LRZip file
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write a set of input files to a torrent LRZip archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write a set of input files to a torrent LRZip archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/LZ4Archive.cs b/SabreTools.Library/FileTypes/LZ4Archive.cs
index b307549a..c7949581 100644
--- a/SabreTools.Library/FileTypes/LZ4Archive.cs
+++ b/SabreTools.Library/FileTypes/LZ4Archive.cs
@@ -13,152 +13,152 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a TorrentLRZip archive for reading and writing
- ///
- /// TODO: Implement from source at https://github.com/lz4/lz4
- public class LZ4Archive : BaseArchive
- {
- #region Constructors
+ ///
+ /// Represents a TorrentLRZip archive for reading and writing
+ ///
+ /// TODO: Implement from source at https://github.com/lz4/lz4
+ public class LZ4Archive : BaseArchive
+ {
+ #region Constructors
- ///
- /// Create a new LZ4Archive with no base file
- ///
- public LZ4Archive()
- : base()
- {
- _fileType = FileType.LZ4Archive;
- }
+ ///
+ /// Create a new LZ4Archive with no base file
+ ///
+ public LZ4Archive()
+ : base()
+ {
+ this.Type = FileType.LZ4Archive;
+ }
- ///
- /// Create a new LZ4Archive from the given file
- ///
- /// Name of the file to use as an archive
- /// True if hashes for this file should be calculated, false otherwise (default)
- public LZ4Archive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.LZ4Archive;
- }
+ ///
+ /// Create a new LZ4Archive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public LZ4Archive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.LZ4Archive;
+ }
- #endregion
+ #endregion
- #region Extraction
+ #region Extraction
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Information
+ #region Information
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Writing
+ #region Writing
- ///
- /// Write an input file to a torrent LZ4 file
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input file to a torrent LZ4 file
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write an input stream to a torrent LZ4 file
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input stream to a torrent LZ4 file
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write a set of input files to a torrent LZ4 archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write a set of input files to a torrent LZ4 archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/RarArchive.cs b/SabreTools.Library/FileTypes/RarArchive.cs
index 425d22c5..24824d27 100644
--- a/SabreTools.Library/FileTypes/RarArchive.cs
+++ b/SabreTools.Library/FileTypes/RarArchive.cs
@@ -37,7 +37,7 @@ namespace SabreTools.Library.FileTypes
public RarArchive()
: base()
{
- _fileType = FileType.RarArchive;
+ this.Type = FileType.RarArchive;
}
///
@@ -49,7 +49,7 @@ namespace SabreTools.Library.FileTypes
public RarArchive(string filename, bool getHashes = false)
: base(filename, getHashes)
{
- _fileType = FileType.RarArchive;
+ this.Type = FileType.RarArchive;
}
#endregion
@@ -71,7 +71,7 @@ namespace SabreTools.Library.FileTypes
Directory.CreateDirectory(outDir);
// Extract all files to the temp directory
- SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(_filename);
+ SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(this.Filename);
foreach (RarArchiveEntry entry in ra.Entries)
{
entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
@@ -155,7 +155,7 @@ namespace SabreTools.Library.FileTypes
try
{
- SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false, });
+ SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
foreach (RarArchiveEntry entry in ra.Entries)
{
if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
@@ -191,11 +191,11 @@ namespace SabreTools.Library.FileTypes
public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
{
List found = new List();
- string gamename = Path.GetFileNameWithoutExtension(_filename);
+ string gamename = Path.GetFileNameWithoutExtension(this.Filename);
try
{
- SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(Utilities.TryOpenRead(_filename));
+ SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(Utilities.TryOpenRead(this.Filename));
foreach (RarArchiveEntry entry in ra.Entries.Where(e => e != null && !e.IsDirectory))
{
// If secure hashes are disabled, do a quickscan
@@ -242,12 +242,12 @@ namespace SabreTools.Library.FileTypes
/// TODO: Write the rest of this RAR file handling
public void GetRarFileInfo()
{
- if (!File.Exists(_filename))
+ if (!File.Exists(this.Filename))
{
return;
}
- BinaryReader br = new BinaryReader(Utilities.TryOpenRead(_filename));
+ BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
// Check for the signature first (Skipping the SFX Module)
byte[] signature = br.ReadBytes(8);
@@ -464,7 +464,7 @@ namespace SabreTools.Library.FileTypes
try
{
- SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false });
+ SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false });
List rarEntries = ra.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
string lastRarEntry = null;
foreach (RarArchiveEntry entry in rarEntries)
diff --git a/SabreTools.Library/FileTypes/SevenZipArchive.cs b/SabreTools.Library/FileTypes/SevenZipArchive.cs
index da8773b4..824467ea 100644
--- a/SabreTools.Library/FileTypes/SevenZipArchive.cs
+++ b/SabreTools.Library/FileTypes/SevenZipArchive.cs
@@ -26,740 +26,740 @@ using SharpCompress.Readers;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a Torrent7zip archive for reading and writing
- ///
- /// TODO: Torrent 7-zip: https://sourceforge.net/p/t7z/code/HEAD/tree/
- public class SevenZipArchive : BaseArchive
- {
- #region Constructors
-
- ///
- /// Create a new TorrentSevenZipArchive with no base file
- ///
- public SevenZipArchive()
- : base()
- {
- _fileType = FileType.SevenZipArchive;
- }
-
- ///
- /// Create a new TorrentSevenZipArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public SevenZipArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.SevenZipArchive;
- }
-
- #endregion
-
- #region Extraction
-
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- bool encounteredErrors = true;
-
- try
- {
- // Create the temp directory
- Directory.CreateDirectory(outDir);
-
- // Extract all files to the temp directory
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(_filename));
- foreach (SevenZipArchiveEntry entry in sza.Entries)
- {
- entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
- }
- encounteredErrors = false;
- sza.Dispose();
- }
- catch (EndOfStreamException)
- {
- // Catch this but don't count it as an error because SharpCompress is unsafe
- }
- catch (InvalidOperationException)
- {
- encounteredErrors = true;
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- encounteredErrors = true;
- }
-
- return encounteredErrors;
- }
-
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- // Try to extract a stream using the given information
- (MemoryStream ms, string realEntry) = CopyToStream(entryName);
-
- // If the memory stream and the entry name are both non-null, we write to file
- if (ms != null && realEntry != null)
- {
- realEntry = Path.Combine(outDir, realEntry);
-
- // Create the output subfolder now
- Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
-
- // Now open and write the file if possible
- FileStream fs = Utilities.TryCreate(realEntry);
- if (fs != null)
- {
- ms.Seek(0, SeekOrigin.Begin);
- byte[] zbuffer = new byte[_bufferSize];
- int zlen;
- while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
- {
- fs.Write(zbuffer, 0, zlen);
- fs.Flush();
- }
-
- ms?.Dispose();
- fs?.Dispose();
- }
- else
- {
- ms?.Dispose();
- fs?.Dispose();
- realEntry = null;
- }
- }
-
- return realEntry;
- }
-
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realEntry = null;
-
- try
- {
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false, });
- foreach (SevenZipArchiveEntry entry in sza.Entries)
- {
- if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
- {
- // Write the file out
- realEntry = entry.Key;
- entry.WriteTo(ms);
- break;
- }
- }
- sza.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- ms = null;
- realEntry = null;
- }
-
- return (ms, realEntry);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- List found = new List();
- string gamename = Path.GetFileNameWithoutExtension(_filename);
-
- try
- {
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(_filename));
- foreach (SevenZipArchiveEntry entry in sza.Entries.Where(e => e != null && !e.IsDirectory))
- {
- // If secure hashes are disabled, do a quickscan
- if (omitFromScan == Hash.SecureHashes)
- {
- found.Add(new BaseFile
- {
- Filename = entry.Key,
- Size = entry.Size,
- CRC = BitConverter.GetBytes(entry.Crc),
- Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null),
-
- Parent = gamename,
- });
- }
- // Otherwise, use the stream directly
- else
- {
- Stream entryStream = entry.OpenEntryStream();
- BaseFile sevenZipEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan: omitFromScan);
- sevenZipEntryRom.Filename = entry.Key;
- sevenZipEntryRom.Parent = gamename;
- sevenZipEntryRom.Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null);
- found.Add(sevenZipEntryRom);
- entryStream.Dispose();
- }
- }
-
- // Dispose of the archive
- sza.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return null;
- }
-
- return found;
- }
-
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- List empties = new List();
-
- try
- {
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false });
- List sevenZipEntries = sza.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
- string lastSevenZipEntry = null;
- foreach (SevenZipArchiveEntry entry in sevenZipEntries)
- {
- if (entry != null)
- {
- // If the current is a superset of last, we skip it
- if (lastSevenZipEntry != null && lastSevenZipEntry.StartsWith(entry.Key))
- {
- // No-op
- }
- // If the entry is a directory, we add it
- else if (entry.IsDirectory)
- {
- empties.Add(entry.Key);
- lastSevenZipEntry = entry.Key;
- }
- }
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- }
-
- return empties;
- }
-
- ///
- /// Check whether the input file is a standardized format
- ///
- /// TODO: Finish reading T7z information
- public override bool IsTorrent()
- {
- bool ist7z = false;
-
- if (File.Exists(_filename))
- {
- try
- {
- Stream fread = Utilities.TryOpenRead(_filename);
- uint ar, offs = 0;
- fread.Seek(0, SeekOrigin.Begin);
- byte[] buffer = new byte[128];
- ar = (uint)fread.Read(buffer, 0, 4 + Constants.Torrent7ZipSignature.Length + 4);
- if (ar < (4 + Constants.Torrent7ZipSignature.Length + 4))
- {
- if (ar >= Constants.Torrent7ZipSignature.Length + 4)
- {
- ar -= (uint)(Constants.Torrent7ZipSignature.Length + 4);
- }
- if (ar <= Constants.Torrent7ZipHeader.Length)
- {
- ar = (uint)Constants.Torrent7ZipHeader.Length;
- }
- // memset(buffer+offs+ar,0,crcsz-ar)
- }
-
- fread.Dispose();
- }
- catch
- {
- Globals.Logger.Warning("File '{0}' could not be opened", _filename);
- ist7z = false;
- }
- }
-
- return ist7z;
- }
-
- #endregion
-
- #region Writing
-
- ///
- /// Write an input file to a torrent7z archive
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- // Get the file stream for the file and write out
- return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
- }
-
- ///
- /// Write an input file to a torrent7z archive
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either input is null or empty, return
- if (inputStream == null || rom == null || rom.Name == null)
- {
- return success;
- }
-
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
-
- // Seek to the beginning of the stream
- inputStream.Seek(0, SeekOrigin.Begin);
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".7z") ? "" : ".7z"));
-
- // Set internal variables
- SevenZipBase.SetLibraryPath("7za.dll");
- SevenZipExtractor oldZipFile = null;
- SevenZipCompressor zipFile;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.SevenZip,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Create the temp directory
- string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
- if (!Directory.Exists(tempPath))
- {
- Directory.CreateDirectory(tempPath);
- }
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(rom.Name, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- using (oldZipFile = new SevenZipExtractor(archiveFileName))
- {
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
-
- // If the old one doesn't contain the new file, then add it
- if (!oldZipFile.ArchiveFileNames.Contains(rom.Name.Replace('\\', '/')))
- {
- inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.FilesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.SevenZip,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(rom.Name, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- Stream oldZipFileEntryStream = new MemoryStream();
- oldZipFile.ExtractFile(index, oldZipFileEntryStream);
- oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- oldZipFileEntryStream.Dispose();
- }
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
- }
- }
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- inputStream?.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- // Now make the file T7Z
- // TODO: Add ACTUAL T7Z compatible code
-
- BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
- bw.Seek(0, SeekOrigin.Begin);
- bw.Write(Constants.Torrent7ZipHeader);
- bw.Seek(0, SeekOrigin.End);
-
- using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
- {
-
- // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
- byte[] tempsig = Constants.Torrent7ZipSignature;
- if (oldZipFile.FilesCount > 1)
- {
- tempsig[16] = 0x2;
- }
- else
- {
- tempsig[16] = 0;
- }
-
- bw.Write(tempsig);
- bw.Flush();
- bw.Dispose();
- }
-
- return true;
- }
-
- ///
- /// Write a set of input files to a torrent7z archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either list of roms is null or empty, return
- if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
- {
- return success;
- }
-
- // If the number of inputs is less than the number of available roms, return
- if (inputFiles.Count < roms.Count)
- {
- return success;
- }
-
- // If one of the files doesn't exist, return
- foreach (string file in inputFiles)
- {
- if (!File.Exists(file))
- {
- return success;
- }
- }
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".7z") ? "" : ".7z"));
-
- // Set internal variables
- SevenZipBase.SetLibraryPath("7za.dll");
- SevenZipExtractor oldZipFile;
- SevenZipCompressor zipFile;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.SevenZip,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
- }
-
- // Sort the keys in TZIP order
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Create the temp directory
- string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
- if (!Directory.Exists(tempPath))
- {
- Directory.CreateDirectory(tempPath);
- }
-
- // Now add all of the files in order
- foreach (string key in keys)
- {
- string newkey = Path.Combine(tempPath, key);
-
- File.Move(inputFiles[inputIndexMap[key]], newkey);
- zipFile.CompressFiles(tempFile, newkey);
- File.Move(newkey, inputFiles[inputIndexMap[key]]);
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
-
- Utilities.CleanDirectory(tempPath);
- Utilities.TryDeleteDirectory(tempPath);
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- using (oldZipFile = new SevenZipExtractor(archiveFileName))
- {
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- // If the old one contains the new file, then just skip out
- if (oldZipFile.ArchiveFileNames.Contains(roms[i].Name.Replace('\\', '/')))
- {
- continue;
- }
-
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.FilesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.SevenZip,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- FileStream inputStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(key, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- Stream oldZipFileEntryStream = new MemoryStream();
- oldZipFile.ExtractFile(index, oldZipFileEntryStream);
- oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- oldZipFileEntryStream.Dispose();
- }
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
- }
- }
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- // Now make the file T7Z
- // TODO: Add ACTUAL T7Z compatible code
-
- BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
- bw.Seek(0, SeekOrigin.Begin);
- bw.Write(Constants.Torrent7ZipHeader);
- bw.Seek(0, SeekOrigin.End);
-
- using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
- {
- // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
- byte[] tempsig = Constants.Torrent7ZipSignature;
- if (oldZipFile.FilesCount > 1)
- {
- tempsig[16] = 0x2;
- }
- else
- {
- tempsig[16] = 0;
- }
-
- bw.Write(tempsig);
- bw.Flush();
- bw.Dispose();
- }
-
- return true;
- }
-
- #endregion
- }
+ ///
+ /// Represents a Torrent7zip archive for reading and writing
+ ///
+ /// TODO: Torrent 7-zip: https://sourceforge.net/p/t7z/code/HEAD/tree/
+ public class SevenZipArchive : BaseArchive
+ {
+ #region Constructors
+
+ ///
+ /// Create a new TorrentSevenZipArchive with no base file
+ ///
+ public SevenZipArchive()
+ : base()
+ {
+ this.Type = FileType.SevenZipArchive;
+ }
+
+ ///
+ /// Create a new TorrentSevenZipArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public SevenZipArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.SevenZipArchive;
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ bool encounteredErrors = true;
+
+ try
+ {
+ // Create the temp directory
+ Directory.CreateDirectory(outDir);
+
+ // Extract all files to the temp directory
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(this.Filename));
+ foreach (SevenZipArchiveEntry entry in sza.Entries)
+ {
+ entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
+ }
+ encounteredErrors = false;
+ sza.Dispose();
+ }
+ catch (EndOfStreamException)
+ {
+ // Catch this but don't count it as an error because SharpCompress is unsafe
+ }
+ catch (InvalidOperationException)
+ {
+ encounteredErrors = true;
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ encounteredErrors = true;
+ }
+
+ return encounteredErrors;
+ }
+
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ // Try to extract a stream using the given information
+ (MemoryStream ms, string realEntry) = CopyToStream(entryName);
+
+ // If the memory stream and the entry name are both non-null, we write to file
+ if (ms != null && realEntry != null)
+ {
+ realEntry = Path.Combine(outDir, realEntry);
+
+ // Create the output subfolder now
+ Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
+
+ // Now open and write the file if possible
+ FileStream fs = Utilities.TryCreate(realEntry);
+ if (fs != null)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ byte[] zbuffer = new byte[_bufferSize];
+ int zlen;
+ while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
+ {
+ fs.Write(zbuffer, 0, zlen);
+ fs.Flush();
+ }
+
+ ms?.Dispose();
+ fs?.Dispose();
+ }
+ else
+ {
+ ms?.Dispose();
+ fs?.Dispose();
+ realEntry = null;
+ }
+ }
+
+ return realEntry;
+ }
+
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realEntry = null;
+
+ try
+ {
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
+ foreach (SevenZipArchiveEntry entry in sza.Entries)
+ {
+ if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
+ {
+ // Write the file out
+ realEntry = entry.Key;
+ entry.WriteTo(ms);
+ break;
+ }
+ }
+ sza.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ ms = null;
+ realEntry = null;
+ }
+
+ return (ms, realEntry);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ List found = new List();
+ string gamename = Path.GetFileNameWithoutExtension(this.Filename);
+
+ try
+ {
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(this.Filename));
+ foreach (SevenZipArchiveEntry entry in sza.Entries.Where(e => e != null && !e.IsDirectory))
+ {
+ // If secure hashes are disabled, do a quickscan
+ if (omitFromScan == Hash.SecureHashes)
+ {
+ found.Add(new BaseFile
+ {
+ Filename = entry.Key,
+ Size = entry.Size,
+ CRC = BitConverter.GetBytes(entry.Crc),
+ Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null),
+
+ Parent = gamename,
+ });
+ }
+ // Otherwise, use the stream directly
+ else
+ {
+ Stream entryStream = entry.OpenEntryStream();
+ BaseFile sevenZipEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan: omitFromScan);
+ sevenZipEntryRom.Filename = entry.Key;
+ sevenZipEntryRom.Parent = gamename;
+ sevenZipEntryRom.Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null);
+ found.Add(sevenZipEntryRom);
+ entryStream.Dispose();
+ }
+ }
+
+ // Dispose of the archive
+ sza.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return null;
+ }
+
+ return found;
+ }
+
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ List empties = new List();
+
+ try
+ {
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false });
+ List sevenZipEntries = sza.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
+ string lastSevenZipEntry = null;
+ foreach (SevenZipArchiveEntry entry in sevenZipEntries)
+ {
+ if (entry != null)
+ {
+ // If the current is a superset of last, we skip it
+ if (lastSevenZipEntry != null && lastSevenZipEntry.StartsWith(entry.Key))
+ {
+ // No-op
+ }
+ // If the entry is a directory, we add it
+ else if (entry.IsDirectory)
+ {
+ empties.Add(entry.Key);
+ lastSevenZipEntry = entry.Key;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ }
+
+ return empties;
+ }
+
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ /// TODO: Finish reading T7z information
+ public override bool IsTorrent()
+ {
+ bool ist7z = false;
+
+ if (File.Exists(this.Filename))
+ {
+ try
+ {
+ Stream fread = Utilities.TryOpenRead(this.Filename);
+ uint ar, offs = 0;
+ fread.Seek(0, SeekOrigin.Begin);
+ byte[] buffer = new byte[128];
+ ar = (uint)fread.Read(buffer, 0, 4 + Constants.Torrent7ZipSignature.Length + 4);
+ if (ar < (4 + Constants.Torrent7ZipSignature.Length + 4))
+ {
+ if (ar >= Constants.Torrent7ZipSignature.Length + 4)
+ {
+ ar -= (uint)(Constants.Torrent7ZipSignature.Length + 4);
+ }
+ if (ar <= Constants.Torrent7ZipHeader.Length)
+ {
+ ar = (uint)Constants.Torrent7ZipHeader.Length;
+ }
+ // memset(buffer+offs+ar,0,crcsz-ar)
+ }
+
+ fread.Dispose();
+ }
+ catch
+ {
+ Globals.Logger.Warning("File '{0}' could not be opened", this.Filename);
+ ist7z = false;
+ }
+ }
+
+ return ist7z;
+ }
+
+ #endregion
+
+ #region Writing
+
+ ///
+ /// Write an input file to a torrent7z archive
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ // Get the file stream for the file and write out
+ return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
+ }
+
+ ///
+ /// Write an input file to a torrent7z archive
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either input is null or empty, return
+ if (inputStream == null || rom == null || rom.Name == null)
+ {
+ return success;
+ }
+
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
+
+ // Seek to the beginning of the stream
+ inputStream.Seek(0, SeekOrigin.Begin);
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".7z") ? "" : ".7z"));
+
+ // Set internal variables
+ SevenZipBase.SetLibraryPath("7za.dll");
+ SevenZipExtractor oldZipFile = null;
+ SevenZipCompressor zipFile;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.SevenZip,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Create the temp directory
+ string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
+ if (!Directory.Exists(tempPath))
+ {
+ Directory.CreateDirectory(tempPath);
+ }
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(rom.Name, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ using (oldZipFile = new SevenZipExtractor(archiveFileName))
+ {
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+
+ // If the old one doesn't contain the new file, then add it
+ if (!oldZipFile.ArchiveFileNames.Contains(rom.Name.Replace('\\', '/')))
+ {
+ inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < oldZipFile.FilesCount; i++)
+ {
+ inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
+ {
+ success = true;
+ return success;
+ }
+
+ // Otherwise, process the old zipfile
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.SevenZip,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(rom.Name, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ Stream oldZipFileEntryStream = new MemoryStream();
+ oldZipFile.ExtractFile(index, oldZipFileEntryStream);
+ oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ oldZipFileEntryStream.Dispose();
+ }
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+ }
+ }
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ inputStream?.Dispose();
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ // Now make the file T7Z
+ // TODO: Add ACTUAL T7Z compatible code
+
+ BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
+ bw.Seek(0, SeekOrigin.Begin);
+ bw.Write(Constants.Torrent7ZipHeader);
+ bw.Seek(0, SeekOrigin.End);
+
+ using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
+ {
+
+ // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
+ byte[] tempsig = Constants.Torrent7ZipSignature;
+ if (oldZipFile.FilesCount > 1)
+ {
+ tempsig[16] = 0x2;
+ }
+ else
+ {
+ tempsig[16] = 0;
+ }
+
+ bw.Write(tempsig);
+ bw.Flush();
+ bw.Dispose();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Write a set of input files to a torrent7z archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either list of roms is null or empty, return
+ if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
+ {
+ return success;
+ }
+
+ // If the number of inputs is less than the number of available roms, return
+ if (inputFiles.Count < roms.Count)
+ {
+ return success;
+ }
+
+ // If one of the files doesn't exist, return
+ foreach (string file in inputFiles)
+ {
+ if (!File.Exists(file))
+ {
+ return success;
+ }
+ }
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".7z") ? "" : ".7z"));
+
+ // Set internal variables
+ SevenZipBase.SetLibraryPath("7za.dll");
+ SevenZipExtractor oldZipFile;
+ SevenZipCompressor zipFile;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.SevenZip,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
+ }
+
+ // Sort the keys in TZIP order
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Create the temp directory
+ string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
+ if (!Directory.Exists(tempPath))
+ {
+ Directory.CreateDirectory(tempPath);
+ }
+
+ // Now add all of the files in order
+ foreach (string key in keys)
+ {
+ string newkey = Path.Combine(tempPath, key);
+
+ File.Move(inputFiles[inputIndexMap[key]], newkey);
+ zipFile.CompressFiles(tempFile, newkey);
+ File.Move(newkey, inputFiles[inputIndexMap[key]]);
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+
+ Utilities.CleanDirectory(tempPath);
+ Utilities.TryDeleteDirectory(tempPath);
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ using (oldZipFile = new SevenZipExtractor(archiveFileName))
+ {
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ // If the old one contains the new file, then just skip out
+ if (oldZipFile.ArchiveFileNames.Contains(roms[i].Name.Replace('\\', '/')))
+ {
+ continue;
+ }
+
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < oldZipFile.FilesCount; i++)
+ {
+ inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
+ {
+ success = true;
+ return success;
+ }
+
+ // Otherwise, process the old zipfile
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.SevenZip,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ FileStream inputStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(key, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ Stream oldZipFileEntryStream = new MemoryStream();
+ oldZipFile.ExtractFile(index, oldZipFileEntryStream);
+ oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ oldZipFileEntryStream.Dispose();
+ }
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+ }
+ }
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ // Now make the file T7Z
+ // TODO: Add ACTUAL T7Z compatible code
+
+ BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
+ bw.Seek(0, SeekOrigin.Begin);
+ bw.Write(Constants.Torrent7ZipHeader);
+ bw.Seek(0, SeekOrigin.End);
+
+ using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
+ {
+ // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
+ byte[] tempsig = Constants.Torrent7ZipSignature;
+ if (oldZipFile.FilesCount > 1)
+ {
+ tempsig[16] = 0x2;
+ }
+ else
+ {
+ tempsig[16] = 0;
+ }
+
+ bw.Write(tempsig);
+ bw.Flush();
+ bw.Dispose();
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/TapeArchive.cs b/SabreTools.Library/FileTypes/TapeArchive.cs
index 31b6d5b9..d2793e94 100644
--- a/SabreTools.Library/FileTypes/TapeArchive.cs
+++ b/SabreTools.Library/FileTypes/TapeArchive.cs
@@ -26,620 +26,620 @@ using SharpCompress.Writers;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a Torrent7zip archive for reading and writing
- ///
- /// TODO: Don't try to read entries to MemoryStream during write
- public class TapeArchive : BaseArchive
- {
- #region Constructors
-
- ///
- /// Create a new TorrentTarArchive with no base file
- ///
- public TapeArchive()
- : base()
- {
- _fileType = FileType.TapeArchive;
- }
-
- ///
- /// Create a new TorrentTarArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public TapeArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.TapeArchive;
- }
-
- #endregion
-
- #region Extraction
-
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- bool encounteredErrors = true;
-
- try
- {
- // Create the temp directory
- Directory.CreateDirectory(outDir);
-
- // Extract all files to the temp directory
- TarArchive ta = TarArchive.Open(_filename);
- foreach (TarArchiveEntry entry in ta.Entries)
- {
- entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
- }
- encounteredErrors = false;
- ta.Dispose();
- }
- catch (EndOfStreamException)
- {
- // Catch this but don't count it as an error because SharpCompress is unsafe
- }
- catch (InvalidOperationException)
- {
- encounteredErrors = true;
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- encounteredErrors = true;
- }
-
- return encounteredErrors;
- }
-
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- // Try to extract a stream using the given information
- (MemoryStream ms, string realEntry) = CopyToStream(entryName);
-
- // If the memory stream and the entry name are both non-null, we write to file
- if (ms != null && realEntry != null)
- {
- realEntry = Path.Combine(outDir, realEntry);
-
- // Create the output subfolder now
- Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
-
- // Now open and write the file if possible
- FileStream fs = Utilities.TryCreate(realEntry);
- if (fs != null)
- {
- ms.Seek(0, SeekOrigin.Begin);
- byte[] zbuffer = new byte[_bufferSize];
- int zlen;
- while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
- {
- fs.Write(zbuffer, 0, zlen);
- fs.Flush();
- }
-
- ms?.Dispose();
- fs?.Dispose();
- }
- else
- {
- ms?.Dispose();
- fs?.Dispose();
- realEntry = null;
- }
- }
-
- return realEntry;
- }
-
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realEntry = null;
-
- try
- {
- TarArchive ta = TarArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false, });
- foreach (TarArchiveEntry entry in ta.Entries)
- {
- if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
- {
- // Write the file out
- realEntry = entry.Key;
- entry.WriteTo(ms);
- }
- }
- ta.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- ms = null;
- realEntry = null;
- }
-
- return (ms, realEntry);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- List found = new List();
- string gamename = Path.GetFileNameWithoutExtension(_filename);
-
- try
- {
- TarArchive ta = TarArchive.Open(Utilities.TryOpenRead(_filename));
- foreach (TarArchiveEntry entry in ta.Entries.Where(e => e != null && !e.IsDirectory))
- {
- // If secure hashes are disabled, do a quickscan
- if (omitFromScan == Hash.SecureHashes)
- {
- found.Add(new BaseFile
- {
- Filename = entry.Key,
- Size = entry.Size,
- CRC = BitConverter.GetBytes(entry.Crc),
- Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null),
-
- Parent = gamename,
- });
- }
- // Otherwise, use the stream directly
- else
- {
- Stream entryStream = entry.OpenEntryStream();
- BaseFile tarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan: omitFromScan);
- tarEntryRom.Filename = entry.Key;
- tarEntryRom.Parent = gamename;
- tarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
- found.Add(tarEntryRom);
- entryStream.Dispose();
- }
- }
-
- // Dispose of the archive
- ta.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return null;
- }
-
- return found;
- }
-
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- List empties = new List();
-
- try
- {
- TarArchive ta = TarArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false });
- List tarEntries = ta.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
- string lastTarEntry = null;
- foreach (TarArchiveEntry entry in tarEntries)
- {
- if (entry != null)
- {
- // If the current is a superset of last, we skip it
- if (lastTarEntry != null && lastTarEntry.StartsWith(entry.Key))
- {
- // No-op
- }
- // If the entry is a directory, we add it
- else if (entry.IsDirectory)
- {
- empties.Add(entry.Key);
- lastTarEntry = entry.Key;
- }
- }
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- }
-
- return empties;
- }
-
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
-
- #endregion
-
- #region Writing
-
- ///
- /// Write an input file to a tape archive
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- // Get the file stream for the file and write out
- return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
- }
-
- ///
- /// Write an input stream to a tape archive
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either input is null or empty, return
- if (inputStream == null || rom == null || rom.Name == null)
- {
- return success;
- }
-
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? "" : ".tar"));
-
- // Set internal variables
- TarArchive oldTarFile = TarArchive.Create();
- TarArchive tarFile = TarArchive.Create();
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- // Get temporary date-time if possible
- DateTime? usableDate = null;
- if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out DateTime dt))
- {
- usableDate = dt;
- }
-
- // Copy the input stream to the output
- inputStream.Seek(0, SeekOrigin.Begin);
- tarFile.AddEntry(rom.Name, inputStream, size: rom.Size, modified: usableDate);
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- oldTarFile = TarArchive.Open(archiveFileName);
-
- // Get a list of all current entries
- List entries = oldTarFile.Entries.Select(i => i.Key).ToList();
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
-
- // If the old one doesn't contain the new file, then add it
- if (!entries.Contains(rom.Name.Replace('\\', '/')))
- {
- inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < entries.Count; i++)
- {
- inputIndexMap.Add(entries[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= entries.Count)
- {
- success = true;
- return success;
- }
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // Get temporary date-time if possible
- DateTime? usableDate = null;
- if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out DateTime dt))
- {
- usableDate = dt;
- }
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Copy the input file to the output
- inputStream.Seek(0, SeekOrigin.Begin);
- tarFile.AddEntry(rom.Name, inputStream, size: rom.Size, modified: usableDate);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- // Get the stream from the original archive
- TarArchiveEntry tae = oldTarFile.Entries.ElementAt(index);
- MemoryStream entry = new MemoryStream();
- tae.OpenEntryStream().CopyTo(entry);
-
- // Copy the input stream to the output
- tarFile.AddEntry(key, entry, size: tae.Size, modified: tae.LastModifiedTime);
- }
- }
- }
-
- // Close the output tar file
- tarFile.SaveTo(tempFile, new WriterOptions(CompressionType.None));
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- inputStream.Dispose();
- tarFile.Dispose();
- oldTarFile.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- return success;
- }
-
- ///
- /// Write a set of input files to a tape archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either list of roms is null or empty, return
- if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
- {
- return success;
- }
-
- // If the number of inputs is less than the number of available roms, return
- if (inputFiles.Count < roms.Count)
- {
- return success;
- }
-
- // If one of the files doesn't exist, return
- foreach (string file in inputFiles)
- {
- if (!File.Exists(file))
- {
- return success;
- }
- }
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".tar") ? "" : ".tar"));
-
- // Set internal variables
- TarArchive oldTarFile = TarArchive.Create();
- TarArchive tarFile = TarArchive.Create();
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
- }
-
- // Sort the keys in TZIP order
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Now add all of the files in order
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // Get temporary date-time if possible
- DateTime? usableDate = null;
- if (date && !String.IsNullOrWhiteSpace(roms[index].Date) && DateTime.TryParse(roms[index].Date.Replace('\\', '/'), out DateTime dt))
- {
- usableDate = dt;
- }
-
- // Copy the input stream to the output
- tarFile.AddEntry(roms[index].Name, Utilities.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
- }
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- oldTarFile = TarArchive.Open(archiveFileName);
-
- // Get a list of all current entries
- List entries = oldTarFile.Entries.Select(i => i.Key).ToList();
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- // If the old one contains the new file, then just skip out
- if (entries.Contains(roms[i].Name.Replace('\\', '/')))
- {
- continue;
- }
-
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < entries.Count; i++)
- {
- inputIndexMap.Add(entries[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= entries.Count)
- {
- success = true;
- return success;
- }
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Get temporary date-time if possible
- DateTime? usableDate = null;
- if (date && !String.IsNullOrWhiteSpace(roms[-index - 1].Date) && DateTime.TryParse(roms[-index - 1].Date.Replace('\\', '/'), out DateTime dt))
- {
- usableDate = dt;
- }
-
- // Copy the input file to the output
- tarFile.AddEntry(roms[-index - 1].Name, Utilities.TryOpenRead(inputFiles[-index - 1]), size: roms[-index - 1].Size, modified: usableDate);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- // Get the stream from the original archive
- TarArchiveEntry tae = oldTarFile.Entries.ElementAt(index);
- MemoryStream entry = new MemoryStream();
- tae.OpenEntryStream().CopyTo(entry);
-
- // Copy the input stream to the output
- tarFile.AddEntry(key, entry, size: tae.Size, modified: tae.LastModifiedTime);
- }
- }
- }
-
- // Close the output tar file
- tarFile.SaveTo(tempFile, new WriterOptions(CompressionType.None));
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- tarFile.Dispose();
- oldTarFile.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- return true;
- }
-
- #endregion
- }
+ ///
+ /// Represents a Torrent7zip archive for reading and writing
+ ///
+ /// TODO: Don't try to read entries to MemoryStream during write
+ public class TapeArchive : BaseArchive
+ {
+ #region Constructors
+
+ ///
+ /// Create a new TorrentTarArchive with no base file
+ ///
+ public TapeArchive()
+ : base()
+ {
+ this.Type = FileType.TapeArchive;
+ }
+
+ ///
+ /// Create a new TorrentTarArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public TapeArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.TapeArchive;
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ bool encounteredErrors = true;
+
+ try
+ {
+ // Create the temp directory
+ Directory.CreateDirectory(outDir);
+
+ // Extract all files to the temp directory
+ TarArchive ta = TarArchive.Open(this.Filename);
+ foreach (TarArchiveEntry entry in ta.Entries)
+ {
+ entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
+ }
+ encounteredErrors = false;
+ ta.Dispose();
+ }
+ catch (EndOfStreamException)
+ {
+ // Catch this but don't count it as an error because SharpCompress is unsafe
+ }
+ catch (InvalidOperationException)
+ {
+ encounteredErrors = true;
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ encounteredErrors = true;
+ }
+
+ return encounteredErrors;
+ }
+
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ // Try to extract a stream using the given information
+ (MemoryStream ms, string realEntry) = CopyToStream(entryName);
+
+ // If the memory stream and the entry name are both non-null, we write to file
+ if (ms != null && realEntry != null)
+ {
+ realEntry = Path.Combine(outDir, realEntry);
+
+ // Create the output subfolder now
+ Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
+
+ // Now open and write the file if possible
+ FileStream fs = Utilities.TryCreate(realEntry);
+ if (fs != null)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ byte[] zbuffer = new byte[_bufferSize];
+ int zlen;
+ while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
+ {
+ fs.Write(zbuffer, 0, zlen);
+ fs.Flush();
+ }
+
+ ms?.Dispose();
+ fs?.Dispose();
+ }
+ else
+ {
+ ms?.Dispose();
+ fs?.Dispose();
+ realEntry = null;
+ }
+ }
+
+ return realEntry;
+ }
+
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realEntry = null;
+
+ try
+ {
+ TarArchive ta = TarArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
+ foreach (TarArchiveEntry entry in ta.Entries)
+ {
+ if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
+ {
+ // Write the file out
+ realEntry = entry.Key;
+ entry.WriteTo(ms);
+ }
+ }
+ ta.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ ms = null;
+ realEntry = null;
+ }
+
+ return (ms, realEntry);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ List found = new List();
+ string gamename = Path.GetFileNameWithoutExtension(this.Filename);
+
+ try
+ {
+ TarArchive ta = TarArchive.Open(Utilities.TryOpenRead(this.Filename));
+ foreach (TarArchiveEntry entry in ta.Entries.Where(e => e != null && !e.IsDirectory))
+ {
+ // If secure hashes are disabled, do a quickscan
+ if (omitFromScan == Hash.SecureHashes)
+ {
+ found.Add(new BaseFile
+ {
+ Filename = entry.Key,
+ Size = entry.Size,
+ CRC = BitConverter.GetBytes(entry.Crc),
+ Date = (date && entry.LastModifiedTime != null ? entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss") : null),
+
+ Parent = gamename,
+ });
+ }
+ // Otherwise, use the stream directly
+ else
+ {
+ Stream entryStream = entry.OpenEntryStream();
+ BaseFile tarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan: omitFromScan);
+ tarEntryRom.Filename = entry.Key;
+ tarEntryRom.Parent = gamename;
+ tarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
+ found.Add(tarEntryRom);
+ entryStream.Dispose();
+ }
+ }
+
+ // Dispose of the archive
+ ta.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return null;
+ }
+
+ return found;
+ }
+
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ List empties = new List();
+
+ try
+ {
+ TarArchive ta = TarArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false });
+ List tarEntries = ta.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
+ string lastTarEntry = null;
+ foreach (TarArchiveEntry entry in tarEntries)
+ {
+ if (entry != null)
+ {
+ // If the current is a superset of last, we skip it
+ if (lastTarEntry != null && lastTarEntry.StartsWith(entry.Key))
+ {
+ // No-op
+ }
+ // If the entry is a directory, we add it
+ else if (entry.IsDirectory)
+ {
+ empties.Add(entry.Key);
+ lastTarEntry = entry.Key;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ }
+
+ return empties;
+ }
+
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region Writing
+
+ ///
+ /// Write an input file to a tape archive
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ // Get the file stream for the file and write out
+ return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
+ }
+
+ ///
+ /// Write an input stream to a tape archive
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either input is null or empty, return
+ if (inputStream == null || rom == null || rom.Name == null)
+ {
+ return success;
+ }
+
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? "" : ".tar"));
+
+ // Set internal variables
+ TarArchive oldTarFile = TarArchive.Create();
+ TarArchive tarFile = TarArchive.Create();
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ // Get temporary date-time if possible
+ DateTime? usableDate = null;
+ if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out DateTime dt))
+ {
+ usableDate = dt;
+ }
+
+ // Copy the input stream to the output
+ inputStream.Seek(0, SeekOrigin.Begin);
+ tarFile.AddEntry(rom.Name, inputStream, size: rom.Size, modified: usableDate);
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ oldTarFile = TarArchive.Open(archiveFileName);
+
+ // Get a list of all current entries
+ List entries = oldTarFile.Entries.Select(i => i.Key).ToList();
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+
+ // If the old one doesn't contain the new file, then add it
+ if (!entries.Contains(rom.Name.Replace('\\', '/')))
+ {
+ inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < entries.Count; i++)
+ {
+ inputIndexMap.Add(entries[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= entries.Count)
+ {
+ success = true;
+ return success;
+ }
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // Get temporary date-time if possible
+ DateTime? usableDate = null;
+ if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out DateTime dt))
+ {
+ usableDate = dt;
+ }
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ // Copy the input file to the output
+ inputStream.Seek(0, SeekOrigin.Begin);
+ tarFile.AddEntry(rom.Name, inputStream, size: rom.Size, modified: usableDate);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ // Get the stream from the original archive
+ TarArchiveEntry tae = oldTarFile.Entries.ElementAt(index);
+ MemoryStream entry = new MemoryStream();
+ tae.OpenEntryStream().CopyTo(entry);
+
+ // Copy the input stream to the output
+ tarFile.AddEntry(key, entry, size: tae.Size, modified: tae.LastModifiedTime);
+ }
+ }
+ }
+
+ // Close the output tar file
+ tarFile.SaveTo(tempFile, new WriterOptions(CompressionType.None));
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ inputStream.Dispose();
+ tarFile.Dispose();
+ oldTarFile.Dispose();
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ return success;
+ }
+
+ ///
+ /// Write a set of input files to a tape archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either list of roms is null or empty, return
+ if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
+ {
+ return success;
+ }
+
+ // If the number of inputs is less than the number of available roms, return
+ if (inputFiles.Count < roms.Count)
+ {
+ return success;
+ }
+
+ // If one of the files doesn't exist, return
+ foreach (string file in inputFiles)
+ {
+ if (!File.Exists(file))
+ {
+ return success;
+ }
+ }
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".tar") ? "" : ".tar"));
+
+ // Set internal variables
+ TarArchive oldTarFile = TarArchive.Create();
+ TarArchive tarFile = TarArchive.Create();
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
+ }
+
+ // Sort the keys in TZIP order
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Now add all of the files in order
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // Get temporary date-time if possible
+ DateTime? usableDate = null;
+ if (date && !String.IsNullOrWhiteSpace(roms[index].Date) && DateTime.TryParse(roms[index].Date.Replace('\\', '/'), out DateTime dt))
+ {
+ usableDate = dt;
+ }
+
+ // Copy the input stream to the output
+ tarFile.AddEntry(roms[index].Name, Utilities.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
+ }
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ oldTarFile = TarArchive.Open(archiveFileName);
+
+ // Get a list of all current entries
+ List entries = oldTarFile.Entries.Select(i => i.Key).ToList();
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ // If the old one contains the new file, then just skip out
+ if (entries.Contains(roms[i].Name.Replace('\\', '/')))
+ {
+ continue;
+ }
+
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < entries.Count; i++)
+ {
+ inputIndexMap.Add(entries[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= entries.Count)
+ {
+ success = true;
+ return success;
+ }
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ // Get temporary date-time if possible
+ DateTime? usableDate = null;
+ if (date && !String.IsNullOrWhiteSpace(roms[-index - 1].Date) && DateTime.TryParse(roms[-index - 1].Date.Replace('\\', '/'), out DateTime dt))
+ {
+ usableDate = dt;
+ }
+
+ // Copy the input file to the output
+ tarFile.AddEntry(roms[-index - 1].Name, Utilities.TryOpenRead(inputFiles[-index - 1]), size: roms[-index - 1].Size, modified: usableDate);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ // Get the stream from the original archive
+ TarArchiveEntry tae = oldTarFile.Entries.ElementAt(index);
+ MemoryStream entry = new MemoryStream();
+ tae.OpenEntryStream().CopyTo(entry);
+
+ // Copy the input stream to the output
+ tarFile.AddEntry(key, entry, size: tae.Size, modified: tae.LastModifiedTime);
+ }
+ }
+ }
+
+ // Close the output tar file
+ tarFile.SaveTo(tempFile, new WriterOptions(CompressionType.None));
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ tarFile.Dispose();
+ oldTarFile.Dispose();
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ return true;
+ }
+
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/XZArchive.cs b/SabreTools.Library/FileTypes/XZArchive.cs
index 6c74ae8a..2055c670 100644
--- a/SabreTools.Library/FileTypes/XZArchive.cs
+++ b/SabreTools.Library/FileTypes/XZArchive.cs
@@ -26,634 +26,634 @@ using SharpCompress.Readers;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a TorrentXZ archive for reading and writing
- ///
- /// TODO: Wait for XZ write to be enabled by SevenZipSharp library
- public class XZArchive : BaseArchive
- {
- #region Constructors
-
- ///
- /// Create a new TorrentGZipArchive with no base file
- ///
- public XZArchive()
- : base()
- {
- _fileType = FileType.XZArchive;
- }
-
- ///
- /// Create a new TorrentGZipArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public XZArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.XZArchive;
- }
-
- #endregion
-
- #region Extraction
-
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- bool encounteredErrors = true;
-
- try
- {
- // Create the temp directory
- Directory.CreateDirectory(outDir);
-
- // Extract all files to the temp directory
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(_filename));
- foreach (SevenZipArchiveEntry entry in sza.Entries)
- {
- entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
- }
- encounteredErrors = false;
- sza.Dispose();
- }
- catch (EndOfStreamException)
- {
- // Catch this but don't count it as an error because SharpCompress is unsafe
- }
- catch (InvalidOperationException)
- {
- encounteredErrors = true;
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- encounteredErrors = true;
- }
-
- return encounteredErrors;
- }
-
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- // Try to extract a stream using the given information
- (MemoryStream ms, string realEntry) = CopyToStream(entryName);
-
- // If the memory stream and the entry name are both non-null, we write to file
- if (ms != null && realEntry != null)
- {
- realEntry = Path.Combine(outDir, realEntry);
-
- // Create the output subfolder now
- Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
-
- // Now open and write the file if possible
- FileStream fs = Utilities.TryCreate(realEntry);
- if (fs != null)
- {
- ms.Seek(0, SeekOrigin.Begin);
- byte[] zbuffer = new byte[_bufferSize];
- int zlen;
- while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
- {
- fs.Write(zbuffer, 0, zlen);
- fs.Flush();
- }
-
- ms?.Dispose();
- fs?.Dispose();
- }
- else
- {
- ms?.Dispose();
- fs?.Dispose();
- realEntry = null;
- }
- }
-
- return realEntry;
- }
-
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realEntry = null;
-
- try
- {
- SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false, });
- foreach (SevenZipArchiveEntry entry in sza.Entries)
- {
- if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
- {
- // Write the file out
- realEntry = entry.Key;
- entry.WriteTo(ms);
- break;
- }
- }
- sza.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- ms = null;
- realEntry = null;
- }
-
- return (ms, realEntry);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
-
- #endregion
-
- #region Writing
-
- ///
- /// Write an input file to a torrent XZ file
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- // Get the file stream for the file and write out
- return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
- }
-
- ///
- /// Write an input file to a torrent XZ archive
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either input is null or empty, return
- if (inputStream == null || rom == null || rom.Name == null)
- {
- return success;
- }
-
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
-
- // Seek to the beginning of the stream
- inputStream.Seek(0, SeekOrigin.Begin);
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".xz") ? "" : ".xz"));
-
- // Set internal variables
- SevenZipBase.SetLibraryPath("7za.dll");
- SevenZipExtractor oldZipFile = null;
- SevenZipCompressor zipFile;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.XZ,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Create the temp directory
- string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
- if (!Directory.Exists(tempPath))
- {
- Directory.CreateDirectory(tempPath);
- }
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(rom.Name, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- using (oldZipFile = new SevenZipExtractor(archiveFileName))
- {
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
-
- // If the old one doesn't contain the new file, then add it
- if (!oldZipFile.ArchiveFileNames.Contains(rom.Name.Replace('\\', '/')))
- {
- inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.FilesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.XZ,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(rom.Name, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- Stream oldZipFileEntryStream = new MemoryStream();
- oldZipFile.ExtractFile(index, oldZipFileEntryStream);
- oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- oldZipFileEntryStream.Dispose();
- }
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
- }
- }
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- inputStream?.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- // Now make the file T7Z
- // TODO: Add ACTUAL T7Z compatible code
-
- BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
- bw.Seek(0, SeekOrigin.Begin);
- bw.Write(Constants.Torrent7ZipHeader);
- bw.Seek(0, SeekOrigin.End);
-
- using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
- {
-
- // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
- byte[] tempsig = Constants.Torrent7ZipSignature;
- if (oldZipFile.FilesCount > 1)
- {
- tempsig[16] = 0x2;
- }
- else
- {
- tempsig[16] = 0;
- }
-
- bw.Write(tempsig);
- bw.Flush();
- bw.Dispose();
- }
-
- return true;
- }
-
- ///
- /// Write a set of input files to a torrent XZ archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either list of roms is null or empty, return
- if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
- {
- return success;
- }
-
- // If the number of inputs is less than the number of available roms, return
- if (inputFiles.Count < roms.Count)
- {
- return success;
- }
-
- // If one of the files doesn't exist, return
- foreach (string file in inputFiles)
- {
- if (!File.Exists(file))
- {
- return success;
- }
- }
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".xz") ? "" : ".xz"));
-
- // Set internal variables
- SevenZipBase.SetLibraryPath("7za.dll");
- SevenZipExtractor oldZipFile;
- SevenZipCompressor zipFile;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.XZ,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
- }
-
- // Sort the keys in TZIP order
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Create the temp directory
- string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
- if (!Directory.Exists(tempPath))
- {
- Directory.CreateDirectory(tempPath);
- }
-
- // Now add all of the files in order
- foreach (string key in keys)
- {
- string newkey = Path.Combine(tempPath, key);
-
- File.Move(inputFiles[inputIndexMap[key]], newkey);
- zipFile.CompressFiles(tempFile, newkey);
- File.Move(newkey, inputFiles[inputIndexMap[key]]);
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
-
- Utilities.CleanDirectory(tempPath);
- Utilities.TryDeleteDirectory(tempPath);
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- using (oldZipFile = new SevenZipExtractor(archiveFileName))
- {
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- // If the old one contains the new file, then just skip out
- if (oldZipFile.ArchiveFileNames.Contains(roms[i].Name.Replace('\\', '/')))
- {
- continue;
- }
-
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.FilesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile = new SevenZipCompressor()
- {
- ArchiveFormat = OutArchiveFormat.XZ,
- CompressionLevel = CompressionLevel.Normal,
- };
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- FileStream inputStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(key, inputStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- Stream oldZipFileEntryStream = new MemoryStream();
- oldZipFile.ExtractFile(index, oldZipFileEntryStream);
- oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
-
- // Create a stream dictionary
- Dictionary dict = new Dictionary();
- dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
-
- // Now add the stream
- zipFile.CompressStreamDictionary(dict, tempFile);
- oldZipFileEntryStream.Dispose();
- }
-
- // After the first file, make sure we're in append mode
- zipFile.CompressionMode = CompressionMode.Append;
- }
- }
- }
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- // Now make the file T7Z
- // TODO: Add ACTUAL T7Z compatible code
-
- BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
- bw.Seek(0, SeekOrigin.Begin);
- bw.Write(Constants.Torrent7ZipHeader);
- bw.Seek(0, SeekOrigin.End);
-
- using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
- {
- // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
- byte[] tempsig = Constants.Torrent7ZipSignature;
- if (oldZipFile.FilesCount > 1)
- {
- tempsig[16] = 0x2;
- }
- else
- {
- tempsig[16] = 0;
- }
-
- bw.Write(tempsig);
- bw.Flush();
- bw.Dispose();
- }
-
- return true;
- }
-
- #endregion
- }
+ ///
+ /// Represents a TorrentXZ archive for reading and writing
+ ///
+ /// TODO: Wait for XZ write to be enabled by SevenZipSharp library
+ public class XZArchive : BaseArchive
+ {
+ #region Constructors
+
+ ///
+ /// Create a new TorrentGZipArchive with no base file
+ ///
+ public XZArchive()
+ : base()
+ {
+ this.Type = FileType.XZArchive;
+ }
+
+ ///
+ /// Create a new TorrentGZipArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public XZArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.XZArchive;
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ bool encounteredErrors = true;
+
+ try
+ {
+ // Create the temp directory
+ Directory.CreateDirectory(outDir);
+
+ // Extract all files to the temp directory
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(Utilities.TryOpenRead(this.Filename));
+ foreach (SevenZipArchiveEntry entry in sza.Entries)
+ {
+ entry.WriteToDirectory(outDir, new ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
+ }
+ encounteredErrors = false;
+ sza.Dispose();
+ }
+ catch (EndOfStreamException)
+ {
+ // Catch this but don't count it as an error because SharpCompress is unsafe
+ }
+ catch (InvalidOperationException)
+ {
+ encounteredErrors = true;
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ encounteredErrors = true;
+ }
+
+ return encounteredErrors;
+ }
+
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ // Try to extract a stream using the given information
+ (MemoryStream ms, string realEntry) = CopyToStream(entryName);
+
+ // If the memory stream and the entry name are both non-null, we write to file
+ if (ms != null && realEntry != null)
+ {
+ realEntry = Path.Combine(outDir, realEntry);
+
+ // Create the output subfolder now
+ Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
+
+ // Now open and write the file if possible
+ FileStream fs = Utilities.TryCreate(realEntry);
+ if (fs != null)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ byte[] zbuffer = new byte[_bufferSize];
+ int zlen;
+ while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
+ {
+ fs.Write(zbuffer, 0, zlen);
+ fs.Flush();
+ }
+
+ ms?.Dispose();
+ fs?.Dispose();
+ }
+ else
+ {
+ ms?.Dispose();
+ fs?.Dispose();
+ realEntry = null;
+ }
+ }
+
+ return realEntry;
+ }
+
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realEntry = null;
+
+ try
+ {
+ SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
+ foreach (SevenZipArchiveEntry entry in sza.Entries)
+ {
+ if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
+ {
+ // Write the file out
+ realEntry = entry.Key;
+ entry.WriteTo(ms);
+ break;
+ }
+ }
+ sza.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ ms = null;
+ realEntry = null;
+ }
+
+ return (ms, realEntry);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region Writing
+
+ ///
+ /// Write an input file to a torrent XZ file
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ // Get the file stream for the file and write out
+ return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
+ }
+
+ ///
+ /// Write an input file to a torrent XZ archive
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either input is null or empty, return
+ if (inputStream == null || rom == null || rom.Name == null)
+ {
+ return success;
+ }
+
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
+
+ // Seek to the beginning of the stream
+ inputStream.Seek(0, SeekOrigin.Begin);
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".xz") ? "" : ".xz"));
+
+ // Set internal variables
+ SevenZipBase.SetLibraryPath("7za.dll");
+ SevenZipExtractor oldZipFile = null;
+ SevenZipCompressor zipFile;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.XZ,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Create the temp directory
+ string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
+ if (!Directory.Exists(tempPath))
+ {
+ Directory.CreateDirectory(tempPath);
+ }
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(rom.Name, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ using (oldZipFile = new SevenZipExtractor(archiveFileName))
+ {
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+
+ // If the old one doesn't contain the new file, then add it
+ if (!oldZipFile.ArchiveFileNames.Contains(rom.Name.Replace('\\', '/')))
+ {
+ inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < oldZipFile.FilesCount; i++)
+ {
+ inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
+ {
+ success = true;
+ return success;
+ }
+
+ // Otherwise, process the old zipfile
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.XZ,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(rom.Name, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ Stream oldZipFileEntryStream = new MemoryStream();
+ oldZipFile.ExtractFile(index, oldZipFileEntryStream);
+ oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ oldZipFileEntryStream.Dispose();
+ }
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+ }
+ }
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ inputStream?.Dispose();
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ // Now make the file T7Z
+ // TODO: Add ACTUAL T7Z compatible code
+
+ BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
+ bw.Seek(0, SeekOrigin.Begin);
+ bw.Write(Constants.Torrent7ZipHeader);
+ bw.Seek(0, SeekOrigin.End);
+
+ using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
+ {
+
+ // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
+ byte[] tempsig = Constants.Torrent7ZipSignature;
+ if (oldZipFile.FilesCount > 1)
+ {
+ tempsig[16] = 0x2;
+ }
+ else
+ {
+ tempsig[16] = 0;
+ }
+
+ bw.Write(tempsig);
+ bw.Flush();
+ bw.Dispose();
+ }
+
+ return true;
+ }
+
+ ///
+ /// Write a set of input files to a torrent XZ archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either list of roms is null or empty, return
+ if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
+ {
+ return success;
+ }
+
+ // If the number of inputs is less than the number of available roms, return
+ if (inputFiles.Count < roms.Count)
+ {
+ return success;
+ }
+
+ // If one of the files doesn't exist, return
+ foreach (string file in inputFiles)
+ {
+ if (!File.Exists(file))
+ {
+ return success;
+ }
+ }
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".xz") ? "" : ".xz"));
+
+ // Set internal variables
+ SevenZipBase.SetLibraryPath("7za.dll");
+ SevenZipExtractor oldZipFile;
+ SevenZipCompressor zipFile;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.XZ,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
+ }
+
+ // Sort the keys in TZIP order
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Create the temp directory
+ string tempPath = Path.Combine(outDir, Guid.NewGuid().ToString());
+ if (!Directory.Exists(tempPath))
+ {
+ Directory.CreateDirectory(tempPath);
+ }
+
+ // Now add all of the files in order
+ foreach (string key in keys)
+ {
+ string newkey = Path.Combine(tempPath, key);
+
+ File.Move(inputFiles[inputIndexMap[key]], newkey);
+ zipFile.CompressFiles(tempFile, newkey);
+ File.Move(newkey, inputFiles[inputIndexMap[key]]);
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+
+ Utilities.CleanDirectory(tempPath);
+ Utilities.TryDeleteDirectory(tempPath);
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ using (oldZipFile = new SevenZipExtractor(archiveFileName))
+ {
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+ for (int i = 0; i < inputFiles.Count; i++)
+ {
+ // If the old one contains the new file, then just skip out
+ if (oldZipFile.ArchiveFileNames.Contains(roms[i].Name.Replace('\\', '/')))
+ {
+ continue;
+ }
+
+ inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < oldZipFile.FilesCount; i++)
+ {
+ inputIndexMap.Add(oldZipFile.ArchiveFileNames[i], i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= oldZipFile.FilesCount)
+ {
+ success = true;
+ return success;
+ }
+
+ // Otherwise, process the old zipfile
+ zipFile = new SevenZipCompressor()
+ {
+ ArchiveFormat = OutArchiveFormat.XZ,
+ CompressionLevel = CompressionLevel.Normal,
+ };
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ FileStream inputStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(key, inputStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ Stream oldZipFileEntryStream = new MemoryStream();
+ oldZipFile.ExtractFile(index, oldZipFileEntryStream);
+ oldZipFileEntryStream.Seek(0, SeekOrigin.Begin);
+
+ // Create a stream dictionary
+ Dictionary dict = new Dictionary();
+ dict.Add(oldZipFile.ArchiveFileNames[index], oldZipFileEntryStream);
+
+ // Now add the stream
+ zipFile.CompressStreamDictionary(dict, tempFile);
+ oldZipFileEntryStream.Dispose();
+ }
+
+ // After the first file, make sure we're in append mode
+ zipFile.CompressionMode = CompressionMode.Append;
+ }
+ }
+ }
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ // Now make the file T7Z
+ // TODO: Add ACTUAL T7Z compatible code
+
+ BinaryWriter bw = new BinaryWriter(Utilities.TryOpenReadWrite(archiveFileName));
+ bw.Seek(0, SeekOrigin.Begin);
+ bw.Write(Constants.Torrent7ZipHeader);
+ bw.Seek(0, SeekOrigin.End);
+
+ using (oldZipFile = new SevenZipExtractor(Utilities.TryOpenReadWrite(archiveFileName)))
+ {
+ // Get the correct signature to use (Default 0, Unicode 1, SingleFile 2, StripFileNames 4)
+ byte[] tempsig = Constants.Torrent7ZipSignature;
+ if (oldZipFile.FilesCount > 1)
+ {
+ tempsig[16] = 0x2;
+ }
+ else
+ {
+ tempsig[16] = 0;
+ }
+
+ bw.Write(tempsig);
+ bw.Flush();
+ bw.Dispose();
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/ZPAQArchive.cs b/SabreTools.Library/FileTypes/ZPAQArchive.cs
index 6e34f189..03d72ace 100644
--- a/SabreTools.Library/FileTypes/ZPAQArchive.cs
+++ b/SabreTools.Library/FileTypes/ZPAQArchive.cs
@@ -13,152 +13,152 @@ using Stream = System.IO.Stream;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a ZPAQArchive archive for reading and writing
- ///
- /// TODO: Implement from source at https://github.com/zpaq/zpaq - In progress as external DLL
- public class ZPAQArchive : BaseArchive
- {
- #region Constructors
+ ///
+ /// Represents a ZPAQArchive archive for reading and writing
+ ///
+ /// TODO: Implement from source at https://github.com/zpaq/zpaq - In progress as external DLL
+ public class ZPAQArchive : BaseArchive
+ {
+ #region Constructors
- ///
- /// Create a new ZPAQArchive with no base file
- ///
- public ZPAQArchive()
- : base()
- {
- _fileType = FileType.ZPAQArchive;
- }
+ ///
+ /// Create a new ZPAQArchive with no base file
+ ///
+ public ZPAQArchive()
+ : base()
+ {
+ this.Type = FileType.ZPAQArchive;
+ }
- ///
- /// Create a new ZPAQArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True if hashes for this file should be calculated, false otherwise (default)
- public ZPAQArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.ZPAQArchive;
- }
+ ///
+ /// Create a new ZPAQArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public ZPAQArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.ZPAQArchive;
+ }
- #endregion
+ #endregion
- #region Extraction
+ #region Extraction
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Information
+ #region Information
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
- #endregion
+ #endregion
- #region Writing
+ #region Writing
- ///
- /// Write an input file to a torrent ZPAQ file
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input file to a torrent ZPAQ file
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write an input stream to a torrent ZPAQ file
- ///
- /// Input stream to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the write was a success, false otherwise
- /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write an input stream to a torrent ZPAQ file
+ ///
+ /// Input stream to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the write was a success, false otherwise
+ /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code.
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- ///
- /// Write a set of input files to a torrent ZPAQ archive (assuming the same output archive name)
- ///
- /// Input files to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- throw new NotImplementedException();
- }
+ ///
+ /// Write a set of input files to a torrent ZPAQ archive (assuming the same output archive name)
+ ///
+ /// Input files to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ throw new NotImplementedException();
+ }
- #endregion
- }
+ #endregion
+ }
}
diff --git a/SabreTools.Library/FileTypes/ZipArchive.cs b/SabreTools.Library/FileTypes/ZipArchive.cs
index 60ef3be0..699ec232 100644
--- a/SabreTools.Library/FileTypes/ZipArchive.cs
+++ b/SabreTools.Library/FileTypes/ZipArchive.cs
@@ -22,823 +22,823 @@ using SharpCompress.Readers;
namespace SabreTools.Library.FileTypes
{
- ///
- /// Represents a Zip archive for reading and writing
- ///
- public class ZipArchive : BaseArchive
- {
- #region Constructors
-
- ///
- /// Create a new TorrentZipArchive with no base file
- ///
- public ZipArchive()
- : base()
- {
- _fileType = FileType.ZipArchive;
- }
-
- ///
- /// Create a new TorrentZipArchive from the given file
- ///
- /// Name of the file to use as an archive
- /// True for opening file as read, false for opening file as write
- /// True if hashes for this file should be calculated, false otherwise (default)
- public ZipArchive(string filename, bool getHashes = false)
- : base(filename, getHashes)
- {
- _fileType = FileType.ZipArchive;
- }
-
- #endregion
-
- #region Extraction
-
- ///
- /// Attempt to extract a file as an archive
- ///
- /// Output directory for archive extraction
- /// True if the extraction was a success, false otherwise
- public override bool CopyAll(string outDir)
- {
- bool encounteredErrors = true;
-
- try
- {
- // Create the temp directory
- Directory.CreateDirectory(outDir);
-
- // Extract all files to the temp directory
- ZipFile zf = new ZipFile();
- ZipReturn zr = zf.Open(_filename, new FileInfo(_filename).LastWriteTime.Ticks, true);
- if (zr != ZipReturn.ZipGood)
- {
- throw new Exception(ZipFile.ZipErrorMessageText(zr));
- }
-
- for (int i = 0; i < zf.EntriesCount && zr == ZipReturn.ZipGood; i++)
- {
- // Open the read stream
- zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
-
- // Create the rest of the path, if needed
- if (!String.IsNullOrWhiteSpace(Path.GetDirectoryName(zf.Entries[i].FileName)))
- {
- Directory.CreateDirectory(Path.Combine(outDir, Path.GetDirectoryName(zf.Entries[i].FileName)));
- }
-
- // If the entry ends with a directory separator, continue to the next item, if any
- if (zf.Entries[i].FileName.EndsWith(Path.DirectorySeparatorChar.ToString())
- || zf.Entries[i].FileName.EndsWith(Path.AltDirectorySeparatorChar.ToString())
- || zf.Entries[i].FileName.EndsWith(Path.PathSeparator.ToString()))
- {
- continue;
- }
-
- FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Entries[i].FileName));
-
- // If the stream is smaller than the buffer, just run one loop through to avoid issues
- if (streamsize < _bufferSize)
- {
- byte[] ibuffer = new byte[streamsize];
- int ilen = readStream.Read(ibuffer, 0, (int)streamsize);
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- // Otherwise, we do the normal loop
- else
- {
- int realBufferSize = (streamsize < _bufferSize ? (int)streamsize : _bufferSize);
- byte[] ibuffer = new byte[realBufferSize];
- int ilen;
- while ((ilen = readStream.Read(ibuffer, 0, realBufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- }
-
- zr = zf.CloseReadStream();
- writeStream.Dispose();
- }
- zf.Close();
- encounteredErrors = false;
- }
- catch (EndOfStreamException)
- {
- // Catch this but don't count it as an error because SharpCompress is unsafe
- }
- catch (InvalidOperationException)
- {
- encounteredErrors = true;
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- encounteredErrors = true;
- }
-
- return encounteredErrors;
- }
-
- ///
- /// Attempt to extract a file from an archive
- ///
- /// Name of the entry to be extracted
- /// Output directory for archive extraction
- /// Name of the extracted file, null on error
- public override string CopyToFile(string entryName, string outDir)
- {
- // Try to extract a stream using the given information
- (MemoryStream ms, string realEntry) = CopyToStream(entryName);
-
- // If the memory stream and the entry name are both non-null, we write to file
- if (ms != null && realEntry != null)
- {
- realEntry = Path.Combine(outDir, realEntry);
-
- // Create the output subfolder now
- Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
-
- // Now open and write the file if possible
- FileStream fs = Utilities.TryCreate(realEntry);
- if (fs != null)
- {
- ms.Seek(0, SeekOrigin.Begin);
- byte[] zbuffer = new byte[_bufferSize];
- int zlen;
- while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
- {
- fs.Write(zbuffer, 0, zlen);
- fs.Flush();
- }
-
- ms?.Dispose();
- fs?.Dispose();
- }
- else
- {
- ms?.Dispose();
- fs?.Dispose();
- realEntry = null;
- }
- }
-
- return realEntry;
- }
-
- ///
- /// Attempt to extract a stream from an archive
- ///
- /// Name of the entry to be extracted
- /// Output representing the entry name that was found
- /// MemoryStream representing the entry, null on error
- public override (MemoryStream, string) CopyToStream(string entryName)
- {
- MemoryStream ms = new MemoryStream();
- string realEntry = null;
-
- try
- {
- ZipFile zf = new ZipFile();
- ZipReturn zr = zf.Open(_filename, new FileInfo(_filename).LastWriteTime.Ticks, true);
- if (zr != ZipReturn.ZipGood)
- {
- throw new Exception(ZipFile.ZipErrorMessageText(zr));
- }
-
- for (int i = 0; i < zf.EntriesCount && zr == ZipReturn.ZipGood; i++)
- {
- if (zf.Entries[i].FileName.Contains(entryName))
- {
- // Open the read stream
- realEntry = zf.Entries[i].FileName;
- zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
-
- // If the stream is smaller than the buffer, just run one loop through to avoid issues
- if (streamsize < _bufferSize)
- {
- byte[] ibuffer = new byte[streamsize];
- int ilen = readStream.Read(ibuffer, 0, (int)streamsize);
- ms.Write(ibuffer, 0, ilen);
- ms.Flush();
- }
- // Otherwise, we do the normal loop
- else
- {
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while (streamsize > _bufferSize)
- {
- ilen = readStream.Read(ibuffer, 0, _bufferSize);
- ms.Write(ibuffer, 0, ilen);
- ms.Flush();
- streamsize -= _bufferSize;
- }
-
- ilen = readStream.Read(ibuffer, 0, (int)streamsize);
- ms.Write(ibuffer, 0, ilen);
- ms.Flush();
- }
-
- zr = zf.CloseReadStream();
- }
- }
-
- zf.Dispose();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- ms = null;
- realEntry = null;
- }
-
- return (ms, realEntry);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Generate a list of DatItem objects from the header values in an archive
- ///
- /// Hash representing the hashes that should be skipped
- /// True if entry dates should be included, false otherwise (default)
- /// List of DatItem objects representing the found data
- /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
- public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
- {
- List found = new List();
- string gamename = Path.GetFileNameWithoutExtension(_filename);
-
- try
- {
- ZipFile zf = new ZipFile();
- ZipReturn zr = zf.Open(_filename, new FileInfo(_filename).LastWriteTime.Ticks, true);
- if (zr != ZipReturn.ZipGood)
- {
- throw new Exception(ZipFile.ZipErrorMessageText(zr));
- }
-
- for (int i = 0; i < zf.EntriesCount; i++)
- {
- // Open the read stream
- zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
-
- // If we get a read error, log it and continue
- if (zr != ZipReturn.ZipGood)
- {
- Globals.Logger.Warning("An error occurred while reading archive {0}: Zip Error - {1}", _filename, zr);
- continue;
- }
-
- // If the entry ends with a directory separator, continue to the next item, if any
- if (zf.Entries[i].FileName.EndsWith(Path.DirectorySeparatorChar.ToString())
- || zf.Entries[i].FileName.EndsWith(Path.AltDirectorySeparatorChar.ToString())
- || zf.Entries[i].FileName.EndsWith(Path.PathSeparator.ToString()))
- {
- continue;
- }
-
- // If secure hashes are disabled, do a quickscan
- if (omitFromScan == Hash.SecureHashes)
- {
- string newname = zf.Entries[i].FileName;
- long newsize = (long)zf.Entries[i].UncompressedSize;
- byte[] newcrc = zf.Entries[i].CRC.Reverse().ToArray();
- string convertedDate = Utilities.ConvertMsDosTimeFormatToDateTime(zf.Entries[i].LastMod).ToString("yyyy/MM/dd hh:mm:ss");
-
- found.Add(new BaseFile
- {
- Filename = newname,
- Size = newsize,
- CRC = newcrc,
- Date = (date ? convertedDate : null),
-
- Parent = gamename,
- });
- }
- // Otherwise, use the stream directly
- else
- {
- BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.Entries[i].UncompressedSize, omitFromScan: omitFromScan, keepReadOpen: true);
- zipEntryRom.Filename = zf.Entries[i].FileName;
- zipEntryRom.Parent = gamename;
- string convertedDate = Utilities.ConvertMsDosTimeFormatToDateTime(zf.Entries[i].LastMod).ToString("yyyy/MM/dd hh:mm:ss");
- zipEntryRom.Date = (date ? convertedDate : null);
- found.Add(zipEntryRom);
- }
- }
-
- // Dispose of the archive
- zr = zf.CloseReadStream();
- zf.Close();
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- return null;
- }
-
- return found;
- }
-
- ///
- /// Generate a list of empty folders in an archive
- ///
- /// Input file to get data from
- /// List of empty folders in the archive
- public override List GetEmptyFolders()
- {
- List empties = new List();
-
- try
- {
- SharpCompress.Archives.Zip.ZipArchive za = SharpCompress.Archives.Zip.ZipArchive.Open(_filename, new ReaderOptions { LeaveStreamOpen = false });
- List zipEntries = za.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
- string lastZipEntry = null;
- foreach (SharpCompress.Archives.Zip.ZipArchiveEntry entry in zipEntries)
- {
- if (entry != null)
- {
- // If the current is a superset of last, we skip it
- if (lastZipEntry != null && lastZipEntry.StartsWith(entry.Key))
- {
- // No-op
- }
- // If the entry is a directory, we add it
- else
- {
- if (entry.IsDirectory)
- {
- empties.Add(entry.Key);
- }
- lastZipEntry = entry.Key;
- }
- }
- }
- }
- catch (Exception ex)
- {
- Globals.Logger.Error(ex.ToString());
- }
-
- return empties;
- }
-
- ///
- /// Check whether the input file is a standardized format
- ///
- public override bool IsTorrent()
- {
- throw new NotImplementedException();
- }
-
- #endregion
-
- #region Writing
-
- ///
- /// Write an input file to a torrentzip archive
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- // Get the file stream for the file and write out
- return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
- }
-
- ///
- /// Write an input stream to a torrentzip archive
- ///
- /// Input filename to be moved
- /// Output directory to build to
- /// DatItem representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either input is null or empty, return
- if (inputStream == null || rom == null || rom.Name == null)
- {
- return success;
- }
-
- // If the stream is not readable, return
- if (!inputStream.CanRead)
- {
- return success;
- }
-
- // Seek to the beginning of the stream
- inputStream.Seek(0, SeekOrigin.Begin);
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? "" : ".zip"));
-
- // Set internal variables
- Stream writeStream = null;
- ZipFile oldZipFile = new ZipFile();
- ZipFile zipFile = new ZipFile();
- ZipReturn zipReturn = ZipReturn.ZipGood;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- inputStream.Seek(0, SeekOrigin.Begin);
- zipReturn = zipFile.Create(tempFile);
-
- // Open the input file for reading
- ulong istreamSize = (ulong)(inputStream.Length);
-
- DateTime dt = DateTime.Now;
- if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out dt))
- {
- uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
- zipFile.OpenWriteStream(false, false, rom.Name.Replace('\\', '/'), istreamSize,
- SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
- }
- else
- {
- zipFile.OpenWriteStream(false, true, rom.Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
- }
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- inputStream.Dispose();
- zipFile.CloseWriteStream(Convert.ToUInt32(rom.CRC, 16));
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- oldZipFile.Open(archiveFileName, new FileInfo(archiveFileName).LastWriteTime.Ticks, true);
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
-
- // If the old one doesn't contain the new file, then add it
- if (!oldZipFile.Contains(rom.Name.Replace('\\', '/')))
- {
- inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.EntriesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.Filename(i), i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.EntriesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile.Create(tempFile);
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Open the input file for reading
- ulong istreamSize = (ulong)(inputStream.Length);
-
- DateTime dt = DateTime.Now;
- if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out dt))
- {
- uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
- zipFile.OpenWriteStream(false, false, rom.Name.Replace('\\', '/'), istreamSize,
- SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
- }
- else
- {
- zipFile.OpenWriteStream(false, true, rom.Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
- }
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- inputStream.Dispose();
- zipFile.CloseWriteStream(Convert.ToUInt32(rom.CRC, 16));
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- // Instantiate the streams
- oldZipFile.OpenReadStream(index, false, out Stream zreadStream, out ulong istreamSize, out SabreTools.Library.Data.CompressionMethod icompressionMethod, out uint lastMod);
- zipFile.OpenWriteStream(false, lastMod == Constants.TorrentZipFileDateTime, oldZipFile.Filename(index),
- istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: lastMod);
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = zreadStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- zipFile.CloseWriteStream(BitConverter.ToUInt32(oldZipFile.CRC32(index), 0));
- }
- }
- }
-
- // Close the output zip file
- zipFile.Close();
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- inputStream?.Dispose();
- zipFile.Dispose();
- oldZipFile.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- return true;
- }
-
- ///
- /// Write a set of input files to a torrentzip archive (assuming the same output archive name)
- ///
- /// Input filenames to be moved
- /// Output directory to build to
- /// List of Rom representing the new information
- /// True if the date from the DAT should be used if available, false otherwise (default)
- /// True if files should be output in Romba depot folders, false otherwise
- /// True if the archive was written properly, false otherwise
- public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
- {
- bool success = false;
- string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
-
- // If either list of roms is null or empty, return
- if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
- {
- return success;
- }
-
- // If the number of inputs is less than the number of available roms, return
- if (inputFiles.Count < roms.Count)
- {
- return success;
- }
-
- // If one of the files doesn't exist, return
- foreach (string file in inputFiles)
- {
- if (!File.Exists(file))
- {
- return success;
- }
- }
-
- // Get the output archive name from the first rebuild rom
- string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? "" : ".zip"));
-
- // Set internal variables
- Stream writeStream = null;
- ZipFile oldZipFile = new ZipFile();
- ZipFile zipFile = new ZipFile();
- ZipReturn zipReturn = ZipReturn.ZipGood;
-
- try
- {
- // If the full output path doesn't exist, create it
- if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
- }
-
- // If the archive doesn't exist, create it and put the single file
- if (!File.Exists(archiveFileName))
- {
- zipReturn = zipFile.Create(tempFile);
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
- }
-
- // Sort the keys in TZIP order
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Now add all of the files in order
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // Open the input file for reading
- Stream freadStream = Utilities.TryOpenRead(inputFiles[index]);
- ulong istreamSize = (ulong)(new FileInfo(inputFiles[index]).Length);
-
- DateTime dt = DateTime.Now;
- if (date && !String.IsNullOrWhiteSpace(roms[index].Date) && DateTime.TryParse(roms[index].Date.Replace('\\', '/'), out dt))
- {
- uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
- zipFile.OpenWriteStream(false, false, roms[index].Name.Replace('\\', '/'), istreamSize,
- SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
- }
- else
- {
- zipFile.OpenWriteStream(false, true, roms[index].Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
- }
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = freadStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- freadStream.Dispose();
- zipFile.CloseWriteStream(Convert.ToUInt32(roms[index].CRC, 16));
- }
- }
-
- // Otherwise, sort the input files and write out in the correct order
- else
- {
- // Open the old archive for reading
- oldZipFile.Open(archiveFileName, new FileInfo(archiveFileName).LastWriteTime.Ticks, true);
-
- // Map all inputs to index
- Dictionary inputIndexMap = new Dictionary();
- for (int i = 0; i < inputFiles.Count; i++)
- {
- // If the old one contains the new file, then just skip out
- if (oldZipFile.Contains(roms[i].Name.Replace('\\', '/')))
- {
- continue;
- }
-
- inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), -(i + 1));
- }
-
- // Then add all of the old entries to it too
- for (int i = 0; i < oldZipFile.EntriesCount; i++)
- {
- inputIndexMap.Add(oldZipFile.Filename(i), i);
- }
-
- // If the number of entries is the same as the old archive, skip out
- if (inputIndexMap.Keys.Count <= oldZipFile.EntriesCount)
- {
- success = true;
- return success;
- }
-
- // Otherwise, process the old zipfile
- zipFile.Create(tempFile);
-
- // Get the order for the entries with the new file
- List keys = inputIndexMap.Keys.ToList();
- keys.Sort(ZipFile.TorrentZipStringCompare);
-
- // Copy over all files to the new archive
- foreach (string key in keys)
- {
- // Get the index mapped to the key
- int index = inputIndexMap[key];
-
- // If we have the input file, add it now
- if (index < 0)
- {
- // Open the input file for reading
- Stream freadStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
- ulong istreamSize = (ulong)(new FileInfo(inputFiles[-index - 1]).Length);
-
- DateTime dt = DateTime.Now;
- if (date && !String.IsNullOrWhiteSpace(roms[-index - 1].Date) && DateTime.TryParse(roms[-index - 1].Date.Replace('\\', '/'), out dt))
- {
- uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
- zipFile.OpenWriteStream(false, false, roms[-index - 1].Name.Replace('\\', '/'), istreamSize,
- SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
- }
- else
- {
- zipFile.OpenWriteStream(false, true, roms[-index - 1].Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
- }
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = freadStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- freadStream.Dispose();
- zipFile.CloseWriteStream(Convert.ToUInt32(roms[-index - 1].CRC, 16));
- }
-
- // Otherwise, copy the file from the old archive
- else
- {
- // Instantiate the streams
- oldZipFile.OpenReadStream(index, false, out Stream zreadStream, out ulong istreamSize, out SabreTools.Library.Data.CompressionMethod icompressionMethod, out uint lastMod);
- zipFile.OpenWriteStream(false, lastMod == Constants.TorrentZipFileDateTime, oldZipFile.Filename(index),
- istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: lastMod);
-
- // Copy the input stream to the output
- byte[] ibuffer = new byte[_bufferSize];
- int ilen;
- while ((ilen = zreadStream.Read(ibuffer, 0, _bufferSize)) > 0)
- {
- writeStream.Write(ibuffer, 0, ilen);
- writeStream.Flush();
- }
- zipFile.CloseWriteStream(BitConverter.ToUInt32(oldZipFile.CRC32(index), 0));
- }
- }
- }
-
- // Close the output zip file
- zipFile.Close();
-
- success = true;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- success = false;
- }
- finally
- {
- zipFile.Dispose();
- oldZipFile.Dispose();
- }
-
- // If the old file exists, delete it and replace
- if (File.Exists(archiveFileName))
- {
- Utilities.TryDeleteFile(archiveFileName);
- }
- File.Move(tempFile, archiveFileName);
-
- return true;
- }
-
- #endregion
- }
+ ///
+ /// Represents a Zip archive for reading and writing
+ ///
+ public class ZipArchive : BaseArchive
+ {
+ #region Constructors
+
+ ///
+ /// Create a new TorrentZipArchive with no base file
+ ///
+ public ZipArchive()
+ : base()
+ {
+ this.Type = FileType.ZipArchive;
+ }
+
+ ///
+ /// Create a new TorrentZipArchive from the given file
+ ///
+ /// Name of the file to use as an archive
+ /// True for opening file as read, false for opening file as write
+ /// True if hashes for this file should be calculated, false otherwise (default)
+ public ZipArchive(string filename, bool getHashes = false)
+ : base(filename, getHashes)
+ {
+ this.Type = FileType.ZipArchive;
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Attempt to extract a file as an archive
+ ///
+ /// Output directory for archive extraction
+ /// True if the extraction was a success, false otherwise
+ public override bool CopyAll(string outDir)
+ {
+ bool encounteredErrors = true;
+
+ try
+ {
+ // Create the temp directory
+ Directory.CreateDirectory(outDir);
+
+ // Extract all files to the temp directory
+ ZipFile zf = new ZipFile();
+ ZipReturn zr = zf.Open(this.Filename, new FileInfo(this.Filename).LastWriteTime.Ticks, true);
+ if (zr != ZipReturn.ZipGood)
+ {
+ throw new Exception(ZipFile.ZipErrorMessageText(zr));
+ }
+
+ for (int i = 0; i < zf.EntriesCount && zr == ZipReturn.ZipGood; i++)
+ {
+ // Open the read stream
+ zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
+
+ // Create the rest of the path, if needed
+ if (!String.IsNullOrWhiteSpace(Path.GetDirectoryName(zf.Entries[i].FileName)))
+ {
+ Directory.CreateDirectory(Path.Combine(outDir, Path.GetDirectoryName(zf.Entries[i].FileName)));
+ }
+
+ // If the entry ends with a directory separator, continue to the next item, if any
+ if (zf.Entries[i].FileName.EndsWith(Path.DirectorySeparatorChar.ToString())
+ || zf.Entries[i].FileName.EndsWith(Path.AltDirectorySeparatorChar.ToString())
+ || zf.Entries[i].FileName.EndsWith(Path.PathSeparator.ToString()))
+ {
+ continue;
+ }
+
+ FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Entries[i].FileName));
+
+ // If the stream is smaller than the buffer, just run one loop through to avoid issues
+ if (streamsize < _bufferSize)
+ {
+ byte[] ibuffer = new byte[streamsize];
+ int ilen = readStream.Read(ibuffer, 0, (int)streamsize);
+ writeStream.Write(ibuffer, 0, ilen);
+ writeStream.Flush();
+ }
+ // Otherwise, we do the normal loop
+ else
+ {
+ int realBufferSize = (streamsize < _bufferSize ? (int)streamsize : _bufferSize);
+ byte[] ibuffer = new byte[realBufferSize];
+ int ilen;
+ while ((ilen = readStream.Read(ibuffer, 0, realBufferSize)) > 0)
+ {
+ writeStream.Write(ibuffer, 0, ilen);
+ writeStream.Flush();
+ }
+ }
+
+ zr = zf.CloseReadStream();
+ writeStream.Dispose();
+ }
+ zf.Close();
+ encounteredErrors = false;
+ }
+ catch (EndOfStreamException)
+ {
+ // Catch this but don't count it as an error because SharpCompress is unsafe
+ }
+ catch (InvalidOperationException)
+ {
+ encounteredErrors = true;
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ encounteredErrors = true;
+ }
+
+ return encounteredErrors;
+ }
+
+ ///
+ /// Attempt to extract a file from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output directory for archive extraction
+ /// Name of the extracted file, null on error
+ public override string CopyToFile(string entryName, string outDir)
+ {
+ // Try to extract a stream using the given information
+ (MemoryStream ms, string realEntry) = CopyToStream(entryName);
+
+ // If the memory stream and the entry name are both non-null, we write to file
+ if (ms != null && realEntry != null)
+ {
+ realEntry = Path.Combine(outDir, realEntry);
+
+ // Create the output subfolder now
+ Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
+
+ // Now open and write the file if possible
+ FileStream fs = Utilities.TryCreate(realEntry);
+ if (fs != null)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ byte[] zbuffer = new byte[_bufferSize];
+ int zlen;
+ while ((zlen = ms.Read(zbuffer, 0, _bufferSize)) > 0)
+ {
+ fs.Write(zbuffer, 0, zlen);
+ fs.Flush();
+ }
+
+ ms?.Dispose();
+ fs?.Dispose();
+ }
+ else
+ {
+ ms?.Dispose();
+ fs?.Dispose();
+ realEntry = null;
+ }
+ }
+
+ return realEntry;
+ }
+
+ ///
+ /// Attempt to extract a stream from an archive
+ ///
+ /// Name of the entry to be extracted
+ /// Output representing the entry name that was found
+ /// MemoryStream representing the entry, null on error
+ public override (MemoryStream, string) CopyToStream(string entryName)
+ {
+ MemoryStream ms = new MemoryStream();
+ string realEntry = null;
+
+ try
+ {
+ ZipFile zf = new ZipFile();
+ ZipReturn zr = zf.Open(this.Filename, new FileInfo(this.Filename).LastWriteTime.Ticks, true);
+ if (zr != ZipReturn.ZipGood)
+ {
+ throw new Exception(ZipFile.ZipErrorMessageText(zr));
+ }
+
+ for (int i = 0; i < zf.EntriesCount && zr == ZipReturn.ZipGood; i++)
+ {
+ if (zf.Entries[i].FileName.Contains(entryName))
+ {
+ // Open the read stream
+ realEntry = zf.Entries[i].FileName;
+ zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
+
+ // If the stream is smaller than the buffer, just run one loop through to avoid issues
+ if (streamsize < _bufferSize)
+ {
+ byte[] ibuffer = new byte[streamsize];
+ int ilen = readStream.Read(ibuffer, 0, (int)streamsize);
+ ms.Write(ibuffer, 0, ilen);
+ ms.Flush();
+ }
+ // Otherwise, we do the normal loop
+ else
+ {
+ byte[] ibuffer = new byte[_bufferSize];
+ int ilen;
+ while (streamsize > _bufferSize)
+ {
+ ilen = readStream.Read(ibuffer, 0, _bufferSize);
+ ms.Write(ibuffer, 0, ilen);
+ ms.Flush();
+ streamsize -= _bufferSize;
+ }
+
+ ilen = readStream.Read(ibuffer, 0, (int)streamsize);
+ ms.Write(ibuffer, 0, ilen);
+ ms.Flush();
+ }
+
+ zr = zf.CloseReadStream();
+ }
+ }
+
+ zf.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ ms = null;
+ realEntry = null;
+ }
+
+ return (ms, realEntry);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Generate a list of DatItem objects from the header values in an archive
+ ///
+ /// Hash representing the hashes that should be skipped
+ /// True if entry dates should be included, false otherwise (default)
+ /// List of DatItem objects representing the found data
+ /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
+ public override List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
+ {
+ List found = new List();
+ string gamename = Path.GetFileNameWithoutExtension(this.Filename);
+
+ try
+ {
+ ZipFile zf = new ZipFile();
+ ZipReturn zr = zf.Open(this.Filename, new FileInfo(this.Filename).LastWriteTime.Ticks, true);
+ if (zr != ZipReturn.ZipGood)
+ {
+ throw new Exception(ZipFile.ZipErrorMessageText(zr));
+ }
+
+ for (int i = 0; i < zf.EntriesCount; i++)
+ {
+ // Open the read stream
+ zr = zf.OpenReadStream(i, false, out Stream readStream, out ulong streamsize, out SabreTools.Library.Data.CompressionMethod cm, out uint lastMod);
+
+ // If we get a read error, log it and continue
+ if (zr != ZipReturn.ZipGood)
+ {
+ Globals.Logger.Warning("An error occurred while reading archive {0}: Zip Error - {1}", this.Filename, zr);
+ continue;
+ }
+
+ // If the entry ends with a directory separator, continue to the next item, if any
+ if (zf.Entries[i].FileName.EndsWith(Path.DirectorySeparatorChar.ToString())
+ || zf.Entries[i].FileName.EndsWith(Path.AltDirectorySeparatorChar.ToString())
+ || zf.Entries[i].FileName.EndsWith(Path.PathSeparator.ToString()))
+ {
+ continue;
+ }
+
+ // If secure hashes are disabled, do a quickscan
+ if (omitFromScan == Hash.SecureHashes)
+ {
+ string newname = zf.Entries[i].FileName;
+ long newsize = (long)zf.Entries[i].UncompressedSize;
+ byte[] newcrc = zf.Entries[i].CRC.Reverse().ToArray();
+ string convertedDate = Utilities.ConvertMsDosTimeFormatToDateTime(zf.Entries[i].LastMod).ToString("yyyy/MM/dd hh:mm:ss");
+
+ found.Add(new BaseFile
+ {
+ Filename = newname,
+ Size = newsize,
+ CRC = newcrc,
+ Date = (date ? convertedDate : null),
+
+ Parent = gamename,
+ });
+ }
+ // Otherwise, use the stream directly
+ else
+ {
+ BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.Entries[i].UncompressedSize, omitFromScan: omitFromScan, keepReadOpen: true);
+ zipEntryRom.Filename = zf.Entries[i].FileName;
+ zipEntryRom.Parent = gamename;
+ string convertedDate = Utilities.ConvertMsDosTimeFormatToDateTime(zf.Entries[i].LastMod).ToString("yyyy/MM/dd hh:mm:ss");
+ zipEntryRom.Date = (date ? convertedDate : null);
+ found.Add(zipEntryRom);
+ }
+ }
+
+ // Dispose of the archive
+ zr = zf.CloseReadStream();
+ zf.Close();
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ return null;
+ }
+
+ return found;
+ }
+
+ ///
+ /// Generate a list of empty folders in an archive
+ ///
+ /// Input file to get data from
+ /// List of empty folders in the archive
+ public override List GetEmptyFolders()
+ {
+ List empties = new List();
+
+ try
+ {
+ SharpCompress.Archives.Zip.ZipArchive za = SharpCompress.Archives.Zip.ZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false });
+ List zipEntries = za.Entries.OrderBy(e => e.Key, new NaturalSort.NaturalReversedComparer()).ToList();
+ string lastZipEntry = null;
+ foreach (SharpCompress.Archives.Zip.ZipArchiveEntry entry in zipEntries)
+ {
+ if (entry != null)
+ {
+ // If the current is a superset of last, we skip it
+ if (lastZipEntry != null && lastZipEntry.StartsWith(entry.Key))
+ {
+ // No-op
+ }
+ // If the entry is a directory, we add it
+ else
+ {
+ if (entry.IsDirectory)
+ {
+ empties.Add(entry.Key);
+ }
+ lastZipEntry = entry.Key;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Error(ex.ToString());
+ }
+
+ return empties;
+ }
+
+ ///
+ /// Check whether the input file is a standardized format
+ ///
+ public override bool IsTorrent()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region Writing
+
+ ///
+ /// Write an input file to a torrentzip archive
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ // Get the file stream for the file and write out
+ return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
+ }
+
+ ///
+ /// Write an input stream to a torrentzip archive
+ ///
+ /// Input filename to be moved
+ /// Output directory to build to
+ /// DatItem representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either input is null or empty, return
+ if (inputStream == null || rom == null || rom.Name == null)
+ {
+ return success;
+ }
+
+ // If the stream is not readable, return
+ if (!inputStream.CanRead)
+ {
+ return success;
+ }
+
+ // Seek to the beginning of the stream
+ inputStream.Seek(0, SeekOrigin.Begin);
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? "" : ".zip"));
+
+ // Set internal variables
+ Stream writeStream = null;
+ ZipFile oldZipFile = new ZipFile();
+ ZipFile zipFile = new ZipFile();
+ ZipReturn zipReturn = ZipReturn.ZipGood;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ inputStream.Seek(0, SeekOrigin.Begin);
+ zipReturn = zipFile.Create(tempFile);
+
+ // Open the input file for reading
+ ulong istreamSize = (ulong)(inputStream.Length);
+
+ DateTime dt = DateTime.Now;
+ if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out dt))
+ {
+ uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
+ zipFile.OpenWriteStream(false, false, rom.Name.Replace('\\', '/'), istreamSize,
+ SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
+ }
+ else
+ {
+ zipFile.OpenWriteStream(false, true, rom.Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
+ }
+
+ // Copy the input stream to the output
+ byte[] ibuffer = new byte[_bufferSize];
+ int ilen;
+ while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
+ {
+ writeStream.Write(ibuffer, 0, ilen);
+ writeStream.Flush();
+ }
+ inputStream.Dispose();
+ zipFile.CloseWriteStream(Convert.ToUInt32(rom.CRC, 16));
+ }
+
+ // Otherwise, sort the input files and write out in the correct order
+ else
+ {
+ // Open the old archive for reading
+ oldZipFile.Open(archiveFileName, new FileInfo(archiveFileName).LastWriteTime.Ticks, true);
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary();
+
+ // If the old one doesn't contain the new file, then add it
+ if (!oldZipFile.Contains(rom.Name.Replace('\\', '/')))
+ {
+ inputIndexMap.Add(rom.Name.Replace('\\', '/'), -1);
+ }
+
+ // Then add all of the old entries to it too
+ for (int i = 0; i < oldZipFile.EntriesCount; i++)
+ {
+ inputIndexMap.Add(oldZipFile.Filename(i), i);
+ }
+
+ // If the number of entries is the same as the old archive, skip out
+ if (inputIndexMap.Keys.Count <= oldZipFile.EntriesCount)
+ {
+ success = true;
+ return success;
+ }
+
+ // Otherwise, process the old zipfile
+ zipFile.Create(tempFile);
+
+ // Get the order for the entries with the new file
+ List keys = inputIndexMap.Keys.ToList();
+ keys.Sort(ZipFile.TorrentZipStringCompare);
+
+ // Copy over all files to the new archive
+ foreach (string key in keys)
+ {
+ // Get the index mapped to the key
+ int index = inputIndexMap[key];
+
+ // If we have the input file, add it now
+ if (index < 0)
+ {
+ // Open the input file for reading
+ ulong istreamSize = (ulong)(inputStream.Length);
+
+ DateTime dt = DateTime.Now;
+ if (date && !String.IsNullOrWhiteSpace(rom.Date) && DateTime.TryParse(rom.Date.Replace('\\', '/'), out dt))
+ {
+ uint msDosDateTime = Utilities.ConvertDateTimeToMsDosTimeFormat(dt);
+ zipFile.OpenWriteStream(false, false, rom.Name.Replace('\\', '/'), istreamSize,
+ SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: msDosDateTime);
+ }
+ else
+ {
+ zipFile.OpenWriteStream(false, true, rom.Name.Replace('\\', '/'), istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream);
+ }
+
+ // Copy the input stream to the output
+ byte[] ibuffer = new byte[_bufferSize];
+ int ilen;
+ while ((ilen = inputStream.Read(ibuffer, 0, _bufferSize)) > 0)
+ {
+ writeStream.Write(ibuffer, 0, ilen);
+ writeStream.Flush();
+ }
+ inputStream.Dispose();
+ zipFile.CloseWriteStream(Convert.ToUInt32(rom.CRC, 16));
+ }
+
+ // Otherwise, copy the file from the old archive
+ else
+ {
+ // Instantiate the streams
+ oldZipFile.OpenReadStream(index, false, out Stream zreadStream, out ulong istreamSize, out SabreTools.Library.Data.CompressionMethod icompressionMethod, out uint lastMod);
+ zipFile.OpenWriteStream(false, lastMod == Constants.TorrentZipFileDateTime, oldZipFile.Filename(index),
+ istreamSize, SabreTools.Library.Data.CompressionMethod.Deflated, out writeStream, lastMod: lastMod);
+
+ // Copy the input stream to the output
+ byte[] ibuffer = new byte[_bufferSize];
+ int ilen;
+ while ((ilen = zreadStream.Read(ibuffer, 0, _bufferSize)) > 0)
+ {
+ writeStream.Write(ibuffer, 0, ilen);
+ writeStream.Flush();
+ }
+ zipFile.CloseWriteStream(BitConverter.ToUInt32(oldZipFile.CRC32(index), 0));
+ }
+ }
+ }
+
+ // Close the output zip file
+ zipFile.Close();
+
+ success = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ success = false;
+ }
+ finally
+ {
+ inputStream?.Dispose();
+ zipFile.Dispose();
+ oldZipFile.Dispose();
+ }
+
+ // If the old file exists, delete it and replace
+ if (File.Exists(archiveFileName))
+ {
+ Utilities.TryDeleteFile(archiveFileName);
+ }
+ File.Move(tempFile, archiveFileName);
+
+ return true;
+ }
+
+ ///
+ /// Write a set of input files to a torrentzip archive (assuming the same output archive name)
+ ///
+ /// Input filenames to be moved
+ /// Output directory to build to
+ /// List of Rom representing the new information
+ /// True if the date from the DAT should be used if available, false otherwise (default)
+ /// True if files should be output in Romba depot folders, false otherwise
+ /// True if the archive was written properly, false otherwise
+ public override bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false)
+ {
+ bool success = false;
+ string tempFile = Path.Combine(outDir, "tmp" + Guid.NewGuid().ToString());
+
+ // If either list of roms is null or empty, return
+ if (inputFiles == null || roms == null || inputFiles.Count == 0 || roms.Count == 0)
+ {
+ return success;
+ }
+
+ // If the number of inputs is less than the number of available roms, return
+ if (inputFiles.Count < roms.Count)
+ {
+ return success;
+ }
+
+ // If one of the files doesn't exist, return
+ foreach (string file in inputFiles)
+ {
+ if (!File.Exists(file))
+ {
+ return success;
+ }
+ }
+
+ // Get the output archive name from the first rebuild rom
+ string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? "" : ".zip"));
+
+ // Set internal variables
+ Stream writeStream = null;
+ ZipFile oldZipFile = new ZipFile();
+ ZipFile zipFile = new ZipFile();
+ ZipReturn zipReturn = ZipReturn.ZipGood;
+
+ try
+ {
+ // If the full output path doesn't exist, create it
+ if (!Directory.Exists(Path.GetDirectoryName(archiveFileName)))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(archiveFileName));
+ }
+
+ // If the archive doesn't exist, create it and put the single file
+ if (!File.Exists(archiveFileName))
+ {
+ zipReturn = zipFile.Create(tempFile);
+
+ // Map all inputs to index
+ Dictionary inputIndexMap = new Dictionary