Add Aaruformat validation and media item type (#29)

* Initial `media` and AaruFormat code

* But... why?

* Fix AIF reading

* Fix D2D, Logiqx cleanup

* Minor cleanup

* Final cleanup round
This commit is contained in:
Matt Nadareski
2020-08-27 16:57:22 -07:00
committed by GitHub
parent 3b481de3b9
commit 4d0a3f55eb
51 changed files with 2853 additions and 908 deletions

View File

@@ -0,0 +1,41 @@
using System.IO;
using System.Text;
namespace SabreTools.Library.FileTypes.Aaru
{
/// <summary>
/// Checksum entry, followed by checksum data itself
/// </summary>
/// <see cref="https://github.com/aaru-dps/Aaru/blob/master/Aaru.Images/AaruFormat/Structs.cs" />
public class ChecksumEntry
{
/// <summary>Checksum algorithm</summary>
public AaruChecksumAlgorithm type;
/// <summary>Length in bytes of checksum that follows this structure</summary>
public uint length;
/// <summary>Checksum that follows this structure</summary>
public byte[] checksum;
/// <summary>
/// Read a stream as an v
/// </summary>
/// <param name="stream">ChecksumEntry as a stream</param>
/// <returns>Populated ChecksumEntry, null on failure</returns>
public static ChecksumEntry Deserialize(Stream stream)
{
ChecksumEntry checksumEntry = new ChecksumEntry();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
checksumEntry.type = (AaruChecksumAlgorithm)br.ReadByte();
checksumEntry.length = br.ReadUInt32();
if (checksumEntry.length == 0)
return null;
checksumEntry.checksum = br.ReadBytes((int)checksumEntry.length);
}
return checksumEntry;
}
}
}

View File

