mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-02-04 00:44:39 +00:00
Add HxCStream support
This commit is contained in:
committed by
Rebecca Wallander
parent
fab19cd6ba
commit
0219f7b4de
@@ -47,7 +47,7 @@ namespace Aaru.CommonTypes.Interfaces;
|
||||
/// <summary>Abstract class to implement flux reading plugins.</summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMemberInSuper.Global")]
|
||||
public interface IFluxImage : IBaseImage
|
||||
public interface IFluxImage : IMediaImage
|
||||
{
|
||||
/// <summary>
|
||||
/// An image may have more than one capture for a specific head/track/sub-track combination. This returns
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<Compile Include="HaStream.cs"/>
|
||||
<Compile Include="Lh5Stream.cs"/>
|
||||
<Compile Include="LzdStream.cs"/>
|
||||
<Compile Include="LZ4.cs" />
|
||||
<Compile Include="LZFSE.cs"/>
|
||||
<Compile Include="LZIP.cs"/>
|
||||
<Compile Include="LZMA.cs"/>
|
||||
|
||||
49
Aaru.Compression/LZ4.cs
Normal file
49
Aaru.Compression/LZ4.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : LZ4.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Compression algorithms.
|
||||
//
|
||||
// --[ 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Aaru.Compression;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <summary>Implements the LZ4 compression algorithm</summary>
|
||||
public partial class LZ4
|
||||
{
|
||||
/// <summary>Set to <c>true</c> if this algorithm is supported, <c>false</c> otherwise.</summary>
|
||||
public static bool IsSupported => Native.IsSupported;
|
||||
|
||||
[LibraryImport("libAaru.Compression.Native", SetLastError = true)]
|
||||
private static partial int AARU_lz4_decode_buffer(byte[] dstBuffer, int dstSize, byte[] srcBuffer, int srcSize);
|
||||
|
||||
/// <summary>Decodes a buffer compressed with LZ4</summary>
|
||||
/// <param name="source">Encoded buffer</param>
|
||||
/// <param name="destination">Buffer where to write the decoded data</param>
|
||||
/// <returns>The number of decoded bytes</returns>
|
||||
public static int DecodeBuffer(byte[] source, byte[] destination) =>
|
||||
Native.IsSupported ? AARU_lz4_decode_buffer(destination, destination.Length, source, source.Length) : 0;
|
||||
}
|
||||
51
Aaru.Images/HxCStream/Constants.cs
Normal file
51
Aaru.Images/HxCStream/Constants.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Constants.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains constants for HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
const ushort DEFAULT_RESOLUTION = 40000;
|
||||
|
||||
readonly byte[] _hxcStreamSignature =
|
||||
[
|
||||
0x43, 0x48, 0x4b, 0x48 // CHKH
|
||||
];
|
||||
|
||||
readonly uint _metadataId = 0x0;
|
||||
readonly uint _ioStreamId = 0x1;
|
||||
readonly uint _streamId = 0x2;
|
||||
}
|
||||
266
Aaru.Images/HxCStream/Helpers.cs
Normal file
266
Aaru.Images/HxCStream/Helpers.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Helpers.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains helpers for HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Aaru.Checksums;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Decodes variable-length encoded pulses from HxCStream format.
|
||||
/// Values < 0x80: single byte value
|
||||
/// 0x80-0xBF: 2-byte value (6 bits + 8 bits)
|
||||
/// 0xC0-0xDF: 3-byte value (5 bits + 8 bits + 8 bits)
|
||||
/// 0xE0-0xEF: 4-byte value (4 bits + 24 bits)
|
||||
/// </summary>
|
||||
/// <param name="unpackedData">The unpacked data buffer</param>
|
||||
/// <param name="unpackedDataSize">Size of unpacked data</param>
|
||||
/// <param name="numberOfPulses">Number of pulses to decode (updated with actual count)</param>
|
||||
/// <returns>Array of decoded pulse values</returns>
|
||||
static uint[] DecodeVariableLengthPulses(byte[] unpackedData, uint unpackedDataSize, ref uint numberOfPulses)
|
||||
{
|
||||
if(numberOfPulses == 0) return [];
|
||||
|
||||
var pulses = new List<uint>();
|
||||
uint k = 0;
|
||||
uint l = 0;
|
||||
|
||||
while(l < numberOfPulses && k < unpackedDataSize)
|
||||
{
|
||||
byte c = unpackedData[k++];
|
||||
uint value = 0;
|
||||
|
||||
if((c & 0x80) == 0)
|
||||
{
|
||||
// Single byte value
|
||||
if(c != 0) pulses.Add(c);
|
||||
}
|
||||
else if((c & 0xC0) == 0x80)
|
||||
{
|
||||
// 2-byte value
|
||||
if(k >= unpackedDataSize) break;
|
||||
value = (uint)((c & 0x3F) << 8) | unpackedData[k++];
|
||||
pulses.Add(value);
|
||||
}
|
||||
else if((c & 0xE0) == 0xC0)
|
||||
{
|
||||
// 3-byte value
|
||||
if(k + 1 >= unpackedDataSize) break;
|
||||
value = (uint)((c & 0x1F) << 16) | ((uint)unpackedData[k++] << 8) | unpackedData[k++];
|
||||
pulses.Add(value);
|
||||
}
|
||||
else if((c & 0xF0) == 0xE0)
|
||||
{
|
||||
// 4-byte value
|
||||
if(k + 2 >= unpackedDataSize) break;
|
||||
value = (uint)((c & 0x0F) << 24) | ((uint)unpackedData[k++] << 16) |
|
||||
((uint)unpackedData[k++] << 8) | unpackedData[k++];
|
||||
pulses.Add(value);
|
||||
}
|
||||
|
||||
l++;
|
||||
}
|
||||
|
||||
// Add dummy pulse (300 ticks)
|
||||
pulses.Add(300);
|
||||
numberOfPulses = (uint)pulses.Count;
|
||||
|
||||
return pulses.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a uint32 pulse value to Aaru's flux representation format.
|
||||
/// Format: byte array where 255 = overflow, remainder = value
|
||||
/// </summary>
|
||||
/// <param name="ticks">The pulse value in ticks</param>
|
||||
/// <returns>Flux representation as byte array</returns>
|
||||
static byte[] UInt32ToFluxRepresentation(uint ticks)
|
||||
{
|
||||
uint over = ticks / 255;
|
||||
|
||||
if(over == 0) return [(byte)ticks];
|
||||
|
||||
var expanded = new byte[over + 1];
|
||||
Array.Fill(expanded, (byte)255, 0, (int)over);
|
||||
expanded[^1] = (byte)(ticks % 255);
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a raw 16-bit IO stream value into a readable IoStreamState structure.
|
||||
/// This provides named properties for known signals (index, write protect) and
|
||||
/// all 16 IO channels, while preserving the raw value for future extensions.
|
||||
/// </summary>
|
||||
/// <param name="rawValue">The raw 16-bit IO stream value</param>
|
||||
/// <returns>Decoded IO stream state with named properties</returns>
|
||||
public static IoStreamState DecodeIoStreamValue(ushort rawValue) => IoStreamState.FromRawValue(rawValue);
|
||||
|
||||
/// <summary>
|
||||
/// Parses HxCStream metadata string and populates ImageInfo fields.
|
||||
/// Metadata format: key-value pairs separated by newlines, values may be quoted strings.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The metadata string to parse</param>
|
||||
/// <param name="imageInfo">ImageInfo structure to populate</param>
|
||||
static void ParseMetadata(string metadata, ImageInfo imageInfo)
|
||||
{
|
||||
if(string.IsNullOrEmpty(metadata)) return;
|
||||
|
||||
var lines = metadata.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach(string line in lines)
|
||||
{
|
||||
string trimmed = line.Trim();
|
||||
|
||||
if(string.IsNullOrEmpty(trimmed)) continue;
|
||||
|
||||
// Find the first space to separate key from value
|
||||
int spaceIndex = trimmed.IndexOf(' ');
|
||||
|
||||
if(spaceIndex <= 0) continue;
|
||||
|
||||
string key = trimmed[..spaceIndex].Trim();
|
||||
string value = trimmed[(spaceIndex + 1)..].Trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if(value.Length >= 2 && value[0] == '"' && value[^1] == '"')
|
||||
value = value[1..^1];
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case "format_version":
|
||||
if(string.IsNullOrEmpty(imageInfo.Version))
|
||||
imageInfo.Version = value;
|
||||
|
||||
break;
|
||||
case "software_version":
|
||||
// Extract version number (e.g., "v1.3.2.1 9 September 2021" -> "v1.3.2.1")
|
||||
int versionEnd = value.IndexOf(' ', StringComparison.Ordinal);
|
||||
|
||||
if(versionEnd > 0)
|
||||
imageInfo.ApplicationVersion = value[..versionEnd];
|
||||
else
|
||||
imageInfo.ApplicationVersion = value;
|
||||
|
||||
// Set application name if not already set
|
||||
if(string.IsNullOrEmpty(imageInfo.Application))
|
||||
imageInfo.Application = "HxC Floppy Emulator";
|
||||
|
||||
break;
|
||||
case "dump_name":
|
||||
if(string.IsNullOrEmpty(imageInfo.MediaTitle))
|
||||
imageInfo.MediaTitle = value;
|
||||
|
||||
break;
|
||||
case "dump_comment":
|
||||
// Combine dump_comment and dump_comment2 if both exist
|
||||
if(string.IsNullOrEmpty(imageInfo.Comments))
|
||||
imageInfo.Comments = value;
|
||||
else
|
||||
imageInfo.Comments = $"{imageInfo.Comments}\n{value}";
|
||||
|
||||
break;
|
||||
case "dump_comment2":
|
||||
if(!string.IsNullOrEmpty(value))
|
||||
{
|
||||
if(string.IsNullOrEmpty(imageInfo.Comments))
|
||||
imageInfo.Comments = value;
|
||||
else
|
||||
imageInfo.Comments = $"{imageInfo.Comments}\n{value}";
|
||||
}
|
||||
|
||||
break;
|
||||
case "operator":
|
||||
if(string.IsNullOrEmpty(imageInfo.Creator))
|
||||
imageInfo.Creator = value;
|
||||
|
||||
break;
|
||||
case "current_time":
|
||||
// Parse date/time: "2025-11-13 16:42:29"
|
||||
if(DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime))
|
||||
{
|
||||
if(imageInfo.CreationTime == default)
|
||||
imageInfo.CreationTime = dateTime;
|
||||
|
||||
imageInfo.LastModificationTime = dateTime;
|
||||
}
|
||||
|
||||
break;
|
||||
case "floppy_drive":
|
||||
// Format: "1 \"5.25-inch Floppy drive\""
|
||||
// Extract the quoted description
|
||||
int quoteStart = value.IndexOf('"');
|
||||
|
||||
if(quoteStart >= 0)
|
||||
{
|
||||
int quoteEnd = value.LastIndexOf('"');
|
||||
|
||||
if(quoteEnd > quoteStart)
|
||||
{
|
||||
string driveDesc = value[(quoteStart + 1)..quoteEnd];
|
||||
|
||||
if(string.IsNullOrEmpty(imageInfo.DriveModel))
|
||||
imageInfo.DriveModel = driveDesc;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "drive_reference":
|
||||
if(!string.IsNullOrEmpty(value) && string.IsNullOrEmpty(imageInfo.DriveSerialNumber))
|
||||
imageInfo.DriveSerialNumber = value;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool VerifyChunkCrc32(byte[] chunkData, uint storedCrc)
|
||||
{
|
||||
var crc32Context = new Crc32Context(0xEDB88320, 0x00000000);
|
||||
crc32Context.Update(chunkData, (uint)chunkData.Length);
|
||||
byte[] crc32Bytes = crc32Context.Final();
|
||||
// Final() returns big-endian, but stored CRC is little-endian
|
||||
Array.Reverse(crc32Bytes);
|
||||
uint crc32 = BitConverter.ToUInt32(crc32Bytes, 0);
|
||||
|
||||
crc32 ^= 0xFFFFFFFF;
|
||||
|
||||
return crc32 == storedCrc;
|
||||
}
|
||||
}
|
||||
75
Aaru.Images/HxCStream/HxCStream.cs
Normal file
75
Aaru.Images/HxCStream/HxCStream.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : HxCStream.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Manages HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
|
||||
/// <summary>Implements reading HxCStream flux images</summary>
|
||||
public sealed partial class HxCStream : IFluxImage, IVerifiableImage
|
||||
{
|
||||
const string MODULE_NAME = "HxCStream plugin";
|
||||
|
||||
ImageInfo _imageInfo;
|
||||
|
||||
List<TrackCapture> _trackCaptures;
|
||||
|
||||
List<string> _trackFilePaths;
|
||||
|
||||
public HxCStream() => _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
|
||||
};
|
||||
}
|
||||
62
Aaru.Images/HxCStream/Identify.cs
Normal file
62
Aaru.Images/HxCStream/Identify.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Identify.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Identifies HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Helpers;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
#region IFluxImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Identify(IFilter imageFilter)
|
||||
{
|
||||
Stream stream = imageFilter.GetDataForkStream();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if(stream.Length < 8) return false;
|
||||
|
||||
var hdr = new byte[4];
|
||||
|
||||
stream.EnsureRead(hdr, 0, 4);
|
||||
|
||||
return _hxcStreamSignature.SequenceEqual(hdr);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
107
Aaru.Images/HxCStream/Properties.cs
Normal file
107
Aaru.Images/HxCStream/Properties.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Properties.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains properties for HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
|
||||
#region IFluxImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
// ReSharper disable once ConvertToAutoProperty
|
||||
public ImageInfo Info => _imageInfo;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Localization.HxCStream_Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("522c6f71-c5e5-4bff-9d2e-44c9af8397e7");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Author => Authors.RebeccaWallander;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Format => "HxCStream";
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<DumpHardware> DumpHardware => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Metadata AaruMetadata => null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWritableImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> KnownExtensions => new[]
|
||||
{
|
||||
".hxcstream"
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MediaTagType> SupportedMediaTags => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MediaType> SupportedMediaTypes => new[]
|
||||
{
|
||||
// TODO: HxCStream supports a lot more formats, please add more whence tested.
|
||||
MediaType.DOS_35_DS_DD_9, MediaType.DOS_35_HD, MediaType.DOS_525_DS_DD_9, MediaType.DOS_525_HD,
|
||||
MediaType.Unknown
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions =>
|
||||
Array.Empty<(string name, Type type, string description, object @default)>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SectorTagType> SupportedSectorTags => Array.Empty<SectorTagType>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsWriting { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ErrorMessage { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
702
Aaru.Images/HxCStream/Read.cs
Normal file
702
Aaru.Images/HxCStream/Read.cs
Normal file
@@ -0,0 +1,702 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Read.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Reads HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Compression;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
#region IFluxImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Open(IFilter imageFilter)
|
||||
{
|
||||
_trackCaptures = [];
|
||||
_trackFilePaths = [];
|
||||
_imageInfo.Heads = 0;
|
||||
_imageInfo.Cylinders = 0;
|
||||
|
||||
string filename = imageFilter.Filename;
|
||||
string parentFolder = imageFilter.ParentFolder;
|
||||
|
||||
// We always open a single file - extract basename to find related track files
|
||||
if(!filename.EndsWith(".hxcstream", StringComparison.OrdinalIgnoreCase))
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Extract basename - remove the track number pattern (e.g., "track00.0.hxcstream" -> "track")
|
||||
// The pattern is {basename}{cylinder:D2}.{head:D1}.hxcstream
|
||||
string basename = filename[..^14]; // Remove ".XX.X.hxcstream" (14 chars)
|
||||
string fullBasename = Path.Combine(parentFolder, basename);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Opening HxCStream image from file: {0}", filename);
|
||||
AaruLogging.Debug(MODULE_NAME, "Basename: {0}", basename);
|
||||
AaruLogging.Debug(MODULE_NAME, "Full basename path: {0}", fullBasename);
|
||||
|
||||
// Discover track files by trying different cylinder/head combinations
|
||||
var trackFiles = new Dictionary<(int cylinder, int head), string>();
|
||||
int minCylinder = int.MaxValue;
|
||||
int maxCylinder = int.MinValue;
|
||||
int minHead = int.MaxValue;
|
||||
int maxHead = int.MinValue;
|
||||
|
||||
// Search for related track files
|
||||
for(int cylinder = 0; cylinder < 166; cylinder++)
|
||||
{
|
||||
for(int head = 0; head < 2; head++)
|
||||
{
|
||||
string trackfile = $"{fullBasename}{cylinder:D2}.{head:D1}.hxcstream";
|
||||
|
||||
if(File.Exists(trackfile))
|
||||
{
|
||||
trackFiles[(cylinder, head)] = trackfile;
|
||||
minCylinder = Math.Min(minCylinder, cylinder);
|
||||
maxCylinder = Math.Max(maxCylinder, cylinder);
|
||||
minHead = Math.Min(minHead, head);
|
||||
maxHead = Math.Max(maxHead, head);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(trackFiles.Count == 0) return ErrorNumber.NoData;
|
||||
|
||||
_imageInfo.Cylinders = (uint)(maxCylinder - minCylinder + 1);
|
||||
_imageInfo.Heads = (uint)(maxHead - minHead + 1);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Found {0} track files", trackFiles.Count);
|
||||
AaruLogging.Debug(MODULE_NAME, "Cylinder range: {0} to {1} ({2} cylinders)", minCylinder, maxCylinder, _imageInfo.Cylinders);
|
||||
AaruLogging.Debug(MODULE_NAME, "Head range: {0} to {1} ({2} heads)", minHead, maxHead, _imageInfo.Heads);
|
||||
|
||||
// Process each track file
|
||||
int trackIndex = 0;
|
||||
int totalTracks = trackFiles.Count;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Processing {0} track files...", totalTracks);
|
||||
|
||||
foreach((int cylinder, int head) key in trackFiles.Keys.OrderBy(k => k.cylinder).ThenBy(k => k.head).ToList())
|
||||
{
|
||||
trackIndex++;
|
||||
string trackfile = trackFiles[key];
|
||||
_trackFilePaths.Add(trackfile);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Processing track {0}/{1}: cylinder {2}, head {3}",
|
||||
trackIndex, totalTracks, key.cylinder, key.head);
|
||||
|
||||
ErrorNumber error = ProcessTrackFile(trackfile, (uint)key.head, (ushort)key.cylinder);
|
||||
|
||||
if(error != ErrorNumber.NoError) return error;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Successfully processed all {0} track files", totalTracks);
|
||||
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.BlockMedia;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber ProcessTrackFile(string trackfile, uint head, ushort track)
|
||||
{
|
||||
if(!File.Exists(trackfile)) return ErrorNumber.NoSuchFile;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Processing track file: {0} (head {1}, track {2})", trackfile, head, track);
|
||||
|
||||
using FileStream fileStream = File.OpenRead(trackfile);
|
||||
byte[] fileData = new byte[fileStream.Length];
|
||||
fileStream.EnsureRead(fileData, 0, (int)fileStream.Length);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Track file size: {0} bytes", fileData.Length);
|
||||
|
||||
uint samplePeriod = DEFAULT_RESOLUTION; // Default 40,000 ns (25 MHz)
|
||||
var fluxPulses = new List<uint>();
|
||||
var ioStream = new List<ushort>();
|
||||
string metadata = null;
|
||||
|
||||
long fileOffset = 0;
|
||||
|
||||
while(fileOffset < fileData.Length)
|
||||
{
|
||||
if(fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>() > fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
HxCStreamChunkHeader chunkHeader = Marshal.ByteArrayToStructureLittleEndian<HxCStreamChunkHeader>(
|
||||
fileData, (int)fileOffset, Marshal.SizeOf<HxCStreamChunkHeader>());
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Chunk at offset {0}: signature = \"{1}\", size = {2}, packetNumber = {3}",
|
||||
fileOffset,
|
||||
StringHandlers.CToString(chunkHeader.signature),
|
||||
chunkHeader.size,
|
||||
chunkHeader.packetNumber);
|
||||
|
||||
if(!_hxcStreamSignature.SequenceEqual(chunkHeader.signature)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(chunkHeader.size > fileData.Length - fileOffset) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Verify CRC32 - calculate CRC of chunk data (excluding the CRC itself)
|
||||
byte[] chunkData = new byte[chunkHeader.size - 4];
|
||||
Array.Copy(fileData, (int)fileOffset, chunkData, 0, (int)(chunkHeader.size - 4));
|
||||
|
||||
uint storedCrc = BitConverter.ToUInt32(fileData, (int)(fileOffset + chunkHeader.size - 4));
|
||||
|
||||
if(!VerifyChunkCrc32(chunkData, storedCrc))
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, "CRC32 mismatch in chunk at offset {0}", fileOffset);
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Chunk CRC32 verified successfully");
|
||||
|
||||
long packetOffset = fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>();
|
||||
long chunkEnd = fileOffset + chunkHeader.size - 4;
|
||||
|
||||
while(packetOffset < chunkEnd)
|
||||
{
|
||||
if(packetOffset + 4 > fileData.Length) break;
|
||||
|
||||
uint type = BitConverter.ToUInt32(fileData, (int)packetOffset);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Packet at offset {0}: type = 0x{1:X8}", packetOffset, type);
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case 0x0: // Metadata
|
||||
{
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>() > fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
HxCStreamMetadataHeader metadataHeader =
|
||||
Marshal.ByteArrayToStructureLittleEndian<HxCStreamMetadataHeader>(fileData,
|
||||
(int)packetOffset,
|
||||
Marshal.SizeOf<HxCStreamMetadataHeader>());
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Metadata packet: type = 0x{0:X8}, payloadSize = {1}",
|
||||
metadataHeader.type, metadataHeader.payloadSize);
|
||||
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>() + metadataHeader.payloadSize >
|
||||
fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
byte[] metadataBytes = new byte[metadataHeader.payloadSize];
|
||||
Array.Copy(fileData,
|
||||
(int)packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>(),
|
||||
metadataBytes,
|
||||
0,
|
||||
(int)metadataHeader.payloadSize);
|
||||
|
||||
metadata = Encoding.UTF8.GetString(metadataBytes);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Metadata content: {0}", metadata);
|
||||
|
||||
// Parse metadata and populate ImageInfo (only parse once, from first chunk)
|
||||
if(string.IsNullOrEmpty(_imageInfo.Application))
|
||||
ParseMetadata(metadata, _imageInfo);
|
||||
|
||||
// Check for sample rate in metadata
|
||||
if(metadata.Contains("sample_rate_hz 25000000"))
|
||||
{
|
||||
samplePeriod = 40000;
|
||||
AaruLogging.Debug(MODULE_NAME, "Sample rate detected: 25 MHz (40000 ns period)");
|
||||
}
|
||||
else if(metadata.Contains("sample_rate_hz 50000000"))
|
||||
{
|
||||
samplePeriod = 20000;
|
||||
AaruLogging.Debug(MODULE_NAME, "Sample rate detected: 50 MHz (20000 ns period)");
|
||||
}
|
||||
else
|
||||
AaruLogging.Debug(MODULE_NAME, "Using default sample rate: 25 MHz (40000 ns period)");
|
||||
|
||||
packetOffset += Marshal.SizeOf<HxCStreamMetadataHeader>() + metadataHeader.payloadSize;
|
||||
|
||||
// Align to 4 bytes
|
||||
if(packetOffset % 4 != 0) packetOffset += 4 - packetOffset % 4;
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x1: // Packed IO stream
|
||||
{
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>() > fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
HxCStreamPackedIoHeader ioHeader =
|
||||
Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedIoHeader>(fileData,
|
||||
(int)packetOffset,
|
||||
Marshal.SizeOf<HxCStreamPackedIoHeader>());
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Packed IO stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}",
|
||||
ioHeader.type, ioHeader.payloadSize, ioHeader.packedSize, ioHeader.unpackedSize);
|
||||
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize >
|
||||
fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
byte[] packedData = new byte[ioHeader.packedSize];
|
||||
Array.Copy(fileData,
|
||||
(int)packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>(),
|
||||
packedData,
|
||||
0,
|
||||
(int)ioHeader.packedSize);
|
||||
|
||||
byte[] unpackedData = new byte[ioHeader.unpackedSize];
|
||||
int decoded = LZ4.DecodeBuffer(packedData, unpackedData);
|
||||
|
||||
if(decoded != ioHeader.unpackedSize) return ErrorNumber.InvalidArgument;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Decompressed IO stream: {0} bytes -> {1} bytes ({2} 16-bit values)",
|
||||
ioHeader.packedSize, decoded, decoded / 2);
|
||||
|
||||
// Convert to ushort array
|
||||
for(int i = 0; i < unpackedData.Length; i += 2)
|
||||
{
|
||||
if(i + 1 < unpackedData.Length)
|
||||
ioStream.Add(BitConverter.ToUInt16(unpackedData, i));
|
||||
}
|
||||
|
||||
packetOffset += Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize;
|
||||
|
||||
// Align to 4 bytes
|
||||
if(packetOffset % 4 != 0) packetOffset += 4 - packetOffset % 4;
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x2: // Packed flux stream
|
||||
{
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>() > fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
HxCStreamPackedStreamHeader streamHeader =
|
||||
Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedStreamHeader>(fileData,
|
||||
(int)packetOffset,
|
||||
Marshal.SizeOf<HxCStreamPackedStreamHeader>());
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Packed flux stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}, numberOfPulses = {4}",
|
||||
streamHeader.type, streamHeader.payloadSize, streamHeader.packedSize,
|
||||
streamHeader.unpackedSize, streamHeader.numberOfPulses);
|
||||
|
||||
if(packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>() + streamHeader.packedSize >
|
||||
fileData.Length)
|
||||
return ErrorNumber.InvalidArgument;
|
||||
|
||||
byte[] packedData = new byte[streamHeader.packedSize];
|
||||
Array.Copy(fileData,
|
||||
(int)packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>(),
|
||||
packedData,
|
||||
0,
|
||||
(int)streamHeader.packedSize);
|
||||
|
||||
byte[] unpackedData = new byte[streamHeader.unpackedSize];
|
||||
int decoded = LZ4.DecodeBuffer(packedData, unpackedData);
|
||||
|
||||
if(decoded != streamHeader.unpackedSize) return ErrorNumber.InvalidArgument;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Decompressed flux stream: {0} bytes -> {1} bytes",
|
||||
streamHeader.packedSize, decoded);
|
||||
|
||||
// Decode variable-length pulses
|
||||
uint numberOfPulses = streamHeader.numberOfPulses;
|
||||
uint[] pulses = DecodeVariableLengthPulses(unpackedData,
|
||||
streamHeader.unpackedSize,
|
||||
ref numberOfPulses);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Decoded {0} flux pulses (expected {1})", pulses.Length, numberOfPulses);
|
||||
|
||||
fluxPulses.AddRange(pulses);
|
||||
|
||||
packetOffset += Marshal.SizeOf<HxCStreamPackedStreamHeader>() + streamHeader.packedSize;
|
||||
|
||||
// Align to 4 bytes
|
||||
if(packetOffset % 4 != 0) packetOffset += 4 - packetOffset % 4;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
AaruLogging.Error(MODULE_NAME, "Unknown packet type: 0x{0:X8}", type);
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
fileOffset += chunkHeader.size;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Finished processing chunks. Total flux pulses: {0}, IO stream values: {1}",
|
||||
fluxPulses.Count, ioStream.Count);
|
||||
|
||||
// Extract index signals from IO stream
|
||||
var indexPositions = new List<uint>();
|
||||
|
||||
if(ioStream.Count > 0)
|
||||
{
|
||||
IoStreamState previousState = DecodeIoStreamValue(ioStream[0]);
|
||||
bool oldIndex = previousState.IndexSignal;
|
||||
uint totalTicks = 0;
|
||||
int pulseIndex = 0;
|
||||
|
||||
for(int i = 0; i < ioStream.Count; i++)
|
||||
{
|
||||
IoStreamState currentState = DecodeIoStreamValue(ioStream[i]);
|
||||
bool currentIndex = currentState.IndexSignal;
|
||||
|
||||
if(currentIndex != oldIndex && currentIndex)
|
||||
{
|
||||
// Index signal transition to high
|
||||
// Map to flux stream position
|
||||
uint targetTicks = (uint)(i * 16);
|
||||
|
||||
while(pulseIndex < fluxPulses.Count && totalTicks < targetTicks)
|
||||
{
|
||||
totalTicks += fluxPulses[pulseIndex];
|
||||
pulseIndex++;
|
||||
}
|
||||
|
||||
if(pulseIndex < fluxPulses.Count) indexPositions.Add((uint)pulseIndex);
|
||||
}
|
||||
|
||||
oldIndex = currentIndex;
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Extracted {0} index positions from IO stream", indexPositions.Count);
|
||||
}
|
||||
else
|
||||
AaruLogging.Debug(MODULE_NAME, "No IO stream data available, no index positions extracted");
|
||||
|
||||
// Create track capture
|
||||
// Note: HxCStream doesn't support subtracks, so subTrack is always 0
|
||||
var capture = new TrackCapture
|
||||
{
|
||||
head = head,
|
||||
track = track,
|
||||
resolution = samplePeriod,
|
||||
fluxPulses = fluxPulses.ToArray(),
|
||||
indexPositions = indexPositions.ToArray()
|
||||
};
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "Created track capture: head = {0}, track = {1}, resolution = {2} ns, fluxPulses = {3}, indexPositions = {4}",
|
||||
capture.head, capture.track, capture.resolution, capture.fluxPulses.Length, capture.indexPositions.Length);
|
||||
|
||||
_trackCaptures.Add(capture);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber CapturesLength(uint head, ushort track, byte subTrack, out uint length)
|
||||
{
|
||||
length = 0;
|
||||
|
||||
if(_trackCaptures == null) return ErrorNumber.NotOpened;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture
|
||||
// Check if a capture exists for this track
|
||||
bool hasCapture = _trackCaptures.Any(c => c.head == head && c.track == track);
|
||||
|
||||
length = hasCapture ? 1u : 0u;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFluxIndexResolution(uint head, ushort track, byte subTrack, uint captureIndex,
|
||||
out ulong resolution)
|
||||
{
|
||||
resolution = 0;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
TrackCapture capture = _trackCaptures.Find(c => c.head == head && c.track == track);
|
||||
|
||||
if(capture == null) return ErrorNumber.OutOfRange;
|
||||
|
||||
resolution = capture.resolution;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFluxDataResolution(uint head, ushort track, byte subTrack, uint captureIndex,
|
||||
out ulong resolution)
|
||||
{
|
||||
resolution = 0;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
TrackCapture capture = _trackCaptures.Find(c => c.head == head && c.track == track);
|
||||
|
||||
if(capture == null) return ErrorNumber.OutOfRange;
|
||||
|
||||
resolution = capture.resolution;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFluxResolution(uint head, ushort track, byte subTrack, uint captureIndex,
|
||||
out ulong indexResolution, out ulong dataResolution)
|
||||
{
|
||||
indexResolution = dataResolution = 0;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
TrackCapture capture = _trackCaptures.Find(c => c.head == head && c.track == track);
|
||||
|
||||
if(capture == null) return ErrorNumber.OutOfRange;
|
||||
|
||||
indexResolution = dataResolution = capture.resolution;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFluxCapture(uint head, ushort track, byte subTrack, uint captureIndex,
|
||||
out ulong indexResolution, out ulong dataResolution, out byte[] indexBuffer,
|
||||
out byte[] dataBuffer)
|
||||
{
|
||||
indexBuffer = dataBuffer = null;
|
||||
indexResolution = dataResolution = 0;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
ErrorNumber error = ReadFluxResolution(head, track, subTrack, captureIndex, out indexResolution,
|
||||
out dataResolution);
|
||||
|
||||
if(error != ErrorNumber.NoError) return error;
|
||||
|
||||
error = ReadFluxDataCapture(head, track, subTrack, captureIndex, out dataBuffer);
|
||||
|
||||
if(error != ErrorNumber.NoError) return error;
|
||||
|
||||
error = ReadFluxIndexCapture(head, track, subTrack, captureIndex, out indexBuffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFluxIndexCapture(uint head, ushort track, byte subTrack, uint captureIndex,
|
||||
out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
TrackCapture capture = _trackCaptures.Find(c => c.head == head && c.track == track);
|
||||
|
||||
if(capture == null) return ErrorNumber.OutOfRange;
|
||||
|
||||
var tmpBuffer = new List<byte> { 0 };
|
||||
uint previousTicks = 0;
|
||||
|
||||
foreach(uint indexPos in capture.indexPositions)
|
||||
{
|
||||
// Convert index position to ticks
|
||||
uint ticks = 0;
|
||||
for(uint i = 0; i < indexPos && i < capture.fluxPulses.Length; i++)
|
||||
ticks += capture.fluxPulses[i];
|
||||
|
||||
uint deltaTicks = ticks - previousTicks;
|
||||
tmpBuffer.AddRange(UInt32ToFluxRepresentation(deltaTicks));
|
||||
previousTicks = ticks;
|
||||
}
|
||||
|
||||
buffer = tmpBuffer.ToArray();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber
|
||||
ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
// HxCStream doesn't support subtracks - only subTrack 0 is valid
|
||||
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
|
||||
if(captureIndex != 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
TrackCapture capture = _trackCaptures.Find(c => c.head == head && c.track == track);
|
||||
|
||||
if(capture == null) return ErrorNumber.OutOfRange;
|
||||
|
||||
var tmpBuffer = new List<byte>();
|
||||
|
||||
foreach(uint pulse in capture.fluxPulses) tmpBuffer.AddRange(UInt32ToFluxRepresentation(pulse));
|
||||
|
||||
buffer = tmpBuffer.ToArray();
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber SubTrackLength(uint head, ushort track, out byte length)
|
||||
{
|
||||
length = 0;
|
||||
|
||||
if(_trackCaptures == null) return ErrorNumber.NotOpened;
|
||||
|
||||
// HxCStream doesn't support subtracks - filenames only contain cylinder and head
|
||||
// Check if any captures exist for this track
|
||||
List<TrackCapture> captures = _trackCaptures.FindAll(c => c.head == head && c.track == track);
|
||||
|
||||
if(captures.Count <= 0) return ErrorNumber.OutOfRange;
|
||||
|
||||
// Always return 1 since HxCStream doesn't support subtracks
|
||||
length = 1;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber GetAllFluxCaptures(out List<FluxCapture> captures)
|
||||
{
|
||||
captures = [];
|
||||
|
||||
if(_trackCaptures is { Count: > 0 })
|
||||
{
|
||||
// Group captures by head/track to assign capture indices
|
||||
// Note: HxCStream doesn't support subtracks, so subTrack is always 0
|
||||
var grouped = _trackCaptures.GroupBy(c => new { c.head, c.track })
|
||||
.ToList();
|
||||
|
||||
foreach(var group in grouped)
|
||||
{
|
||||
uint captureIndex = 0;
|
||||
|
||||
foreach(TrackCapture trackCapture in group)
|
||||
{
|
||||
captures.Add(new FluxCapture
|
||||
{
|
||||
Head = trackCapture.head,
|
||||
Track = trackCapture.track,
|
||||
SubTrack = 0, // HxCStream doesn't support subtracks
|
||||
CaptureIndex = captureIndex++,
|
||||
IndexResolution = trackCapture.resolution,
|
||||
DataResolution = trackCapture.resolution
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IMediaImage Members
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = SectorStatus.NotDumped;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
|
||||
out SectorStatus sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = SectorStatus.NotDumped;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = null;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||
out SectorStatus[] sectorStatus)
|
||||
{
|
||||
buffer = null;
|
||||
sectorStatus = null;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
|
||||
out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
206
Aaru.Images/HxCStream/Structs.cs
Normal file
206
Aaru.Images/HxCStream/Structs.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Structs.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Contains structures for HxC Stream flux 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
#region Nested type: HxCStreamChunkHeader
|
||||
|
||||
/// <summary>
|
||||
/// Represents a chunk header in an HxCStream file. A chunk is a container that holds
|
||||
/// multiple packet blocks (metadata, IO stream, flux stream) and ends with a CRC32 checksum.
|
||||
/// Each track file can contain multiple chunks, allowing data to be split across chunks.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct HxCStreamChunkHeader
|
||||
{
|
||||
/// <summary>Chunk signature, always "CHKH" (0x43, 0x48, 0x4B, 0x48)</summary>
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public byte[] signature;
|
||||
/// <summary>Total size of the chunk including header, all packet blocks, and CRC32 (4 bytes)</summary>
|
||||
public uint size;
|
||||
/// <summary>Packet number, used for sequencing chunks</summary>
|
||||
public uint packetNumber;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: HxCStreamChunkBlockHeader
|
||||
|
||||
/// <summary>
|
||||
/// Base header structure for packet blocks within a chunk. This is the common header
|
||||
/// that all packet types share. The actual packet headers extend this with additional fields.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct HxCStreamChunkBlockHeader
|
||||
{
|
||||
/// <summary>Packet type identifier (0x0 = metadata, 0x1 = IO stream, 0x2 = flux stream)</summary>
|
||||
public uint type;
|
||||
/// <summary>Size of the packet payload data (excluding this header)</summary>
|
||||
public uint payloadSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: HxCStreamMetadataHeader
|
||||
|
||||
/// <summary>
|
||||
/// Header for a metadata packet block (type 0x0). Contains text-based metadata information
|
||||
/// such as sample rate, IO channel names, etc. The payload is UTF-8 encoded text.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct HxCStreamMetadataHeader
|
||||
{
|
||||
/// <summary>Packet type, always 0x0 for metadata</summary>
|
||||
public uint type;
|
||||
/// <summary>Size of the metadata text payload in bytes</summary>
|
||||
public uint payloadSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: HxCStreamPackedIoHeader
|
||||
|
||||
/// <summary>
|
||||
/// Header for a packed IO stream packet block (type 0x1). Contains LZ4-compressed
|
||||
/// 16-bit IO values representing index signals, write protect status, and other
|
||||
/// IO channel states. Also, see <see cref="IoStreamState" /> for more information.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct HxCStreamPackedIoHeader
|
||||
{
|
||||
/// <summary>Packet type, always 0x1 for packed IO stream</summary>
|
||||
public uint type;
|
||||
/// <summary>Total size of the packet including this header and packed data</summary>
|
||||
public uint payloadSize;
|
||||
/// <summary>Size of the LZ4-compressed data in bytes</summary>
|
||||
public uint packedSize;
|
||||
/// <summary>Size of the uncompressed data in bytes (should be even, as it's 16-bit values)</summary>
|
||||
public uint unpackedSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: HxCStreamPackedStreamHeader
|
||||
|
||||
/// <summary>
|
||||
/// Header for a packed flux stream packet block (type 0x2). Contains LZ4-compressed
|
||||
/// variable-length encoded flux pulse data. The pulses represent time intervals between
|
||||
/// flux reversals, encoded using a variable-length encoding scheme to save space.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct HxCStreamPackedStreamHeader
|
||||
{
|
||||
/// <summary>Packet type, always 0x2 for packed flux stream</summary>
|
||||
public uint type;
|
||||
/// <summary>Total size of the packet including this header and packed data</summary>
|
||||
public uint payloadSize;
|
||||
/// <summary>Size of the LZ4-compressed data in bytes</summary>
|
||||
public uint packedSize;
|
||||
/// <summary>Size of the uncompressed variable-length encoded pulse data in bytes</summary>
|
||||
public uint unpackedSize;
|
||||
/// <summary>Number of flux pulses in this packet (may be updated during decoding)</summary>
|
||||
public uint numberOfPulses;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: IoStreamState
|
||||
|
||||
/// <summary>
|
||||
/// Represents the decoded state of a 16-bit IO stream value from an HxCStream file.
|
||||
/// The IO stream contains signals sampled at regular intervals. Currently, only the
|
||||
/// index signal (bit 0) and write protect (bit 5) are used. The raw value is preserved
|
||||
/// for future extensions and can be accessed to check other bits if needed.
|
||||
/// </summary>
|
||||
public struct IoStreamState
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw 16-bit IO value. Use this to access other bits that may be defined in the future.
|
||||
/// Bits can be checked using: (RawValue & bitMask) != 0
|
||||
/// </summary>
|
||||
public ushort RawValue { get; set; }
|
||||
|
||||
/// <summary>Index signal state (bit 0). True when index signal is active/high.</summary>
|
||||
public bool IndexSignal => (RawValue & 0x01) != 0;
|
||||
|
||||
/// <summary>Write protect state (bit 5). True when write protect is active.</summary>
|
||||
public bool WriteProtect => (RawValue & 0x20) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IoStreamState from a raw 16-bit value
|
||||
/// </summary>
|
||||
/// <param name="rawValue">The raw 16-bit IO stream value</param>
|
||||
/// <returns>Decoded IO stream state</returns>
|
||||
public static IoStreamState FromRawValue(ushort rawValue) => new() { RawValue = rawValue };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: TrackCapture
|
||||
|
||||
/// <summary>
|
||||
/// Represents a complete flux capture for a single track. Contains the decoded flux pulse
|
||||
/// data, index signal positions, and resolution information. This is the internal representation
|
||||
/// used to store parsed track data from HxCStream files.
|
||||
/// </summary>
|
||||
public class TrackCapture
|
||||
{
|
||||
public uint head;
|
||||
public ushort track;
|
||||
/// <summary>
|
||||
/// Resolution (sample rate) of the flux capture in picoseconds.
|
||||
/// Default is 40,000 picoseconds (40 nanoseconds = 25 MHz sample rate).
|
||||
/// Can be 20,000 picoseconds (20 nanoseconds = 50 MHz sample rate) if metadata indicates 50 MHz.
|
||||
/// </summary>
|
||||
public uint resolution;
|
||||
/// <summary>
|
||||
/// Array of flux pulse intervals in ticks. Each value represents the time interval
|
||||
/// between flux reversals, measured in resolution units (picoseconds).
|
||||
/// </summary>
|
||||
public uint[] fluxPulses;
|
||||
/// <summary>
|
||||
/// Array of index positions. Each value is an index into the fluxPulses array
|
||||
/// indicating where an index signal occurs. These positions are extracted from
|
||||
/// the IO stream (bit 0 transitions) and mapped to flux stream positions.
|
||||
/// </summary>
|
||||
public uint[] indexPositions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
90
Aaru.Images/HxCStream/Verify.cs
Normal file
90
Aaru.Images/HxCStream/Verify.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Verify.cs
|
||||
// Author(s) : Rebecca Wallander <sakcheen+github@gmail.com>
|
||||
//
|
||||
// Component : Disk image plugins.
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// Verifies HxC Stream flux images.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This library is free software; you can redistribute it and it 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 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Aaru.Checksums;
|
||||
using Aaru.Helpers;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class HxCStream
|
||||
{
|
||||
public bool? VerifyMediaImage()
|
||||
{
|
||||
if(_trackCaptures == null || _trackCaptures.Count == 0) return null;
|
||||
|
||||
if(_trackFilePaths == null || _trackFilePaths.Count == 0) return null;
|
||||
|
||||
// Verify CRC32 checksums in all track files
|
||||
foreach(string trackfile in _trackFilePaths)
|
||||
{
|
||||
if(!File.Exists(trackfile)) return false;
|
||||
|
||||
using FileStream fileStream = File.OpenRead(trackfile);
|
||||
byte[] fileData = new byte[fileStream.Length];
|
||||
fileStream.EnsureRead(fileData, 0, (int)fileStream.Length);
|
||||
|
||||
long fileOffset = 0;
|
||||
|
||||
while(fileOffset < fileData.Length)
|
||||
{
|
||||
if(fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>() > fileData.Length) return false;
|
||||
|
||||
HxCStreamChunkHeader chunkHeader = Marshal.ByteArrayToStructureLittleEndian<HxCStreamChunkHeader>(
|
||||
fileData, (int)fileOffset, Marshal.SizeOf<HxCStreamChunkHeader>());
|
||||
|
||||
if(!_hxcStreamSignature.SequenceEqual(chunkHeader.signature)) return false;
|
||||
|
||||
if(chunkHeader.size > fileData.Length - fileOffset) return false;
|
||||
|
||||
// Verify CRC32 - calculate CRC of chunk data (excluding the CRC itself)
|
||||
byte[] chunkData = new byte[chunkHeader.size - 4];
|
||||
Array.Copy(fileData, (int)fileOffset, chunkData, 0, (int)(chunkHeader.size - 4));
|
||||
|
||||
uint storedCrc = BitConverter.ToUInt32(fileData, (int)(fileOffset + chunkHeader.size - 4));
|
||||
|
||||
if(!VerifyChunkCrc32(chunkData, storedCrc)) return false;
|
||||
|
||||
fileOffset += chunkHeader.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Basic verification: check that all track captures have valid data
|
||||
foreach(TrackCapture capture in _trackCaptures)
|
||||
{
|
||||
if(capture.fluxPulses == null || capture.fluxPulses.Length == 0) return false;
|
||||
|
||||
if(capture.resolution == 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5907,5 +5907,11 @@ namespace Aaru.Images {
|
||||
return ResourceManager.GetString("Overflow_sectors_are_not_supported", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string HxCStream_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("HxCStream_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2965,4 +2965,7 @@
|
||||
<data name="Overflow_sectors_are_not_supported" xml:space="preserve">
|
||||
<value>Overflow sectors are not supported.</value>
|
||||
</data>
|
||||
<data name="HxCStream_Name" xml:space="preserve">
|
||||
<value>HxCStream</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user