Refactor XGD dumping and corrected L1 video dumping, now it generates the correct file size and matches with redump expectations.

Fixes #832
This commit is contained in:
2026-07-06 19:09:27 +01:00
parent 898c22c3e6
commit 1793f3e43f
4 changed files with 177 additions and 117 deletions

View File

@@ -76,6 +76,8 @@ partial class Dump
double maxSpeed = double.MinValue; double maxSpeed = double.MinValue;
double minSpeed = double.MaxValue; double minSpeed = double.MaxValue;
UpdateStatus?.Invoke(string.Format(Localization.Core.Media_identified_as_0, dskType.Humanize()));
if(_outputPlugin is not IWritableImage outputFormat) if(_outputPlugin is not IWritableImage outputFormat)
{ {
StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting); StoppingErrorMessage?.Invoke(Localization.Core.Image_is_not_writable_aborting);
@@ -97,9 +99,9 @@ partial class Dump
} }
} }
if(mediaTags.ContainsKey(MediaTagType.DVD_PFI)) mediaTags.Remove(MediaTagType.DVD_PFI); mediaTags.Remove(MediaTagType.DVD_PFI);
if(mediaTags.ContainsKey(MediaTagType.DVD_DMI)) mediaTags.Remove(MediaTagType.DVD_DMI); mediaTags.Remove(MediaTagType.DVD_DMI);
// Drive shall move to lock state when a new disc is inserted. Old kreon versions do not lock correctly so save this // Drive shall move to lock state when a new disc is inserted. Old kreon versions do not lock correctly so save this
sense = _dev.ReadCapacity(out byte[] coldReadCapacity, out ReadOnlySpan<byte> senseBuf, _dev.Timeout, out _); sense = _dev.ReadCapacity(out byte[] coldReadCapacity, out ReadOnlySpan<byte> senseBuf, _dev.Timeout, out _);
@@ -497,14 +499,14 @@ partial class Dump
var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin", var mhddLog = new MhddLog(_outputPrefix + ".mhddlog.bin",
_dev, _dev,
blocks, totalSize,
blockSize, blockSize,
blocksToRead, blocksToRead,
_private, _private,
_dimensions); _dimensions);
var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010); var ibgLog = new IbgLog(_outputPrefix + ".ibg", 0x0010);
ret = outputFormat.Create(_outputPath, dskType, _formatOptions, blocks, 0, 0, blockSize); ret = outputFormat.Create(_outputPath, dskType, _formatOptions, totalSize, 0, 0, blockSize);
// Cannot create image // Cannot create image
if(!ret) if(!ret)
@@ -586,7 +588,14 @@ partial class Dump
InitProgress?.Invoke(); InitProgress?.Invoke();
double elapsed = 0; double elapsed = 0;
for(var e = 0; e <= 16; e++) // XGD1 uses 16 protection extents (indices 0-15).
// XGD2 and XGD3 only have actual protection zones at indices 0 and 3;
// the other entries contain challenge-response authentication data, not unreadable sector ranges.
int[] protectionExtentIndices = dskType == MediaType.XGD
? [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
: [0, 3];
for(var ei = 0; ei <= protectionExtentIndices.Length; ei++)
{ {
if(_aborted) if(_aborted)
{ {
@@ -602,8 +611,10 @@ partial class Dump
ulong extentStart, extentEnd; ulong extentStart, extentEnd;
// Extents // Extents
if(e < 16) if(ei < protectionExtentIndices.Length)
{ {
int e = protectionExtentIndices[ei];
if(xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN) if(xboxSs.Value.Extents[e].StartPSN <= xboxSs.Value.Layer0EndPSN)
extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000; extentStart = xboxSs.Value.Extents[e].StartPSN - 0x30000;
else else
@@ -623,11 +634,11 @@ partial class Dump
} }
} }
// After last extent // After last extent — sentinel: extentEnd < extentStart so the protection write loop does not run
else else
{ {
extentStart = blocks; extentStart = blocks;
extentEnd = blocks; extentEnd = blocks - 1;
} }
if(currentSector > extentEnd) continue; if(currentSector > extentEnd) continue;
@@ -793,11 +804,15 @@ partial class Dump
EndProgress?.Invoke(); EndProgress?.Invoke();
// Ensure middle zone starts right after the locked-capacity boundary regardless of where
// the game loop ended (protection zone 3 may push currentSector past blocks)
if(!_aborted) currentSector = blocks;
// Middle Zone D // Middle Zone D
UpdateStatus?.Invoke(Localization.Core.Writing_Middle_Zone_D_empty); UpdateStatus?.Invoke(Localization.Core.Writing_Middle_Zone_D_empty);
InitProgress?.Invoke(); InitProgress?.Invoke();
for(ulong middle = currentSector - blocks - 1; middle < middleZone - 1; middle += blocksToRead) for(ulong middle = 0; middle < middleZone; middle += blocksToRead)
{ {
if(_aborted) if(_aborted)
{ {
@@ -807,29 +822,28 @@ partial class Dump
break; break;
} }
if(middleZone - 1 - middle < blocksToRead) blocksToRead = (uint)(middleZone - 1 - middle); if(middleZone - middle < blocksToRead) blocksToRead = (uint)(middleZone - middle);
UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2, UpdateProgress?.Invoke(string.Format(Localization.Core.Reading_sector_0_of_1_2,
middle + currentSector, currentSector,
totalSize, totalSize,
ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()), ByteSize.FromMegabytes(currentSpeed).Per(_oneSecond).Humanize()),
(long)(middle + currentSector), (long)currentSector,
(long)totalSize); (long)totalSize);
mhddLog.Write(middle + currentSector, _speedStopwatch.Elapsed.TotalMilliseconds, blocksToRead); mhddLog.Write(currentSector, _speedStopwatch.Elapsed.TotalMilliseconds, blocksToRead);
ibgLog.Write(middle + currentSector, currentSpeed * 1024); ibgLog.Write(currentSector, currentSpeed * 1024);
// Write empty data // Write empty data
_writeStopwatch.Restart(); _writeStopwatch.Restart();
outputFormat.WriteSectors(new byte[blockSize * blocksToRead], outputFormat.WriteSectors(new byte[blockSize * blocksToRead],
middle + currentSector, currentSector,
false, false,
blocksToRead, blocksToRead,
Enumerable.Repeat(SectorStatus.NotDumped, (int)blocksToRead).ToArray()); Enumerable.Repeat(SectorStatus.NotDumped, (int)blocksToRead).ToArray());
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds; imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
extents.Add(currentSector, blocksToRead, true);
_writeStopwatch.Stop(); _writeStopwatch.Stop();
currentSector += blocksToRead; currentSector += blocksToRead;

View File

@@ -51,49 +51,48 @@ public sealed partial class HxCStream
/// <inheritdoc /> /// <inheritdoc />
public ErrorNumber Open(IFilter imageFilter) public ErrorNumber Open(IFilter imageFilter)
{ {
_trackCaptures = []; _trackCaptures = [];
_trackFilePaths = []; _trackFilePaths = [];
_mediaTags = []; _mediaTags = [];
_imageInfo.Heads = 0; _imageInfo.Heads = 0;
_imageInfo.Cylinders = 0; _imageInfo.Cylinders = 0;
string filename = imageFilter.Filename; string filename = imageFilter.Filename;
string parentFolder = imageFilter.ParentFolder; string parentFolder = imageFilter.ParentFolder;
// We always open a single file - extract basename to find related track files // We always open a single file - extract basename to find related track files
if(!filename.EndsWith(".hxcstream", StringComparison.OrdinalIgnoreCase)) if(!filename.EndsWith(".hxcstream", StringComparison.OrdinalIgnoreCase)) return ErrorNumber.InvalidArgument;
return ErrorNumber.InvalidArgument;
// Extract basename - remove the track number pattern (e.g., "track00.0.hxcstream" -> "track") // Extract basename - remove the track number pattern (e.g., "track00.0.hxcstream" -> "track")
// The pattern is {basename}{cylinder:D2}.{head:D1}.hxcstream // The pattern is {basename}{cylinder:D2}.{head:D1}.hxcstream
string basename = filename[..^14]; // Remove ".XX.X.hxcstream" (14 chars) string basename = filename[..^14]; // Remove ".XX.X.hxcstream" (14 chars)
string fullBasename = Path.Combine(parentFolder, basename); string fullBasename = Path.Combine(parentFolder, basename);
AaruLogging.Debug(MODULE_NAME, "Opening HxCStream image from file: {0}", filename); AaruLogging.Debug(MODULE_NAME, "Opening HxCStream image from file: {0}", filename);
AaruLogging.Debug(MODULE_NAME, "Basename: {0}", basename); AaruLogging.Debug(MODULE_NAME, "Basename: {0}", basename);
AaruLogging.Debug(MODULE_NAME, "Full basename path: {0}", fullBasename); AaruLogging.Debug(MODULE_NAME, "Full basename path: {0}", fullBasename);
// Discover track files by trying different cylinder/head combinations // Discover track files by trying different cylinder/head combinations
var trackFiles = new Dictionary<(int cylinder, int head), string>(); var trackFiles = new Dictionary<(int cylinder, int head), string>();
int minCylinder = int.MaxValue; int minCylinder = int.MaxValue;
int maxCylinder = int.MinValue; int maxCylinder = int.MinValue;
int minHead = int.MaxValue; int minHead = int.MaxValue;
int maxHead = int.MinValue; int maxHead = int.MinValue;
// Search for related track files // Search for related track files
for(int cylinder = 0; cylinder < 166; cylinder++) for(var cylinder = 0; cylinder < 166; cylinder++)
{ {
for(int head = 0; head < 2; head++) for(var head = 0; head < 2; head++)
{ {
string trackfile = $"{fullBasename}{cylinder:D2}.{head:D1}.hxcstream"; var trackfile = $"{fullBasename}{cylinder:D2}.{head:D1}.hxcstream";
if(File.Exists(trackfile)) if(File.Exists(trackfile))
{ {
trackFiles[(cylinder, head)] = trackfile; trackFiles[(cylinder, head)] = trackfile;
minCylinder = Math.Min(minCylinder, cylinder); minCylinder = Math.Min(minCylinder, cylinder);
maxCylinder = Math.Max(maxCylinder, cylinder); maxCylinder = Math.Max(maxCylinder, cylinder);
minHead = Math.Min(minHead, head); minHead = Math.Min(minHead, head);
maxHead = Math.Max(maxHead, head); maxHead = Math.Max(maxHead, head);
} }
} }
} }
@@ -101,14 +100,20 @@ public sealed partial class HxCStream
if(trackFiles.Count == 0) return ErrorNumber.NoData; if(trackFiles.Count == 0) return ErrorNumber.NoData;
_imageInfo.Cylinders = (uint)(maxCylinder - minCylinder + 1); _imageInfo.Cylinders = (uint)(maxCylinder - minCylinder + 1);
_imageInfo.Heads = (uint)(maxHead - minHead + 1); _imageInfo.Heads = (uint)(maxHead - minHead + 1);
AaruLogging.Debug(MODULE_NAME, "Found {0} track files", trackFiles.Count); 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,
"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); AaruLogging.Debug(MODULE_NAME, "Head range: {0} to {1} ({2} heads)", minHead, maxHead, _imageInfo.Heads);
// Process each track file // Process each track file
int trackIndex = 0; var trackIndex = 0;
int totalTracks = trackFiles.Count; int totalTracks = trackFiles.Count;
AaruLogging.Debug(MODULE_NAME, "Processing {0} track files...", totalTracks); AaruLogging.Debug(MODULE_NAME, "Processing {0} track files...", totalTracks);
@@ -119,8 +124,12 @@ public sealed partial class HxCStream
string trackfile = trackFiles[key]; string trackfile = trackFiles[key];
_trackFilePaths.Add(trackfile); _trackFilePaths.Add(trackfile);
AaruLogging.Debug(MODULE_NAME, "Processing track {0}/{1}: cylinder {2}, head {3}", AaruLogging.Debug(MODULE_NAME,
trackIndex, totalTracks, key.cylinder, key.head); "Processing track {0}/{1}: cylinder {2}, head {3}",
trackIndex,
totalTracks,
key.cylinder,
key.head);
ErrorNumber error = ProcessTrackFile(trackfile, (uint)key.head, (ushort)key.cylinder); ErrorNumber error = ProcessTrackFile(trackfile, (uint)key.head, (ushort)key.cylinder);
@@ -141,15 +150,15 @@ public sealed partial class HxCStream
AaruLogging.Debug(MODULE_NAME, "Processing track file: {0} (head {1}, track {2})", trackfile, head, track); AaruLogging.Debug(MODULE_NAME, "Processing track file: {0} (head {1}, track {2})", trackfile, head, track);
using FileStream fileStream = File.OpenRead(trackfile); using FileStream fileStream = File.OpenRead(trackfile);
byte[] fileData = new byte[fileStream.Length]; var fileData = new byte[fileStream.Length];
fileStream.EnsureRead(fileData, 0, (int)fileStream.Length); fileStream.EnsureRead(fileData, 0, (int)fileStream.Length);
AaruLogging.Debug(MODULE_NAME, "Track file size: {0} bytes", fileData.Length); AaruLogging.Debug(MODULE_NAME, "Track file size: {0} bytes", fileData.Length);
uint samplePeriod = DEFAULT_RESOLUTION; // Default 40,000 ns (25 MHz) uint samplePeriod = DEFAULT_RESOLUTION; // Default 40,000 ns (25 MHz)
var fluxPulses = new List<uint>(); var fluxPulses = new List<uint>();
var ioStream = new List<ushort>(); var ioStream = new List<ushort>();
string metadata = null; string metadata = null;
long fileOffset = 0; long fileOffset = 0;
@@ -158,10 +167,13 @@ public sealed partial class HxCStream
if(fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>() > fileData.Length) if(fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>() > fileData.Length)
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
HxCStreamChunkHeader chunkHeader = Marshal.ByteArrayToStructureLittleEndian<HxCStreamChunkHeader>( HxCStreamChunkHeader chunkHeader =
fileData, (int)fileOffset, Marshal.SizeOf<HxCStreamChunkHeader>()); Marshal.ByteArrayToStructureLittleEndian<HxCStreamChunkHeader>(fileData,
(int)fileOffset,
Marshal.SizeOf<HxCStreamChunkHeader>());
AaruLogging.Debug(MODULE_NAME, "Chunk at offset {0}: signature = \"{1}\", size = {2}, packetNumber = {3}", AaruLogging.Debug(MODULE_NAME,
"Chunk at offset {0}: signature = \"{1}\", size = {2}, packetNumber = {3}",
fileOffset, fileOffset,
StringHandlers.CToString(chunkHeader.signature), StringHandlers.CToString(chunkHeader.signature),
chunkHeader.size, chunkHeader.size,
@@ -172,27 +184,28 @@ public sealed partial class HxCStream
if(chunkHeader.size > fileData.Length - fileOffset) return ErrorNumber.InvalidArgument; if(chunkHeader.size > fileData.Length - fileOffset) return ErrorNumber.InvalidArgument;
// Verify CRC32 - calculate CRC of chunk data (excluding the CRC itself) // Verify CRC32 - calculate CRC of chunk data (excluding the CRC itself)
byte[] chunkData = new byte[chunkHeader.size - 4]; var chunkData = new byte[chunkHeader.size - 4];
Array.Copy(fileData, (int)fileOffset, chunkData, 0, (int)(chunkHeader.size - 4)); Array.Copy(fileData, (int)fileOffset, chunkData, 0, (int)(chunkHeader.size - 4));
uint storedCrc = BitConverter.ToUInt32(fileData, (int)(fileOffset + chunkHeader.size - 4)); var storedCrc = BitConverter.ToUInt32(fileData, (int)(fileOffset + chunkHeader.size - 4));
if(!VerifyChunkCrc32(chunkData, storedCrc)) if(!VerifyChunkCrc32(chunkData, storedCrc))
{ {
AaruLogging.Error(MODULE_NAME, "CRC32 mismatch in chunk at offset {0}", fileOffset); AaruLogging.Error(MODULE_NAME, "CRC32 mismatch in chunk at offset {0}", fileOffset);
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
} }
AaruLogging.Debug(MODULE_NAME, "Chunk CRC32 verified successfully"); AaruLogging.Debug(MODULE_NAME, "Chunk CRC32 verified successfully");
long packetOffset = fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>(); long packetOffset = fileOffset + Marshal.SizeOf<HxCStreamChunkHeader>();
long chunkEnd = fileOffset + chunkHeader.size - 4; long chunkEnd = fileOffset + chunkHeader.size - 4;
while(packetOffset < chunkEnd) while(packetOffset < chunkEnd)
{ {
if(packetOffset + 4 > fileData.Length) break; if(packetOffset + 4 > fileData.Length) break;
uint type = BitConverter.ToUInt32(fileData, (int)packetOffset); var type = BitConverter.ToUInt32(fileData, (int)packetOffset);
AaruLogging.Debug(MODULE_NAME, "Packet at offset {0}: type = 0x{1:X8}", packetOffset, type); AaruLogging.Debug(MODULE_NAME, "Packet at offset {0}: type = 0x{1:X8}", packetOffset, type);
@@ -205,17 +218,20 @@ public sealed partial class HxCStream
HxCStreamMetadataHeader metadataHeader = HxCStreamMetadataHeader metadataHeader =
Marshal.ByteArrayToStructureLittleEndian<HxCStreamMetadataHeader>(fileData, Marshal.ByteArrayToStructureLittleEndian<HxCStreamMetadataHeader>(fileData,
(int)packetOffset, (int)packetOffset,
Marshal.SizeOf<HxCStreamMetadataHeader>()); Marshal.SizeOf<HxCStreamMetadataHeader>());
AaruLogging.Debug(MODULE_NAME, "Metadata packet: type = 0x{0:X8}, payloadSize = {1}", AaruLogging.Debug(MODULE_NAME,
metadataHeader.type, metadataHeader.payloadSize); "Metadata packet: type = 0x{0:X8}, payloadSize = {1}",
metadataHeader.type,
metadataHeader.payloadSize);
if(packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>() + metadataHeader.payloadSize > if(packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>() + metadataHeader.payloadSize >
fileData.Length) fileData.Length)
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
byte[] metadataBytes = new byte[metadataHeader.payloadSize]; var metadataBytes = new byte[metadataHeader.payloadSize];
Array.Copy(fileData, Array.Copy(fileData,
(int)packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>(), (int)packetOffset + Marshal.SizeOf<HxCStreamMetadataHeader>(),
metadataBytes, metadataBytes,
@@ -227,8 +243,7 @@ public sealed partial class HxCStream
AaruLogging.Debug(MODULE_NAME, "Metadata content: {0}", metadata); AaruLogging.Debug(MODULE_NAME, "Metadata content: {0}", metadata);
// Parse metadata and populate ImageInfo (only parse once, from first chunk) // Parse metadata and populate ImageInfo (only parse once, from first chunk)
if(string.IsNullOrEmpty(_imageInfo.Application)) if(string.IsNullOrEmpty(_imageInfo.Application)) ParseMetadata(metadata, _imageInfo);
ParseMetadata(metadata, _imageInfo);
// Check for sample rate in metadata // Check for sample rate in metadata
if(metadata.Contains("sample_rate_hz 25000000")) if(metadata.Contains("sample_rate_hz 25000000"))
@@ -258,38 +273,43 @@ public sealed partial class HxCStream
HxCStreamPackedIoHeader ioHeader = HxCStreamPackedIoHeader ioHeader =
Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedIoHeader>(fileData, Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedIoHeader>(fileData,
(int)packetOffset, (int)packetOffset,
Marshal.SizeOf<HxCStreamPackedIoHeader>()); Marshal.SizeOf<HxCStreamPackedIoHeader>());
AaruLogging.Debug(MODULE_NAME, AaruLogging.Debug(MODULE_NAME,
"Packed IO stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}", "Packed IO stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}",
ioHeader.type, ioHeader.payloadSize, ioHeader.packedSize, ioHeader.unpackedSize); ioHeader.type,
ioHeader.payloadSize,
ioHeader.packedSize,
ioHeader.unpackedSize);
if(packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize > if(packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize >
fileData.Length) fileData.Length)
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
byte[] packedData = new byte[ioHeader.packedSize]; var packedData = new byte[ioHeader.packedSize];
Array.Copy(fileData, Array.Copy(fileData,
(int)packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>(), (int)packetOffset + Marshal.SizeOf<HxCStreamPackedIoHeader>(),
packedData, packedData,
0, 0,
(int)ioHeader.packedSize); (int)ioHeader.packedSize);
byte[] unpackedData = new byte[ioHeader.unpackedSize]; var unpackedData = new byte[ioHeader.unpackedSize];
int decoded = LZ4.DecodeBuffer(packedData, unpackedData); int decoded = LZ4.DecodeBuffer(packedData, unpackedData);
if(decoded != ioHeader.unpackedSize) return ErrorNumber.InvalidArgument; if(decoded != ioHeader.unpackedSize) return ErrorNumber.InvalidArgument;
AaruLogging.Debug(MODULE_NAME, "Decompressed IO stream: {0} bytes -> {1} bytes ({2} 16-bit values)", AaruLogging.Debug(MODULE_NAME,
ioHeader.packedSize, decoded, decoded / 2); "Decompressed IO stream: {0} bytes -> {1} bytes ({2} 16-bit values)",
ioHeader.packedSize,
decoded,
decoded / 2);
// Convert to ushort array // Convert to ushort array
for(int i = 0; i < unpackedData.Length; i += 2) for(var i = 0; i < unpackedData.Length; i += 2)
{
if(i + 1 < unpackedData.Length) if(i + 1 < unpackedData.Length)
ioStream.Add(BitConverter.ToUInt16(unpackedData, i)); ioStream.Add(BitConverter.ToUInt16(unpackedData, i));
}
packetOffset += Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize; packetOffset += Marshal.SizeOf<HxCStreamPackedIoHeader>() + ioHeader.packedSize;
@@ -305,40 +325,50 @@ public sealed partial class HxCStream
HxCStreamPackedStreamHeader streamHeader = HxCStreamPackedStreamHeader streamHeader =
Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedStreamHeader>(fileData, Marshal.ByteArrayToStructureLittleEndian<HxCStreamPackedStreamHeader>(fileData,
(int)packetOffset, (int)packetOffset,
Marshal.SizeOf<HxCStreamPackedStreamHeader>()); Marshal.SizeOf<HxCStreamPackedStreamHeader>());
AaruLogging.Debug(MODULE_NAME, AaruLogging.Debug(MODULE_NAME,
"Packed flux stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}, numberOfPulses = {4}", "Packed flux stream packet: type = 0x{0:X8}, payloadSize = {1}, packedSize = {2}, unpackedSize = {3}, numberOfPulses = {4}",
streamHeader.type, streamHeader.payloadSize, streamHeader.packedSize, streamHeader.type,
streamHeader.unpackedSize, streamHeader.numberOfPulses); streamHeader.payloadSize,
streamHeader.packedSize,
streamHeader.unpackedSize,
streamHeader.numberOfPulses);
if(packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>() + streamHeader.packedSize > if(packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>() + streamHeader.packedSize >
fileData.Length) fileData.Length)
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
byte[] packedData = new byte[streamHeader.packedSize]; var packedData = new byte[streamHeader.packedSize];
Array.Copy(fileData, Array.Copy(fileData,
(int)packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>(), (int)packetOffset + Marshal.SizeOf<HxCStreamPackedStreamHeader>(),
packedData, packedData,
0, 0,
(int)streamHeader.packedSize); (int)streamHeader.packedSize);
byte[] unpackedData = new byte[streamHeader.unpackedSize]; var unpackedData = new byte[streamHeader.unpackedSize];
int decoded = LZ4.DecodeBuffer(packedData, unpackedData); int decoded = LZ4.DecodeBuffer(packedData, unpackedData);
if(decoded != streamHeader.unpackedSize) return ErrorNumber.InvalidArgument; if(decoded != streamHeader.unpackedSize) return ErrorNumber.InvalidArgument;
AaruLogging.Debug(MODULE_NAME, "Decompressed flux stream: {0} bytes -> {1} bytes", AaruLogging.Debug(MODULE_NAME,
streamHeader.packedSize, decoded); "Decompressed flux stream: {0} bytes -> {1} bytes",
streamHeader.packedSize,
decoded);
// Decode variable-length pulses // Decode variable-length pulses
uint numberOfPulses = streamHeader.numberOfPulses; uint numberOfPulses = streamHeader.numberOfPulses;
uint[] pulses = DecodeVariableLengthPulses(unpackedData, uint[] pulses = DecodeVariableLengthPulses(unpackedData,
streamHeader.unpackedSize, streamHeader.unpackedSize,
ref numberOfPulses); ref numberOfPulses);
AaruLogging.Debug(MODULE_NAME, "Decoded {0} flux pulses (expected {1})", pulses.Length, numberOfPulses); AaruLogging.Debug(MODULE_NAME,
"Decoded {0} flux pulses (expected {1})",
pulses.Length,
numberOfPulses);
if(pulses.Length != numberOfPulses) return ErrorNumber.InvalidArgument; if(pulses.Length != numberOfPulses) return ErrorNumber.InvalidArgument;
@@ -353,6 +383,7 @@ public sealed partial class HxCStream
} }
default: default:
AaruLogging.Error(MODULE_NAME, "Unknown packet type: 0x{0:X8}", type); AaruLogging.Error(MODULE_NAME, "Unknown packet type: 0x{0:X8}", type);
return ErrorNumber.InvalidArgument; return ErrorNumber.InvalidArgument;
} }
} }
@@ -360,20 +391,24 @@ public sealed partial class HxCStream
fileOffset += chunkHeader.size; fileOffset += chunkHeader.size;
} }
AaruLogging.Debug(MODULE_NAME, "Finished processing chunks. Total flux pulses: {0}, IO stream values: {1}", AaruLogging.Debug(MODULE_NAME,
fluxPulses.Count, ioStream.Count); "Finished processing chunks. Total flux pulses: {0}, IO stream values: {1}",
fluxPulses.Count,
ioStream.Count);
// Extract write protect status from IO stream // Extract write protect status from IO stream
// The write protect flag should be constant for a capture, so we only need to check the first value // The write protect flag should be constant for a capture, so we only need to check the first value
if(ioStream.Count > 0 && !_mediaTags.ContainsKey(MediaTagType.Floppy_WriteProtection)) if(ioStream.Count > 0 && !_mediaTags.ContainsKey(MediaTagType.Floppy_WriteProtection))
{ {
IoStreamState firstState = DecodeIoStreamValue(ioStream[0]); IoStreamState firstState = DecodeIoStreamValue(ioStream[0]);
bool writeProtected = firstState.WriteProtect; bool writeProtected = firstState.WriteProtect;
// Store as boolean: 1 byte where 0 = false (not write protected), 1 = true (write protected) // Store as boolean: 1 byte where 0 = false (not write protected), 1 = true (write protected)
_mediaTags[MediaTagType.Floppy_WriteProtection] = writeProtected ? [1] : [0]; _mediaTags[MediaTagType.Floppy_WriteProtection] = writeProtected ? [1] : [0];
AaruLogging.Debug(MODULE_NAME, "Write protect status from IO stream: {0}", writeProtected ? "write protected" : "not write protected"); AaruLogging.Debug(MODULE_NAME,
"Write protect status from IO stream: {0}",
writeProtected ? "write protected" : "not write protected");
} }
// Extract index signals from IO stream // Extract index signals from IO stream
@@ -382,26 +417,23 @@ public sealed partial class HxCStream
if(ioStream.Count > 0) if(ioStream.Count > 0)
{ {
IoStreamState previousState = DecodeIoStreamValue(ioStream[0]); IoStreamState previousState = DecodeIoStreamValue(ioStream[0]);
bool oldIndex = previousState.IndexSignal; bool oldIndex = previousState.IndexSignal;
bool startsAtIndex = previousState.IndexSignal; bool startsAtIndex = previousState.IndexSignal;
if(startsAtIndex) if(startsAtIndex) indexPositions.Add(0);
{
indexPositions.Add(0);
}
uint totalTicks = 0; uint totalTicks = 0;
int pulseIndex = 0; var pulseIndex = 0;
for(int i = 0; i < ioStream.Count; i++) for(var i = 0; i < ioStream.Count; i++)
{ {
IoStreamState currentState = DecodeIoStreamValue(ioStream[i]); IoStreamState currentState = DecodeIoStreamValue(ioStream[i]);
bool currentIndex = currentState.IndexSignal; bool currentIndex = currentState.IndexSignal;
if(currentIndex != oldIndex && currentIndex) if(currentIndex != oldIndex && currentIndex)
{ {
// Index signal transition to high // Index signal transition to high
// Map to flux stream position // Map to flux stream position
uint targetTicks = (uint)(i * 16); var targetTicks = (uint)(i * 16);
while(pulseIndex < fluxPulses.Count && totalTicks < targetTicks) while(pulseIndex < fluxPulses.Count && totalTicks < targetTicks)
{ {
@@ -431,8 +463,13 @@ public sealed partial class HxCStream
indexPositions = indexPositions.ToArray() indexPositions = indexPositions.ToArray()
}; };
AaruLogging.Debug(MODULE_NAME, "Created track capture: head = {0}, track = {1}, resolution = {2} ns, fluxPulses = {3}, indexPositions = {4}", AaruLogging.Debug(MODULE_NAME,
capture.head, capture.track, capture.resolution, capture.fluxPulses.Length, capture.indexPositions.Length); "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); _trackCaptures.Add(capture);
@@ -526,7 +563,7 @@ public sealed partial class HxCStream
out ulong indexResolution, out ulong dataResolution, out byte[] indexBuffer, out ulong indexResolution, out ulong dataResolution, out byte[] indexBuffer,
out byte[] dataBuffer) out byte[] dataBuffer)
{ {
indexBuffer = dataBuffer = null; indexBuffer = dataBuffer = null;
indexResolution = dataResolution = 0; indexResolution = dataResolution = 0;
// HxCStream doesn't support subtracks - only subTrack 0 is valid // HxCStream doesn't support subtracks - only subTrack 0 is valid
@@ -535,8 +572,8 @@ public sealed partial class HxCStream
// HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0) // HxCStream has one file per track/head, which results in exactly one capture (captureIndex 0)
if(captureIndex != 0) return ErrorNumber.OutOfRange; if(captureIndex != 0) return ErrorNumber.OutOfRange;
ErrorNumber error = ReadFluxResolution(head, track, subTrack, captureIndex, out indexResolution, ErrorNumber error =
out dataResolution); ReadFluxResolution(head, track, subTrack, captureIndex, out indexResolution, out dataResolution);
if(error != ErrorNumber.NoError) return error; if(error != ErrorNumber.NoError) return error;
@@ -565,15 +602,14 @@ public sealed partial class HxCStream
if(capture == null) return ErrorNumber.OutOfRange; if(capture == null) return ErrorNumber.OutOfRange;
var tmpBuffer = new List<byte>(); var tmpBuffer = new List<byte>();
uint previousTicks = 0; uint previousTicks = 0;
foreach(uint indexPos in capture.indexPositions) foreach(uint indexPos in capture.indexPositions)
{ {
// Convert index position to ticks // Convert index position to ticks
uint ticks = 0; uint ticks = 0;
for(uint i = 0; i < indexPos && i < capture.fluxPulses.Length; i++) for(uint i = 0; i < indexPos && i < capture.fluxPulses.Length; i++) ticks += capture.fluxPulses[i];
ticks += capture.fluxPulses[i];
uint deltaTicks = ticks - previousTicks; uint deltaTicks = ticks - previousTicks;
tmpBuffer.AddRange(UInt32ToFluxRepresentation(deltaTicks)); tmpBuffer.AddRange(UInt32ToFluxRepresentation(deltaTicks));
@@ -586,8 +622,7 @@ public sealed partial class HxCStream
} }
/// <inheritdoc /> /// <inheritdoc />
public ErrorNumber public ErrorNumber ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
ReadFluxDataCapture(uint head, ushort track, byte subTrack, uint captureIndex, out byte[] buffer)
{ {
buffer = null; buffer = null;
@@ -638,8 +673,12 @@ public sealed partial class HxCStream
{ {
// Group captures by head/track to assign capture indices // Group captures by head/track to assign capture indices
// Note: HxCStream doesn't support subtracks, so subTrack is always 0 // Note: HxCStream doesn't support subtracks, so subTrack is always 0
var grouped = _trackCaptures.GroupBy(c => new { c.head, c.track }) var grouped = _trackCaptures.GroupBy(c => new
.ToList(); {
c.head,
c.track
})
.ToList();
foreach(var group in grouped) foreach(var group in grouped)
{ {
@@ -678,6 +717,7 @@ public sealed partial class HxCStream
{ {
buffer = new byte[tagData.Length]; buffer = new byte[tagData.Length];
Array.Copy(tagData, buffer, tagData.Length); Array.Copy(tagData, buffer, tagData.Length);
return ErrorNumber.NoError; return ErrorNumber.NoError;
} }
@@ -687,8 +727,9 @@ public sealed partial class HxCStream
/// <inheritdoc /> /// <inheritdoc />
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
{ {
buffer = null; buffer = null;
sectorStatus = SectorStatus.NotDumped; sectorStatus = SectorStatus.NotDumped;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
@@ -696,8 +737,9 @@ public sealed partial class HxCStream
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer, public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
out SectorStatus sectorStatus) out SectorStatus sectorStatus)
{ {
buffer = null; buffer = null;
sectorStatus = SectorStatus.NotDumped; sectorStatus = SectorStatus.NotDumped;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
@@ -705,8 +747,9 @@ public sealed partial class HxCStream
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer, public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus) out SectorStatus[] sectorStatus)
{ {
buffer = null; buffer = null;
sectorStatus = null; sectorStatus = null;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
@@ -714,8 +757,9 @@ public sealed partial class HxCStream
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer, public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
out SectorStatus[] sectorStatus) out SectorStatus[] sectorStatus)
{ {
buffer = null; buffer = null;
sectorStatus = null; sectorStatus = null;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
@@ -724,6 +768,7 @@ public sealed partial class HxCStream
out byte[] buffer) out byte[] buffer)
{ {
buffer = null; buffer = null;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
@@ -731,8 +776,9 @@ public sealed partial class HxCStream
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer) public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
{ {
buffer = null; buffer = null;
return ErrorNumber.NotImplemented; return ErrorNumber.NotImplemented;
} }
#endregion #endregion
} }

View File

@@ -1524,7 +1524,7 @@
<value>[fuchsiaLOCATE[/] [slateblue1]funciona.[/]</value> <value>[fuchsiaLOCATE[/] [slateblue1]funciona.[/]</value>
</data> </data>
<data name="Locking_drive" xml:space="preserve"> <data name="Locking_drive" xml:space="preserve">
<value>Bloqueando la unidad.</value> <value>[slateblue1]Bloqueando la unidad.[/]</value>
</data> </data>
<data name="Log_section_separator" xml:space="preserve"> <data name="Log_section_separator" xml:space="preserve">
<value>####################################################################</value> <value>####################################################################</value>

View File

@@ -1734,7 +1734,7 @@
<value>[slateblue1]Getting video partition size[/]</value> <value>[slateblue1]Getting video partition size[/]</value>
</data> </data>
<data name="Locking_drive" xml:space="preserve"> <data name="Locking_drive" xml:space="preserve">
<value>Locking drive.</value> <value>[slateblue1]Locking drive.[/]</value>
</data> </data>
<data name="Cannot_lock_drive_not_continuing" xml:space="preserve"> <data name="Cannot_lock_drive_not_continuing" xml:space="preserve">
<value>[red]Cannot lock drive, not continuing.[/]</value> <value>[red]Cannot lock drive, not continuing.[/]</value>
@@ -1779,7 +1779,7 @@
<value>[red]Cannot decode PFI.[/]</value> <value>[red]Cannot decode PFI.[/]</value>
</data> </data>
<data name="WxRipper_PFI_Data_Area_Start_PSN_0_sectors" xml:space="preserve"> <data name="WxRipper_PFI_Data_Area_Start_PSN_0_sectors" xml:space="preserve">
<value>[slateblue1][italic]WxRipper[/] PFI's Data Area Start PSN: [lime]{0}[/lime] sectors[/]</value> <value>[slateblue1][italic]WxRipper[/] PFI's Data Area Start PSN: [lime]{0}[/] sectors[/]</value>
</data> </data>
<data name="WxRipper_PFI_Layer_0_End_PSN_0_sectors" xml:space="preserve"> <data name="WxRipper_PFI_Layer_0_End_PSN_0_sectors" xml:space="preserve">
<value>[slateblue1][italic]WxRipper[/] PFI's Layer [teal]0[/] End PSN: [lime]{0}[/] sectors[/]</value> <value>[slateblue1][italic]WxRipper[/] PFI's Layer [teal]0[/] End PSN: [lime]{0}[/] sectors[/]</value>