@@ -0,0 +1,39 @@
using System.IO;
using System.Text;
namespace SabreTools.Library.FileTypes.Aaru
{
/// <summary>
/// Checksum block, contains a checksum of all user data sectors
/// (except for optical discs that is 2352 bytes raw sector if available
/// </summary>
/// <see cref="https://github.com/aaru-dps/Aaru/blob/master/Aaru.Images/AaruFormat/Structs.cs" />
public class ChecksumHeader
{
/// <summary>Identifier, <see cref="BlockType.ChecksumBlock" /></summary>
public AaruBlockType identifier;
/// <summary>Length in bytes of the block</summary>
public uint length;
/// <summary>How many checksums follow</summary>
public byte entries;
/// <summary>
/// Read a stream as an ChecksumHeader
/// </summary>
/// <param name="stream">ChecksumHeader as a stream</param>
/// <returns>Populated ChecksumHeader, null on failure</returns>
public static ChecksumHeader Deserialize(Stream stream)
{
ChecksumHeader checksumHeader = new ChecksumHeader();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
checksumHeader.identifier = (AaruBlockType)br.ReadUInt32();
checksumHeader.length = br.ReadUInt32();
checksumHeader.entries = br.ReadByte();
}
return checksumHeader;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.IO;
using System.Text;
namespace SabreTools.Library.FileTypes.Aaru
{
/// <summary>
/// Index entry
/// </summary>
/// <see cref="https://github.com/aaru-dps/Aaru/blob/master/Aaru.Images/AaruFormat/Structs.cs" />
public class IndexEntry
{
/// <summary>Type of item pointed by this entry</summary>
public AaruBlockType blockType;
/// <summary>Type of data contained by the block pointed by this entry</summary>
public AaruDataType dataType;
/// <summary>Offset in file where item is stored</summary>
public ulong offset;
/// <summary>
/// Read a stream as an IndexHeader
/// </summary>
/// <param name="stream">IndexHeader as a stream</param>
/// <returns>Populated IndexHeader, null on failure</returns>
public static IndexEntry Deserialize(Stream stream)
{
IndexEntry indexEntry = new IndexEntry();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
indexEntry.blockType = (AaruBlockType)br.ReadUInt32();
indexEntry.dataType = (AaruDataType)br.ReadUInt16();
indexEntry.offset = br.ReadUInt64();
}
return indexEntry;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.IO;
using System.Text;
namespace SabreTools.Library.FileTypes.Aaru
{
/// <summary>
/// Header for the index, followed by entries
/// </summary>
/// <see cref="https://github.com/aaru-dps/Aaru/blob/master/Aaru.Images/AaruFormat/Structs.cs" />
public class IndexHeader
{
/// <summary>Identifier, <see cref="BlockType.Index" /></summary>
public AaruBlockType identifier;
/// <summary>How many entries follow this header</summary>
public ushort entries;
/// <summary>CRC64-ECMA of the index</summary>
public ulong crc64;
/// <summary>
/// Read a stream as an IndexHeader
/// </summary>
/// <param name="stream">IndexHeader as a stream</param>
/// <returns>Populated IndexHeader, null on failure</returns>
public static IndexHeader Deserialize(Stream stream)
{
IndexHeader indexHeader = new IndexHeader();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
indexHeader.identifier = (AaruBlockType)br.ReadUInt32();
indexHeader.entries = br.ReadUInt16();
indexHeader.crc64 = br.ReadUInt64();
}
return indexHeader;
}
}
}

View File

@@ -0,0 +1,229 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.FileTypes.Aaru;
using SabreTools.Library.IO;
namespace SabreTools.Library.FileTypes
{
/// <summary>
/// AaruFormat code is based on the Aaru project
/// See https://github.com/aaru-dps/Aaru/tree/master/Aaru.Images/AaruFormat
/// </summary>
public class AaruFormat : BaseFile
{
#region Private instance variables
#region Header
protected ulong Identifier; // 'AARUFRMT' (0x544D524655524141)
protected string Application; // Name of application that created image
protected byte ImageMajorVersion; // Image format major version
protected byte ImageMinorVersion; // Image format minor version
protected byte ApplicationMajorVersion; // Major version of application that created image
protected byte ApplicationMinorVersion; // Minor version of application that created image
protected AaruMediaType MediaType; // Media type contained in image
protected ulong IndexOffset; // Offset to index
protected long CreationTime; // Windows filetime of creation time
protected long LastWrittenTime; // Windows filetime of last written time
#endregion
#region Internal Values
protected IndexHeader IndexHeader;
protected IndexEntry[] IndexEntries;
#endregion
#region Hashes
// TODO: Support SpamSum
#endregion
#endregion // Private instance variables
#region Constructors
/// <summary>
/// Create a new AaruFormat from an input file
/// </summary>
/// <param name="filename">Filename respresenting the AaruFormat file</param>
public static AaruFormat Create(string filename)
{
using (FileStream fs = FileExtensions.TryOpenRead(filename))
{
return Create(fs);
}
}
/// <summary>
/// Create a new AaruFormat from an input stream
/// </summary>
/// <param name="aarustream">Stream representing the AaruFormat file</param>
public static AaruFormat Create(Stream aarustream)
{
try
{
// Validate that this is actually a valid AaruFormat (by magic string alone)
bool validated = ValidateHeader(aarustream);
aarustream.Seek(-8, SeekOrigin.Current); // Seek back to start
if (!validated)
return null;
// Read and retrun the current AaruFormat
AaruFormat generated = Deserialize(aarustream);
if (generated != null)
generated.Type = FileType.AaruFormat;
return generated;
}
catch
{
return null;
}
}
#endregion
#region Header Parsing
/// <summary>
/// Validate we start with the right magic number
/// </summary>
public static bool ValidateHeader(Stream aarustream)
{
// Read the magic string
byte[] magicBytes = new byte[8];
int read = aarustream.Read(magicBytes, 0, 8);
// If we didn't read the magic fully, we don't have an AaruFormat
if (read < 8)
return false;
// If the bytes don't match, we don't have an AaruFormat
if (!magicBytes.StartsWith(Constants.AaruFormatSignature))
return false;
return true;
}
/// <summary>
/// Read a stream as an AaruFormat
/// </summary>
/// <param name="stream">AaruFormat file as a stream</param>
/// <returns>Populated AaruFormat file, null on failure</returns>
public static AaruFormat Deserialize(Stream stream)
{
try
{
AaruFormat aif = new AaruFormat();
using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true))
{
aif.Identifier = br.ReadUInt64();
aif.Application = Encoding.Unicode.GetString(br.ReadBytes(64), 0, 64);
aif.ImageMajorVersion = br.ReadByte();
aif.ImageMinorVersion = br.ReadByte();
aif.ApplicationMajorVersion = br.ReadByte();
aif.ApplicationMinorVersion = br.ReadByte();
aif.MediaType = (AaruMediaType)br.ReadUInt32();
aif.IndexOffset = br.ReadUInt64();
aif.CreationTime = br.ReadInt64();
aif.LastWrittenTime = br.ReadInt64();
// If the offset is bigger than the stream, we can't read it
if (aif.IndexOffset > (ulong)stream.Length)
return null;
// Otherwise, we read in the index header
stream.Seek((long)aif.IndexOffset, SeekOrigin.Begin);
aif.IndexHeader = IndexHeader.Deserialize(stream);
if (aif.IndexHeader.entries == 0)
return null;
// Get the list of entries
aif.IndexEntries = new IndexEntry[aif.IndexHeader.entries];
for (ushort index = 0; index < aif.IndexHeader.entries; index++)
{
aif.IndexEntries[index] = IndexEntry.Deserialize(stream);
switch (aif.IndexEntries[index].blockType)
{
// We don't do anything with these block types currently
case AaruBlockType.DataBlock:
case AaruBlockType.DeDuplicationTable:
case AaruBlockType.Index:
case AaruBlockType.Index2:
case AaruBlockType.GeometryBlock:
case AaruBlockType.MetadataBlock:
case AaruBlockType.TracksBlock:
case AaruBlockType.CicmBlock:
case AaruBlockType.DataPositionMeasurementBlock:
case AaruBlockType.SnapshotBlock:
case AaruBlockType.ParentBlock:
case AaruBlockType.DumpHardwareBlock:
case AaruBlockType.TapeFileBlock:
case AaruBlockType.TapePartitionBlock:
case AaruBlockType.CompactDiscIndexesBlock:
// No-op
break;
// Read in all available hashes
case AaruBlockType.ChecksumBlock:
// If the offset is bigger than the stream, we can't read it
if (aif.IndexEntries[index].offset > (ulong)stream.Length)
return null;
// Otherwise, we read in the block
stream.Seek((long)aif.IndexEntries[index].offset, SeekOrigin.Begin);
ChecksumHeader checksumHeader = ChecksumHeader.Deserialize(stream);
if (checksumHeader.entries == 0)
return null;
// Read through each and pick out the ones we care about
for (byte entry = 0; entry < checksumHeader.entries; entry++)
{
ChecksumEntry checksumEntry = ChecksumEntry.Deserialize(stream);
if (checksumEntry == null)
continue;
switch (checksumEntry.type)
{
case AaruChecksumAlgorithm.Invalid:
break;
case AaruChecksumAlgorithm.Md5:
aif.MD5 = checksumEntry.checksum;
break;
case AaruChecksumAlgorithm.Sha1:
aif.SHA1 = checksumEntry.checksum;
break;
case AaruChecksumAlgorithm.Sha256:
aif.SHA256 = checksumEntry.checksum;
break;
case AaruChecksumAlgorithm.SpamSum:
// TODO: Support SpamSum
break;
}
}
// Once we got hashes, we return early
return aif;
}
}
}
return aif;
}
catch
{
// We don't care what the error was at this point
return null;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -362,9 +362,9 @@ namespace SabreTools.Library.FileTypes
if (rom.ItemType == ItemType.Rom)
{
if (date && !string.IsNullOrWhiteSpace(((Rom)rom).Date))
if (date && !string.IsNullOrWhiteSpace((rom as Rom).Date))
{
File.SetCreationTime(fileName, DateTime.Parse(((Rom)rom).Date));
File.SetCreationTime(fileName, DateTime.Parse((rom as Rom).Date));
}
}

View File

@@ -204,7 +204,7 @@ namespace SabreTools.Library.FileTypes
else
{
Stream entryStream = entry.OpenEntryStream();
BaseFile rarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
BaseFile rarEntryRom = entryStream.GetInfo(size: entry.Size, omitFromScan: omitFromScan);
rarEntryRom.Filename = entry.Key;
rarEntryRom.Parent = gamename;
rarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");

View File

@@ -316,7 +316,7 @@ namespace SabreTools.Library.FileTypes
// Otherwise, use the stream directly
else
{
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
BaseFile zipEntryRom = readStream.GetInfo(size: (long)zf.UncompressedSize(i), omitFromScan: omitFromScan, keepReadOpen: true);
zipEntryRom.Filename = zf.Filename(i);
zipEntryRom.Parent = gamename;
found.Add(zipEntryRom);

View File

@@ -209,7 +209,7 @@ namespace SabreTools.Library.FileTypes
else
{
Stream entryStream = entry.OpenEntryStream();
BaseFile tarEntryRom = entryStream.GetInfo(entry.Size, omitFromScan);
BaseFile tarEntryRom = entryStream.GetInfo(size: entry.Size, omitFromScan: omitFromScan);
tarEntryRom.Filename = entry.Key;
tarEntryRom.Parent = gamename;
tarEntryRom.Date = entry.LastModifiedTime?.ToString("yyyy/MM/dd hh:mm:ss");

View File

@@ -319,7 +319,7 @@ namespace SabreTools.Library.FileTypes
// Otherwise, use the stream directly
else
{
BaseFile zipEntryRom = readStream.GetInfo((long)zf.UncompressedSize(i), omitFromScan, true);
BaseFile zipEntryRom = readStream.GetInfo(size: (long)zf.UncompressedSize(i), omitFromScan: omitFromScan, keepReadOpen: true);
zipEntryRom.Filename = zf.Filename(i);
zipEntryRom.Parent = gamename;
string convertedDate = zf.LastModified(i).ToString("yyyy/MM/dd hh:mm:ss");