mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
Implement EWF disk image support.
This commit is contained in:
110
Aaru.Images/EWF/Consts.cs
Normal file
110
Aaru.Images/EWF/Consts.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Consts.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains constants for Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
/// <summary>EWF v1 disk image signature: "EVF\x09\x0D\x0A\xFF\x00"</summary>
|
||||
static readonly byte[] EVF_SIGNATURE = [0x45, 0x56, 0x46, 0x09, 0x0D, 0x0A, 0xFF, 0x00];
|
||||
|
||||
/// <summary>EWF v1 logical evidence signature: "LVF\x09\x0D\x0A\xFF\x00"</summary>
|
||||
static readonly byte[] LVF_SIGNATURE = [0x4C, 0x56, 0x46, 0x09, 0x0D, 0x0A, 0xFF, 0x00];
|
||||
|
||||
/// <summary>EWF v2 disk image signature: "EVF2\x0D\x0A\x81\x00"</summary>
|
||||
static readonly byte[] EVF2_SIGNATURE = [0x45, 0x56, 0x46, 0x32, 0x0D, 0x0A, 0x81, 0x00];
|
||||
|
||||
/// <summary>EWF v2 logical evidence signature: "LEF2\x0D\x0A\x81\x00"</summary>
|
||||
static readonly byte[] LEF2_SIGNATURE = [0x4C, 0x45, 0x46, 0x32, 0x0D, 0x0A, 0x81, 0x00];
|
||||
|
||||
/// <summary>SMART volume section signature</summary>
|
||||
static readonly byte[] SMART_SIGNATURE = [0x53, 0x4D, 0x41, 0x52, 0x54];
|
||||
|
||||
const int SIGNATURE_LENGTH = 8;
|
||||
|
||||
/// <summary>Section type strings for EWF v1</summary>
|
||||
const string SECTION_TYPE_HEADER = "header";
|
||||
const string SECTION_TYPE_HEADER2 = "header2";
|
||||
const string SECTION_TYPE_VOLUME = "volume";
|
||||
const string SECTION_TYPE_DATA = "data";
|
||||
const string SECTION_TYPE_TABLE = "table";
|
||||
const string SECTION_TYPE_TABLE2 = "table2";
|
||||
const string SECTION_TYPE_SECTORS = "sectors";
|
||||
const string SECTION_TYPE_HASH = "hash";
|
||||
const string SECTION_TYPE_DIGEST = "digest";
|
||||
const string SECTION_TYPE_ERROR2 = "error2";
|
||||
const string SECTION_TYPE_SESSION = "session";
|
||||
const string SECTION_TYPE_DONE = "done";
|
||||
const string SECTION_TYPE_NEXT = "next";
|
||||
|
||||
/// <summary>EWF v1 section descriptor size</summary>
|
||||
const int SECTION_DESCRIPTOR_V1_SIZE = 76;
|
||||
|
||||
/// <summary>Default sectors per chunk</summary>
|
||||
const uint DEFAULT_SECTORS_PER_CHUNK = 64;
|
||||
|
||||
/// <summary>Default bytes per sector</summary>
|
||||
const uint DEFAULT_BYTES_PER_SECTOR = 512;
|
||||
|
||||
/// <summary>Default chunk size (64 * 512 = 32768)</summary>
|
||||
const uint DEFAULT_CHUNK_SIZE = DEFAULT_SECTORS_PER_CHUNK * DEFAULT_BYTES_PER_SECTOR;
|
||||
|
||||
/// <summary>Compression flag in EWF v1 table entry (most significant bit)</summary>
|
||||
const uint TABLE_ENTRY_V1_COMPRESSED_FLAG = 0x80000000;
|
||||
|
||||
/// <summary>Offset mask for EWF v1 table entry (lower 31 bits)</summary>
|
||||
const uint TABLE_ENTRY_V1_OFFSET_MASK = 0x7FFFFFFF;
|
||||
|
||||
/// <summary>Maximum cached chunks</summary>
|
||||
const int MAX_CACHE_SIZE = 16777216;
|
||||
|
||||
/// <summary>Maximum cached sectors</summary>
|
||||
const int MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512;
|
||||
|
||||
/// <summary>EWF v1 file header size</summary>
|
||||
const int FILE_HEADER_V1_SIZE = 13;
|
||||
|
||||
/// <summary>EWF v2 file header size</summary>
|
||||
const int FILE_HEADER_V2_SIZE = 32;
|
||||
|
||||
/// <summary>EWF v1 EnCase 5+ volume section data size</summary>
|
||||
const int VOLUME_SECTION_SIZE_ENCASE = 1052;
|
||||
|
||||
/// <summary>EWF v1 SMART/old EnCase volume section data size</summary>
|
||||
const int VOLUME_SECTION_SIZE_SMART = 94;
|
||||
|
||||
/// <summary>Session entry flag: audio track</summary>
|
||||
const uint SESSION_ENTRY_FLAG_IS_AUDIO = 0x00000001;
|
||||
}
|
||||
121
Aaru.Images/EWF/EWF.cs
Normal file
121
Aaru.Images/EWF/EWF.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : EWF.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Manages Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Track = Aaru.CommonTypes.Structs.Track;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IOpticalMediaImage" />
|
||||
/// <summary>Implements reading Expert Witness Format (EWF) disk images</summary>
|
||||
public sealed partial class Ewf : IOpticalMediaImage
|
||||
{
|
||||
const string MODULE_NAME = "EWF plugin";
|
||||
|
||||
/// <summary>List of bad sector ranges from error2 sections</summary>
|
||||
List<(ulong start, uint count)> _badSectors;
|
||||
uint _bytesPerSector;
|
||||
|
||||
/// <summary>Cache of decompressed chunks</summary>
|
||||
Dictionary<ulong, byte[]> _chunkCache;
|
||||
uint _chunkSize;
|
||||
|
||||
/// <summary>Map from chunk index to location in segment file</summary>
|
||||
Dictionary<ulong, (int segmentIndex, long offset, uint size, bool compressed)> _chunkTable;
|
||||
|
||||
/// <summary>Compression method for EWF v2</summary>
|
||||
EwfCompressionMethod _compressionMethod;
|
||||
|
||||
/// <summary>Parsed header metadata key/value pairs</summary>
|
||||
Dictionary<string, string> _headerValues;
|
||||
|
||||
ImageInfo _imageInfo;
|
||||
|
||||
/// <summary>Whether the image uses the SMART volume section format</summary>
|
||||
bool _isSmart;
|
||||
|
||||
/// <summary>Whether the image is EWF v2 format</summary>
|
||||
bool _isV2;
|
||||
|
||||
int _maxChunkCache;
|
||||
|
||||
/// <summary>Stored MD5 hash from hash/digest section</summary>
|
||||
byte[] _md5Stored;
|
||||
|
||||
/// <summary>Populated metadata from header sections</summary>
|
||||
Metadata _metadata;
|
||||
|
||||
/// <summary>Cache of read sectors</summary>
|
||||
Dictionary<ulong, byte[]> _sectorCache;
|
||||
|
||||
uint _sectorsPerChunk;
|
||||
|
||||
/// <summary>Ordered list of open segment file streams</summary>
|
||||
List<Stream> _segmentStreams;
|
||||
|
||||
/// <summary>Session list for optical media</summary>
|
||||
List<Session> _sessions;
|
||||
|
||||
/// <summary>Stored SHA1 hash from digest section</summary>
|
||||
byte[] _sha1Stored;
|
||||
|
||||
/// <summary>Track list for optical media</summary>
|
||||
List<Track> _tracks;
|
||||
|
||||
public Ewf() => _imageInfo = new ImageInfo
|
||||
{
|
||||
ReadableSectorTags = [],
|
||||
ReadableMediaTags = [],
|
||||
HasPartitions = false,
|
||||
HasSessions = false,
|
||||
Version = null,
|
||||
Application = null,
|
||||
ApplicationVersion = null,
|
||||
Creator = null,
|
||||
Comments = null,
|
||||
MediaManufacturer = null,
|
||||
MediaModel = null,
|
||||
MediaSerialNumber = null,
|
||||
MediaBarcode = null,
|
||||
MediaPartNumber = null,
|
||||
MediaSequence = 0,
|
||||
LastMediaSequence = 0,
|
||||
DriveManufacturer = null,
|
||||
DriveModel = null,
|
||||
DriveSerialNumber = null,
|
||||
DriveFirmwareRevision = null
|
||||
};
|
||||
}
|
||||
99
Aaru.Images/EWF/Enums.cs
Normal file
99
Aaru.Images/EWF/Enums.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Enums.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains enumerations for Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
/// <summary>EWF media type as stored in the volume section</summary>
|
||||
enum EwfMediaType : byte
|
||||
{
|
||||
Removable = 0x00,
|
||||
Fixed = 0x01,
|
||||
Optical = 0x03,
|
||||
SingleFiles = 0x0E,
|
||||
Memory = 0x10
|
||||
}
|
||||
|
||||
/// <summary>EWF media flags as stored in the volume section</summary>
|
||||
enum EwfMediaFlags : byte
|
||||
{
|
||||
Image = 0x01,
|
||||
Physical = 0x02,
|
||||
Fastbloc = 0x04,
|
||||
Tableau = 0x08
|
||||
}
|
||||
|
||||
/// <summary>EWF v1 compression level</summary>
|
||||
enum EwfCompressionLevel : byte
|
||||
{
|
||||
None = 0x00,
|
||||
Fast = 0x01,
|
||||
Best = 0x02
|
||||
}
|
||||
|
||||
/// <summary>EWF v2 compression method as stored in the file header</summary>
|
||||
enum EwfCompressionMethod : ushort
|
||||
{
|
||||
None = 0,
|
||||
Deflate = 1,
|
||||
Bzip2 = 2
|
||||
}
|
||||
|
||||
/// <summary>EWF v2 section type codes</summary>
|
||||
enum EwfSectionTypeV2 : uint
|
||||
{
|
||||
DeviceInformation = 0x00000001,
|
||||
CaseData = 0x00000002,
|
||||
SectorData = 0x00000003,
|
||||
SectorTable = 0x00000004,
|
||||
ErrorTable = 0x00000005,
|
||||
SessionTable = 0x00000006,
|
||||
IncrementData = 0x00000007,
|
||||
Md5Hash = 0x00000008,
|
||||
Sha1Hash = 0x00000009,
|
||||
RestartData = 0x0000000A,
|
||||
EncryptionKeys = 0x0000000B,
|
||||
MemoryExtentsTable = 0x0000000C,
|
||||
Next = 0x0000000D,
|
||||
FinalInformation = 0x0000000E,
|
||||
Done = 0x0000000F,
|
||||
AnalyticalData = 0x00000010,
|
||||
SingleFilesData = 0x00000020,
|
||||
SingleFilesTree = 0x00000021,
|
||||
SingleFilesMetadata = 0x00000022,
|
||||
SingleFilesSource = 0x00000023
|
||||
}
|
||||
}
|
||||
342
Aaru.Images/EWF/Helpers.cs
Normal file
342
Aaru.Images/EWF/Helpers.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Helpers.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains helper methods for Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Track = Aaru.CommonTypes.Structs.Track;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the next segment filename following EWF naming conventions.
|
||||
/// E01→E02→...→E99→EAA→EAB→...→EZZ→FAA→...→ZZZ for v1 EnCase.
|
||||
/// Ex01→Ex02→...→Ex99→ExAA→...→ExZZ for v2.
|
||||
/// s01→s02→...→s99→saa→...→szz for SMART.
|
||||
/// </summary>
|
||||
static string GetNextSegmentFilename(string currentPath)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(currentPath) ?? "";
|
||||
string name = Path.GetFileNameWithoutExtension(currentPath);
|
||||
string ext = Path.GetExtension(currentPath);
|
||||
|
||||
if(string.IsNullOrEmpty(ext) || ext.Length < 2) return null;
|
||||
|
||||
// Determine if we're in v2 mode (.Ex01 has 5-char extension)
|
||||
bool isV2 = ext.Length == 5; // .Ex01
|
||||
bool isLower = char.IsLower(ext[1]);
|
||||
|
||||
if(isV2)
|
||||
{
|
||||
// .Ex01 → .Ex02 → ... → .Ex99 → .ExAA → ... → .ExZZ
|
||||
string numPart = ext.Substring(3); // "01" from ".Ex01"
|
||||
char prefix1 = ext[1]; // 'E'
|
||||
char prefix2 = ext[2]; // 'x'
|
||||
|
||||
if(int.TryParse(numPart, out int num) && num < 99)
|
||||
return Path.Combine(dir, name + ext.Substring(0, 3) + (num + 1).ToString("D2"));
|
||||
|
||||
if(int.TryParse(numPart, out _))
|
||||
{
|
||||
// 99 → AA
|
||||
char a = isLower ? 'a' : 'A';
|
||||
|
||||
return Path.Combine(dir, name + $".{prefix1}{prefix2}{a}{a}");
|
||||
}
|
||||
|
||||
// AA → AB → ... → AZ → BA → ... → ZZ
|
||||
char c1 = ext[3];
|
||||
char c2 = ext[4];
|
||||
|
||||
char baseChar = isLower ? 'a' : 'A';
|
||||
char maxChar = isLower ? 'z' : 'Z';
|
||||
|
||||
if(c2 < maxChar) return Path.Combine(dir, name + $".{prefix1}{prefix2}{c1}{(char)(c2 + 1)}");
|
||||
|
||||
if(c1 < maxChar) return Path.Combine(dir, name + $".{prefix1}{prefix2}{(char)(c1 + 1)}{baseChar}");
|
||||
|
||||
return null; // ZZ reached
|
||||
}
|
||||
|
||||
// v1: .E01 → .E02 → ... → .E99 → .EAA → ... → .EZZ → .FAA → ... → .ZZZ
|
||||
// SMART: .s01 → .s02 → ... → .s99 → .saa → ... → .szz
|
||||
char firstChar = ext[1]; // 'E' or 's'
|
||||
string suffix = ext.Substring(2); // "01" from ".E01"
|
||||
|
||||
if(int.TryParse(suffix, out int segNum) && segNum < 99)
|
||||
return Path.Combine(dir, name + $".{firstChar}{segNum + 1:D2}");
|
||||
|
||||
if(int.TryParse(suffix, out _))
|
||||
{
|
||||
// 99 → AA (or aa for lowercase)
|
||||
char a = isLower ? 'a' : 'A';
|
||||
|
||||
return Path.Combine(dir, name + $".{firstChar}{a}{a}");
|
||||
}
|
||||
|
||||
// Letter suffixes: EAA → EAB → ... → EAZ → EBA → ... → EZZ → FAA → ... → ZZZ
|
||||
char ch1 = ext[1]; // first letter (E, F, ..., Z) or (s for SMART - but SMART only goes to szz)
|
||||
char ch2 = ext[2]; // second letter
|
||||
char ch3 = ext[3]; // third letter
|
||||
|
||||
char lBase = isLower ? 'a' : 'A';
|
||||
char lMax = isLower ? 'z' : 'Z';
|
||||
|
||||
if(ch3 < lMax) return Path.Combine(dir, name + $".{ch1}{ch2}{(char)(ch3 + 1)}");
|
||||
|
||||
if(ch2 < lMax) return Path.Combine(dir, name + $".{ch1}{(char)(ch2 + 1)}{lBase}");
|
||||
|
||||
if(ch1 < lMax) return Path.Combine(dir, name + $".{(char)(ch1 + 1)}{lBase}{lBase}");
|
||||
|
||||
return null; // ZZZ reached
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses EWF header text from a raw (already decompressed) byte array.
|
||||
/// header sections use ASCII; header2 sections use UTF-16 LE.
|
||||
/// </summary>
|
||||
static Dictionary<string, string> ParseHeaderText(byte[] data, bool isHeader2)
|
||||
{
|
||||
string text;
|
||||
|
||||
if(isHeader2)
|
||||
{
|
||||
// UTF-16 LE, may have BOM
|
||||
if(data.Length >= 2 && data[0] == 0xFF && data[1] == 0xFE)
|
||||
text = Encoding.Unicode.GetString(data, 2, data.Length - 2);
|
||||
else
|
||||
text = Encoding.Unicode.GetString(data);
|
||||
}
|
||||
else
|
||||
text = Encoding.ASCII.GetString(data);
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
// Header text format is category-based with newline-delimited sections.
|
||||
// Most common format:
|
||||
// Line 1: category number (e.g., "1" or "3")
|
||||
// Line 2: "main" or empty
|
||||
// Line 3: tab-separated field identifiers (e.g., "c\tn\ta\te\tt\tav\tov\tm\tu\tp\tr")
|
||||
// Line 4: tab-separated field values
|
||||
// The remaining lines may repeat the pattern or be empty.
|
||||
|
||||
string[] lines = text.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(lines.Length < 4) return result;
|
||||
|
||||
// Try to find the field identifiers and values lines
|
||||
// Look for the line containing tab-separated single-letter or short identifiers
|
||||
for(var i = 0; i < lines.Length - 1; i++)
|
||||
{
|
||||
string line = lines[i].Trim();
|
||||
|
||||
if(!line.Contains('\t')) continue;
|
||||
|
||||
string[] keys = line.Split('\t');
|
||||
|
||||
// Verify this looks like an identifier line (short keys)
|
||||
var isKeyLine = true;
|
||||
|
||||
foreach(string key in keys)
|
||||
{
|
||||
if(key.Length > 4)
|
||||
{
|
||||
isKeyLine = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isKeyLine) continue;
|
||||
|
||||
// Next line should contain the values
|
||||
if(i + 1 >= lines.Length) break;
|
||||
|
||||
string[] values = lines[i + 1].Trim().Split('\t');
|
||||
|
||||
for(var j = 0; j < keys.Length && j < values.Length; j++)
|
||||
{
|
||||
string key = keys[j].Trim();
|
||||
string value = values[j].Trim();
|
||||
|
||||
if(!string.IsNullOrEmpty(key) && !result.ContainsKey(key)) result[key] = value;
|
||||
}
|
||||
|
||||
break; // Only process the first key/value pair set
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses zlib (RFC 1950) compressed data by stripping the 2-byte header
|
||||
/// and using DeflateStream.
|
||||
/// </summary>
|
||||
static byte[] DecompressZlib(byte[] compressedData, int uncompressedSize)
|
||||
{
|
||||
if(compressedData == null || compressedData.Length < 3 || uncompressedSize <= 0)
|
||||
return new byte[Math.Max(uncompressedSize, 0)];
|
||||
|
||||
var decompressed = new byte[uncompressedSize];
|
||||
|
||||
// Skip 2-byte zlib header (CMF + FLG)
|
||||
using var ms = new MemoryStream(compressedData, 2, compressedData.Length - 2);
|
||||
using var deflate = new DeflateStream(ms, CompressionMode.Decompress);
|
||||
var totalRead = 0;
|
||||
|
||||
while(totalRead < uncompressedSize)
|
||||
{
|
||||
int read = deflate.Read(decompressed, totalRead, uncompressedSize - totalRead);
|
||||
|
||||
if(read == 0) break;
|
||||
|
||||
totalRead += read;
|
||||
}
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds session and track lists from EWF session entries using the libewf reconstruction algorithm.
|
||||
/// </summary>
|
||||
static void BuildSessionsAndTracks(List<(ulong startSector, uint flags)> sessionEntries, ulong totalSectors,
|
||||
uint bytesPerSector, out List<Session> sessions, out List<Track> tracks)
|
||||
{
|
||||
sessions = [];
|
||||
tracks = [];
|
||||
|
||||
if(sessionEntries is not { Count: > 0 })
|
||||
{
|
||||
// No session entries — create a single session with one data track
|
||||
sessions.Add(new Session
|
||||
{
|
||||
Sequence = 1,
|
||||
StartSector = 0,
|
||||
EndSector = totalSectors - 1,
|
||||
StartTrack = 1,
|
||||
EndTrack = 1
|
||||
});
|
||||
|
||||
tracks.Add(new Track
|
||||
{
|
||||
Sequence = 1,
|
||||
Session = 1,
|
||||
StartSector = 0,
|
||||
EndSector = totalSectors - 1,
|
||||
Type = TrackType.Data,
|
||||
BytesPerSector = (int)bytesPerSector,
|
||||
RawBytesPerSector = (int)bytesPerSector,
|
||||
SubchannelType = TrackSubchannelType.None
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Build tracks from session entries
|
||||
// Each entry defines the start of a region; its type is determined by the audio flag.
|
||||
ushort sessionSequence = 1;
|
||||
uint trackSequence = 1;
|
||||
ulong sessionStart = 0;
|
||||
uint sessionStartTrack = 1;
|
||||
|
||||
for(var i = 0; i < sessionEntries.Count; i++)
|
||||
{
|
||||
(ulong startSector, uint flags) = sessionEntries[i];
|
||||
bool isAudio = (flags & SESSION_ENTRY_FLAG_IS_AUDIO) != 0;
|
||||
ulong endSector = i + 1 < sessionEntries.Count ? sessionEntries[i + 1].startSector - 1 : totalSectors - 1;
|
||||
|
||||
// If this is a data entry and there was a previous audio entry, we're starting a new session
|
||||
if(!isAudio && i > 0 && (sessionEntries[i - 1].flags & SESSION_ENTRY_FLAG_IS_AUDIO) != 0)
|
||||
{
|
||||
// Close previous session
|
||||
sessions.Add(new Session
|
||||
{
|
||||
Sequence = sessionSequence,
|
||||
StartSector = sessionStart,
|
||||
EndSector = startSector - 1,
|
||||
StartTrack = sessionStartTrack,
|
||||
EndTrack = trackSequence - 1
|
||||
});
|
||||
|
||||
sessionSequence++;
|
||||
sessionStart = startSector;
|
||||
sessionStartTrack = trackSequence;
|
||||
}
|
||||
else if(!isAudio && i > 0 && (sessionEntries[i - 1].flags & SESSION_ENTRY_FLAG_IS_AUDIO) == 0)
|
||||
{
|
||||
// Data followed by data — new session boundary
|
||||
sessions.Add(new Session
|
||||
{
|
||||
Sequence = sessionSequence,
|
||||
StartSector = sessionStart,
|
||||
EndSector = startSector - 1,
|
||||
StartTrack = sessionStartTrack,
|
||||
EndTrack = trackSequence - 1
|
||||
});
|
||||
|
||||
sessionSequence++;
|
||||
sessionStart = startSector;
|
||||
sessionStartTrack = trackSequence;
|
||||
}
|
||||
|
||||
tracks.Add(new Track
|
||||
{
|
||||
Sequence = trackSequence,
|
||||
Session = sessionSequence,
|
||||
StartSector = startSector,
|
||||
EndSector = endSector,
|
||||
Type = isAudio ? TrackType.Audio : TrackType.CdMode1,
|
||||
BytesPerSector = isAudio ? 2352 : (int)bytesPerSector,
|
||||
RawBytesPerSector = isAudio ? 2352 : (int)bytesPerSector,
|
||||
SubchannelType = TrackSubchannelType.None
|
||||
});
|
||||
|
||||
trackSequence++;
|
||||
}
|
||||
|
||||
// Close last session
|
||||
sessions.Add(new Session
|
||||
{
|
||||
Sequence = sessionSequence,
|
||||
StartSector = sessionStart,
|
||||
EndSector = totalSectors - 1,
|
||||
StartTrack = sessionStartTrack,
|
||||
EndTrack = trackSequence - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
70
Aaru.Images/EWF/Identify.cs
Normal file
70
Aaru.Images/EWF/Identify.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Identify.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Identifies Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Identify(IFilter imageFilter)
|
||||
{
|
||||
Stream stream = imageFilter.GetDataForkStream();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if(stream.Length < SIGNATURE_LENGTH) return false;
|
||||
|
||||
var signatureBytes = new byte[SIGNATURE_LENGTH];
|
||||
stream.EnsureRead(signatureBytes, 0, SIGNATURE_LENGTH);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Ewf_Identify_Signature_0, BitConverter.ToString(signatureBytes));
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(EVF_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LVF_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(EVF2_SIGNATURE)) return true;
|
||||
|
||||
if(signatureBytes.AsSpan().SequenceEqual(LEF2_SIGNATURE)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
808
Aaru.Images/EWF/Open.cs
Normal file
808
Aaru.Images/EWF/Open.cs
Normal file
@@ -0,0 +1,808 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Open.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Opens Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
using Marshal = Aaru.Helpers.Marshal;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Open(IFilter imageFilter)
|
||||
{
|
||||
Stream stream = imageFilter.GetDataForkStream();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if(stream.Length < SIGNATURE_LENGTH) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Read signature to determine version
|
||||
var signatureBytes = new byte[SIGNATURE_LENGTH];
|
||||
stream.EnsureRead(signatureBytes, 0, SIGNATURE_LENGTH);
|
||||
|
||||
_isV2 = signatureBytes.AsSpan().SequenceEqual(EVF2_SIGNATURE) ||
|
||||
signatureBytes.AsSpan().SequenceEqual(LEF2_SIGNATURE);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "EWF version {0}", _isV2 ? "2" : "1");
|
||||
|
||||
// Discover all segment files
|
||||
_segmentStreams = [];
|
||||
List<IFilter> segmentFilters = DiscoverSegments(imageFilter);
|
||||
|
||||
if(segmentFilters.Count == 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
foreach(IFilter filter in segmentFilters) _segmentStreams.Add(filter.GetDataForkStream());
|
||||
|
||||
// Initialize data structures
|
||||
_chunkTable = new Dictionary<ulong, (int segmentIndex, long offset, uint size, bool compressed)>();
|
||||
_chunkCache = new Dictionary<ulong, byte[]>();
|
||||
_sectorCache = new Dictionary<ulong, byte[]>();
|
||||
_badSectors = [];
|
||||
_headerValues = new Dictionary<string, string>();
|
||||
_md5Stored = null;
|
||||
_sha1Stored = null;
|
||||
_isSmart = false;
|
||||
|
||||
_sectorsPerChunk = DEFAULT_SECTORS_PER_CHUNK;
|
||||
_bytesPerSector = DEFAULT_BYTES_PER_SECTOR;
|
||||
_chunkSize = DEFAULT_CHUNK_SIZE;
|
||||
|
||||
ulong totalSectors = 0;
|
||||
uint chsC = 0;
|
||||
uint chsH = 0;
|
||||
uint chsS = 0;
|
||||
byte mediaType = 0;
|
||||
byte mediaFlags = 0;
|
||||
var volumeFound = false;
|
||||
|
||||
var sessionEntries = new List<(ulong startSector, uint flags)>();
|
||||
ulong currentChunk = 0;
|
||||
|
||||
byte[] headerData = null;
|
||||
byte[] header2Data = null;
|
||||
|
||||
// Process each segment file
|
||||
for(var segIdx = 0; segIdx < _segmentStreams.Count; segIdx++)
|
||||
{
|
||||
Stream segStream = _segmentStreams[segIdx];
|
||||
|
||||
if(_isV2)
|
||||
{
|
||||
ParseSegmentV2(segStream,
|
||||
segIdx,
|
||||
ref currentChunk,
|
||||
ref volumeFound,
|
||||
ref totalSectors,
|
||||
ref mediaType,
|
||||
ref mediaFlags,
|
||||
ref chsC,
|
||||
ref chsH,
|
||||
ref chsS,
|
||||
sessionEntries);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseSegmentV1(segStream,
|
||||
segIdx,
|
||||
ref currentChunk,
|
||||
ref volumeFound,
|
||||
ref totalSectors,
|
||||
ref headerData,
|
||||
ref header2Data,
|
||||
ref mediaType,
|
||||
ref mediaFlags,
|
||||
ref chsC,
|
||||
ref chsH,
|
||||
ref chsS,
|
||||
sessionEntries);
|
||||
}
|
||||
}
|
||||
|
||||
if(!volumeFound)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, "No volume section found in any segment file");
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
_chunkSize = _sectorsPerChunk * _bytesPerSector;
|
||||
_maxChunkCache = (int)(MAX_CACHE_SIZE / _chunkSize);
|
||||
|
||||
if(_maxChunkCache < 1) _maxChunkCache = 1;
|
||||
|
||||
// Parse header metadata (prefer header2 over header)
|
||||
if(header2Data is { Length: > 0 })
|
||||
_headerValues = ParseHeaderText(header2Data, true);
|
||||
else if(headerData is { Length: > 0 }) _headerValues = ParseHeaderText(headerData, false);
|
||||
|
||||
// Populate image info
|
||||
_imageInfo.Sectors = totalSectors;
|
||||
_imageInfo.SectorSize = _bytesPerSector;
|
||||
_imageInfo.Cylinders = chsC;
|
||||
_imageInfo.Heads = chsH;
|
||||
_imageInfo.SectorsPerTrack = chsS;
|
||||
_imageInfo.ImageSize = totalSectors * _bytesPerSector;
|
||||
_imageInfo.Application = "EnCase";
|
||||
_imageInfo.CreationTime = imageFilter.CreationTime;
|
||||
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
|
||||
|
||||
if(_isSmart) _imageInfo.Application = "SMART";
|
||||
|
||||
// Map header values to image info
|
||||
if(_headerValues.TryGetValue("a", out string description)) _imageInfo.Comments = description;
|
||||
|
||||
if(_headerValues.TryGetValue("av", out string appVersion)) _imageInfo.ApplicationVersion = appVersion;
|
||||
|
||||
if(_headerValues.TryGetValue("ov", out string osVersion)) _imageInfo.Creator = osVersion;
|
||||
|
||||
if(_headerValues.TryGetValue("md", out string model)) _imageInfo.MediaModel = model;
|
||||
|
||||
if(_headerValues.TryGetValue("sn", out string serial)) _imageInfo.MediaSerialNumber = serial;
|
||||
|
||||
// Determine media type
|
||||
if(_isSmart)
|
||||
{
|
||||
_imageInfo.MediaType = MediaType.GENERIC_HDD;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ewfMediaType = (EwfMediaType)mediaType;
|
||||
|
||||
switch(ewfMediaType)
|
||||
{
|
||||
case EwfMediaType.Fixed:
|
||||
_imageInfo.MediaType = MediaType.GENERIC_HDD;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
|
||||
break;
|
||||
case EwfMediaType.Removable:
|
||||
_imageInfo.MediaType = MediaType.FlashDrive;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
|
||||
break;
|
||||
case EwfMediaType.Optical:
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
|
||||
|
||||
// Refine optical type based on sector size and count
|
||||
if(_bytesPerSector == 2048)
|
||||
{
|
||||
_imageInfo.MediaType = totalSectors <= 360000
|
||||
? MediaType.CDROM
|
||||
: totalSectors <= 2295104
|
||||
? MediaType.DVDROM
|
||||
: MediaType.BDROM;
|
||||
}
|
||||
else
|
||||
_imageInfo.MediaType = MediaType.CD;
|
||||
|
||||
break;
|
||||
case EwfMediaType.Memory:
|
||||
_imageInfo.MediaType = MediaType.GENERIC_HDD;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
|
||||
break;
|
||||
default:
|
||||
_imageInfo.MediaType = MediaType.Unknown;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build sessions and tracks
|
||||
if(_imageInfo.MetadataMediaType == MetadataMediaType.OpticalDisc)
|
||||
{
|
||||
_imageInfo.HasSessions = true;
|
||||
_imageInfo.HasPartitions = true;
|
||||
|
||||
BuildSessionsAndTracks(sessionEntries, totalSectors, _bytesPerSector, out _sessions, out _tracks);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sessions = null;
|
||||
_tracks = null;
|
||||
}
|
||||
|
||||
// Build metadata from header values
|
||||
_metadata = null;
|
||||
|
||||
if(_headerValues.Count > 0) BuildAaruMetadata();
|
||||
|
||||
_imageInfo.Version = _isV2 ? "2" : "1";
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Image has {0} sectors of {1} bytes", totalSectors, _bytesPerSector);
|
||||
AaruLogging.Debug(MODULE_NAME, "Chunk table has {0} entries", _chunkTable.Count);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>Discovers all segment files related to the primary image file.</summary>
|
||||
List<IFilter> DiscoverSegments(IFilter primaryFilter)
|
||||
{
|
||||
List<IFilter> segments = [primaryFilter];
|
||||
string currentPath = primaryFilter.BasePath;
|
||||
|
||||
while(true)
|
||||
{
|
||||
string nextPath = GetNextSegmentFilename(currentPath);
|
||||
|
||||
if(nextPath == null) break;
|
||||
|
||||
IFilter nextFilter = PluginRegister.Singleton.GetFilter(nextPath);
|
||||
|
||||
if(nextFilter == null) break;
|
||||
|
||||
segments.Add(nextFilter);
|
||||
currentPath = nextPath;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Found {0} segment files", segments.Count);
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v1 segment file, extracting all sections.</summary>
|
||||
void ParseSegmentV1(Stream segStream, int segIdx, ref ulong currentChunk, ref bool volumeFound,
|
||||
ref ulong totalSectors, ref byte[] headerData, ref byte[] header2Data, ref byte mediaType,
|
||||
ref byte mediaFlags, ref uint chsC, ref uint chsH, ref uint chsS,
|
||||
List<(ulong startSector, uint flags)> sessionEntries)
|
||||
{
|
||||
// Skip file header (13 bytes)
|
||||
segStream.Seek(FILE_HEADER_V1_SIZE, SeekOrigin.Begin);
|
||||
|
||||
while(segStream.Position < segStream.Length)
|
||||
{
|
||||
long sectionStart = segStream.Position;
|
||||
|
||||
// Read section descriptor
|
||||
var descBytes = new byte[SECTION_DESCRIPTOR_V1_SIZE];
|
||||
int bytesRead = segStream.Read(descBytes, 0, SECTION_DESCRIPTOR_V1_SIZE);
|
||||
|
||||
if(bytesRead < SECTION_DESCRIPTOR_V1_SIZE) break;
|
||||
|
||||
EwfSectionDescriptorV1 descriptor =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfSectionDescriptorV1>(descBytes);
|
||||
|
||||
// Get section type string (null-terminated)
|
||||
string sectionType = Encoding.ASCII.GetString(descriptor.type_string).TrimEnd('\0');
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Section type: {0} at offset {1}, next at {2}, size {3}",
|
||||
sectionType,
|
||||
sectionStart,
|
||||
descriptor.next_offset,
|
||||
descriptor.size);
|
||||
|
||||
// Calculate data size (section size minus section descriptor size)
|
||||
long dataSize = (long)descriptor.size - SECTION_DESCRIPTOR_V1_SIZE;
|
||||
|
||||
switch(sectionType)
|
||||
{
|
||||
case SECTION_TYPE_HEADER:
|
||||
if(headerData == null && dataSize > 0)
|
||||
{
|
||||
var compressedHeader = new byte[dataSize];
|
||||
segStream.EnsureRead(compressedHeader, 0, (int)dataSize);
|
||||
|
||||
try
|
||||
{
|
||||
headerData = DecompressZlib(compressedHeader, (int)dataSize * 10);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Failed to decompress header: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_HEADER2:
|
||||
if(header2Data == null && dataSize > 0)
|
||||
{
|
||||
var compressedHeader2 = new byte[dataSize];
|
||||
segStream.EnsureRead(compressedHeader2, 0, (int)dataSize);
|
||||
|
||||
try
|
||||
{
|
||||
header2Data = DecompressZlib(compressedHeader2, (int)dataSize * 10);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Failed to decompress header2: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_VOLUME:
|
||||
case SECTION_TYPE_DATA:
|
||||
if(!volumeFound)
|
||||
{
|
||||
ParseVolumeSection(segStream,
|
||||
dataSize,
|
||||
ref volumeFound,
|
||||
ref totalSectors,
|
||||
ref mediaType,
|
||||
ref mediaFlags,
|
||||
ref chsC,
|
||||
ref chsH,
|
||||
ref chsS);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_TABLE:
|
||||
case SECTION_TYPE_TABLE2:
|
||||
ParseTableSectionV1(segStream, segIdx, dataSize, ref currentChunk, (long)descriptor.next_offset);
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_HASH:
|
||||
if(_md5Stored == null &&
|
||||
dataSize >= System.Runtime.InteropServices.Marshal.SizeOf<EwfHashSection>())
|
||||
{
|
||||
var hashBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfHashSection>()];
|
||||
segStream.EnsureRead(hashBytes, 0, hashBytes.Length);
|
||||
|
||||
EwfHashSection hash = Marshal.ByteArrayToStructureLittleEndian<EwfHashSection>(hashBytes);
|
||||
|
||||
_md5Stored = hash.md5_hash;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"MD5 from hash section: {0}",
|
||||
BitConverter.ToString(_md5Stored).Replace("-", "").ToLowerInvariant());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_DIGEST:
|
||||
if(dataSize >= System.Runtime.InteropServices.Marshal.SizeOf<EwfDigestSection>())
|
||||
{
|
||||
var digestBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfDigestSection>()];
|
||||
segStream.EnsureRead(digestBytes, 0, digestBytes.Length);
|
||||
|
||||
EwfDigestSection digest =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfDigestSection>(digestBytes);
|
||||
|
||||
_md5Stored = digest.md5_hash;
|
||||
_sha1Stored = digest.sha1_hash;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"MD5 from digest section: {0}",
|
||||
BitConverter.ToString(_md5Stored).Replace("-", "").ToLowerInvariant());
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"SHA1 from digest section: {0}",
|
||||
BitConverter.ToString(_sha1Stored).Replace("-", "").ToLowerInvariant());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_ERROR2:
|
||||
ParseErrorSectionV1(segStream, dataSize);
|
||||
|
||||
break;
|
||||
|
||||
case SECTION_TYPE_SESSION:
|
||||
ParseSessionSectionV1(segStream, dataSize, sessionEntries);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to next section
|
||||
if(descriptor.next_offset == 0 || sectionType is SECTION_TYPE_DONE or SECTION_TYPE_NEXT) break;
|
||||
|
||||
segStream.Seek((long)descriptor.next_offset, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses the volume (or data) section to extract media parameters.</summary>
|
||||
void ParseVolumeSection(Stream segStream, long dataSize, ref bool volumeFound, ref ulong totalSectors,
|
||||
ref byte mediaType, ref byte mediaFlags, ref uint chsC, ref uint chsH, ref uint chsS)
|
||||
{
|
||||
if(dataSize < VOLUME_SECTION_SIZE_SMART) return;
|
||||
|
||||
// Read all the volume data available
|
||||
var volumeData = new byte[dataSize];
|
||||
segStream.EnsureRead(volumeData, 0, (int)dataSize);
|
||||
|
||||
// Determine format by checking if it's a small (94-byte) or large (1052-byte) volume section
|
||||
if(dataSize < VOLUME_SECTION_SIZE_ENCASE)
|
||||
{
|
||||
// Small volume section: SMART or old EnCase format (94 bytes of data)
|
||||
EwfVolumeSmartSection smartVol =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfVolumeSmartSection>(volumeData);
|
||||
|
||||
_sectorsPerChunk = smartVol.sectors_per_chunk;
|
||||
_bytesPerSector = smartVol.bytes_per_sector;
|
||||
totalSectors = smartVol.number_of_sectors;
|
||||
|
||||
// Check SMART signature
|
||||
if(smartVol.signature != null && smartVol.signature.AsSpan().SequenceEqual(SMART_SIGNATURE))
|
||||
_isSmart = true;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"{0} volume: {1} sectors, {2} bytes/sector, {3} sectors/chunk",
|
||||
_isSmart ? "SMART" : "Old EnCase",
|
||||
totalSectors,
|
||||
_bytesPerSector,
|
||||
_sectorsPerChunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Large volume section: EnCase 5+ format (1052 bytes of data)
|
||||
EwfVolumeSection vol = Marshal.ByteArrayToStructureLittleEndian<EwfVolumeSection>(volumeData);
|
||||
|
||||
_sectorsPerChunk = vol.sectors_per_chunk;
|
||||
_bytesPerSector = vol.bytes_per_sector;
|
||||
totalSectors = vol.number_of_sectors;
|
||||
chsC = vol.chs_cylinders;
|
||||
chsH = vol.chs_heads;
|
||||
chsS = vol.chs_sectors;
|
||||
mediaType = vol.media_type;
|
||||
mediaFlags = vol.media_flags;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"EnCase volume: {0} sectors, {1} bytes/sector, {2} sectors/chunk, C/H/S={3}/{4}/{5}",
|
||||
totalSectors,
|
||||
_bytesPerSector,
|
||||
_sectorsPerChunk,
|
||||
chsC,
|
||||
chsH,
|
||||
chsS);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Media type: {0}, media flags: {1}, compression: {2}",
|
||||
(EwfMediaType)vol.media_type,
|
||||
(EwfMediaFlags)vol.media_flags,
|
||||
(EwfCompressionLevel)vol.compression_level);
|
||||
}
|
||||
|
||||
volumeFound = true;
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v1 table section, building the chunk→location mapping.</summary>
|
||||
void ParseTableSectionV1(Stream segStream, int segIdx, long dataSize, ref ulong currentChunk,
|
||||
long nextSectionOffset)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV1>()) return;
|
||||
|
||||
// Read table header
|
||||
var tableHeaderBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV1>()];
|
||||
segStream.EnsureRead(tableHeaderBytes, 0, tableHeaderBytes.Length);
|
||||
|
||||
EwfTableHeaderV1 tableHeader = Marshal.ByteArrayToStructureLittleEndian<EwfTableHeaderV1>(tableHeaderBytes);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Table: {0} entries, base offset {1}",
|
||||
tableHeader.number_of_entries,
|
||||
tableHeader.base_offset);
|
||||
|
||||
// Read all table entries
|
||||
var entryCount = (int)tableHeader.number_of_entries;
|
||||
var entryData = new byte[entryCount * 4];
|
||||
segStream.EnsureRead(entryData, 0, entryData.Length);
|
||||
|
||||
// Skip entries checksum (4 bytes)
|
||||
segStream.Seek(4, SeekOrigin.Current);
|
||||
|
||||
for(var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var rawEntry = BitConverter.ToUInt32(entryData, i * 4);
|
||||
bool compressed = (rawEntry & TABLE_ENTRY_V1_COMPRESSED_FLAG) != 0;
|
||||
var offset = (long)(tableHeader.base_offset + (rawEntry & TABLE_ENTRY_V1_OFFSET_MASK));
|
||||
|
||||
// Calculate chunk size: distance to next chunk or next section for the last entry
|
||||
uint size;
|
||||
|
||||
if(i + 1 < entryCount)
|
||||
{
|
||||
var nextRawEntry = BitConverter.ToUInt32(entryData, (i + 1) * 4);
|
||||
var nextOffset = (long)(tableHeader.base_offset + (nextRawEntry & TABLE_ENTRY_V1_OFFSET_MASK));
|
||||
size = (uint)(nextOffset - offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For the last entry, use the next section offset to compute size
|
||||
if(nextSectionOffset > offset)
|
||||
size = (uint)(nextSectionOffset - offset);
|
||||
else
|
||||
size = _chunkSize + 4; // fallback: uncompressed chunk + Adler-32
|
||||
}
|
||||
|
||||
ulong chunkIndex = currentChunk + (ulong)i;
|
||||
|
||||
if(!_chunkTable.ContainsKey(chunkIndex)) _chunkTable[chunkIndex] = (segIdx, offset, size, compressed);
|
||||
}
|
||||
|
||||
currentChunk += (ulong)entryCount;
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v1 error2 section.</summary>
|
||||
void ParseErrorSectionV1(Stream segStream, long dataSize)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorHeaderV1>()) return;
|
||||
|
||||
var headerBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorHeaderV1>()];
|
||||
segStream.EnsureRead(headerBytes, 0, headerBytes.Length);
|
||||
|
||||
EwfErrorHeaderV1 errorHeader = Marshal.ByteArrayToStructureLittleEndian<EwfErrorHeaderV1>(headerBytes);
|
||||
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorEntryV1>();
|
||||
|
||||
for(var i = 0; i < (int)errorHeader.number_of_entries; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.EnsureRead(entryBytes, 0, entrySize);
|
||||
|
||||
EwfErrorEntryV1 entry = Marshal.ByteArrayToStructureLittleEndian<EwfErrorEntryV1>(entryBytes);
|
||||
|
||||
_badSectors.Add((entry.start_sector, entry.number_of_sectors));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Bad sector range: start={0}, count={1}",
|
||||
entry.start_sector,
|
||||
entry.number_of_sectors);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v1 session section.</summary>
|
||||
void ParseSessionSectionV1(Stream segStream, long dataSize, List<(ulong startSector, uint flags)> sessionEntries)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionHeaderV1>()) return;
|
||||
|
||||
var headerBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionHeaderV1>()];
|
||||
segStream.EnsureRead(headerBytes, 0, headerBytes.Length);
|
||||
|
||||
EwfSessionHeaderV1 sessionHeader = Marshal.ByteArrayToStructureLittleEndian<EwfSessionHeaderV1>(headerBytes);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Session section: {0} entries", sessionHeader.number_of_entries);
|
||||
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionEntryV1>();
|
||||
|
||||
for(var i = 0; i < (int)sessionHeader.number_of_entries; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.EnsureRead(entryBytes, 0, entrySize);
|
||||
|
||||
EwfSessionEntryV1 entry = Marshal.ByteArrayToStructureLittleEndian<EwfSessionEntryV1>(entryBytes);
|
||||
|
||||
sessionEntries.Add((entry.start_sector, entry.flags));
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Session entry: start={0}, flags=0x{1:X8}", entry.start_sector, entry.flags);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v2 segment file.</summary>
|
||||
void ParseSegmentV2(Stream segStream, int segIdx, ref ulong currentChunk, ref bool volumeFound,
|
||||
ref ulong totalSectors, ref byte mediaType, ref byte mediaFlags, ref uint chsC, ref uint chsH,
|
||||
ref uint chsS, List<(ulong startSector, uint flags)> sessionEntries)
|
||||
{
|
||||
// Read v2 file header
|
||||
segStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var headerBytes = new byte[FILE_HEADER_V2_SIZE];
|
||||
segStream.EnsureRead(headerBytes, 0, FILE_HEADER_V2_SIZE);
|
||||
|
||||
EwfFileHeaderV2 fileHeader = Marshal.ByteArrayToStructureLittleEndian<EwfFileHeaderV2>(headerBytes);
|
||||
|
||||
_compressionMethod = (EwfCompressionMethod)fileHeader.compression_method;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"EWF v2 segment {0}, compression method: {1}",
|
||||
fileHeader.segment_number,
|
||||
_compressionMethod);
|
||||
|
||||
// EWF v2 sections are read backward from the end of the file
|
||||
int descriptorSize = System.Runtime.InteropServices.Marshal.SizeOf<EwfSectionDescriptorV2>();
|
||||
long position = segStream.Length - descriptorSize;
|
||||
|
||||
while(position >= FILE_HEADER_V2_SIZE)
|
||||
{
|
||||
segStream.Seek(position, SeekOrigin.Begin);
|
||||
|
||||
var descBytes = new byte[descriptorSize];
|
||||
segStream.EnsureRead(descBytes, 0, descriptorSize);
|
||||
|
||||
EwfSectionDescriptorV2 descriptor =
|
||||
Marshal.ByteArrayToStructureLittleEndian<EwfSectionDescriptorV2>(descBytes);
|
||||
|
||||
var sectionType = (EwfSectionTypeV2)descriptor.type;
|
||||
long dataStart = position - (long)descriptor.data_size;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"V2 section type: {0} at {1}, data size {2}, previous at {3}",
|
||||
sectionType,
|
||||
position,
|
||||
descriptor.data_size,
|
||||
descriptor.previous_offset);
|
||||
|
||||
switch(sectionType)
|
||||
{
|
||||
case EwfSectionTypeV2.SectorTable:
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
ParseTableSectionV2(segStream, segIdx, (long)descriptor.data_size, ref currentChunk);
|
||||
|
||||
break;
|
||||
|
||||
case EwfSectionTypeV2.Md5Hash:
|
||||
if(_md5Stored == null &&
|
||||
(long)descriptor.data_size >= System.Runtime.InteropServices.Marshal.SizeOf<EwfMd5HashV2>())
|
||||
{
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
|
||||
var hashBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfMd5HashV2>()];
|
||||
segStream.EnsureRead(hashBytes, 0, hashBytes.Length);
|
||||
|
||||
EwfMd5HashV2 hash = Marshal.ByteArrayToStructureLittleEndian<EwfMd5HashV2>(hashBytes);
|
||||
|
||||
_md5Stored = hash.md5_hash;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EwfSectionTypeV2.Sha1Hash:
|
||||
if(_sha1Stored == null &&
|
||||
(long)descriptor.data_size >= System.Runtime.InteropServices.Marshal.SizeOf<EwfSha1HashV2>())
|
||||
{
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
|
||||
var hashBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfSha1HashV2>()];
|
||||
segStream.EnsureRead(hashBytes, 0, hashBytes.Length);
|
||||
|
||||
EwfSha1HashV2 hash = Marshal.ByteArrayToStructureLittleEndian<EwfSha1HashV2>(hashBytes);
|
||||
|
||||
_sha1Stored = hash.sha1_hash;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EwfSectionTypeV2.ErrorTable:
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
ParseErrorSectionV2(segStream, (long)descriptor.data_size);
|
||||
|
||||
break;
|
||||
|
||||
case EwfSectionTypeV2.SessionTable:
|
||||
segStream.Seek(dataStart, SeekOrigin.Begin);
|
||||
ParseSessionSectionV2(segStream, (long)descriptor.data_size, sessionEntries);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to previous section
|
||||
if(descriptor.previous_offset == 0) break;
|
||||
|
||||
position = (long)descriptor.previous_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v2 table section.</summary>
|
||||
void ParseTableSectionV2(Stream segStream, int segIdx, long dataSize, ref ulong currentChunk)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV2>()) return;
|
||||
|
||||
var tableHeaderBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfTableHeaderV2>()];
|
||||
segStream.EnsureRead(tableHeaderBytes, 0, tableHeaderBytes.Length);
|
||||
|
||||
EwfTableHeaderV2 tableHeader = Marshal.ByteArrayToStructureLittleEndian<EwfTableHeaderV2>(tableHeaderBytes);
|
||||
ulong firstChunk = tableHeader.first_chunk_number;
|
||||
var entryCount = (int)tableHeader.number_of_entries;
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfTableEntryV2>();
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "V2 Table: {0} entries, first chunk {1}", entryCount, firstChunk);
|
||||
|
||||
for(var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.EnsureRead(entryBytes, 0, entrySize);
|
||||
|
||||
EwfTableEntryV2 entry = Marshal.ByteArrayToStructureLittleEndian<EwfTableEntryV2>(entryBytes);
|
||||
|
||||
// Flag 0x00000001 means uncompressed in v2
|
||||
bool compressed = (entry.chunk_data_flags & 0x00000001) == 0;
|
||||
|
||||
ulong chunkIndex = firstChunk + (ulong)i;
|
||||
|
||||
if(!_chunkTable.ContainsKey(chunkIndex))
|
||||
_chunkTable[chunkIndex] = (segIdx, (long)entry.chunk_data_offset, entry.chunk_data_size, compressed);
|
||||
}
|
||||
|
||||
if(firstChunk + (ulong)entryCount > currentChunk) currentChunk = firstChunk + (ulong)entryCount;
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v2 error section.</summary>
|
||||
void ParseErrorSectionV2(Stream segStream, long dataSize)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorHeaderV2>()) return;
|
||||
|
||||
var headerBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorHeaderV2>()];
|
||||
segStream.EnsureRead(headerBytes, 0, headerBytes.Length);
|
||||
|
||||
EwfErrorHeaderV2 errorHeader = Marshal.ByteArrayToStructureLittleEndian<EwfErrorHeaderV2>(headerBytes);
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfErrorEntryV2>();
|
||||
|
||||
for(var i = 0; i < (int)errorHeader.number_of_entries; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.EnsureRead(entryBytes, 0, entrySize);
|
||||
|
||||
EwfErrorEntryV2 entry = Marshal.ByteArrayToStructureLittleEndian<EwfErrorEntryV2>(entryBytes);
|
||||
|
||||
_badSectors.Add((entry.start_sector, entry.number_of_sectors));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses an EWF v2 session section.</summary>
|
||||
void ParseSessionSectionV2(Stream segStream, long dataSize, List<(ulong startSector, uint flags)> sessionEntries)
|
||||
{
|
||||
if(dataSize < System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionHeaderV2>()) return;
|
||||
|
||||
var headerBytes = new byte[System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionHeaderV2>()];
|
||||
segStream.EnsureRead(headerBytes, 0, headerBytes.Length);
|
||||
|
||||
EwfSessionHeaderV2 sessionHeader = Marshal.ByteArrayToStructureLittleEndian<EwfSessionHeaderV2>(headerBytes);
|
||||
int entrySize = System.Runtime.InteropServices.Marshal.SizeOf<EwfSessionEntryV2>();
|
||||
|
||||
for(var i = 0; i < (int)sessionHeader.number_of_entries; i++)
|
||||
{
|
||||
var entryBytes = new byte[entrySize];
|
||||
segStream.EnsureRead(entryBytes, 0, entrySize);
|
||||
|
||||
EwfSessionEntryV2 entry = Marshal.ByteArrayToStructureLittleEndian<EwfSessionEntryV2>(entryBytes);
|
||||
|
||||
sessionEntries.Add((entry.start_sector, entry.flags));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds AaruMetadata from parsed header values.</summary>
|
||||
void BuildAaruMetadata()
|
||||
{
|
||||
// EWF header metadata doesn't map cleanly to AaruMetadata, but we can store some info
|
||||
// in the image's Comments and Creator fields (already done above).
|
||||
// For full metadata support, the AaruMetadata object would need forensic-specific fields.
|
||||
// For now, we leave _metadata as null and expose data through ImageInfo fields.
|
||||
}
|
||||
}
|
||||
188
Aaru.Images/EWF/Optical.cs
Normal file
188
Aaru.Images/EWF/Optical.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Optical.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains IOpticalMediaImage implementation for Expert Witness Format.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Track = Aaru.CommonTypes.Structs.Track;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSector(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = SectorStatus.NotDumped;
|
||||
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) return ErrorNumber.NotSupported;
|
||||
|
||||
Track trk = _tracks?.FirstOrDefault(t => t.Sequence == track);
|
||||
|
||||
if(trk == null) return ErrorNumber.SectorNotFound;
|
||||
|
||||
return ReadSector(trk.StartSector + sectorAddress, false, out buffer, out sectorStatus);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = null;
|
||||
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) return ErrorNumber.NotSupported;
|
||||
|
||||
Track trk = _tracks?.FirstOrDefault(t => t.Sequence == track);
|
||||
|
||||
if(trk == null) return ErrorNumber.SectorNotFound;
|
||||
|
||||
return ReadSectors(trk.StartSector + sectorAddress, false, length, out buffer, out sectorStatus);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
|
||||
out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer,
|
||||
out SectorStatus sectorStatus) =>
|
||||
ReadSector(sectorAddress, track, out buffer, out sectorStatus);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus) =>
|
||||
ReadSectors(sectorAddress, length, track, out buffer, out sectorStatus);
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Track> GetSessionTracks(Session session)
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc || _tracks == null) return null;
|
||||
|
||||
return _tracks.Where(t => t.Session == session.Sequence).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Track> GetSessionTracks(ushort session)
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc || _tracks == null) return null;
|
||||
|
||||
return _tracks.Where(t => t.Session == session).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool? VerifySector(ulong sectorAddress) => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas,
|
||||
out List<ulong> unknownLbas)
|
||||
{
|
||||
failingLbas = [];
|
||||
unknownLbas = [];
|
||||
|
||||
if(_badSectors is not { Count: > 0 })
|
||||
{
|
||||
for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
for(ulong i = sectorAddress; i < sectorAddress + length; i++)
|
||||
{
|
||||
var isBad = false;
|
||||
|
||||
foreach((ulong start, uint count) in _badSectors)
|
||||
{
|
||||
if(i < start || i >= start + count) continue;
|
||||
|
||||
isBad = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(isBad)
|
||||
failingLbas.Add(i);
|
||||
else
|
||||
unknownLbas.Add(i);
|
||||
}
|
||||
|
||||
if(unknownLbas.Count > 0) return null;
|
||||
|
||||
return failingLbas.Count <= 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas,
|
||||
out List<ulong> unknownLbas)
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc || _tracks == null)
|
||||
{
|
||||
failingLbas = [];
|
||||
unknownLbas = [];
|
||||
|
||||
for(ulong i = sectorAddress; i < sectorAddress + length; i++) unknownLbas.Add(i);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Track trk = _tracks.FirstOrDefault(t => t.Sequence == track);
|
||||
|
||||
if(trk == null)
|
||||
{
|
||||
failingLbas = [];
|
||||
unknownLbas = [];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return VerifySectors(trk.StartSector + sectorAddress, length, out failingLbas, out unknownLbas);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
152
Aaru.Images/EWF/Properties.cs
Normal file
152
Aaru.Images/EWF/Properties.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Properties.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains properties for Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Partition = Aaru.CommonTypes.Partition;
|
||||
using Track = Aaru.CommonTypes.Structs.Track;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
// ReSharper disable once ConvertToAutoProperty
|
||||
public ImageInfo Info => _imageInfo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Localization.Ewf_Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("E5F3B186-2382-4BDE-89B2-A6FC2B464C6F");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Author => Authors.NataliaPortillo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Format => _isSmart
|
||||
? "Expert Witness Format (SMART)"
|
||||
: _isV2
|
||||
? "Expert Witness Format v2"
|
||||
: "Expert Witness Format";
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<DumpHardware> DumpHardware => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Metadata AaruMetadata => _metadata;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MediaTagType> SupportedMediaTags => [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SectorTagType> SupportedSectorTags => [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MediaType> SupportedMediaTypes =>
|
||||
[
|
||||
MediaType.Unknown, MediaType.GENERIC_HDD, MediaType.FlashDrive, MediaType.CompactFlash,
|
||||
MediaType.CompactFlashType2, MediaType.PCCardTypeI, MediaType.PCCardTypeII, MediaType.PCCardTypeIII,
|
||||
MediaType.PCCardTypeIV, MediaType.CD, MediaType.CDDA, MediaType.CDG, MediaType.CDEG, MediaType.CDI,
|
||||
MediaType.CDROM, MediaType.CDROMXA, MediaType.CDPLUS, MediaType.CDR, MediaType.CDRW, MediaType.CDMRW,
|
||||
MediaType.DVDROM, MediaType.DVDR, MediaType.DVDRW, MediaType.DVDPR, MediaType.DVDPRW, MediaType.DVDRDL,
|
||||
MediaType.DVDPRWDL, MediaType.DVDPRDL, MediaType.DVDRAM, MediaType.DVDRWDL, MediaType.BDROM, MediaType.BDR,
|
||||
MediaType.BDRE, MediaType.BDRXL, MediaType.BDREXL
|
||||
];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions => [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> KnownExtensions => [".E01", ".Ex01", ".s01"];
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsWriting => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ErrorMessage => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Track> Tracks
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) return null;
|
||||
|
||||
return _tracks;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Session> Sessions
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) return null;
|
||||
|
||||
return _sessions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Partition> Partitions
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_imageInfo.MetadataMediaType != MetadataMediaType.OpticalDisc) return null;
|
||||
|
||||
List<Partition> parts =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Start = 0,
|
||||
Length = _imageInfo.Sectors,
|
||||
Offset = 0,
|
||||
Sequence = 0,
|
||||
Type = "MODE1/2048",
|
||||
Size = _imageInfo.Sectors * _imageInfo.SectorSize
|
||||
}
|
||||
];
|
||||
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
181
Aaru.Images/EWF/Read.cs
Normal file
181
Aaru.Images/EWF/Read.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Read.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Reads Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Compression;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
/// <summary>Reads and decompresses a chunk, using cache when possible.</summary>
|
||||
ErrorNumber ReadChunk(ulong chunkIndex, out byte[] chunkData)
|
||||
{
|
||||
chunkData = null;
|
||||
|
||||
// Check cache
|
||||
if(_chunkCache.TryGetValue(chunkIndex, out chunkData)) return ErrorNumber.NoError;
|
||||
|
||||
if(!_chunkTable.TryGetValue(chunkIndex, out (int segmentIndex, long offset, uint size, bool compressed) chunk))
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, "Chunk {0} not found in chunk table", chunkIndex);
|
||||
|
||||
return ErrorNumber.SectorNotFound;
|
||||
}
|
||||
|
||||
Stream segStream = _segmentStreams[chunk.segmentIndex];
|
||||
segStream.Seek(chunk.offset, SeekOrigin.Begin);
|
||||
|
||||
if(chunk.compressed)
|
||||
{
|
||||
var compressedData = new byte[chunk.size];
|
||||
segStream.EnsureRead(compressedData, 0, (int)chunk.size);
|
||||
|
||||
if(_isV2 && _compressionMethod == EwfCompressionMethod.Bzip2)
|
||||
{
|
||||
// BZip2 decompression
|
||||
chunkData = new byte[_chunkSize];
|
||||
BZip2.DecodeBuffer(compressedData, chunkData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Zlib/Deflate decompression (default for v1, and deflate method for v2)
|
||||
chunkData = DecompressZlib(compressedData, (int)_chunkSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Uncompressed chunk: data + 4-byte Adler-32 checksum
|
||||
uint dataLen = chunk.size >= 4 ? chunk.size - 4 : chunk.size;
|
||||
chunkData = new byte[dataLen];
|
||||
segStream.EnsureRead(chunkData, 0, (int)dataLen);
|
||||
|
||||
// Skip checksum (we trust the data for now)
|
||||
if(chunk.size >= 4) segStream.Seek(4, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// Cache chunk
|
||||
if(_chunkCache.Count >= _maxChunkCache) _chunkCache.Clear();
|
||||
|
||||
_chunkCache[chunkIndex] = chunkData;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Determines the sector status by checking against the bad sectors list.</summary>
|
||||
SectorStatus GetSectorStatus(ulong sectorAddress)
|
||||
{
|
||||
foreach((ulong start, uint count) in _badSectors)
|
||||
{
|
||||
if(sectorAddress >= start && sectorAddress < start + count) return SectorStatus.Errored;
|
||||
}
|
||||
|
||||
return SectorStatus.Dumped;
|
||||
}
|
||||
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = SectorStatus.NotDumped;
|
||||
|
||||
if(sectorAddress >= _imageInfo.Sectors) return ErrorNumber.OutOfRange;
|
||||
|
||||
// Check sector cache
|
||||
if(_sectorCache.TryGetValue(sectorAddress, out buffer))
|
||||
{
|
||||
sectorStatus = GetSectorStatus(sectorAddress);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Calculate which chunk contains this sector
|
||||
ulong chunkIndex = sectorAddress / _sectorsPerChunk;
|
||||
|
||||
// Read and decompress the chunk
|
||||
ErrorNumber errno = ReadChunk(chunkIndex, out byte[] chunkData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
// Extract the requested sector from the chunk
|
||||
ulong sectorInChunk = sectorAddress % _sectorsPerChunk;
|
||||
var sectorOffset = (long)(sectorInChunk * _bytesPerSector);
|
||||
|
||||
// Handle last chunk which may be smaller
|
||||
if(sectorOffset + _bytesPerSector > chunkData.Length) return ErrorNumber.OutOfRange;
|
||||
|
||||
buffer = new byte[_bytesPerSector];
|
||||
Array.Copy(chunkData, sectorOffset, buffer, 0, _bytesPerSector);
|
||||
|
||||
sectorStatus = GetSectorStatus(sectorAddress);
|
||||
|
||||
// Cache sector
|
||||
if(_sectorCache.Count >= MAX_CACHED_SECTORS) _sectorCache.Clear();
|
||||
|
||||
_sectorCache[sectorAddress] = buffer;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = null;
|
||||
|
||||
if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange;
|
||||
|
||||
buffer = new byte[length * _bytesPerSector];
|
||||
sectorStatus = new SectorStatus[length];
|
||||
|
||||
for(uint i = 0; i < length; i++)
|
||||
{
|
||||
ErrorNumber errno = ReadSector(sectorAddress + i, negative, out byte[] sector, out SectorStatus status);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
Array.Copy(sector, 0, buffer, i * _bytesPerSector, _bytesPerSector);
|
||||
sectorStatus[i] = status;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
418
Aaru.Images/EWF/Structs.cs
Normal file
418
Aaru.Images/EWF/Structs.cs
Normal file
@@ -0,0 +1,418 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Structs.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains structures for Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region Nested type: EwfFileHeaderV1
|
||||
|
||||
/// <summary>EWF v1 file header, 13 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfFileHeaderV1
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] signature;
|
||||
public byte fields_start;
|
||||
public ushort segment_number;
|
||||
public ushort fields_end;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfFileHeaderV2
|
||||
|
||||
/// <summary>EWF v2 file header, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfFileHeaderV2
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public byte[] signature;
|
||||
public byte major_version;
|
||||
public byte minor_version;
|
||||
public ushort compression_method;
|
||||
public uint segment_number;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] set_identifier;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSectionDescriptorV1
|
||||
|
||||
/// <summary>EWF v1 section descriptor, 76 bytes. Sections are forward-linked.</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSectionDescriptorV1
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] type_string;
|
||||
public ulong next_offset;
|
||||
public ulong size;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
|
||||
public byte[] padding;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSectionDescriptorV2
|
||||
|
||||
/// <summary>EWF v2 section descriptor, 64 bytes. Sections are backward-linked.</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSectionDescriptorV2
|
||||
{
|
||||
public uint type;
|
||||
public uint data_flags;
|
||||
public ulong previous_offset;
|
||||
public ulong data_size;
|
||||
public uint descriptor_size;
|
||||
public uint padding_size;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] data_integrity_hash;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] reserved;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfVolumeSection
|
||||
|
||||
/// <summary>EWF v1 EnCase volume section data, 1024 bytes (followed by checksum)</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfVolumeSection
|
||||
{
|
||||
public byte media_type;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown1;
|
||||
public uint number_of_chunks;
|
||||
public uint sectors_per_chunk;
|
||||
public uint bytes_per_sector;
|
||||
public ulong number_of_sectors;
|
||||
public uint chs_cylinders;
|
||||
public uint chs_heads;
|
||||
public uint chs_sectors;
|
||||
public byte media_flags;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown2;
|
||||
public uint palm_volume_start_sector;
|
||||
public uint unknown3;
|
||||
public uint smart_logs_start_sector;
|
||||
public byte compression_level;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] unknown4;
|
||||
public uint error_granularity;
|
||||
public uint unknown5;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] set_identifier;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 963)]
|
||||
public byte[] unknown6;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
public byte[] signature;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfVolumeSmartSection
|
||||
|
||||
/// <summary>EWF SMART/old EnCase volume section data, 94 bytes total</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfVolumeSmartSection
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public byte[] unknown1;
|
||||
public uint number_of_chunks;
|
||||
public uint sectors_per_chunk;
|
||||
public uint bytes_per_sector;
|
||||
public uint number_of_sectors;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] unknown2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 45)]
|
||||
public byte[] unknown3;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
public byte[] signature;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfTableHeaderV1
|
||||
|
||||
/// <summary>EWF v1 table section header, 24 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableHeaderV1
|
||||
{
|
||||
public uint number_of_entries;
|
||||
public uint padding1;
|
||||
public ulong base_offset;
|
||||
public uint padding2;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfTableHeaderV2
|
||||
|
||||
/// <summary>EWF v2 table section header, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableHeaderV2
|
||||
{
|
||||
public ulong first_chunk_number;
|
||||
public uint number_of_entries;
|
||||
public uint unknown1;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfTableEntryV2
|
||||
|
||||
/// <summary>EWF v2 table entry, 16 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfTableEntryV2
|
||||
{
|
||||
public ulong chunk_data_offset;
|
||||
public uint chunk_data_size;
|
||||
public uint chunk_data_flags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfHashSection
|
||||
|
||||
/// <summary>EWF v1 hash section, 36 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfHashSection
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] md5_hash;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] unknown1;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfDigestSection
|
||||
|
||||
/// <summary>EWF v1 digest section, 80 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfDigestSection
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] md5_hash;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] sha1_hash;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
|
||||
public byte[] padding1;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSessionHeaderV1
|
||||
|
||||
/// <summary>EWF v1 session section header, 36 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSessionHeaderV1
|
||||
{
|
||||
public uint number_of_entries;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
|
||||
public byte[] unknown1;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSessionEntryV1
|
||||
|
||||
/// <summary>EWF v1 session entry, 32 bytes. Note: flags first, then start_sector.</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSessionEntryV1
|
||||
{
|
||||
public uint flags;
|
||||
public uint start_sector;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
|
||||
public byte[] unknown1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSessionHeaderV2
|
||||
|
||||
/// <summary>EWF v2 session section header, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSessionHeaderV2
|
||||
{
|
||||
public uint number_of_entries;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] unknown1;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSessionEntryV2
|
||||
|
||||
/// <summary>EWF v2 session entry, 32 bytes. Note: start_sector first, then flags (reversed from v1).</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSessionEntryV2
|
||||
{
|
||||
public ulong start_sector;
|
||||
public uint flags;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] unknown1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfErrorHeaderV1
|
||||
|
||||
/// <summary>EWF v1 error2 section header, 520 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfErrorHeaderV1
|
||||
{
|
||||
public uint number_of_entries;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
|
||||
public byte[] unknown1;
|
||||
public uint checksum;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfErrorEntryV1
|
||||
|
||||
/// <summary>EWF v1 error entry, 8 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfErrorEntryV1
|
||||
{
|
||||
public uint start_sector;
|
||||
public uint number_of_sectors;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfErrorHeaderV2
|
||||
|
||||
/// <summary>EWF v2 error section header, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfErrorHeaderV2
|
||||
{
|
||||
public uint number_of_entries;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] unknown1;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfErrorEntryV2
|
||||
|
||||
/// <summary>EWF v2 error entry, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfErrorEntryV2
|
||||
{
|
||||
public ulong start_sector;
|
||||
public uint number_of_sectors;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] unknown1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfMd5HashV2
|
||||
|
||||
/// <summary>EWF v2 MD5 hash section, 32 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfMd5HashV2
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
||||
public byte[] md5_hash;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EwfSha1HashV2
|
||||
|
||||
/// <summary>EWF v2 SHA1 hash section, 48 bytes</summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct EwfSha1HashV2
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
|
||||
public byte[] sha1_hash;
|
||||
public uint checksum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
|
||||
public byte[] padding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
77
Aaru.Images/EWF/Unsupported.cs
Normal file
77
Aaru.Images/EWF/Unsupported.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Unsupported.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains features unsupported by Expert Witness Format disk images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using Aaru.CommonTypes.Enums;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class Ewf
|
||||
{
|
||||
#region IOpticalMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
|
||||
out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
|
||||
out SectorStatus sectorStatus) =>
|
||||
ReadSector(sectorAddress, negative, out buffer, out sectorStatus);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus) =>
|
||||
ReadSectors(sectorAddress, negative, length, out buffer, out sectorStatus);
|
||||
|
||||
#endregion
|
||||
}
|
||||
12
Aaru.Images/Localization/Localization.Designer.cs
generated
12
Aaru.Images/Localization/Localization.Designer.cs
generated
@@ -5967,5 +5967,17 @@ namespace Aaru.Images {
|
||||
return ResourceManager.GetString("Redumper_disc_image", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Ewf_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Ewf_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Ewf_Identify_Signature_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Ewf_Identify_Signature_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2976,4 +2976,10 @@
|
||||
<data name="Redumper_disc_image" xml:space="preserve">
|
||||
<value>Imagen de disco DVD crudo de Redumper</value>
|
||||
</data>
|
||||
<data name="Ewf_Name" xml:space="preserve">
|
||||
<value>Formato Expert Witness (EWF)</value>
|
||||
</data>
|
||||
<data name="Ewf_Identify_Signature_0" xml:space="preserve">
|
||||
<value>Firma: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2995,4 +2995,10 @@
|
||||
<data name="Redumper_disc_image" xml:space="preserve">
|
||||
<value>Redumper raw DVD disc image</value>
|
||||
</data>
|
||||
<data name="Ewf_Name" xml:space="preserve">
|
||||
<value>Expert Witness Format (EWF)</value>
|
||||
</data>
|
||||
<data name="Ewf_Identify_Signature_0" xml:space="preserve">
|
||||
<value>Signature: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user