mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-05 21:44:17 +00:00
Merge pull request #927 from aaru-dps/fakeshemp/kryoflux
Read kryoflux flux data
This commit is contained in:
@@ -47,4 +47,12 @@ public sealed partial class KryoFlux
|
|||||||
const string KF_HW_RV = "hwrv";
|
const string KF_HW_RV = "hwrv";
|
||||||
const string KF_SCK = "sck";
|
const string KF_SCK = "sck";
|
||||||
const string KF_ICK = "ick";
|
const string KF_ICK = "ick";
|
||||||
|
|
||||||
|
// Per KryoFlux spec: Clock frequencies for current hardware
|
||||||
|
// mck = Master Clock Frequency = ((18432000 * 73) / 14) / 2
|
||||||
|
// sck = Sample Frequency = mck / 2
|
||||||
|
// ick = Index Frequency = mck / 16
|
||||||
|
const double MCK = (18432000.0 * 73.0 / 14.0) / 2.0;
|
||||||
|
const double SCK = MCK / 2.0;
|
||||||
|
const double ICK = MCK / 16.0;
|
||||||
}
|
}
|
||||||
76
Aaru.Images/KryoFlux/Helpers.cs
Normal file
76
Aaru.Images/KryoFlux/Helpers.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Helpers.cs
|
||||||
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
|
//
|
||||||
|
// Component : Disk image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Contains helpers for KryoFlux STREAM 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.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||||
|
public sealed partial class KryoFlux
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a uint32 cell value to Aaru's flux representation format.
|
||||||
|
/// Format: byte array where 255 = overflow, remainder = value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ticks">The cell value in clock cycles</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>
|
||||||
|
/// Calculates resolution in picoseconds from sample clock frequency.
|
||||||
|
/// Resolution = (1 / sck) * 1e12 picoseconds
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sck">Sample clock frequency in Hz</param>
|
||||||
|
/// <returns>Resolution in picoseconds</returns>
|
||||||
|
static ulong CalculateResolution(double sck)
|
||||||
|
{
|
||||||
|
if(sck <= 0) return 0;
|
||||||
|
|
||||||
|
double periodSeconds = 1.0 / sck;
|
||||||
|
double periodPicoseconds = periodSeconds * 1e12;
|
||||||
|
|
||||||
|
return (ulong)periodPicoseconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -37,16 +37,18 @@ using Aaru.CommonTypes.Structs;
|
|||||||
|
|
||||||
namespace Aaru.Images;
|
namespace Aaru.Images;
|
||||||
|
|
||||||
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IMediaImage" />
|
/// <inheritdoc cref="IFluxImage" />
|
||||||
/// <summary>Implements reading KryoFlux flux images</summary>
|
/// <summary>Implements reading KryoFlux flux images</summary>
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public sealed partial class KryoFlux : IMediaImage, IVerifiableSectorsImage
|
public sealed partial class KryoFlux : IVerifiableSectorsImage, IFluxImage
|
||||||
{
|
{
|
||||||
const string MODULE_NAME = "KryoFlux plugin";
|
const string MODULE_NAME = "KryoFlux plugin";
|
||||||
|
|
||||||
// TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0
|
// TODO: These variables have been made public so create-sidecar can access to this information until I define an API >4.0
|
||||||
public SortedDictionary<byte, IFilter> tracks;
|
public SortedDictionary<byte, IFilter> tracks;
|
||||||
|
|
||||||
|
List<TrackCapture> _trackCaptures;
|
||||||
|
|
||||||
public KryoFlux() => _imageInfo = new ImageInfo
|
public KryoFlux() => _imageInfo = new ImageInfo
|
||||||
{
|
{
|
||||||
ReadableSectorTags = [],
|
ReadableSectorTags = [],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// /***************************************************************************
|
// /***************************************************************************
|
||||||
// Aaru Data Preservation Suite
|
// Aaru Data Preservation Suite
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -37,9 +37,11 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
using Aaru.CommonTypes.Interfaces;
|
using Aaru.CommonTypes.Interfaces;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
using Aaru.Filters;
|
using Aaru.Filters;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
using Aaru.Logging;
|
using Aaru.Logging;
|
||||||
|
using Aaru.CommonTypes;
|
||||||
|
|
||||||
namespace Aaru.Images;
|
namespace Aaru.Images;
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ public sealed partial class KryoFlux
|
|||||||
|
|
||||||
// TODO: This is supposing NoFilter, shouldn't
|
// TODO: This is supposing NoFilter, shouldn't
|
||||||
tracks = new SortedDictionary<byte, IFilter>();
|
tracks = new SortedDictionary<byte, IFilter>();
|
||||||
|
_trackCaptures = [];
|
||||||
byte step = 1;
|
byte step = 1;
|
||||||
byte heads = 2;
|
byte heads = 2;
|
||||||
var topHead = false;
|
var topHead = false;
|
||||||
@@ -131,46 +134,87 @@ public sealed partial class KryoFlux
|
|||||||
_imageInfo.CreationTime = DateTime.MaxValue;
|
_imageInfo.CreationTime = DateTime.MaxValue;
|
||||||
_imageInfo.LastModificationTime = DateTime.MinValue;
|
_imageInfo.LastModificationTime = DateTime.MinValue;
|
||||||
|
|
||||||
Stream trackStream = trackFilter.GetDataForkStream();
|
ErrorNumber processError = ProcessTrackFile(trackfile, (uint)head, (ushort)cylinder, trackFilter);
|
||||||
|
|
||||||
while(trackStream.Position < trackStream.Length)
|
if(processError != ErrorNumber.NoError) return processError;
|
||||||
|
|
||||||
|
tracks.Add(t, trackFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
_imageInfo.Heads = heads;
|
||||||
|
_imageInfo.Cylinders = (uint)(tracks.Count / heads);
|
||||||
|
// TODO: Find a way to determine the media type from the track data.
|
||||||
|
_imageInfo.MediaType = MediaType.DOS_35_HD;
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorNumber ProcessTrackFile(string trackfile, uint head, ushort track, IFilter trackFilter)
|
||||||
|
{
|
||||||
|
if(!File.Exists(trackfile)) return ErrorNumber.NoSuchFile;
|
||||||
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME, "Processing track file: {0} (head {1}, track {2})", trackfile, head, track);
|
||||||
|
|
||||||
|
Stream trackStream = trackFilter.GetDataForkStream();
|
||||||
|
trackStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
byte[] fileData = new byte[trackStream.Length];
|
||||||
|
trackStream.EnsureRead(fileData, 0, (int)trackStream.Length);
|
||||||
|
|
||||||
|
int fileSize = fileData.Length;
|
||||||
|
|
||||||
|
uint[] cellValues = new uint[fileSize];
|
||||||
|
uint[] cellStreamPositions = new uint[fileSize];
|
||||||
|
|
||||||
|
uint cellAccumulator = 0;
|
||||||
|
int streamOfs = 0;
|
||||||
|
uint streamPos = 0;
|
||||||
|
int cellPos = 0;
|
||||||
|
|
||||||
|
// Sample Frequency
|
||||||
|
double sck = SCK;
|
||||||
|
|
||||||
|
List<(uint streamPosition, uint timer, uint sysTime)> indexEvents = [];
|
||||||
|
|
||||||
|
int oobHeaderSize = Marshal.SizeOf<OobBlock>();
|
||||||
|
|
||||||
|
while(streamOfs < fileSize)
|
||||||
|
{
|
||||||
|
byte curOp = fileData[streamOfs];
|
||||||
|
int curOpLen;
|
||||||
|
|
||||||
|
if(curOp == (byte)BlockIds.Oob)
|
||||||
{
|
{
|
||||||
var blockId = (byte)trackStream.ReadByte();
|
if(fileSize - streamOfs < oobHeaderSize)
|
||||||
|
return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
switch(blockId)
|
byte[] oobBytes = new byte[oobHeaderSize];
|
||||||
|
Array.Copy(fileData, streamOfs, oobBytes, 0, oobHeaderSize);
|
||||||
|
OobBlock oobBlk = Marshal.ByteArrayToStructureLittleEndian<OobBlock>(oobBytes);
|
||||||
|
|
||||||
|
if(oobBlk.blockType == OobTypes.EOF)
|
||||||
|
break;
|
||||||
|
|
||||||
|
curOpLen = oobHeaderSize + oobBlk.length;
|
||||||
|
|
||||||
|
if(fileSize - streamOfs < curOpLen)
|
||||||
|
return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
int oobDataStart = streamOfs + oobHeaderSize;
|
||||||
|
|
||||||
|
switch(oobBlk.blockType)
|
||||||
{
|
{
|
||||||
case (byte)BlockIds.Oob:
|
case OobTypes.KFInfo:
|
||||||
{
|
{
|
||||||
trackStream.Position--;
|
byte[] kfinfo = new byte[oobBlk.length];
|
||||||
|
Array.Copy(fileData, oobDataStart, kfinfo, 0, oobBlk.length);
|
||||||
|
|
||||||
var oob = new byte[Marshal.SizeOf<OobBlock>()];
|
string kfinfoStr = StringHandlers.CToString(kfinfo);
|
||||||
trackStream.EnsureRead(oob, 0, Marshal.SizeOf<OobBlock>());
|
string[] lines = kfinfoStr.Split([','], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
OobBlock oobBlk = Marshal.ByteArrayToStructureLittleEndian<OobBlock>(oob);
|
|
||||||
|
|
||||||
if(oobBlk.blockType == OobTypes.EOF)
|
|
||||||
{
|
|
||||||
trackStream.Position = trackStream.Length;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(oobBlk.blockType != OobTypes.KFInfo)
|
|
||||||
{
|
|
||||||
trackStream.Position += oobBlk.length;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var kfinfo = new byte[oobBlk.length];
|
|
||||||
trackStream.EnsureRead(kfinfo, 0, oobBlk.length);
|
|
||||||
string kfinfoStr = StringHandlers.CToString(kfinfo);
|
|
||||||
|
|
||||||
string[] lines = kfinfoStr.Split([','], StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
DateTime blockDate = DateTime.Now;
|
DateTime blockDate = DateTime.Now;
|
||||||
DateTime blockTime = DateTime.Now;
|
DateTime blockTime = DateTime.Now;
|
||||||
var foundDate = false;
|
bool foundDate = false;
|
||||||
|
|
||||||
foreach(string[] kvp in lines.Select(static line => line.Split('='))
|
foreach(string[] kvp in lines.Select(static line => line.Split('='))
|
||||||
.Where(static kvp => kvp.Length == 2))
|
.Where(static kvp => kvp.Length == 2))
|
||||||
@@ -205,18 +249,31 @@ public sealed partial class KryoFlux
|
|||||||
case KF_VERSION:
|
case KF_VERSION:
|
||||||
_imageInfo.ApplicationVersion = kvp[1];
|
_imageInfo.ApplicationVersion = kvp[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case KF_SCK:
|
||||||
|
if(double.TryParse(kvp[1], NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||||
|
out double parsedSck))
|
||||||
|
sck = parsedSck;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case KF_ICK:
|
||||||
|
// Parse KF_ICK for validation purposes; parsed value is not currently used.
|
||||||
|
// We use sample frequency space instead.
|
||||||
|
_ = double.TryParse(kvp[1], NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||||
|
out _);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(foundDate)
|
if(foundDate)
|
||||||
{
|
{
|
||||||
var blockTimestamp = new DateTime(blockDate.Year,
|
DateTime blockTimestamp = new DateTime(blockDate.Year,
|
||||||
blockDate.Month,
|
blockDate.Month,
|
||||||
blockDate.Day,
|
blockDate.Day,
|
||||||
blockTime.Hour,
|
blockTime.Hour,
|
||||||
blockTime.Minute,
|
blockTime.Minute,
|
||||||
blockTime.Second);
|
blockTime.Second);
|
||||||
|
|
||||||
AaruLogging.Debug(MODULE_NAME, Localization.Found_timestamp_0, blockTimestamp);
|
AaruLogging.Debug(MODULE_NAME, Localization.Found_timestamp_0, blockTimestamp);
|
||||||
|
|
||||||
@@ -228,37 +285,199 @@ public sealed partial class KryoFlux
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case (byte)BlockIds.Flux2:
|
case OobTypes.StreamInfo:
|
||||||
case (byte)BlockIds.Flux2_1:
|
{
|
||||||
case (byte)BlockIds.Flux2_2:
|
if(oobDataStart + Marshal.SizeOf<OobStreamRead>() <= fileSize)
|
||||||
case (byte)BlockIds.Flux2_3:
|
{
|
||||||
case (byte)BlockIds.Flux2_4:
|
byte[] streamReadBytes = new byte[Marshal.SizeOf<OobStreamRead>()];
|
||||||
case (byte)BlockIds.Flux2_5:
|
|
||||||
case (byte)BlockIds.Flux2_6:
|
|
||||||
case (byte)BlockIds.Flux2_7:
|
|
||||||
case (byte)BlockIds.Nop2:
|
|
||||||
trackStream.Position++;
|
|
||||||
|
|
||||||
continue;
|
Array.Copy(fileData, oobDataStart, streamReadBytes, 0,
|
||||||
case (byte)BlockIds.Nop3:
|
Marshal.SizeOf<OobStreamRead>());
|
||||||
case (byte)BlockIds.Flux3:
|
|
||||||
trackStream.Position += 2;
|
|
||||||
|
|
||||||
continue;
|
OobStreamRead oobStreamRead =
|
||||||
default:
|
Marshal.ByteArrayToStructureLittleEndian<OobStreamRead>(streamReadBytes);
|
||||||
continue;
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME,
|
||||||
|
"Stream Read at position {0}, elapsed time {1} ms",
|
||||||
|
oobStreamRead.streamPosition,
|
||||||
|
oobStreamRead.trTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OobTypes.Index:
|
||||||
|
{
|
||||||
|
if(oobDataStart + Marshal.SizeOf<OobIndex>() <= fileSize)
|
||||||
|
{
|
||||||
|
byte[] indexBytes = new byte[Marshal.SizeOf<OobIndex>()];
|
||||||
|
Array.Copy(fileData, oobDataStart, indexBytes, 0, Marshal.SizeOf<OobIndex>());
|
||||||
|
|
||||||
|
OobIndex oobIndex = Marshal.ByteArrayToStructureLittleEndian<OobIndex>(indexBytes);
|
||||||
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME,
|
||||||
|
"Index signal at stream position {0}, timer {1}, sysTime {2}",
|
||||||
|
oobIndex.streamPosition,
|
||||||
|
oobIndex.timer,
|
||||||
|
oobIndex.sysTime);
|
||||||
|
|
||||||
|
indexEvents.Add((oobIndex.streamPosition, oobIndex.timer, oobIndex.sysTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OobTypes.StreamEnd:
|
||||||
|
{
|
||||||
|
if(oobDataStart + Marshal.SizeOf<OobStreamEnd>() <= fileSize)
|
||||||
|
{
|
||||||
|
byte[] streamEndBytes = new byte[Marshal.SizeOf<OobStreamEnd>()];
|
||||||
|
|
||||||
|
Array.Copy(fileData, oobDataStart, streamEndBytes, 0,
|
||||||
|
Marshal.SizeOf<OobStreamEnd>());
|
||||||
|
|
||||||
|
OobStreamEnd oobStreamEnd =
|
||||||
|
Marshal.ByteArrayToStructureLittleEndian<OobStreamEnd>(streamEndBytes);
|
||||||
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME,
|
||||||
|
"Stream End at position {0}, result {1}",
|
||||||
|
oobStreamEnd.streamPosition,
|
||||||
|
oobStreamEnd.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
streamOfs += curOpLen;
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks.Add(t, trackFilter);
|
// Non-OOB: determine operation length and decode flux data
|
||||||
|
bool newCell = false;
|
||||||
|
|
||||||
|
switch(curOp)
|
||||||
|
{
|
||||||
|
case (byte)BlockIds.Nop1:
|
||||||
|
curOpLen = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case (byte)BlockIds.Nop2:
|
||||||
|
curOpLen = 2;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case (byte)BlockIds.Nop3:
|
||||||
|
curOpLen = 3;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case (byte)BlockIds.Ovl16:
|
||||||
|
curOpLen = 1;
|
||||||
|
cellAccumulator += 0x10000;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case (byte)BlockIds.Flux3:
|
||||||
|
curOpLen = 3;
|
||||||
|
cellAccumulator += (uint)((fileData[streamOfs + 1] << 8) | fileData[streamOfs + 2]);
|
||||||
|
newCell = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(curOp >= 0x0E)
|
||||||
|
{
|
||||||
|
curOpLen = 1;
|
||||||
|
cellAccumulator += curOp;
|
||||||
|
newCell = true;
|
||||||
|
}
|
||||||
|
else if((curOp & 0xF8) == 0)
|
||||||
|
{
|
||||||
|
curOpLen = 2;
|
||||||
|
cellAccumulator += ((uint)curOp << 8) | fileData[streamOfs + 1];
|
||||||
|
newCell = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fileSize - streamOfs < curOpLen)
|
||||||
|
return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
if(newCell)
|
||||||
|
{
|
||||||
|
cellValues[cellPos] = cellAccumulator;
|
||||||
|
cellStreamPositions[cellPos] = streamPos;
|
||||||
|
cellPos++;
|
||||||
|
cellAccumulator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos += (uint)curOpLen;
|
||||||
|
streamOfs += curOpLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageInfo.Heads = heads;
|
// Store final partial cell for index resolution boundary
|
||||||
_imageInfo.Cylinders = (uint)(tracks.Count / heads);
|
cellValues[cellPos] = cellAccumulator;
|
||||||
|
cellStreamPositions[cellPos] = streamPos;
|
||||||
|
|
||||||
AaruLogging.Error(Localization.Flux_decoding_is_not_yet_implemented);
|
int totalCells = cellPos;
|
||||||
|
|
||||||
return ErrorNumber.NotImplemented;
|
// Resolve index stream positions to cell positions
|
||||||
|
List<uint> indexPositions = [];
|
||||||
|
|
||||||
|
if(indexEvents.Count > 0)
|
||||||
|
{
|
||||||
|
int nextIndex = 0;
|
||||||
|
uint nextIndexStreamPos = indexEvents[nextIndex].streamPosition;
|
||||||
|
|
||||||
|
for(int i = 0; i < totalCells; i++)
|
||||||
|
{
|
||||||
|
if(nextIndex >= indexEvents.Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int nextCellPos = i + 1;
|
||||||
|
|
||||||
|
if(nextIndexStreamPos <= cellStreamPositions[nextCellPos])
|
||||||
|
{
|
||||||
|
if(i == 0 && cellStreamPositions[0] >= nextIndexStreamPos)
|
||||||
|
nextCellPos = 0;
|
||||||
|
|
||||||
|
indexPositions.Add((uint)nextCellPos);
|
||||||
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME,
|
||||||
|
"Index {0} resolved to cell position {1}",
|
||||||
|
nextIndex, nextCellPos);
|
||||||
|
|
||||||
|
nextIndex++;
|
||||||
|
|
||||||
|
if(nextIndex < indexEvents.Count)
|
||||||
|
nextIndexStreamPos = indexEvents[nextIndex].streamPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build flux pulses array
|
||||||
|
uint[] fluxPulses = new uint[totalCells];
|
||||||
|
Array.Copy(cellValues, fluxPulses, totalCells);
|
||||||
|
|
||||||
|
ulong resolution = CalculateResolution(sck);
|
||||||
|
|
||||||
|
AaruLogging.Debug(MODULE_NAME,
|
||||||
|
"Decoded {0} flux pulses, {1} index signals, resolution {2} ps",
|
||||||
|
fluxPulses.Length,
|
||||||
|
indexPositions.Count,
|
||||||
|
resolution);
|
||||||
|
|
||||||
|
TrackCapture capture = new TrackCapture
|
||||||
|
{
|
||||||
|
head = head,
|
||||||
|
track = track,
|
||||||
|
resolution = resolution,
|
||||||
|
fluxPulses = fluxPulses,
|
||||||
|
indexPositions = indexPositions.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
_trackCaptures.Add(capture);
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -319,5 +538,232 @@ public sealed partial class KryoFlux
|
|||||||
return ErrorNumber.NotImplemented;
|
return ErrorNumber.NotImplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IFluxImage Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber CapturesLength(uint head, ushort track, byte subTrack, out uint length)
|
||||||
|
{
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
if(_trackCaptures == null) return ErrorNumber.NotOpened;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux has one file per track/head, which results in exactly one capture
|
||||||
|
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;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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>();
|
||||||
|
uint previousPosition = 0;
|
||||||
|
|
||||||
|
foreach(uint indexPos in capture.indexPositions)
|
||||||
|
{
|
||||||
|
// Calculate ticks from start to this index position
|
||||||
|
uint ticks = 0;
|
||||||
|
for(uint i = previousPosition; i < indexPos && i < capture.fluxPulses.Length; i++)
|
||||||
|
ticks += capture.fluxPulses[i];
|
||||||
|
|
||||||
|
uint deltaTicks = ticks;
|
||||||
|
tmpBuffer.AddRange(UInt32ToFluxRepresentation(deltaTicks));
|
||||||
|
previousPosition = indexPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = tmpBuffer.ToArray();
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber
|
||||||
|
ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
// KryoFlux doesn't support subtracks - only subTrack 0 is valid
|
||||||
|
if(subTrack != 0) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
// KryoFlux 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;
|
||||||
|
|
||||||
|
// KryoFlux 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 KryoFlux 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: KryoFlux 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, // KryoFlux doesn't support subtracks
|
||||||
|
CaptureIndex = captureIndex++,
|
||||||
|
IndexResolution = trackCapture.resolution,
|
||||||
|
DataResolution = trackCapture.resolution
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -46,5 +46,81 @@ public sealed partial class KryoFlux
|
|||||||
public readonly ushort length;
|
public readonly ushort length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Nested type: OobStreamRead
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per KryoFlux spec: OOB Stream Read block structure (4 bytes after OOB header).
|
||||||
|
/// Contains stream position and elapsed time since last transfer.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
readonly struct OobStreamRead
|
||||||
|
{
|
||||||
|
public readonly uint streamPosition;
|
||||||
|
public readonly uint trTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Nested type: OobIndex
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per KryoFlux spec: OOB Index block structure (12 bytes after OOB header).
|
||||||
|
/// Contains stream position, timer value, and system time when index was detected.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
readonly struct OobIndex
|
||||||
|
{
|
||||||
|
public readonly uint streamPosition;
|
||||||
|
public readonly uint timer;
|
||||||
|
public readonly uint sysTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Nested type: OobStreamEnd
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per KryoFlux spec: OOB Stream End block structure (12 bytes after OOB header).
|
||||||
|
/// Contains stream position and result code.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
readonly struct OobStreamEnd
|
||||||
|
{
|
||||||
|
public readonly uint streamPosition;
|
||||||
|
// The specification says it's a ulong, but the actual data is a uint
|
||||||
|
public readonly uint result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Nested type: TrackCapture
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal structure representing a flux capture for a single track.
|
||||||
|
/// Contains decoded flux pulse data, index signal positions, and resolution information.
|
||||||
|
/// </summary>
|
||||||
|
public class TrackCapture
|
||||||
|
{
|
||||||
|
public uint head;
|
||||||
|
public ushort track;
|
||||||
|
/// <summary>
|
||||||
|
/// Resolution (sample rate) of the flux capture in picoseconds.
|
||||||
|
/// Calculated from KryoFlux clock frequencies (sck).
|
||||||
|
/// </summary>
|
||||||
|
public ulong resolution;
|
||||||
|
/// <summary>
|
||||||
|
/// Array of flux pulse intervals in clock cycles. Each value represents the time interval
|
||||||
|
/// between flux reversals, measured in KryoFlux clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
public uint[] fluxPulses;
|
||||||
|
/// <summary>
|
||||||
|
/// Array of index positions. Each value is an index into the fluxPulses array
|
||||||
|
/// indicating where an index signal occurs.
|
||||||
|
/// </summary>
|
||||||
|
public uint[] indexPositions;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user