mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
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:
41
SabreTools.Library/FileTypes/Aaru/ChecksumEntry.cs
Normal file
41
SabreTools.Library/FileTypes/Aaru/ChecksumEntry.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
SabreTools.Library/FileTypes/Aaru/ChecksumHeader.cs
Normal file
39
SabreTools.Library/FileTypes/Aaru/ChecksumHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SabreTools.Library/FileTypes/Aaru/IndexEntry.cs
Normal file
38
SabreTools.Library/FileTypes/Aaru/IndexEntry.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SabreTools.Library/FileTypes/Aaru/IndexHeader.cs
Normal file
38
SabreTools.Library/FileTypes/Aaru/IndexHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
229
SabreTools.Library/FileTypes/AaruFormat.cs
Normal file
229
SabreTools.Library/FileTypes/AaruFormat.cs
Normal 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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user