mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 17:56:18 +00:00
[CHD] Add support for v5 images. Fixes #106
This commit is contained in:
@@ -71,12 +71,14 @@ public sealed partial class Chd : IOpticalMediaImage, IVerifiableImage
|
||||
int _maxSectorCache;
|
||||
Dictionary<ulong, uint> _offsetmap;
|
||||
List<Partition> _partitions;
|
||||
byte[] _rawMap;
|
||||
SectorBuilder _sectorBuilder;
|
||||
Dictionary<ulong, byte[]> _sectorCache;
|
||||
uint _sectorsPerHunk;
|
||||
bool _swapAudio;
|
||||
uint _totalHunks;
|
||||
Dictionary<uint, Track> _tracks;
|
||||
uint _unitBytes;
|
||||
|
||||
public Chd() => _imageInfo = new ImageInfo
|
||||
{
|
||||
|
||||
@@ -76,6 +76,15 @@ public sealed partial class Chd
|
||||
|
||||
const int MAX_CACHE_SIZE = 16777216;
|
||||
|
||||
const int CD_MAX_SECTOR_DATA = 2352;
|
||||
const int CD_MAX_SUBCODE_DATA = 96;
|
||||
const int CD_FRAME_SIZE = CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA;
|
||||
|
||||
static readonly byte[] _cdSyncHeader =
|
||||
{
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
|
||||
};
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
const string TRACK_TYPE_MODE1 = "MODE1";
|
||||
const string TRACK_TYPE_MODE1_2K = "MODE1/2048";
|
||||
|
||||
@@ -81,6 +81,46 @@ public sealed partial class Chd
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: CompressionV5
|
||||
|
||||
enum CompressionV5 : uint
|
||||
{
|
||||
None = 0,
|
||||
Zlib = 0x7A6C6962,
|
||||
Lzma = 0x6C7A6D61,
|
||||
Huffman = 0x68756666,
|
||||
Flac = 0x666C6163,
|
||||
Zstd = 0x7A737464,
|
||||
CdZlib = 0x63647A6C,
|
||||
CdLzma = 0x63646C7A,
|
||||
CdFlac = 0x6364666C,
|
||||
CdZstd = 0x63647A73
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: EntryFlagsV5
|
||||
|
||||
enum EntryFlagsV5 : byte
|
||||
{
|
||||
/// <summary>Compressed with compressor 0</summary>
|
||||
Compressed0 = 0,
|
||||
/// <summary>Compressed with compressor 1</summary>
|
||||
Compressed1 = 1,
|
||||
/// <summary>Compressed with compressor 2</summary>
|
||||
Compressed2 = 2,
|
||||
/// <summary>Compressed with compressor 3</summary>
|
||||
Compressed3 = 3,
|
||||
/// <summary>Uncompressed</summary>
|
||||
Uncompressed = 4,
|
||||
/// <summary>Same as another hunk in file</summary>
|
||||
SelfHunk = 5,
|
||||
/// <summary>Same as another hunk in parent</summary>
|
||||
ParentHunk = 6
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: SubTypeOld
|
||||
|
||||
enum SubTypeOld : uint
|
||||
|
||||
@@ -229,14 +229,23 @@ public sealed partial class Chd
|
||||
if(_hdrCompression == 0)
|
||||
{
|
||||
buffer = new byte[_bytesPerHunk];
|
||||
_imageStream.Seek(_hunkTableSmall[hunkNo] * _bytesPerHunk, SeekOrigin.Begin);
|
||||
_imageStream.EnsureRead(buffer, 0, buffer.Length);
|
||||
|
||||
if(_hunkTableSmall[hunkNo] == 0)
|
||||
{
|
||||
// Entry value 0 means zeroed hunk
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageStream.Seek((long)_hunkTableSmall[hunkNo] * _bytesPerHunk, SeekOrigin.Begin);
|
||||
_imageStream.EnsureRead(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(Localization.Compressed_v5_hunks_not_yet_supported);
|
||||
ErrorNumber err = GetHunkV5Compressed(hunkNo, out buffer);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
if(err != ErrorNumber.NoError) return err;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -252,4 +261,884 @@ public sealed partial class Chd
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber GetHunkV5Compressed(ulong hunkNo, out byte[] buffer)
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
if(hunkNo >= _totalHunks) return ErrorNumber.OutOfRange;
|
||||
|
||||
var mapOffset = (int)(hunkNo * 12);
|
||||
byte compType = _rawMap[mapOffset];
|
||||
|
||||
var compLength = (uint)(_rawMap[mapOffset + 1] << 16 | _rawMap[mapOffset + 2] << 8 | _rawMap[mapOffset + 3]);
|
||||
|
||||
ulong fileOffset = (ulong)_rawMap[mapOffset + 4] << 40 |
|
||||
(ulong)_rawMap[mapOffset + 5] << 32 |
|
||||
(ulong)_rawMap[mapOffset + 6] << 24 |
|
||||
(ulong)_rawMap[mapOffset + 7] << 16 |
|
||||
(ulong)_rawMap[mapOffset + 8] << 8 |
|
||||
_rawMap[mapOffset + 9];
|
||||
|
||||
switch((EntryFlagsV5)compType)
|
||||
{
|
||||
case EntryFlagsV5.Compressed0:
|
||||
case EntryFlagsV5.Compressed1:
|
||||
case EntryFlagsV5.Compressed2:
|
||||
case EntryFlagsV5.Compressed3:
|
||||
{
|
||||
uint[] codecs = [_hdrCompression, _hdrCompression1, _hdrCompression2, _hdrCompression3];
|
||||
|
||||
uint codec = codecs[compType];
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"GetHunkV5Compressed: hunk={0} compType={1} codec=0x{2:X8} compLength={3} fileOffset={4}",
|
||||
hunkNo,
|
||||
compType,
|
||||
codec,
|
||||
compLength,
|
||||
fileOffset);
|
||||
|
||||
var compData = new byte[compLength];
|
||||
_imageStream.Seek((long)fileOffset, SeekOrigin.Begin);
|
||||
_imageStream.EnsureRead(compData, 0, compData.Length);
|
||||
|
||||
buffer = new byte[_bytesPerHunk];
|
||||
|
||||
ErrorNumber err = DecompressV5Codec(codec, compData, (int)compLength, buffer, (int)_bytesPerHunk);
|
||||
|
||||
if(err != ErrorNumber.NoError) return err;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EntryFlagsV5.Uncompressed:
|
||||
buffer = new byte[_bytesPerHunk];
|
||||
_imageStream.Seek((long)fileOffset, SeekOrigin.Begin);
|
||||
_imageStream.EnsureRead(buffer, 0, buffer.Length);
|
||||
|
||||
break;
|
||||
|
||||
case EntryFlagsV5.SelfHunk:
|
||||
return GetHunk(fileOffset, out buffer);
|
||||
|
||||
case EntryFlagsV5.ParentHunk:
|
||||
AaruLogging.Error(Localization.Parent_images_are_not_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
|
||||
default:
|
||||
AaruLogging.Error(string.Format(Localization.Hunk_type_0_is_not_supported, compType));
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressV5Codec(uint codec, byte[] src, int srcLen, byte[] dest, int destLen)
|
||||
{
|
||||
switch((CompressionV5)codec)
|
||||
{
|
||||
case CompressionV5.Zlib:
|
||||
{
|
||||
var zStream = new DeflateStream(new MemoryStream(src, 0, srcLen), CompressionMode.Decompress);
|
||||
|
||||
int read = zStream.EnsureRead(dest, 0, destLen);
|
||||
zStream.Close();
|
||||
|
||||
if(read != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
read,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Lzma:
|
||||
{
|
||||
byte[] properties = ComputeLzmaProperties(_bytesPerHunk);
|
||||
int decoded = LZMA.DecodeBuffer(src, dest, properties);
|
||||
|
||||
if(decoded != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Huffman:
|
||||
{
|
||||
ErrorNumber err = DecompressHuffmanHunk(src, srcLen, dest, destLen);
|
||||
|
||||
if(err != ErrorNumber.NoError) return err;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Flac:
|
||||
{
|
||||
if(!FLAC.IsSupported)
|
||||
{
|
||||
AaruLogging.Error(Localization.FLAC_is_not_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
bool swapEndian = src[0] == 'B';
|
||||
var flacData = new byte[srcLen - 1];
|
||||
Array.Copy(src, 1, flacData, 0, srcLen - 1);
|
||||
|
||||
int decoded = FLAC.DecodeBuffer(flacData, dest);
|
||||
|
||||
if(decoded != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
if(swapEndian)
|
||||
{
|
||||
for(var i = 0; i < destLen; i += 2) (dest[i], dest[i + 1]) = (dest[i + 1], dest[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Zstd:
|
||||
{
|
||||
if(!ZSTD.IsSupported)
|
||||
{
|
||||
AaruLogging.Error(Localization.FLAC_is_not_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
int decoded = ZSTD.DecodeBuffer(src, dest);
|
||||
|
||||
if(decoded != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.CdZlib:
|
||||
return DecompressCdCodec(CompressionV5.Zlib, CompressionV5.Zlib, src, srcLen, dest, destLen);
|
||||
|
||||
case CompressionV5.CdLzma:
|
||||
return DecompressCdCodec(CompressionV5.Lzma, CompressionV5.Zlib, src, srcLen, dest, destLen);
|
||||
|
||||
case CompressionV5.CdZstd:
|
||||
return DecompressCdCodec(CompressionV5.Zstd, CompressionV5.Zstd, src, srcLen, dest, destLen);
|
||||
|
||||
case CompressionV5.CdFlac:
|
||||
return DecompressCdFlac(src, srcLen, dest, destLen);
|
||||
|
||||
default:
|
||||
AaruLogging.Error(string.Format(Localization.Unsupported_compression_0, (CompressionV5)codec));
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressCdCodec(CompressionV5 baseCodec, CompressionV5 subCodec, byte[] src, int srcLen, byte[] dest,
|
||||
int destLen)
|
||||
{
|
||||
int frames = destLen / CD_FRAME_SIZE;
|
||||
int compLenBytes = destLen < 65536 ? 2 : 3;
|
||||
int eccBytes = (frames + 7) / 8;
|
||||
int headerBytes = eccBytes + compLenBytes;
|
||||
|
||||
int baseCompLen = compLenBytes == 2
|
||||
? src[eccBytes] << 8 | src[eccBytes + 1]
|
||||
: src[eccBytes] << 16 | src[eccBytes + 1] << 8 | src[eccBytes + 2];
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"DecompressCdCodec: srcLen={0} destLen={1} frames={2} eccBytes={3} compLenBytes={4} headerBytes={5} baseCompLen={6} headerBytes+baseCompLen={7}",
|
||||
srcLen,
|
||||
destLen,
|
||||
frames,
|
||||
eccBytes,
|
||||
compLenBytes,
|
||||
headerBytes,
|
||||
baseCompLen,
|
||||
headerBytes + baseCompLen);
|
||||
|
||||
// Decompress sector data
|
||||
var sectorBuf = new byte[frames * CD_MAX_SECTOR_DATA];
|
||||
ErrorNumber err = DecompressRawCodec(baseCodec, src, headerBytes, baseCompLen, sectorBuf, sectorBuf.Length);
|
||||
|
||||
if(err != ErrorNumber.NoError) return err;
|
||||
|
||||
// Decompress subcode data
|
||||
int subOffset = headerBytes + baseCompLen;
|
||||
int subCompLen = srcLen - subOffset;
|
||||
var subBuf = new byte[frames * CD_MAX_SUBCODE_DATA];
|
||||
|
||||
err = DecompressRawCodec(subCodec, src, subOffset, subCompLen, subBuf, subBuf.Length);
|
||||
|
||||
if(err != ErrorNumber.NoError) return err;
|
||||
|
||||
// Reassemble interleaved frames
|
||||
for(var i = 0; i < frames; i++)
|
||||
{
|
||||
Array.Copy(sectorBuf, i * CD_MAX_SECTOR_DATA, dest, i * CD_FRAME_SIZE, CD_MAX_SECTOR_DATA);
|
||||
|
||||
Array.Copy(subBuf,
|
||||
i * CD_MAX_SUBCODE_DATA,
|
||||
dest,
|
||||
i * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA,
|
||||
CD_MAX_SUBCODE_DATA);
|
||||
|
||||
// Restore ECC/sync if bitmap bit set
|
||||
int bytePos = i / 8;
|
||||
int bitPos = i % 8;
|
||||
|
||||
if((src[bytePos] & 1 << bitPos) == 0) continue;
|
||||
|
||||
// Copy sync header
|
||||
Array.Copy(_cdSyncHeader, 0, dest, i * CD_FRAME_SIZE, 12);
|
||||
|
||||
// Reconstruct ECC for this sector
|
||||
var sectorData = new byte[CD_MAX_SECTOR_DATA];
|
||||
Array.Copy(dest, i * CD_FRAME_SIZE, sectorData, 0, CD_MAX_SECTOR_DATA);
|
||||
_sectorBuilder?.ReconstructEcc(ref sectorData, TrackType.CdMode1);
|
||||
Array.Copy(sectorData, 0, dest, i * CD_FRAME_SIZE, CD_MAX_SECTOR_DATA);
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressCdFlac(byte[] src, int srcLen, byte[] dest, int destLen)
|
||||
{
|
||||
if(!FLAC.IsSupported)
|
||||
{
|
||||
AaruLogging.Error(Localization.FLAC_is_not_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
int frames = destLen / CD_FRAME_SIZE;
|
||||
|
||||
bool swapEndian = src[0] == 'B';
|
||||
|
||||
// Decode FLAC audio data (sector data)
|
||||
var flacSrc = new byte[srcLen - 1];
|
||||
Array.Copy(src, 1, flacSrc, 0, srcLen - 1);
|
||||
|
||||
var sectorBuf = new byte[frames * CD_MAX_SECTOR_DATA];
|
||||
int decoded = FLAC.DecodeBuffer(flacSrc, sectorBuf);
|
||||
|
||||
if(decoded != frames * CD_MAX_SECTOR_DATA)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
frames * CD_MAX_SECTOR_DATA));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
if(swapEndian)
|
||||
{
|
||||
for(var i = 0; i < sectorBuf.Length; i += 2)
|
||||
(sectorBuf[i], sectorBuf[i + 1]) = (sectorBuf[i + 1], sectorBuf[i]);
|
||||
}
|
||||
|
||||
// The FLAC stream length is unknown without consumed byte tracking,
|
||||
// so decompress subcode by trying from the end.
|
||||
// The subcode compressed data is everything after the FLAC stream.
|
||||
// We try to find it by scanning backwards for valid zlib data.
|
||||
var subBuf = new byte[frames * CD_MAX_SUBCODE_DATA];
|
||||
var subDecoded = false;
|
||||
|
||||
// Try binary search approach: FLAC must produce exactly frames*2352 bytes
|
||||
// The compressed FLAC stream is between src[1] and some offset before srcLen
|
||||
// Subcode zlib data follows immediately after FLAC data
|
||||
for(int flacEnd = srcLen - 1; flacEnd >= 2; flacEnd--)
|
||||
{
|
||||
try
|
||||
{
|
||||
int subLen = srcLen - 1 - flacEnd;
|
||||
var subSrc = new byte[subLen];
|
||||
Array.Copy(src, 1 + flacEnd, subSrc, 0, subLen);
|
||||
|
||||
var zStream = new DeflateStream(new MemoryStream(subSrc), CompressionMode.Decompress);
|
||||
int read = zStream.EnsureRead(subBuf, 0, subBuf.Length);
|
||||
zStream.Close();
|
||||
|
||||
if(read == subBuf.Length)
|
||||
{
|
||||
subDecoded = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(InvalidDataException)
|
||||
{
|
||||
// Invalid zlib data at this offset, try next
|
||||
}
|
||||
catch(InvalidOperationException)
|
||||
{
|
||||
// Invalid zlib data at this offset, try next
|
||||
}
|
||||
}
|
||||
|
||||
if(!subDecoded) Array.Clear(subBuf, 0, subBuf.Length);
|
||||
|
||||
// Reassemble interleaved frames
|
||||
for(var i = 0; i < frames; i++)
|
||||
{
|
||||
Array.Copy(sectorBuf, i * CD_MAX_SECTOR_DATA, dest, i * CD_FRAME_SIZE, CD_MAX_SECTOR_DATA);
|
||||
|
||||
Array.Copy(subBuf,
|
||||
i * CD_MAX_SUBCODE_DATA,
|
||||
dest,
|
||||
i * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA,
|
||||
CD_MAX_SUBCODE_DATA);
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressRawCodec(CompressionV5 codec, byte[] src, int srcOffset, int srcLen, byte[] dest, int destLen)
|
||||
{
|
||||
switch(codec)
|
||||
{
|
||||
case CompressionV5.Zlib:
|
||||
{
|
||||
var zStream = new DeflateStream(new MemoryStream(src, srcOffset, srcLen), CompressionMode.Decompress);
|
||||
|
||||
int read = zStream.EnsureRead(dest, 0, destLen);
|
||||
zStream.Close();
|
||||
|
||||
if(read != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
read,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Lzma:
|
||||
{
|
||||
byte[] properties = ComputeLzmaProperties((uint)destLen);
|
||||
byte[] lzmaSrc;
|
||||
|
||||
if(src[srcOffset] != 0x00)
|
||||
{
|
||||
// Old-style LZMA stream (LZMA SDK before 2018):
|
||||
// Byte 0 is part of the range coder code (bytes 0-3 = initial code).
|
||||
// Modern LZMA SDK expects byte 0 = 0x00 (check only), bytes 1-4 = code.
|
||||
// Prepend a 0x00 check byte to make old-style data compatible.
|
||||
lzmaSrc = new byte[srcLen + 1];
|
||||
lzmaSrc[0] = 0x00;
|
||||
Array.Copy(src, srcOffset, lzmaSrc, 1, srcLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
lzmaSrc = new byte[srcLen];
|
||||
Array.Copy(src, srcOffset, lzmaSrc, 0, srcLen);
|
||||
}
|
||||
|
||||
int decoded = LZMA.DecodeBuffer(lzmaSrc, dest, properties);
|
||||
|
||||
if(decoded != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CompressionV5.Zstd:
|
||||
{
|
||||
if(!ZSTD.IsSupported)
|
||||
{
|
||||
AaruLogging.Error(Localization.FLAC_is_not_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
}
|
||||
|
||||
var zstdSrc = new byte[srcLen];
|
||||
Array.Copy(src, srcOffset, zstdSrc, 0, srcLen);
|
||||
|
||||
int decoded = ZSTD.DecodeBuffer(zstdSrc, dest);
|
||||
|
||||
if(decoded != destLen)
|
||||
{
|
||||
AaruLogging.Error(string.Format(Localization
|
||||
.Unable_to_decompress_hunk_correctly_got_0_bytes_expected_1,
|
||||
decoded,
|
||||
destLen));
|
||||
|
||||
return ErrorNumber.InOutError;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
AaruLogging.Error(string.Format(Localization.Unsupported_compression_0, codec));
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
ErrorNumber DecompressHuffmanHunk(byte[] src, int srcLen, byte[] dest, int destLen)
|
||||
{
|
||||
var bitstream = new BitStreamReader(src, srcLen);
|
||||
|
||||
// Import Huffman tree with RLE (256 symbols, maxbits=16)
|
||||
var numbits = 5; // maxbits >= 16
|
||||
|
||||
var codeLengths = new int[256];
|
||||
var curNode = 0;
|
||||
|
||||
while(curNode < 256)
|
||||
{
|
||||
int nodeBits = bitstream.Read(numbits);
|
||||
|
||||
if(nodeBits != 1)
|
||||
codeLengths[curNode++] = nodeBits;
|
||||
else
|
||||
{
|
||||
nodeBits = bitstream.Read(numbits);
|
||||
|
||||
if(nodeBits == 1)
|
||||
codeLengths[curNode++] = 1;
|
||||
else
|
||||
{
|
||||
int repCount = bitstream.Read(numbits) + 3;
|
||||
|
||||
for(var r = 0; r < repCount && curNode < 256; r++) codeLengths[curNode++] = nodeBits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build canonical Huffman lookup table
|
||||
var maxBits = 16;
|
||||
int[] lookup = BuildHuffmanLookup(codeLengths, 256, maxBits);
|
||||
|
||||
if(lookup == null)
|
||||
{
|
||||
AaruLogging.Error(Localization.Invalid_hunk_found);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Decode each byte
|
||||
for(var i = 0; i < destLen; i++)
|
||||
{
|
||||
int bits = bitstream.Peek(maxBits);
|
||||
int val = lookup[bits];
|
||||
int symbol = val >> 5;
|
||||
int codeLen = val & 0x1F;
|
||||
bitstream.Remove(codeLen);
|
||||
dest[i] = (byte)symbol;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
static ErrorNumber DecompressV5Map(Stream stream, ulong mapOffset, uint totalHunks, uint hunkBytes, uint unitBytes,
|
||||
out byte[] rawMap)
|
||||
{
|
||||
rawMap = null;
|
||||
|
||||
// Read 16-byte map header
|
||||
var mapHeader = new byte[16];
|
||||
stream.Seek((long)mapOffset, SeekOrigin.Begin);
|
||||
stream.EnsureRead(mapHeader, 0, 16);
|
||||
|
||||
var mapBytes = BigEndianBitConverter.ToUInt32(mapHeader, 0);
|
||||
|
||||
ulong firstOffs = (ulong)mapHeader[4] << 40 |
|
||||
(ulong)mapHeader[5] << 32 |
|
||||
(ulong)mapHeader[6] << 24 |
|
||||
(ulong)mapHeader[7] << 16 |
|
||||
(ulong)mapHeader[8] << 8 |
|
||||
mapHeader[9];
|
||||
|
||||
var mapCrc = (ushort)(mapHeader[10] << 8 | mapHeader[11]);
|
||||
|
||||
int lengthBits = mapHeader[12];
|
||||
int selfBits = mapHeader[13];
|
||||
int parentBits = mapHeader[14];
|
||||
|
||||
// Read compressed map data
|
||||
var mapData = new byte[mapBytes];
|
||||
stream.EnsureRead(mapData, 0, (int)mapBytes);
|
||||
|
||||
var bitstream = new BitStreamReader(mapData, (int)mapBytes);
|
||||
|
||||
// Import Huffman tree with RLE (16 symbols, maxbits=8)
|
||||
var numbits = 4; // maxbits >= 8
|
||||
|
||||
var codeLengths = new int[16];
|
||||
var curNode = 0;
|
||||
|
||||
while(curNode < 16)
|
||||
{
|
||||
int nodeBits = bitstream.Read(numbits);
|
||||
|
||||
if(nodeBits != 1)
|
||||
codeLengths[curNode++] = nodeBits;
|
||||
else
|
||||
{
|
||||
nodeBits = bitstream.Read(numbits);
|
||||
|
||||
if(nodeBits == 1)
|
||||
codeLengths[curNode++] = 1;
|
||||
else
|
||||
{
|
||||
int treeRepCount = bitstream.Read(numbits) + 3;
|
||||
|
||||
for(var r = 0; r < treeRepCount && curNode < 16; r++) codeLengths[curNode++] = nodeBits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build canonical Huffman lookup table
|
||||
var maxBits = 8;
|
||||
|
||||
int[] lookup = BuildHuffmanLookup(codeLengths, 16, maxBits);
|
||||
|
||||
if(lookup == null)
|
||||
{
|
||||
AaruLogging.Error(Localization.Invalid_hunk_found);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Pass 1: Decode compression types (literal port from working Python)
|
||||
var compTypes = new int[totalHunks];
|
||||
var repCount = 0;
|
||||
var lastComp = 0;
|
||||
|
||||
for(uint hunk = 0; hunk < totalHunks; hunk++)
|
||||
{
|
||||
if(repCount > 0)
|
||||
{
|
||||
compTypes[hunk] = lastComp;
|
||||
repCount -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int bits = bitstream.Peek(maxBits);
|
||||
int val = lookup[bits];
|
||||
int symbol = val >> 5;
|
||||
int codeLen = val & 0x1F;
|
||||
bitstream.Remove(codeLen);
|
||||
|
||||
if(symbol == 7)
|
||||
{
|
||||
// RLE small
|
||||
compTypes[hunk] = lastComp;
|
||||
bits = bitstream.Peek(maxBits);
|
||||
val = lookup[bits];
|
||||
int count = val >> 5;
|
||||
codeLen = val & 0x1F;
|
||||
bitstream.Remove(codeLen);
|
||||
repCount = 2 + count;
|
||||
}
|
||||
else if(symbol == 8)
|
||||
{
|
||||
// RLE large
|
||||
compTypes[hunk] = lastComp;
|
||||
bits = bitstream.Peek(maxBits);
|
||||
val = lookup[bits];
|
||||
int high = val >> 5;
|
||||
codeLen = val & 0x1F;
|
||||
bitstream.Remove(codeLen);
|
||||
repCount = 2 + 16 + (high << 4);
|
||||
bits = bitstream.Peek(maxBits);
|
||||
val = lookup[bits];
|
||||
int low = val >> 5;
|
||||
codeLen = val & 0x1F;
|
||||
bitstream.Remove(codeLen);
|
||||
repCount += low;
|
||||
}
|
||||
else
|
||||
compTypes[hunk] = lastComp = symbol;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Decode lengths, offsets, CRCs (literal port from working Python)
|
||||
rawMap = new byte[totalHunks * 12];
|
||||
ulong curOffset = firstOffs;
|
||||
uint lastSelf = 0;
|
||||
ulong lastParent = 0;
|
||||
|
||||
for(uint hunk = 0; hunk < totalHunks; hunk++)
|
||||
{
|
||||
var entryOffset = (int)(hunk * 12);
|
||||
int compType = compTypes[hunk];
|
||||
ulong offset = curOffset;
|
||||
uint length = 0;
|
||||
ushort crc = 0;
|
||||
|
||||
if(compType >= 0 && compType <= 3)
|
||||
{
|
||||
length = (uint)bitstream.Read(lengthBits);
|
||||
crc = (ushort)bitstream.Read(16);
|
||||
curOffset += length;
|
||||
}
|
||||
else if(compType == 4)
|
||||
{
|
||||
length = hunkBytes;
|
||||
crc = (ushort)bitstream.Read(16);
|
||||
curOffset += hunkBytes;
|
||||
}
|
||||
else if(compType == 5)
|
||||
{
|
||||
lastSelf = (uint)bitstream.Read(selfBits);
|
||||
offset = lastSelf;
|
||||
}
|
||||
else if(compType == 6)
|
||||
{
|
||||
offset = (ulong)bitstream.Read(parentBits);
|
||||
lastParent = offset;
|
||||
}
|
||||
else if(compType == 10)
|
||||
{
|
||||
lastSelf += 1;
|
||||
compType = 5;
|
||||
offset = lastSelf;
|
||||
}
|
||||
else if(compType == 9)
|
||||
{
|
||||
compType = 5;
|
||||
offset = lastSelf;
|
||||
}
|
||||
else if(compType == 11)
|
||||
{
|
||||
compType = 6;
|
||||
lastParent = (ulong)hunk * hunkBytes / unitBytes;
|
||||
offset = lastParent;
|
||||
}
|
||||
else if(compType == 13)
|
||||
{
|
||||
lastParent += hunkBytes / unitBytes;
|
||||
compType = 6;
|
||||
offset = lastParent;
|
||||
}
|
||||
else if(compType == 12)
|
||||
{
|
||||
compType = 6;
|
||||
offset = lastParent;
|
||||
}
|
||||
|
||||
rawMap[entryOffset] = (byte)(compType & 0xFF);
|
||||
rawMap[entryOffset + 1] = (byte)(length >> 16 & 0xFF);
|
||||
rawMap[entryOffset + 2] = (byte)(length >> 8 & 0xFF);
|
||||
rawMap[entryOffset + 3] = (byte)(length & 0xFF);
|
||||
rawMap[entryOffset + 4] = (byte)(offset >> 40 & 0xFF);
|
||||
rawMap[entryOffset + 5] = (byte)(offset >> 32 & 0xFF);
|
||||
rawMap[entryOffset + 6] = (byte)(offset >> 24 & 0xFF);
|
||||
rawMap[entryOffset + 7] = (byte)(offset >> 16 & 0xFF);
|
||||
rawMap[entryOffset + 8] = (byte)(offset >> 8 & 0xFF);
|
||||
rawMap[entryOffset + 9] = (byte)(offset & 0xFF);
|
||||
rawMap[entryOffset + 10] = (byte)(crc >> 8 & 0xFF);
|
||||
rawMap[entryOffset + 11] = (byte)(crc & 0xFF);
|
||||
}
|
||||
|
||||
// Verify map CRC16 (CCITT polynomial 0x1021, init 0xFFFF)
|
||||
ushort computedCrc = 0xFFFF;
|
||||
|
||||
foreach(byte b in rawMap)
|
||||
{
|
||||
computedCrc ^= (ushort)(b << 8);
|
||||
|
||||
for(var bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if((computedCrc & 0x8000) != 0)
|
||||
computedCrc = (ushort)(computedCrc << 1 ^ 0x1021);
|
||||
else
|
||||
computedCrc = (ushort)(computedCrc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(mapCrc != computedCrc)
|
||||
{
|
||||
AaruLogging.Error("Map CRC mismatch: expected 0x{0:X4}, computed 0x{1:X4}", mapCrc, computedCrc);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
static int[] BuildHuffmanLookup(int[] codeLengths, int numCodes, int maxBits)
|
||||
{
|
||||
// Build histogram of bit lengths
|
||||
var bitHisto = new int[33];
|
||||
|
||||
for(var i = 0; i < numCodes; i++)
|
||||
{
|
||||
if(codeLengths[i] > maxBits) return null;
|
||||
|
||||
if(codeLengths[i] <= 32) bitHisto[codeLengths[i]]++;
|
||||
}
|
||||
|
||||
// Assign starting codes from length 32 down to 1
|
||||
uint curStart = 0;
|
||||
|
||||
for(var codeLen = 32; codeLen > 0; codeLen--)
|
||||
{
|
||||
var nextStart = (uint)(curStart + bitHisto[codeLen] >> 1);
|
||||
bitHisto[codeLen] = (int)curStart;
|
||||
curStart = nextStart;
|
||||
}
|
||||
|
||||
// Assign canonical codes
|
||||
var codes = new uint[numCodes];
|
||||
|
||||
for(var i = 0; i < numCodes; i++)
|
||||
{
|
||||
if(codeLengths[i] > 0) codes[i] = (uint)bitHisto[codeLengths[i]]++;
|
||||
}
|
||||
|
||||
// Build lookup table
|
||||
var lookup = new int[1 << maxBits];
|
||||
|
||||
for(var i = 0; i < numCodes; i++)
|
||||
{
|
||||
if(codeLengths[i] <= 0) continue;
|
||||
|
||||
int value = i << 5 | codeLengths[i] & 0x1F;
|
||||
int shift = maxBits - codeLengths[i];
|
||||
var start = (int)(codes[i] << shift);
|
||||
var end = (int)((codes[i] + 1 << shift) - 1);
|
||||
|
||||
for(int j = start; j <= end; j++) lookup[j] = value;
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
static byte[] ComputeLzmaProperties(uint hunkBytes)
|
||||
{
|
||||
var properties = new byte[5];
|
||||
|
||||
// lc=3, lp=0, pb=2 → 2*45+0+3 = 93 = 0x5D
|
||||
properties[0] = 0x5D;
|
||||
|
||||
uint dictSize = 1;
|
||||
|
||||
while(dictSize < hunkBytes && dictSize < 1u << 26) dictSize <<= 1;
|
||||
|
||||
properties[1] = (byte)dictSize;
|
||||
properties[2] = (byte)(dictSize >> 8);
|
||||
properties[3] = (byte)(dictSize >> 16);
|
||||
properties[4] = (byte)(dictSize >> 24);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/// <summary>MSB-first bitstream reader matching MAME's bitstream_in implementation</summary>
|
||||
sealed class BitStreamReader
|
||||
{
|
||||
readonly byte[] _data;
|
||||
readonly int _dataLength;
|
||||
|
||||
internal BitStreamReader(byte[] data, int dataLength)
|
||||
{
|
||||
_data = data;
|
||||
_dataLength = dataLength;
|
||||
DOffset = 0;
|
||||
DBitOffs = 0;
|
||||
Buffer = 0;
|
||||
Bits = 0;
|
||||
}
|
||||
|
||||
internal int DOffset { get; private set; }
|
||||
|
||||
internal int DBitOffs { get; private set; }
|
||||
|
||||
internal uint Buffer { get; private set; }
|
||||
|
||||
internal int Bits { get; private set; }
|
||||
|
||||
internal int Peek(int numBits)
|
||||
{
|
||||
if(numBits == 0) return 0;
|
||||
|
||||
if(numBits > Bits)
|
||||
{
|
||||
while(Bits < 32)
|
||||
{
|
||||
uint newBits = 0;
|
||||
|
||||
if(DOffset < _dataLength) newBits = (uint)(_data[DOffset] << DBitOffs & 0xFF);
|
||||
|
||||
if(Bits + 8 > 32)
|
||||
{
|
||||
DBitOffs = 32 - Bits;
|
||||
newBits >>= 8 - DBitOffs;
|
||||
Buffer |= newBits;
|
||||
Bits += DBitOffs;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer |= newBits << 24 - Bits;
|
||||
Bits += 8 - DBitOffs;
|
||||
DBitOffs = 0;
|
||||
DOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(Buffer >> 32 - numBits & (uint)((1 << numBits) - 1));
|
||||
}
|
||||
|
||||
internal void Remove(int numBits)
|
||||
{
|
||||
Buffer = Buffer << numBits & 0xFFFFFFFF;
|
||||
Bits -= numBits;
|
||||
}
|
||||
|
||||
internal int Read(int numBits)
|
||||
{
|
||||
int result = Peek(numBits);
|
||||
Remove(numBits);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,11 +343,6 @@ public sealed partial class Chd
|
||||
|
||||
case 5:
|
||||
{
|
||||
// TODO: Check why reading is misaligned
|
||||
AaruLogging.Error(Localization.CHD_version_5_is_not_yet_supported);
|
||||
|
||||
return ErrorNumber.NotImplemented;
|
||||
|
||||
HeaderV5 hdrV5 = Marshal.ByteArrayToStructureBigEndian<HeaderV5>(buffer);
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "hdrV5.tag = \"{0}\"", Encoding.ASCII.GetString(hdrV5.tag));
|
||||
@@ -387,69 +382,54 @@ public sealed partial class Chd
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "hdrV5.rawsha1 = {0}", ArrayHelpers.ByteArrayToHex(hdrV5.rawsha1));
|
||||
|
||||
// TODO: Implement compressed CHD v5
|
||||
if(hdrV5.compressor0 == 0)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
|
||||
hunkMapStopwatch.Restart();
|
||||
|
||||
_hunkTableSmall = new uint[hdrV5.logicalbytes / hdrV5.hunkbytes];
|
||||
|
||||
var hunkSectorCount = (uint)Math.Ceiling((double)_hunkTableSmall.Length * 4 / 512);
|
||||
|
||||
var hunkSectorBytes = new byte[512];
|
||||
|
||||
stream.Seek((long)hdrV5.mapoffset, SeekOrigin.Begin);
|
||||
|
||||
for(var i = 0; i < hunkSectorCount; i++)
|
||||
{
|
||||
stream.EnsureRead(hunkSectorBytes, 0, 512);
|
||||
|
||||
// This does the big-endian trick but reverses the order of elements also
|
||||
Array.Reverse(hunkSectorBytes);
|
||||
|
||||
HunkSectorSmall hunkSector =
|
||||
Marshal.ByteArrayToStructureLittleEndian<HunkSectorSmall>(hunkSectorBytes);
|
||||
|
||||
// This restores the order of elements
|
||||
Array.Reverse(hunkSector.hunkEntry);
|
||||
|
||||
if(_hunkTableSmall.Length >= i * 512 / 4 + 512 / 4)
|
||||
Array.Copy(hunkSector.hunkEntry, 0, _hunkTableSmall, i * 512 / 4, 512 / 4);
|
||||
else
|
||||
{
|
||||
Array.Copy(hunkSector.hunkEntry,
|
||||
0,
|
||||
_hunkTableSmall,
|
||||
i * 512 / 4,
|
||||
_hunkTableSmall.Length - i * 512 / 4);
|
||||
}
|
||||
}
|
||||
|
||||
hunkMapStopwatch.Stop();
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
AaruLogging.Error(Localization.Cannot_read_compressed_CHD_version_5);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
nextMetaOff = hdrV5.metaoffset;
|
||||
|
||||
_imageInfo.ImageSize = hdrV5.logicalbytes;
|
||||
_imageInfo.Version = "5";
|
||||
|
||||
_totalHunks = (uint)(hdrV5.logicalbytes / hdrV5.hunkbytes);
|
||||
_totalHunks = (uint)((hdrV5.logicalbytes + hdrV5.hunkbytes - 1) / hdrV5.hunkbytes);
|
||||
_bytesPerHunk = hdrV5.hunkbytes;
|
||||
_unitBytes = hdrV5.unitbytes;
|
||||
_hdrCompression = hdrV5.compressor0;
|
||||
_hdrCompression1 = hdrV5.compressor1;
|
||||
_hdrCompression2 = hdrV5.compressor2;
|
||||
_hdrCompression3 = hdrV5.compressor3;
|
||||
_mapVersion = 5;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Reading_Hunk_map);
|
||||
hunkMapStopwatch.Restart();
|
||||
|
||||
if(hdrV5.compressor0 == 0)
|
||||
{
|
||||
// Uncompressed map: 4 bytes per hunk (big-endian offset / hunkbytes)
|
||||
_hunkTableSmall = new uint[_totalHunks];
|
||||
stream.Seek((long)hdrV5.mapoffset, SeekOrigin.Begin);
|
||||
|
||||
for(uint i = 0; i < _totalHunks; i++)
|
||||
{
|
||||
var entryBytes = new byte[4];
|
||||
stream.EnsureRead(entryBytes, 0, 4);
|
||||
|
||||
_hunkTableSmall[i] = BigEndianBitConverter.ToUInt32(entryBytes, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compressed map
|
||||
ErrorNumber mapErr = DecompressV5Map(stream,
|
||||
hdrV5.mapoffset,
|
||||
_totalHunks,
|
||||
_bytesPerHunk,
|
||||
_unitBytes,
|
||||
out _rawMap);
|
||||
|
||||
if(mapErr != ErrorNumber.NoError) return mapErr;
|
||||
}
|
||||
|
||||
hunkMapStopwatch.Stop();
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Took_0_seconds, hunkMapStopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
nextMetaOff = hdrV5.metaoffset;
|
||||
|
||||
_imageInfo.ImageSize = hdrV5.logicalbytes;
|
||||
_imageInfo.Version = "5";
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1208,8 +1188,7 @@ public sealed partial class Chd
|
||||
}
|
||||
else if(_isCdrom)
|
||||
{
|
||||
// Hardcoded on MAME for CD-ROM
|
||||
_sectorsPerHunk = 4;
|
||||
_sectorsPerHunk = _bytesPerHunk / CD_FRAME_SIZE;
|
||||
_imageInfo.MediaType = MediaType.CDROM;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
|
||||
|
||||
@@ -1218,8 +1197,7 @@ public sealed partial class Chd
|
||||
}
|
||||
else if(_isGdrom)
|
||||
{
|
||||
// Hardcoded on MAME for GD-ROM
|
||||
_sectorsPerHunk = 4;
|
||||
_sectorsPerHunk = _bytesPerHunk / CD_FRAME_SIZE;
|
||||
_imageInfo.MediaType = MediaType.GDROM;
|
||||
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
|
||||
|
||||
@@ -1358,8 +1336,8 @@ public sealed partial class Chd
|
||||
|
||||
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
|
||||
|
||||
var track = new Track();
|
||||
uint sectorSize;
|
||||
Track track = _isHdd ? new Track() : GetTrack(sectorAddress);
|
||||
uint sectorSize;
|
||||
|
||||
if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector))
|
||||
{
|
||||
@@ -1367,8 +1345,6 @@ public sealed partial class Chd
|
||||
sectorSize = _imageInfo.SectorSize;
|
||||
else
|
||||
{
|
||||
track = GetTrack(sectorAddress);
|
||||
|
||||
sectorSize = (uint)track.RawBytesPerSector +
|
||||
(track.SubchannelType != TrackSubchannelType.None ? 96u : 0u);
|
||||
}
|
||||
@@ -1502,14 +1478,12 @@ public sealed partial class Chd
|
||||
|
||||
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
|
||||
|
||||
var track = new Track();
|
||||
Track track = GetTrack(sectorAddress);
|
||||
|
||||
uint sectorSize;
|
||||
|
||||
if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector))
|
||||
{
|
||||
track = GetTrack(sectorAddress);
|
||||
|
||||
sectorSize = (uint)track.RawBytesPerSector + (track.SubchannelType != TrackSubchannelType.None ? 96u : 0u);
|
||||
|
||||
ulong hunkNo = sectorAddress / _sectorsPerHunk;
|
||||
@@ -1842,12 +1816,10 @@ public sealed partial class Chd
|
||||
|
||||
if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange;
|
||||
|
||||
var track = new Track();
|
||||
Track track = GetTrack(sectorAddress);
|
||||
|
||||
if(!_sectorCache.TryGetValue(sectorAddress, out byte[] sector))
|
||||
{
|
||||
track = GetTrack(sectorAddress);
|
||||
|
||||
uint sectorSize = (uint)track.RawBytesPerSector +
|
||||
(track.SubchannelType != TrackSubchannelType.None ? 96u : 0u);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user