mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Remove .NET Framework 4.6.2/4.7.2 (#24)
* Remove < .NET 4.8, general cleanup * Abstract * Tango * Banner * Scan no more * Common * Application * Access * Filter-feeder * Graffiti * Paint-over * Law and Order * XOR-o * Unused staircase * Maybe * Maybe not * Delete this * The word is "no" * Emit * Improper * Aye aye * Fence * Barrier * Monkey * Pail * Lines
This commit is contained in:
@@ -3,6 +3,7 @@ using System.IO;
|
||||
|
||||
using SabreTools.Library.Data;
|
||||
using SabreTools.Library.DatItems;
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
@@ -34,6 +35,83 @@ namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an archive object from a filename, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Name of the file to create the archive from</param>
|
||||
/// <returns>Archive object representing the inputs</returns>
|
||||
public static BaseArchive Create(string input)
|
||||
{
|
||||
BaseArchive archive = null;
|
||||
|
||||
// First get the archive type
|
||||
FileType? at = input.GetFileType();
|
||||
|
||||
// If we got back null, then it's not an archive, so we we return
|
||||
if (at == null)
|
||||
return archive;
|
||||
|
||||
// Create the archive based on the type
|
||||
Globals.Logger.Verbose($"Found archive of type: {at}");
|
||||
switch (at)
|
||||
{
|
||||
case FileType.GZipArchive:
|
||||
archive = new GZipArchive(input);
|
||||
break;
|
||||
|
||||
case FileType.RarArchive:
|
||||
archive = new RarArchive(input);
|
||||
break;
|
||||
|
||||
case FileType.SevenZipArchive:
|
||||
archive = new SevenZipArchive(input);
|
||||
break;
|
||||
|
||||
case FileType.TapeArchive:
|
||||
archive = new TapeArchive(input);
|
||||
break;
|
||||
|
||||
case FileType.ZipArchive:
|
||||
archive = new ZipArchive(input);
|
||||
break;
|
||||
|
||||
default:
|
||||
// We ignore all other types for now
|
||||
break;
|
||||
}
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an archive object of the specified type, if possible
|
||||
/// </summary>
|
||||
/// <param name="archiveType">SharpCompress.Common.ArchiveType representing the archive to create</param>
|
||||
/// <returns>Archive object representing the inputs</returns>
|
||||
public static BaseArchive Create(FileType archiveType)
|
||||
{
|
||||
switch (archiveType)
|
||||
{
|
||||
case FileType.GZipArchive:
|
||||
return new GZipArchive();
|
||||
|
||||
case FileType.RarArchive:
|
||||
return new RarArchive();
|
||||
|
||||
case FileType.SevenZipArchive:
|
||||
return new SevenZipArchive();
|
||||
|
||||
case FileType.TapeArchive:
|
||||
return new TapeArchive();
|
||||
|
||||
case FileType.ZipArchive:
|
||||
return new ZipArchive();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
@@ -17,7 +17,9 @@ namespace SabreTools.Library.FileTypes
|
||||
public long? Size { get; set; }
|
||||
public byte[] CRC { get; set; }
|
||||
public byte[] MD5 { get; set; }
|
||||
#if NET_FRAMEWORK
|
||||
public byte[] RIPEMD160 { get; set; }
|
||||
#endif
|
||||
public byte[] SHA1 { get; set; }
|
||||
public byte[] SHA256 { get; set; }
|
||||
public byte[] SHA384 { get; set; }
|
||||
@@ -45,14 +47,16 @@ namespace SabreTools.Library.FileTypes
|
||||
|
||||
if (getHashes)
|
||||
{
|
||||
BaseFile temp = Utilities.GetFileInfo(this.Filename);
|
||||
BaseFile temp = FileExtensions.GetInfo(this.Filename);
|
||||
if (temp != null)
|
||||
{
|
||||
this.Parent = temp.Parent;
|
||||
this.Date = temp.Date;
|
||||
this.CRC = temp.CRC;
|
||||
this.MD5 = temp.MD5;
|
||||
#if NET_FRAMEWORK
|
||||
this.RIPEMD160 = temp.RIPEMD160;
|
||||
#endif
|
||||
this.SHA1 = temp.SHA1;
|
||||
this.SHA256 = temp.SHA256;
|
||||
this.SHA384 = temp.SHA384;
|
||||
@@ -73,21 +77,23 @@ namespace SabreTools.Library.FileTypes
|
||||
|
||||
if (getHashes)
|
||||
{
|
||||
BaseFile temp = Utilities.GetStreamInfo(stream, stream.Length);
|
||||
if(temp != null)
|
||||
BaseFile temp = stream.GetInfo();
|
||||
if (temp != null)
|
||||
{
|
||||
this.Parent = temp.Parent;
|
||||
this.Date = temp.Date;
|
||||
this.CRC = temp.CRC;
|
||||
this.MD5 = temp.MD5;
|
||||
#if NET_FRAMEWORK
|
||||
this.RIPEMD160 = temp.RIPEMD160;
|
||||
#endif
|
||||
this.SHA1 = temp.SHA1;
|
||||
this.SHA256 = temp.SHA256;
|
||||
this.SHA384 = temp.SHA384;
|
||||
this.SHA512 = temp.SHA512;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Data;
|
||||
using SabreTools.Library.Tools;
|
||||
@@ -9,477 +11,137 @@ 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
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ----------------------------------------------
|
||||
/// 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
|
||||
/// ----------------------------------------------
|
||||
/// </remarks>
|
||||
public class CHDFile : BaseFile
|
||||
public abstract 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;
|
||||
}
|
||||
}
|
||||
// Common header fields
|
||||
protected char[] tag = new char[8]; // 'MComprHD'
|
||||
protected uint length; // length of header (including tag and length fields)
|
||||
protected uint version; // drive format version
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, blank CHDFile
|
||||
/// </summary>
|
||||
public CHDFile()
|
||||
{
|
||||
this.Type = FileType.CHD;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new CHDFile from an input file
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
public CHDFile(string filename)
|
||||
: this(Utilities.TryOpenRead(filename))
|
||||
/// <param name="filename">Filename respresenting the CHD file</param>
|
||||
public static CHDFile Create(string filename)
|
||||
{
|
||||
using (FileStream fs = FileExtensions.TryOpenRead(filename))
|
||||
{
|
||||
return Create(fs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new CHDFile from an input stream
|
||||
/// </summary>
|
||||
/// <param name="chdstream">Stream representing the CHD file</param>
|
||||
public CHDFile(Stream chdstream)
|
||||
public static CHDFile Create(Stream chdstream)
|
||||
{
|
||||
this.Type = FileType.CHD;
|
||||
m_br = new BinaryReader(chdstream);
|
||||
// Read the standard CHD headers
|
||||
(char[] tag, uint length, uint version) = GetHeaderValues(chdstream);
|
||||
chdstream.Seek(-16, SeekOrigin.Current); // Seek back to start
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
// Validate that this is actually a valid CHD
|
||||
uint validatedVersion = ValidateHeader(tag, length, version);
|
||||
if (validatedVersion == 0)
|
||||
return null;
|
||||
|
||||
// Read and retrun the current CHD
|
||||
CHDFile generated = ReadAsVersion(chdstream, version);
|
||||
if (generated != null)
|
||||
generated.Type = FileType.CHD;
|
||||
|
||||
return generated;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Abstract functionality
|
||||
|
||||
/// <summary>
|
||||
/// Return the best-available hash for a particular CHD version
|
||||
/// </summary>
|
||||
public abstract byte[] GetHash();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Header Parsing
|
||||
|
||||
/// <summary>
|
||||
/// Validate the initial signature, version, and header size
|
||||
/// Get the generic header values of a CHD, if possible
|
||||
/// </summary>
|
||||
/// <returns>Unsigned int containing the version number, null if invalid</returns>
|
||||
private uint? ValidateHeaderVersion()
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
private static (char[] tag, uint length, uint version) GetHeaderValues(Stream stream)
|
||||
{
|
||||
try
|
||||
char[] parsedTag = new char[8];
|
||||
uint parsedLength = 0;
|
||||
uint parsedVersion = 0;
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
// Seek to the beginning to make sure we're reading the correct bytes
|
||||
m_br.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
parsedTag = br.ReadCharsBigEndian(8);
|
||||
parsedLength = br.ReadUInt32BigEndian();
|
||||
parsedVersion = br.ReadUInt32BigEndian();
|
||||
}
|
||||
|
||||
// Read and verify the CHD signature
|
||||
m_signature = m_br.ReadBytes(8);
|
||||
return (parsedTag, parsedLength, parsedVersion);
|
||||
}
|
||||
|
||||
// If no signature could be read, return null
|
||||
if (m_signature == null || m_signature.Length == 0)
|
||||
/// <summary>
|
||||
/// Validate the header values
|
||||
/// </summary>
|
||||
/// <returns>Matching version, 0 if none</returns>
|
||||
private static uint ValidateHeader(char[] tag, uint length, uint version)
|
||||
{
|
||||
if (!string.Equals(new string(tag), "MComprHD", StringComparison.Ordinal))
|
||||
return 0;
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
return length == CHDFileV1.HeaderSize ? version : 0;
|
||||
case 2:
|
||||
return length == CHDFileV2.HeaderSize ? version : 0;
|
||||
case 3:
|
||||
return length == CHDFileV3.HeaderSize ? version : 0;
|
||||
case 4:
|
||||
return length == CHDFileV4.HeaderSize ? version : 0;
|
||||
case 5:
|
||||
return length == CHDFileV5.HeaderSize ? version : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a stream as a particular CHD version
|
||||
/// </summary>
|
||||
/// <param name="stream">CHD file as a stream</param>
|
||||
/// <param name="version">CHD version to parse</param>
|
||||
/// <returns>Populated CHD file, null on failure</returns>
|
||||
private static CHDFile ReadAsVersion(Stream stream, uint version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
return CHDFileV1.Deserialize(stream);
|
||||
case 2:
|
||||
return CHDFileV2.Deserialize(stream);
|
||||
case 3:
|
||||
return CHDFileV3.Deserialize(stream);
|
||||
case 4:
|
||||
return CHDFileV4.Deserialize(stream);
|
||||
case 5:
|
||||
return CHDFileV5.Deserialize(stream);
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal MD5 (v1, v2) or SHA-1 (v3, v4, v5) from the CHD
|
||||
/// </summary>
|
||||
/// <returns>MD5 as a byte array, null on error</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CHD v1 header
|
||||
/// </summary>
|
||||
/// <returns>The extracted MD5 on success, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CHD v2 header
|
||||
/// </summary>
|
||||
/// <returns>The extracted MD5 on success, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CHD v3 header
|
||||
/// </summary>
|
||||
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CHD v4 header
|
||||
/// </summary>
|
||||
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CHD v5 header
|
||||
/// </summary>
|
||||
/// <returns>The extracted SHA-1 on success, null otherwise</returns>
|
||||
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
|
||||
|
||||
90
SabreTools.Library/FileTypes/CHDFileV1.cs
Normal file
90
SabreTools.Library/FileTypes/CHDFileV1.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD V1 File
|
||||
/// </summary>
|
||||
public class CHDFileV1 : CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Flags : uint
|
||||
{
|
||||
DriveHasParent = 0x00000001,
|
||||
DriveAllowsWrites = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression being used in CHD
|
||||
/// </summary>
|
||||
public enum Compression : uint
|
||||
{
|
||||
CHDCOMPRESSION_NONE = 0,
|
||||
CHDCOMPRESSION_ZLIB = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map format
|
||||
/// </summary>
|
||||
public class Map
|
||||
{
|
||||
public ulong offset; // 44; starting offset within the file
|
||||
public ulong length; // 20; length of data; if == hunksize, data is uncompressed
|
||||
}
|
||||
|
||||
public const int HeaderSize = 76;
|
||||
public const uint Version = 1;
|
||||
|
||||
// V1-specific header values
|
||||
public Flags flags; // flags (see above)
|
||||
public Compression compression; // compression type
|
||||
public uint hunksize; // 512-byte sectors per hunk
|
||||
public uint totalhunks; // total # of hunks represented
|
||||
public uint cylinders; // number of cylinders on hard disk
|
||||
public uint heads; // number of heads on hard disk
|
||||
public uint sectors; // number of sectors on hard disk
|
||||
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
|
||||
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
|
||||
|
||||
/// <summary>
|
||||
/// Parse and validate the header as if it's V1
|
||||
/// </summary>
|
||||
public static CHDFileV1 Deserialize(Stream stream)
|
||||
{
|
||||
CHDFileV1 chd = new CHDFileV1();
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
chd.tag = br.ReadCharsBigEndian(8);
|
||||
chd.length = br.ReadUInt32BigEndian();
|
||||
chd.version = br.ReadUInt32BigEndian();
|
||||
chd.flags = (Flags)br.ReadUInt32BigEndian();
|
||||
chd.compression = (Compression)br.ReadUInt32BigEndian();
|
||||
chd.hunksize = br.ReadUInt32BigEndian();
|
||||
chd.totalhunks = br.ReadUInt32BigEndian();
|
||||
chd.cylinders = br.ReadUInt32BigEndian();
|
||||
chd.heads = br.ReadUInt32BigEndian();
|
||||
chd.sectors = br.ReadUInt32BigEndian();
|
||||
chd.md5 = br.ReadBytesBigEndian(16);
|
||||
chd.parentmd5 = br.ReadBytesBigEndian(16);
|
||||
}
|
||||
|
||||
return chd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return internal MD5 hash
|
||||
/// </summary>
|
||||
public override byte[] GetHash()
|
||||
{
|
||||
return md5;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
SabreTools.Library/FileTypes/CHDFileV2.cs
Normal file
92
SabreTools.Library/FileTypes/CHDFileV2.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD V2 File
|
||||
/// </summary>
|
||||
public class CHDFileV2 : CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Flags : uint
|
||||
{
|
||||
DriveHasParent = 0x00000001,
|
||||
DriveAllowsWrites = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression being used in CHD
|
||||
/// </summary>
|
||||
public enum Compression : uint
|
||||
{
|
||||
CHDCOMPRESSION_NONE = 0,
|
||||
CHDCOMPRESSION_ZLIB = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map format
|
||||
/// </summary>
|
||||
public class Map
|
||||
{
|
||||
public ulong offset; // 44; starting offset within the file
|
||||
public ulong length; // 20; length of data; if == hunksize, data is uncompressed
|
||||
}
|
||||
|
||||
public const int HeaderSize = 80;
|
||||
public const uint Version = 2;
|
||||
|
||||
// V2-specific header values
|
||||
public Flags flags; // flags (see above)
|
||||
public Compression compression; // compression type
|
||||
public uint hunksize; // 512-byte sectors per hunk
|
||||
public uint totalhunks; // total # of hunks represented
|
||||
public uint cylinders; // number of cylinders on hard disk
|
||||
public uint heads; // number of heads on hard disk
|
||||
public uint sectors; // number of sectors on hard disk
|
||||
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
|
||||
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
|
||||
public uint seclen; // number of bytes per sector
|
||||
|
||||
/// <summary>
|
||||
/// Parse and validate the header as if it's V2
|
||||
/// </summary>
|
||||
public static CHDFileV2 Deserialize(Stream stream)
|
||||
{
|
||||
CHDFileV2 chd = new CHDFileV2();
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
chd.tag = br.ReadCharsBigEndian(8);
|
||||
chd.length = br.ReadUInt32BigEndian();
|
||||
chd.version = br.ReadUInt32BigEndian();
|
||||
chd.flags = (Flags)br.ReadUInt32BigEndian();
|
||||
chd.compression = (Compression)br.ReadUInt32BigEndian();
|
||||
chd.hunksize = br.ReadUInt32BigEndian();
|
||||
chd.totalhunks = br.ReadUInt32BigEndian();
|
||||
chd.cylinders = br.ReadUInt32BigEndian();
|
||||
chd.heads = br.ReadUInt32BigEndian();
|
||||
chd.sectors = br.ReadUInt32BigEndian();
|
||||
chd.md5 = br.ReadBytesBigEndian(16);
|
||||
chd.parentmd5 = br.ReadBytesBigEndian(16);
|
||||
chd.seclen = br.ReadUInt32BigEndian();
|
||||
}
|
||||
|
||||
return chd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return internal MD5 hash
|
||||
/// </summary>
|
||||
public override byte[] GetHash()
|
||||
{
|
||||
return md5;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
SabreTools.Library/FileTypes/CHDFileV3.cs
Normal file
96
SabreTools.Library/FileTypes/CHDFileV3.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD V3 File
|
||||
/// </summary>
|
||||
public class CHDFileV3 : CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Flags : uint
|
||||
{
|
||||
DriveHasParent = 0x00000001,
|
||||
DriveAllowsWrites = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression being used in CHD
|
||||
/// </summary>
|
||||
public enum Compression : uint
|
||||
{
|
||||
CHDCOMPRESSION_NONE = 0,
|
||||
CHDCOMPRESSION_ZLIB = 1,
|
||||
CHDCOMPRESSION_ZLIB_PLUS = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map format
|
||||
/// </summary>
|
||||
public class Map
|
||||
{
|
||||
public ulong offset; // starting offset within the file
|
||||
public uint crc32; // 32-bit CRC of the uncompressed data
|
||||
public ushort length_lo; // lower 16 bits of length
|
||||
public byte length_hi; // upper 8 bits of length
|
||||
public byte flags; // flags, indicating compression info
|
||||
}
|
||||
|
||||
public const int HeaderSize = 120;
|
||||
public const uint Version = 3;
|
||||
|
||||
// V3-specific header values
|
||||
public Flags flags; // flags (see above)
|
||||
public Compression compression; // compression type
|
||||
public uint totalhunks; // total # of hunks represented
|
||||
public ulong logicalbytes; // logical size of the data (in bytes)
|
||||
public ulong metaoffset; // offset to the first blob of metadata
|
||||
public byte[] md5 = new byte[16]; // MD5 checksum of raw data
|
||||
public byte[] parentmd5 = new byte[16]; // MD5 checksum of parent file
|
||||
public uint hunkbytes; // number of bytes per hunk
|
||||
public byte[] sha1 = new byte[20]; // SHA1 checksum of raw data
|
||||
public byte[] parentsha1 = new byte[20]; // SHA1 checksum of parent file
|
||||
|
||||
/// <summary>
|
||||
/// Parse and validate the header as if it's V3
|
||||
/// </summary>
|
||||
public static CHDFileV3 Deserialize(Stream stream)
|
||||
{
|
||||
CHDFileV3 chd = new CHDFileV3();
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
chd.tag = br.ReadCharsBigEndian(8);
|
||||
chd.length = br.ReadUInt32BigEndian();
|
||||
chd.version = br.ReadUInt32BigEndian();
|
||||
chd.flags = (Flags)br.ReadUInt32BigEndian();
|
||||
chd.compression = (Compression)br.ReadUInt32BigEndian();
|
||||
chd.totalhunks = br.ReadUInt32BigEndian();
|
||||
chd.logicalbytes = br.ReadUInt64BigEndian();
|
||||
chd.metaoffset = br.ReadUInt64BigEndian();
|
||||
chd.md5 = br.ReadBytesBigEndian(16);
|
||||
chd.parentmd5 = br.ReadBytesBigEndian(16);
|
||||
chd.hunkbytes = br.ReadUInt32BigEndian();
|
||||
chd.sha1 = br.ReadBytesBigEndian(20);
|
||||
chd.parentsha1 = br.ReadBytesBigEndian(20);
|
||||
}
|
||||
|
||||
return chd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return internal SHA1 hash
|
||||
/// </summary>
|
||||
public override byte[] GetHash()
|
||||
{
|
||||
return sha1;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
SabreTools.Library/FileTypes/CHDFileV4.cs
Normal file
95
SabreTools.Library/FileTypes/CHDFileV4.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD V4 File
|
||||
/// </summary>
|
||||
public class CHDFileV4 : CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Flags : uint
|
||||
{
|
||||
DriveHasParent = 0x00000001,
|
||||
DriveAllowsWrites = 0x00000002,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression being used in CHD
|
||||
/// </summary>
|
||||
public enum Compression : uint
|
||||
{
|
||||
CHDCOMPRESSION_NONE = 0,
|
||||
CHDCOMPRESSION_ZLIB = 1,
|
||||
CHDCOMPRESSION_ZLIB_PLUS = 2,
|
||||
CHDCOMPRESSION_AV = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map format
|
||||
/// </summary>
|
||||
public class Map
|
||||
{
|
||||
public ulong offset; // starting offset within the file
|
||||
public uint crc32; // 32-bit CRC of the uncompressed data
|
||||
public ushort length_lo; // lower 16 bits of length
|
||||
public byte length_hi; // upper 8 bits of length
|
||||
public byte flags; // flags, indicating compression info
|
||||
}
|
||||
|
||||
public const int HeaderSize = 108;
|
||||
public const uint Version = 4;
|
||||
|
||||
// V4-specific header values
|
||||
public Flags flags; // flags (see above)
|
||||
public Compression compression; // compression type
|
||||
public uint totalhunks; // total # of hunks represented
|
||||
public ulong logicalbytes; // logical size of the data (in bytes)
|
||||
public ulong metaoffset; // offset to the first blob of metadata
|
||||
public uint hunkbytes; // number of bytes per hunk
|
||||
public byte[] sha1 = new byte[20]; // combined raw+meta SHA1
|
||||
public byte[] parentsha1 = new byte[20]; // combined raw+meta SHA1 of parent
|
||||
public byte[] rawsha1 = new byte[20]; // raw data SHA1
|
||||
|
||||
/// <summary>
|
||||
/// Parse and validate the header as if it's V4
|
||||
/// </summary>
|
||||
public static CHDFileV4 Deserialize(Stream stream)
|
||||
{
|
||||
CHDFileV4 chd = new CHDFileV4();
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
chd.tag = br.ReadCharsBigEndian(8);
|
||||
chd.length = br.ReadUInt32BigEndian();
|
||||
chd.version = br.ReadUInt32BigEndian();
|
||||
chd.flags = (Flags)br.ReadUInt32BigEndian();
|
||||
chd.compression = (Compression)br.ReadUInt32BigEndian();
|
||||
chd.totalhunks = br.ReadUInt32BigEndian();
|
||||
chd.logicalbytes = br.ReadUInt64BigEndian();
|
||||
chd.metaoffset = br.ReadUInt64BigEndian();
|
||||
chd.hunkbytes = br.ReadUInt32BigEndian();
|
||||
chd.sha1 = br.ReadBytesBigEndian(20);
|
||||
chd.parentsha1 = br.ReadBytesBigEndian(20);
|
||||
chd.rawsha1 = br.ReadBytesBigEndian(20);
|
||||
}
|
||||
|
||||
return chd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return internal SHA1 hash
|
||||
/// </summary>
|
||||
public override byte[] GetHash()
|
||||
{
|
||||
return sha1;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
SabreTools.Library/FileTypes/CHDFileV5.cs
Normal file
98
SabreTools.Library/FileTypes/CHDFileV5.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Library.Tools;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// CHD V5 File
|
||||
/// </summary>
|
||||
public class CHDFileV5 : CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Uncompressed map format
|
||||
/// </summary>
|
||||
private class UncompressedMap
|
||||
{
|
||||
public uint offset; // starting offset within the file
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compressed map header format
|
||||
/// </summary>
|
||||
private class CompressedMapHeader
|
||||
{
|
||||
public uint length; // length of compressed map
|
||||
public byte[] datastart = new byte[12]; // UINT48; offset of first block
|
||||
public ushort crc; // crc-16 of the map
|
||||
public byte lengthbits; // bits used to encode complength
|
||||
public byte hunkbits; // bits used to encode self-refs
|
||||
public byte parentunitbits; // bits used to encode parent unit refs
|
||||
public byte reserved; // future use
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compressed map entry format
|
||||
/// </summary>
|
||||
private class CompressedMapEntry
|
||||
{
|
||||
public byte compression; // compression type
|
||||
public byte[] complength = new byte[6]; // UINT24; compressed length
|
||||
public byte[] offset = new byte[12]; // UINT48; offset
|
||||
public ushort crc; // crc-16 of the data
|
||||
}
|
||||
|
||||
public const int HeaderSize = 124;
|
||||
public const uint Version = 5;
|
||||
|
||||
// V5-specific header values
|
||||
public uint[] compressors = new uint[4]; // which custom compressors are used?
|
||||
public ulong logicalbytes; // logical size of the data (in bytes)
|
||||
public ulong mapoffset; // offset to the map
|
||||
public ulong metaoffset; // offset to the first blob of metadata
|
||||
public uint hunkbytes; // number of bytes per hunk
|
||||
public uint unitbytes; // number of bytes per unit within each hunk
|
||||
public byte[] rawsha1 = new byte[20]; // raw data SHA1
|
||||
public byte[] sha1 = new byte[20]; // combined raw+meta SHA1
|
||||
public byte[] parentsha1 = new byte[20]; // combined raw+meta SHA1 of parent
|
||||
|
||||
/// <summary>
|
||||
/// Parse and validate the header as if it's V5
|
||||
/// </summary>
|
||||
public static CHDFileV5 Deserialize(Stream stream)
|
||||
{
|
||||
CHDFileV5 chd = new CHDFileV5();
|
||||
|
||||
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
|
||||
{
|
||||
chd.tag = br.ReadCharsBigEndian(8);
|
||||
chd.length = br.ReadUInt32BigEndian();
|
||||
chd.version = br.ReadUInt32BigEndian();
|
||||
chd.compressors = new uint[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
chd.compressors[i] = br.ReadUInt32BigEndian();
|
||||
}
|
||||
chd.logicalbytes = br.ReadUInt64BigEndian();
|
||||
chd.mapoffset = br.ReadUInt64BigEndian();
|
||||
chd.metaoffset = br.ReadUInt64BigEndian();
|
||||
chd.hunkbytes = br.ReadUInt32BigEndian();
|
||||
chd.unitbytes = br.ReadUInt32BigEndian();
|
||||
chd.rawsha1 = br.ReadBytesBigEndian(20);
|
||||
chd.sha1 = br.ReadBytesBigEndian(20);
|
||||
chd.parentsha1 = br.ReadBytesBigEndian(20);
|
||||
}
|
||||
|
||||
return chd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return internal SHA1 hash
|
||||
/// </summary>
|
||||
public override byte[] GetHash()
|
||||
{
|
||||
return sha1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using SabreTools.Library.Data;
|
||||
using SabreTools.Library.DatItems;
|
||||
|
||||
/// <summary>
|
||||
/// This code is based on the header format described at http://www.rarlab.com/technote.htm#srvheaders
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ---------------------------------------------
|
||||
/// vint
|
||||
///
|
||||
/// Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data
|
||||
/// and highest bit in every byte is the continuation flag.If highest bit is 0, this is the last byte in sequence.
|
||||
/// So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present,
|
||||
/// contains next 7 bits and so on.
|
||||
///
|
||||
/// Currently RAR format uses vint to store up to 64 bit integers, resulting in 10 bytes maximum. This value may
|
||||
/// be increased in the future if necessary for some reason.
|
||||
///
|
||||
/// Sometimes RAR needs to pre-allocate space for vint before knowing its exact value. In such situation it can
|
||||
/// allocate more space than really necessary and then fill several leading bytes with 0x80 hexadecimal, which means
|
||||
/// 0 with continuation flag set.
|
||||
/// ----------------------------------------------
|
||||
/// General archive layout:
|
||||
///
|
||||
/// Self-extracting module(optional) (RAR assumes the maximum SFX module size to not exceed 1 MB, but this value
|
||||
/// can be increased in the future.
|
||||
/// RAR 5.0 signature (RAR 5.0 signature consists of 8 bytes: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00.
|
||||
/// You need to search for this signature in supposed archive from beginning and up to maximum SFX
|
||||
/// module size. Just for comparison this is RAR 4.x 7 byte length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00.)
|
||||
/// Archive encryption header(optional)
|
||||
/// Main archive header
|
||||
/// Archive comment service header(optional)
|
||||
/// File header 1
|
||||
/// Service headers(NTFS ACL, streams, etc.) for preceding file(optional).
|
||||
/// ...
|
||||
/// File header N
|
||||
/// Service headers(NTFS ACL, streams, etc.) for preceding file(optional).
|
||||
/// Recovery record(optional).
|
||||
/// End of archive header.
|
||||
/// ----------------------------------------------
|
||||
/// General archive block format:
|
||||
///
|
||||
/// Header CRC32: uint32 (CRC32 of header data starting from Header size field and up to and including the optional extra area.)
|
||||
/// Header size: vint (Size of header data starting from Header type field and up to and including the optional extra area.
|
||||
/// This field must not be longer than 3 bytes in current implementation, resulting in 2 MB maximum header size.)
|
||||
/// Header type: vint (Type of archive header. Possible values are: )
|
||||
/// 1 Main archive header.
|
||||
/// 2 File header.
|
||||
/// 3 Service header.
|
||||
/// 4 Archive encryption header.
|
||||
/// 5 End of archive header.
|
||||
/// Header flags: vint (Flags common for all headers:)
|
||||
/// 0x0001 Extra area is present in the end of header.
|
||||
/// 0x0002 Data area is present in the end of header.
|
||||
/// 0x0004 Blocks with unknown type and this flag must be skipped when updating an archive.
|
||||
/// 0x0008 Data area is continuing from previous volume.
|
||||
/// 0x0010 Data area is continuing in next volume.
|
||||
/// 0x0020 Block depends on preceding file block.
|
||||
/// 0x0040 Preserve a child block if host block is modified.
|
||||
/// Extra area size: vint (Size of extra area. Optional field, present only if 0x0001 header flag is set.)
|
||||
/// Data size: vint (Size of data area. Optional field, present only if 0x0002 header flag is set.)
|
||||
/// ...: ... (Fields specific for current block type. See concrete block type descriptions for details)
|
||||
/// Extra data: ... (Optional area containing additional header fields, present only if 0x0001 header flag is set.)
|
||||
/// Data area: vint (Optional data area, present only if 0x0002 header flag is set. Used to store large data amounts, such as
|
||||
/// compressed file data. Not counted in Header CRC and Header size fields.
|
||||
/// ----------------------------------------------
|
||||
/// General extra area format
|
||||
///
|
||||
/// Size: vint (Size of record data starting from Type.)
|
||||
/// Type: vint (Record type. Different archive blocks have different associated extra area record types. Read the
|
||||
/// concrete archive block description for details. New record types can be added in the future, so unknown
|
||||
/// record types need to be skipped without interrupting an operation.)
|
||||
/// Data: ... (Record dependent data. May be missing if record consists only from size and type.)
|
||||
/// ----------------------------------------------
|
||||
/// Archive encryption header:
|
||||
///
|
||||
/// Header CRC32: uint32
|
||||
/// Header size: vint
|
||||
/// Header type: vint (4)
|
||||
/// Header flags: vint
|
||||
/// Encryption version: vint (Version of encryption algorithm. Now only 0 version(AES-256) is supported.)
|
||||
/// Encryption flags: vint
|
||||
/// 0x0001 Password check data is present.
|
||||
/// KDF count: 1 byte (Binary logarithm of iteration number for PBKDF2 function.RAR can refuse to process
|
||||
/// KDF count exceeding some threshold. Concrete value of threshold is a version dependent.)
|
||||
/// Salt: 16 bytes (Salt value used globally for all encrypted archive headers.)
|
||||
/// Check value: 12 bytes (Value used to verify the password validity. Present only if 0x0001 encryption
|
||||
/// flag is set.First 8 bytes are calculated using additional PBKDF2 rounds, 4 last bytes is the additional
|
||||
/// checksum. Together with the standard header CRC32 we have 64 bit checksum to reliably verify this field
|
||||
/// integrity and distinguish invalid password and damaged data. Further details can be found in UnRAR source code.)
|
||||
/// ----------------------------------------------
|
||||
/// Main archive header:
|
||||
///
|
||||
/// Header CRC32: uint32 (CRC32 of header data starting from Header size field and up to and including the optional extra area.)
|
||||
/// Header size: vint (Size of header data starting from Header type field and up to and including the optional extra area. This field must not be longer than 3 bytes in current implementation, resulting in 2 MB maximum header size.)
|
||||
/// Header type: vint (1)
|
||||
/// Header flags: vint (Flags common for all headers)
|
||||
/// Extra area size: vint (Size of extra area. Optional field, present only if 0x0001 header flag is set.)
|
||||
/// Archive flags: vint
|
||||
/// 0x0001 Volume.Archive is a part of multivolume set.
|
||||
/// 0x0002 Volume number field is present.This flag is present in all volumes except first.
|
||||
/// 0x0004 Solid archive.
|
||||
/// 0x0008 Recovery record is present.
|
||||
/// 0x0010 Locked archive.
|
||||
/// Volume number: vint (Optional field, present only if 0x0002 archive flag is set. Not present for first volume,
|
||||
/// 1 for second volume, 2 for third and so on.)
|
||||
/// Extra area: ... (Optional area containing additional header fields, present only if 0x0001 header flag is set.)
|
||||
/// [Extra area of main archive header can contain following record types
|
||||
/// Type Name Description
|
||||
/// 0x01 Locator Contains positions of different service blocks, so they can be accessed quickly, without scanning
|
||||
/// the entire archive.This record is optional.If it is missing, it is still necessary to scan the entire archive
|
||||
/// to verify presence of service blocks.]
|
||||
/// ----------------------------------------------
|
||||
/// </remarks>
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
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;
|
||||
|
||||
// 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
|
||||
|
||||
// Entry Information
|
||||
public List<CoreRarArchiveEntry> Entries = new List<CoreRarArchiveEntry>();
|
||||
|
||||
#region Unimplemented methods
|
||||
|
||||
public override bool CopyAll(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 List<BaseFile> GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override List<string> GetEmptyFolders()
|
||||
{
|
||||
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(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool Write(List<string> inputFiles, string outDir, List<Rom> roms, bool date = false, bool romba = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
// 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 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 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
|
||||
|
||||
// Service Data Information
|
||||
public uint ServiceSize; // vint
|
||||
public byte[] ServiceData;
|
||||
}
|
||||
|
||||
// BELOW ARE CONCRETE IMPLEMENTATIONS OF HEADER DETAILS
|
||||
|
||||
/// <summary>
|
||||
/// General archive block format used by all RAR block types
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General extra area format used by all RAR extra area records
|
||||
/// </summary>
|
||||
public class GeneralExtraAreaFormat
|
||||
{
|
||||
public ulong Size; // vint
|
||||
public ulong Type; // vint
|
||||
public byte[] Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encryption header only present in encrypted archives
|
||||
///
|
||||
/// Every proceeding header is started from 16 byte AES-256
|
||||
/// initialization vectors followed by encrypted header data
|
||||
/// </summary>
|
||||
public class ArchiveEncryptionHeader : GeneralArchiveBlockFormat
|
||||
{
|
||||
public new HeaderType HeaderType = HeaderType.ArchiveEncryptionHeader;
|
||||
public ulong EncryptionVersion; // vint
|
||||
public ulong EncryptionFlags; // vint
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Types of archive header
|
||||
/// </summary>
|
||||
public enum HeaderType : ulong // vint
|
||||
{
|
||||
MainArchiveHeader = 1,
|
||||
FileHeader = 2,
|
||||
ServiceHeader = 3,
|
||||
ArchiveEncryptionHeader = 4,
|
||||
EndOfArchiveHeader = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flags common for all headers
|
||||
/// </summary>
|
||||
[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,
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,55 @@ namespace SabreTools.Library.FileTypes
|
||||
this.Type = FileType.Folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an folder object of the specified type, if possible
|
||||
/// </summary>
|
||||
/// <param name="outputFormat">SabreTools.Library.Data.OutputFormat representing the archive to create</param>
|
||||
/// <returns>Archive object representing the inputs</returns>
|
||||
public static Folder Create(OutputFormat outputFormat)
|
||||
{
|
||||
switch (outputFormat)
|
||||
{
|
||||
case OutputFormat.Folder:
|
||||
return new Folder();
|
||||
|
||||
case OutputFormat.TapeArchive:
|
||||
return new TapeArchive();
|
||||
|
||||
case OutputFormat.Torrent7Zip:
|
||||
return new SevenZipArchive();
|
||||
|
||||
case OutputFormat.TorrentGzip:
|
||||
case OutputFormat.TorrentGzipRomba:
|
||||
return new GZipArchive();
|
||||
|
||||
case OutputFormat.TorrentLRZip:
|
||||
return new LRZipArchive();
|
||||
|
||||
case OutputFormat.TorrentLZ4:
|
||||
return new LZ4Archive();
|
||||
|
||||
case OutputFormat.TorrentRar:
|
||||
return new RarArchive();
|
||||
|
||||
case OutputFormat.TorrentXZ:
|
||||
case OutputFormat.TorrentXZRomba:
|
||||
return new XZArchive();
|
||||
|
||||
case OutputFormat.TorrentZip:
|
||||
return new ZipArchive();
|
||||
|
||||
case OutputFormat.TorrentZPAQ:
|
||||
return new ZPAQArchive();
|
||||
|
||||
case OutputFormat.TorrentZstd:
|
||||
return new ZstdArchive();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
@@ -129,7 +178,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
// Get all files from the input directory
|
||||
List<string> files = Utilities.RetrieveFiles(this.Filename, new List<string>());
|
||||
List<string> files = DirectoryExtensions.GetFilesOrdered(this.Filename);
|
||||
|
||||
// Now sort through to find the first file that matches
|
||||
string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
|
||||
@@ -168,7 +217,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(this.Filename);
|
||||
|
||||
// Get all files from the input directory
|
||||
List<string> files = Utilities.RetrieveFiles(this.Filename, new List<string>());
|
||||
List<string> files = DirectoryExtensions.GetFilesOrdered(this.Filename);
|
||||
|
||||
// Now sort through to find the first file that matches
|
||||
string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault();
|
||||
@@ -176,7 +225,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If we had a file, copy that over to the new name
|
||||
if (!string.IsNullOrWhiteSpace(match))
|
||||
{
|
||||
Utilities.TryOpenRead(match).CopyTo(ms);
|
||||
FileExtensions.TryOpenRead(match).CopyTo(ms);
|
||||
realentry = match;
|
||||
}
|
||||
}
|
||||
@@ -207,7 +256,7 @@ namespace SabreTools.Library.FileTypes
|
||||
_children = new List<BaseFile>();
|
||||
foreach (string file in Directory.EnumerateFiles(this.Filename, "*", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date);
|
||||
BaseFile nf = FileExtensions.GetInfo(file, omitFromScan: omitFromScan, date: date);
|
||||
_children.Add(nf);
|
||||
}
|
||||
|
||||
@@ -228,7 +277,7 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <returns>List of empty folders in the folder</returns>
|
||||
public virtual List<string> GetEmptyFolders()
|
||||
{
|
||||
return Utilities.GetEmptyDirectories(this.Filename).ToList();
|
||||
return DirectoryExtensions.ListEmpty(this.Filename);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -247,7 +296,7 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <remarks>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.</remarks>
|
||||
public virtual bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
|
||||
{
|
||||
FileStream fs = Utilities.TryOpenRead(inputFile);
|
||||
FileStream fs = FileExtensions.TryOpenRead(inputFile);
|
||||
return Write(fs, outDir, rom, date, romba);
|
||||
}
|
||||
|
||||
@@ -281,7 +330,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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));
|
||||
string fileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName), Sanitizer.RemovePathUnsafeCharacters(rom.Name));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -292,7 +341,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// Overwrite output files by default
|
||||
outputStream = Utilities.TryCreate(fileName);
|
||||
outputStream = FileExtensions.TryCreate(fileName);
|
||||
|
||||
// If the output stream isn't null
|
||||
if (outputStream != null)
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
// Decompress the _filename stream
|
||||
FileStream outstream = Utilities.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
|
||||
FileStream outstream = FileExtensions.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
|
||||
var gz = new gZip();
|
||||
ZipReturn ret = gz.ZipFileOpen(this.Filename);
|
||||
ret = gz.ZipFileOpenReadStream(0, out Stream gzstream, out ulong streamSize);
|
||||
@@ -110,7 +110,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -145,7 +145,7 @@ namespace SabreTools.Library.FileTypes
|
||||
public override (MemoryStream, string) CopyToStream(string entryName)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
string realEntry = null;
|
||||
string realEntry;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -215,11 +215,10 @@ namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
Filename = gamename,
|
||||
};
|
||||
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
|
||||
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
|
||||
br.BaseStream.Seek(-8, SeekOrigin.End);
|
||||
byte[] headercrc = br.ReadBytesReverse(4);
|
||||
tempRom.CRC = headercrc;
|
||||
tempRom.Size = br.ReadInt32Reverse();
|
||||
tempRom.CRC = br.ReadBytesBigEndian(4);
|
||||
tempRom.Size = br.ReadInt32BigEndian();
|
||||
br.Dispose();
|
||||
|
||||
_children.Add(tempRom);
|
||||
@@ -230,7 +229,7 @@ namespace SabreTools.Library.FileTypes
|
||||
var gz = new gZip();
|
||||
ZipReturn ret = gz.ZipFileOpen(this.Filename);
|
||||
ret = gz.ZipFileOpenReadStream(0, out Stream gzstream, out ulong streamSize);
|
||||
BaseFile gzipEntryRom = Utilities.GetStreamInfo(gzstream, gzstream.Length, omitFromScan);
|
||||
BaseFile gzipEntryRom = gzstream.GetInfo(omitFromScan: omitFromScan);
|
||||
gzipEntryRom.Filename = gz.Filename(0);
|
||||
gzipEntryRom.Parent = gamename;
|
||||
gzipEntryRom.Date = (date && gz.TimeStamp > 0 ? gz.TimeStamp.ToString() : null);
|
||||
@@ -267,9 +266,7 @@ namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
// 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;
|
||||
@@ -296,15 +293,11 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// 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();
|
||||
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
|
||||
byte[] header = br.ReadBytes(12); // Get preamble header for checking
|
||||
br.ReadBytes(16); // headermd5
|
||||
br.ReadBytes(4); // headercrc
|
||||
br.ReadUInt64(); // headersz
|
||||
br.Dispose();
|
||||
|
||||
// If the header is not correct, return a blank rom
|
||||
@@ -313,15 +306,13 @@ namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
@@ -367,7 +358,7 @@ namespace SabreTools.Library.FileTypes
|
||||
byte[] headermd5; // MD5
|
||||
byte[] headercrc; // CRC
|
||||
ulong headersz; // Int64 size
|
||||
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
|
||||
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
|
||||
header = br.ReadBytes(12);
|
||||
headermd5 = br.ReadBytes(16);
|
||||
headercrc = br.ReadBytes(4);
|
||||
@@ -429,10 +420,11 @@ namespace SabreTools.Library.FileTypes
|
||||
Globals.Logger.Warning($"File '{inputFile}' does not exist!");
|
||||
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);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -451,27 +443,24 @@ namespace SabreTools.Library.FileTypes
|
||||
|
||||
// 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));
|
||||
rom = new Rom(inputStream.GetInfo(keepReadOpen: true));
|
||||
|
||||
// Get the output file name
|
||||
string outfile = null;
|
||||
string outfile;
|
||||
|
||||
// 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
|
||||
outfile = Path.Combine(outDir, PathExtensions.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)))
|
||||
@@ -489,7 +478,7 @@ namespace SabreTools.Library.FileTypes
|
||||
if (!File.Exists(outfile))
|
||||
{
|
||||
// Compress the input stream
|
||||
FileStream outputStream = Utilities.TryCreate(outfile);
|
||||
FileStream outputStream = FileExtensions.TryCreate(outfile);
|
||||
|
||||
// Open the output file for writing
|
||||
BinaryWriter sw = new BinaryWriter(outputStream);
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -183,7 +183,7 @@ namespace SabreTools.Library.FileTypes
|
||||
|
||||
try
|
||||
{
|
||||
SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(Utilities.TryOpenRead(this.Filename));
|
||||
SharpCompress.Archives.Rar.RarArchive ra = SharpCompress.Archives.Rar.RarArchive.Open(FileExtensions.TryOpenRead(this.Filename));
|
||||
foreach (RarArchiveEntry entry in ra.Entries.Where(e => e != null && !e.IsDirectory))
|
||||
{
|
||||
// If secure hashes are disabled, do a quickscan
|
||||
@@ -203,7 +203,7 @@ namespace SabreTools.Library.FileTypes
|
||||
else
|
||||
{
|
||||
Stream entryStream = entry.OpenEntryStream();
|
||||
BaseFile rarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan);
|
||||
BaseFile rarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
|
||||
rarEntryRom.Filename = entry.Key;
|
||||
rarEntryRom.Parent = gamename;
|
||||
rarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
|
||||
@@ -224,223 +224,6 @@ namespace SabreTools.Library.FileTypes
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (INCOMPLETE) Retrieve file information for a RAR file
|
||||
/// </summary>
|
||||
/// TODO: Write the rest of this RAR file handling
|
||||
public void GetRarFileInfo()
|
||||
{
|
||||
if (!File.Exists(this.Filename))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BinaryReader br = new BinaryReader(Utilities.TryOpenRead(this.Filename));
|
||||
|
||||
// Check for the signature first (Skipping the SFX Module)
|
||||
byte[] signature = br.ReadBytes(8);
|
||||
int startpos = 0;
|
||||
while (startpos < Constants.MibiByte && !signature.StartsWith(Constants.RarSignature, exact: true) && !signature.StartsWith(Constants.RarFiveSignature, exact: true))
|
||||
{
|
||||
startpos++;
|
||||
br.BaseStream.Position = startpos;
|
||||
signature = br.ReadBytes(8);
|
||||
}
|
||||
if (!signature.StartsWith(Constants.RarSignature, exact: true) && !signature.StartsWith(Constants.RarFiveSignature, exact: true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CoreRarArchive cra = new CoreRarArchive();
|
||||
if (startpos > 0)
|
||||
{
|
||||
br.BaseStream.Position = 0;
|
||||
cra.SFX = br.ReadBytes(startpos);
|
||||
}
|
||||
|
||||
// Get all archive header information
|
||||
cra.HeaderCRC32 = br.ReadUInt32();
|
||||
cra.HeaderSize = br.ReadUInt32();
|
||||
uint headerType = br.ReadUInt32();
|
||||
|
||||
// Special encryption information
|
||||
bool hasEncryptionHeader = false;
|
||||
|
||||
// If it's encrypted
|
||||
if (headerType == (uint)RarHeaderType.ArchiveEncryption)
|
||||
{
|
||||
hasEncryptionHeader = true;
|
||||
cra.EncryptionHeaderCRC32 = cra.HeaderCRC32;
|
||||
cra.EncryptionHeaderSize = cra.HeaderSize;
|
||||
cra.EncryptionHeaderFlags = (RarHeaderFlags)br.ReadUInt32();
|
||||
cra.EncryptionVersion = br.ReadUInt32();
|
||||
cra.EncryptionFlags = br.ReadUInt32();
|
||||
cra.KDFCount = br.ReadByte();
|
||||
cra.Salt = br.ReadBytes(16);
|
||||
cra.CheckValue = br.ReadBytes(12);
|
||||
|
||||
cra.HeaderCRC32 = br.ReadUInt32();
|
||||
cra.HeaderSize = br.ReadUInt32();
|
||||
headerType = br.ReadUInt32();
|
||||
}
|
||||
|
||||
cra.HeaderFlags = (RarHeaderFlags)br.ReadUInt32();
|
||||
if ((cra.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
|
||||
{
|
||||
cra.ExtraAreaSize = br.ReadUInt32();
|
||||
}
|
||||
cra.ArchiveFlags = (RarArchiveFlags)br.ReadUInt32();
|
||||
if ((cra.ArchiveFlags & RarArchiveFlags.VolumeNumberField) != 0)
|
||||
{
|
||||
cra.VolumeNumber = br.ReadUInt32();
|
||||
}
|
||||
if (((cra.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0) && cra.ExtraAreaSize != 0)
|
||||
{
|
||||
cra.ExtraArea = br.ReadBytes((int)cra.ExtraAreaSize);
|
||||
}
|
||||
|
||||
// Archive Comment Service Header
|
||||
|
||||
// Now for file headers
|
||||
for (; ; )
|
||||
{
|
||||
CoreRarArchiveEntry crae = new CoreRarArchiveEntry();
|
||||
crae.HeaderCRC32 = br.ReadUInt32();
|
||||
crae.HeaderSize = br.ReadUInt32();
|
||||
crae.HeaderType = (RarHeaderType)br.ReadUInt32();
|
||||
|
||||
if (crae.HeaderType == RarHeaderType.EndOfArchive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
crae.HeaderFlags = (RarHeaderFlags)br.ReadUInt32();
|
||||
if ((crae.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
|
||||
{
|
||||
crae.ExtraAreaSize = br.ReadUInt32();
|
||||
}
|
||||
if ((crae.HeaderFlags & RarHeaderFlags.DataAreaPresent) != 0)
|
||||
{
|
||||
crae.DataAreaSize = br.ReadUInt32();
|
||||
}
|
||||
crae.FileFlags = (RarFileFlags)br.ReadUInt32();
|
||||
crae.UnpackedSize = br.ReadUInt32();
|
||||
if ((crae.FileFlags & RarFileFlags.UnpackedSizeUnknown) != 0)
|
||||
{
|
||||
crae.UnpackedSize = 0;
|
||||
}
|
||||
crae.Attributes = br.ReadUInt32();
|
||||
crae.mtime = br.ReadUInt32();
|
||||
crae.DataCRC32 = br.ReadUInt32();
|
||||
crae.CompressionInformation = br.ReadUInt32();
|
||||
crae.HostOS = br.ReadUInt32();
|
||||
crae.NameLength = br.ReadUInt32();
|
||||
crae.Name = br.ReadBytes((int)crae.NameLength);
|
||||
if ((crae.HeaderFlags & RarHeaderFlags.ExtraAreaPresent) != 0)
|
||||
{
|
||||
uint extraSize = br.ReadUInt32();
|
||||
switch (br.ReadUInt32()) // Extra Area Type
|
||||
{
|
||||
case 0x01: // File encryption information
|
||||
crae.EncryptionSize = extraSize;
|
||||
crae.EncryptionFlags = (RarEncryptionFlags)br.ReadUInt32();
|
||||
crae.KDFCount = br.ReadByte();
|
||||
crae.Salt = br.ReadBytes(16);
|
||||
crae.IV = br.ReadBytes(16);
|
||||
crae.CheckValue = br.ReadBytes(12);
|
||||
break;
|
||||
|
||||
case 0x02: // File data hash
|
||||
crae.HashSize = extraSize;
|
||||
crae.HashType = br.ReadUInt32();
|
||||
crae.HashData = br.ReadBytes(32);
|
||||
break;
|
||||
|
||||
case 0x03: // High precision file time
|
||||
crae.TimeSize = extraSize;
|
||||
crae.TimeFlags = (RarTimeFlags)br.ReadUInt32();
|
||||
if ((crae.TimeFlags & RarTimeFlags.TimeInUnixFormat) != 0)
|
||||
{
|
||||
if ((crae.TimeFlags & RarTimeFlags.ModificationTimePresent) != 0)
|
||||
{
|
||||
crae.TimeMtime64 = br.ReadUInt64();
|
||||
}
|
||||
if ((crae.TimeFlags & RarTimeFlags.CreationTimePresent) != 0)
|
||||
{
|
||||
crae.TimeCtime64 = br.ReadUInt64();
|
||||
}
|
||||
if ((crae.TimeFlags & RarTimeFlags.LastAccessTimePresent) != 0)
|
||||
{
|
||||
crae.TimeLtime64 = br.ReadUInt64();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((crae.TimeFlags & RarTimeFlags.ModificationTimePresent) != 0)
|
||||
{
|
||||
crae.TimeMtime = br.ReadUInt32();
|
||||
}
|
||||
if ((crae.TimeFlags & RarTimeFlags.CreationTimePresent) != 0)
|
||||
{
|
||||
crae.TimeCtime = br.ReadUInt32();
|
||||
}
|
||||
if ((crae.TimeFlags & RarTimeFlags.LastAccessTimePresent) != 0)
|
||||
{
|
||||
crae.TimeLtime = br.ReadUInt32();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: // File version number
|
||||
crae.VersionSize = extraSize;
|
||||
/* crae.VersionFlags = */
|
||||
br.ReadUInt32();
|
||||
crae.VersionNumber = br.ReadUInt32();
|
||||
break;
|
||||
|
||||
case 0x05: // File system redirection
|
||||
crae.RedirectionSize = extraSize;
|
||||
crae.RedirectionType = (RarRedirectionType)br.ReadUInt32();
|
||||
crae.RedirectionFlags = br.ReadUInt32();
|
||||
crae.RedirectionNameLength = br.ReadUInt32();
|
||||
crae.RedirectionName = br.ReadBytes((int)crae.RedirectionNameLength);
|
||||
break;
|
||||
|
||||
case 0x06: // Unix owner and group information
|
||||
crae.UnixOwnerSize = extraSize;
|
||||
crae.UnixOwnerFlags = (RarUnixOwnerRecordFlags)br.ReadUInt32();
|
||||
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.UserNameStringIsPresent) != 0)
|
||||
{
|
||||
crae.UnixOwnerUserNameLength = br.ReadUInt32();
|
||||
crae.UnixOwnerUserName = br.ReadBytes((int)crae.UnixOwnerUserNameLength);
|
||||
}
|
||||
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.GroupNameStringIsPresent) != 0)
|
||||
{
|
||||
crae.UnixOwnerGroupNameLength = br.ReadUInt32();
|
||||
crae.UnixOwnerGroupName = br.ReadBytes((int)crae.UnixOwnerGroupNameLength);
|
||||
}
|
||||
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.NumericUserIdIsPresent) != 0)
|
||||
{
|
||||
crae.UnixOwnerUserId = br.ReadUInt32();
|
||||
}
|
||||
if ((crae.UnixOwnerFlags & RarUnixOwnerRecordFlags.NumericGroupIdIsPresent) != 0)
|
||||
{
|
||||
crae.UnixOwnerGroupId = br.ReadUInt32();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07: // Service header data array
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((crae.HeaderFlags & RarHeaderFlags.DataAreaPresent) != 0)
|
||||
{
|
||||
crae.DataArea = br.ReadBytes((int)crae.DataAreaSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of empty folders in an archive
|
||||
/// </summary>
|
||||
@@ -505,7 +288,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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, romba);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace SabreTools.Library.FileTypes
|
||||
continue;
|
||||
}
|
||||
|
||||
FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Filename(i)));
|
||||
FileStream writeStream = FileExtensions.TryCreate(Path.Combine(outDir, zf.Filename(i)));
|
||||
|
||||
// If the stream is smaller than the buffer, just run one loop through to avoid issues
|
||||
if (streamsize < _bufferSize)
|
||||
@@ -152,7 +152,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -314,7 +314,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// Otherwise, use the stream directly
|
||||
else
|
||||
{
|
||||
BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.UncompressedSize(i), omitFromScan, true);
|
||||
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
|
||||
zipEntryRom.Filename = zf.Filename(i);
|
||||
zipEntryRom.Parent = gamename;
|
||||
found.Add(zipEntryRom);
|
||||
@@ -417,7 +417,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -450,7 +450,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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") ? string.Empty : ".zip"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
|
||||
|
||||
// Set internal variables
|
||||
Stream writeStream = null;
|
||||
@@ -615,7 +615,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
@@ -658,7 +658,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// 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") ? string.Empty : ".zip"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
|
||||
|
||||
// Set internal variables
|
||||
Stream writeStream = null;
|
||||
@@ -697,7 +697,7 @@ namespace SabreTools.Library.FileTypes
|
||||
int index = inputIndexMap[key];
|
||||
|
||||
// Open the input file for reading
|
||||
Stream freadStream = Utilities.TryOpenRead(inputFiles[index]);
|
||||
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[index]);
|
||||
ulong istreamSize = (ulong)(new FileInfo(inputFiles[index]).Length);
|
||||
|
||||
DateTime dt = DateTime.Now;
|
||||
@@ -780,7 +780,7 @@ namespace SabreTools.Library.FileTypes
|
||||
if (index < 0)
|
||||
{
|
||||
// Open the input file for reading
|
||||
Stream freadStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
|
||||
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[-index - 1]);
|
||||
ulong istreamSize = (ulong)(new FileInfo(inputFiles[-index - 1]).Length);
|
||||
|
||||
DateTime dt = DateTime.Now;
|
||||
@@ -842,7 +842,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -187,7 +187,7 @@ namespace SabreTools.Library.FileTypes
|
||||
|
||||
try
|
||||
{
|
||||
TarArchive ta = TarArchive.Open(Utilities.TryOpenRead(this.Filename));
|
||||
TarArchive ta = TarArchive.Open(FileExtensions.TryOpenRead(this.Filename));
|
||||
foreach (TarArchiveEntry entry in ta.Entries.Where(e => e != null && !e.IsDirectory))
|
||||
{
|
||||
// If secure hashes are disabled, do a quickscan
|
||||
@@ -207,7 +207,7 @@ namespace SabreTools.Library.FileTypes
|
||||
else
|
||||
{
|
||||
Stream entryStream = entry.OpenEntryStream();
|
||||
BaseFile tarEntryRom = Utilities.GetStreamInfo(entryStream, entry.Size, omitFromScan);
|
||||
BaseFile tarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
|
||||
tarEntryRom.Filename = entry.Key;
|
||||
tarEntryRom.Parent = gamename;
|
||||
tarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");
|
||||
@@ -292,7 +292,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -322,7 +322,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// Get the output archive name from the first rebuild rom
|
||||
string archiveFileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
|
||||
|
||||
// Set internal variables
|
||||
TarArchive oldTarFile = TarArchive.Create();
|
||||
@@ -441,7 +441,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
@@ -484,7 +484,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// 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") ? string.Empty : ".tar"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".tar") ? string.Empty : ".tar"));
|
||||
|
||||
// Set internal variables
|
||||
TarArchive oldTarFile = TarArchive.Create();
|
||||
@@ -526,7 +526,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// Copy the input stream to the output
|
||||
tarFile.AddEntry(roms[index].Name, Utilities.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
|
||||
tarFile.AddEntry(roms[index].Name, FileExtensions.TryOpenRead(inputFiles[index]), size: roms[index].Size, modified: usableDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// 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);
|
||||
tarFile.AddEntry(roms[-index - 1].Name, FileExtensions.TryOpenRead(inputFiles[-index - 1]), size: roms[-index - 1].Size, modified: usableDate);
|
||||
}
|
||||
|
||||
// Otherwise, copy the file from the old archive
|
||||
@@ -622,7 +622,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using SabreTools.Library.Data;
|
||||
using SabreTools.Library.DatItems;
|
||||
using SabreTools.Library.Tools;
|
||||
using Compress.ZipFile;
|
||||
using SevenZip;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
|
||||
namespace SabreTools.Library.FileTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TorrentXZ archive for reading and writing
|
||||
/// </summary>
|
||||
/// TODO: Wait for XZ write to be enabled by SevenZipSharp library
|
||||
public class XZArchive : BaseArchive
|
||||
{
|
||||
#region Constructors
|
||||
@@ -61,14 +56,16 @@ namespace SabreTools.Library.FileTypes
|
||||
// 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 SharpCompress.Common.ExtractionOptions { PreserveFileTime = true, ExtractFullPath = true, Overwrite = true });
|
||||
}
|
||||
// Decompress the _filename stream
|
||||
FileStream outstream = FileExtensions.TryCreate(Path.Combine(outDir, Path.GetFileNameWithoutExtension(this.Filename)));
|
||||
var xz = new XZStream(File.OpenRead(this.Filename));
|
||||
xz.CopyTo(outstream);
|
||||
|
||||
// Dispose of the streams
|
||||
outstream.Dispose();
|
||||
xz.Dispose();
|
||||
|
||||
encounteredErrors = false;
|
||||
sza.Dispose();
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
@@ -107,7 +104,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -142,22 +139,26 @@ namespace SabreTools.Library.FileTypes
|
||||
public override (MemoryStream, string) CopyToStream(string entryName)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
string realEntry = null;
|
||||
string realEntry;
|
||||
|
||||
try
|
||||
{
|
||||
SharpCompress.Archives.SevenZip.SevenZipArchive sza = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(this.Filename, new ReaderOptions { LeaveStreamOpen = false, });
|
||||
foreach (SevenZipArchiveEntry entry in sza.Entries)
|
||||
// Decompress the _filename stream
|
||||
realEntry = Path.GetFileNameWithoutExtension(this.Filename);
|
||||
var xz = new XZStream(File.OpenRead(this.Filename));
|
||||
|
||||
// Write the file out
|
||||
byte[] xbuffer = new byte[_bufferSize];
|
||||
int xlen;
|
||||
while ((xlen = xz.Read(xbuffer, 0, _bufferSize)) > 0)
|
||||
{
|
||||
if (entry != null && !entry.IsDirectory && entry.Key.Contains(entryName))
|
||||
{
|
||||
// Write the file out
|
||||
realEntry = entry.Key;
|
||||
entry.WriteTo(ms);
|
||||
break;
|
||||
}
|
||||
|
||||
ms.Write(xbuffer, 0, xlen);
|
||||
ms.Flush();
|
||||
}
|
||||
sza.Dispose();
|
||||
|
||||
// Dispose of the streams
|
||||
xz.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -182,7 +183,58 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <remarks>TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually</remarks>
|
||||
public override List<BaseFile> GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_children == null || _children.Count == 0)
|
||||
{
|
||||
_children = new List<BaseFile>();
|
||||
|
||||
string gamename = Path.GetFileNameWithoutExtension(this.Filename);
|
||||
|
||||
BaseFile possibleTxz = GetTorrentXZFileInfo();
|
||||
|
||||
// If it was, then add it to the outputs and continue
|
||||
if (possibleTxz != null && possibleTxz.Filename != null)
|
||||
{
|
||||
_children.Add(possibleTxz);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// If secure hashes are disabled, do a quickscan
|
||||
if (omitFromScan == Hash.SecureHashes)
|
||||
{
|
||||
BaseFile tempRom = new BaseFile()
|
||||
{
|
||||
Filename = gamename,
|
||||
};
|
||||
BinaryReader br = new BinaryReader(FileExtensions.TryOpenRead(this.Filename));
|
||||
br.BaseStream.Seek(-8, SeekOrigin.End);
|
||||
tempRom.CRC = br.ReadBytesBigEndian(4);
|
||||
tempRom.Size = br.ReadInt32BigEndian();
|
||||
br.Dispose();
|
||||
|
||||
_children.Add(tempRom);
|
||||
}
|
||||
// Otherwise, use the stream directly
|
||||
else
|
||||
{
|
||||
var xzStream = new XZStream(File.OpenRead(this.Filename));
|
||||
BaseFile xzEntryRom = xzStream.GetInfo(omitFromScan: omitFromScan);
|
||||
xzEntryRom.Filename = gamename;
|
||||
xzEntryRom.Parent = gamename;
|
||||
_children.Add(xzEntryRom);
|
||||
xzStream.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Globals.Logger.Error(ex.ToString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -192,7 +244,8 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <returns>List of empty folders in the archive</returns>
|
||||
public override List<string> GetEmptyFolders()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// XZ files don't contain directories
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -200,7 +253,50 @@ namespace SabreTools.Library.FileTypes
|
||||
/// </summary>
|
||||
public override bool IsTorrent()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Check for the file existing first
|
||||
if (!File.Exists(this.Filename))
|
||||
return false;
|
||||
|
||||
string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
|
||||
|
||||
// Check if the name is the right length
|
||||
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
|
||||
{
|
||||
Globals.Logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(this.Filename)}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve file information for a single torrent XZ file
|
||||
/// </summary>
|
||||
/// <returns>Populated DatItem object if success, empty one on error</returns>
|
||||
public BaseFile GetTorrentXZFileInfo()
|
||||
{
|
||||
// Check for the file existing first
|
||||
if (!File.Exists(this.Filename))
|
||||
return null;
|
||||
|
||||
string datum = Path.GetFileName(this.Filename).ToLowerInvariant();
|
||||
|
||||
// Check if the name is the right length
|
||||
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz")) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length
|
||||
{
|
||||
Globals.Logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(this.Filename)}'");
|
||||
return null;
|
||||
}
|
||||
|
||||
BaseFile baseFile = new BaseFile
|
||||
{
|
||||
Filename = Path.GetFileNameWithoutExtension(this.Filename).ToLowerInvariant(),
|
||||
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
|
||||
@@ -219,8 +315,17 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <remarks>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.</remarks>
|
||||
public override bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false)
|
||||
{
|
||||
// Check that the input file exists
|
||||
if (!File.Exists(inputFile))
|
||||
{
|
||||
Globals.Logger.Warning($"File '{inputFile}' does not exist!");
|
||||
return false;
|
||||
}
|
||||
|
||||
inputFile = Path.GetFullPath(inputFile);
|
||||
|
||||
// Get the file stream for the file and write out
|
||||
return Write(Utilities.TryOpenRead(inputFile), outDir, rom, date: date);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date, romba);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -235,185 +340,48 @@ namespace SabreTools.Library.FileTypes
|
||||
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()}");
|
||||
|
||||
// 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;
|
||||
|
||||
// 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(inputStream.GetInfo(keepReadOpen: true));
|
||||
|
||||
// Get the output file name
|
||||
string outfile;
|
||||
|
||||
// If we have a romba output, add the romba path
|
||||
if (romba)
|
||||
{
|
||||
outfile = Path.Combine(outDir, PathExtensions.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 + ".xz"); // TODO: When updating to SHA-256, this needs to update to SHA256
|
||||
}
|
||||
|
||||
// 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") ? string.Empty : ".xz"));
|
||||
|
||||
// Set internal variables
|
||||
SevenZipBase.SetLibraryPath("7za.dll");
|
||||
SevenZipExtractor oldZipFile = null;
|
||||
SevenZipCompressor zipFile;
|
||||
|
||||
try
|
||||
// If the output file exists, don't try to write again
|
||||
if (!File.Exists(outfile))
|
||||
{
|
||||
// If the full output path doesn't exist, create it
|
||||
if (!Directory.Exists(Path.GetDirectoryName(tempFile)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
|
||||
}
|
||||
// Compress the input stream
|
||||
XZStream outputStream = new XZStream(FileExtensions.TryCreate(outfile));
|
||||
inputStream.CopyTo(outputStream);
|
||||
|
||||
// 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<string, Stream> dict = new Dictionary<string, Stream>();
|
||||
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<string, int> inputIndexMap = new Dictionary<string, int>();
|
||||
|
||||
// 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<string> keys = inputIndexMap.Keys.ToList();
|
||||
keys.Sort(ZipFile.TrrntZipStringCompare);
|
||||
|
||||
// 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<string, Stream> dict = new Dictionary<string, Stream>();
|
||||
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<string, Stream> dict = new Dictionary<string, Stream>();
|
||||
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();
|
||||
// Dispose of everything
|
||||
outputStream.Dispose();
|
||||
inputStream.Dispose();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -430,216 +398,7 @@ namespace SabreTools.Library.FileTypes
|
||||
/// <returns>True if the archive was written properly, false otherwise</returns>
|
||||
public override bool Write(List<string> inputFiles, string outDir, List<Rom> roms, bool date = false, bool romba = false)
|
||||
{
|
||||
bool success = false;
|
||||
string tempFile = Path.Combine(outDir, $"tmp{Guid.NewGuid()}");
|
||||
|
||||
// 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") ? string.Empty : ".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<string, int> inputIndexMap = new Dictionary<string, int>();
|
||||
for (int i = 0; i < inputFiles.Count; i++)
|
||||
{
|
||||
inputIndexMap.Add(roms[i].Name.Replace('\\', '/'), i);
|
||||
}
|
||||
|
||||
// Sort the keys in TZIP order
|
||||
List<string> keys = inputIndexMap.Keys.ToList();
|
||||
keys.Sort(ZipFile.TrrntZipStringCompare);
|
||||
|
||||
// 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<string, int> inputIndexMap = new Dictionary<string, int>();
|
||||
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<string> keys = inputIndexMap.Keys.ToList();
|
||||
keys.Sort(ZipFile.TrrntZipStringCompare);
|
||||
|
||||
// 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<string, Stream> dict = new Dictionary<string, Stream>();
|
||||
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<string, Stream> dict = new Dictionary<string, Stream>();
|
||||
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;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace SabreTools.Library.FileTypes
|
||||
continue;
|
||||
}
|
||||
|
||||
FileStream writeStream = Utilities.TryCreate(Path.Combine(outDir, zf.Filename(i)));
|
||||
FileStream writeStream = FileExtensions.TryCreate(Path.Combine(outDir, zf.Filename(i)));
|
||||
|
||||
// If the stream is smaller than the buffer, just run one loop through to avoid issues
|
||||
if (streamsize < _bufferSize)
|
||||
@@ -153,7 +153,7 @@ namespace SabreTools.Library.FileTypes
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(realEntry));
|
||||
|
||||
// Now open and write the file if possible
|
||||
FileStream fs = Utilities.TryCreate(realEntry);
|
||||
FileStream fs = FileExtensions.TryCreate(realEntry);
|
||||
if (fs != null)
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
@@ -317,7 +317,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// Otherwise, use the stream directly
|
||||
else
|
||||
{
|
||||
BaseFile zipEntryRom = Utilities.GetStreamInfo(readStream, (long)zf.UncompressedSize(i), omitFromScan, true);
|
||||
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
|
||||
zipEntryRom.Filename = zf.Filename(i);
|
||||
zipEntryRom.Parent = gamename;
|
||||
string convertedDate = zf.LastModified(i).ToString("yyyy/MM/dd hh:mm:ss");
|
||||
@@ -422,7 +422,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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);
|
||||
return Write(FileExtensions.TryOpenRead(inputFile), outDir, rom, date: date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -455,7 +455,7 @@ namespace SabreTools.Library.FileTypes
|
||||
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") ? string.Empty : ".zip"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(rom.MachineName) + (rom.MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
|
||||
|
||||
// Set internal variables
|
||||
Stream writeStream = null;
|
||||
@@ -621,7 +621,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
@@ -664,7 +664,7 @@ namespace SabreTools.Library.FileTypes
|
||||
}
|
||||
|
||||
// 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") ? string.Empty : ".zip"));
|
||||
string archiveFileName = Path.Combine(outDir, Sanitizer.RemovePathUnsafeCharacters(roms[0].MachineName) + (roms[0].MachineName.EndsWith(".zip") ? string.Empty : ".zip"));
|
||||
|
||||
// Set internal variables
|
||||
Stream writeStream = null;
|
||||
@@ -703,7 +703,7 @@ namespace SabreTools.Library.FileTypes
|
||||
int index = inputIndexMap[key];
|
||||
|
||||
// Open the input file for reading
|
||||
Stream freadStream = Utilities.TryOpenRead(inputFiles[index]);
|
||||
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[index]);
|
||||
ulong istreamSize = (ulong)(new FileInfo(inputFiles[index]).Length);
|
||||
|
||||
DateTime dt = DateTime.Now;
|
||||
@@ -786,7 +786,7 @@ namespace SabreTools.Library.FileTypes
|
||||
if (index < 0)
|
||||
{
|
||||
// Open the input file for reading
|
||||
Stream freadStream = Utilities.TryOpenRead(inputFiles[-index - 1]);
|
||||
Stream freadStream = FileExtensions.TryOpenRead(inputFiles[-index - 1]);
|
||||
ulong istreamSize = (ulong)(new FileInfo(inputFiles[-index - 1]).Length);
|
||||
|
||||
DateTime dt = DateTime.Now;
|
||||
@@ -849,7 +849,7 @@ namespace SabreTools.Library.FileTypes
|
||||
// If the old file exists, delete it and replace
|
||||
if (File.Exists(archiveFileName))
|
||||
{
|
||||
Utilities.TryDeleteFile(archiveFileName);
|
||||
FileExtensions.TryDelete(archiveFileName);
|
||||
}
|
||||
File.Move(tempFile, archiveFileName);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user