using System.IO; using System.Text; using SabreTools.IO; using SabreTools.Core; using SabreTools.FileTypes.Aaru; namespace SabreTools.FileTypes { /// /// AaruFormat code is based on the Aaru project /// See https://github.com/aaru-dps/Aaru/tree/master/Aaru.Images/AaruFormat /// 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 #endregion // Private instance variables #region Constructors /// /// Create a new AaruFormat from an input file /// /// Filename respresenting the AaruFormat file public static AaruFormat Create(string filename) { using (FileStream fs = File.OpenRead(filename)) { return Create(fs); } } /// /// Create a new AaruFormat from an input stream /// /// Stream representing the AaruFormat file public static AaruFormat Create(Stream aarustream) { try { // Validate that this is actually a valid AaruFormat (by magic string alone) bool validated = ValidateHeader(aarustream); aarustream.SeekIfPossible(); // 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 /// /// Validate we start with the right magic number /// 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; } /// /// Read a stream as an AaruFormat /// /// AaruFormat file as a stream /// Populated AaruFormat file, null on failure 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: aif.SpamSum = checksumEntry.checksum; 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 } }