mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-06 14:05:21 +00:00
Add redumper DVD image
This commit is contained in:
@@ -35,11 +35,11 @@ using System.Linq;
|
|||||||
using Aaru.CommonTypes;
|
using Aaru.CommonTypes;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
using Aaru.CommonTypes.Interfaces;
|
using Aaru.CommonTypes.Interfaces;
|
||||||
using Aaru.Core.Image.Ngcw;
|
using Aaru.Decryption.Ngcw;
|
||||||
using Aaru.Decoders.Nintendo;
|
using Aaru.Decoders.Nintendo;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
using Aaru.Localization;
|
using Aaru.Localization;
|
||||||
using NgcwPartitions = Aaru.Core.Image.Ngcw.Partitions;
|
using NgcwPartitions = Aaru.Decryption.Ngcw.Partitions;
|
||||||
|
|
||||||
namespace Aaru.Core.Devices.Dumping;
|
namespace Aaru.Core.Devices.Dumping;
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ public partial class Convert
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errno = ConvertNgcwSectors();
|
errno = ConvertNgcwSectors(useLong);
|
||||||
|
|
||||||
if(errno != ErrorNumber.NoError) return errno;
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
}
|
}
|
||||||
@@ -367,6 +367,7 @@ public partial class Convert
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Enable for Ngcw
|
||||||
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _negativeSectors > 0)
|
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _negativeSectors > 0)
|
||||||
{
|
{
|
||||||
errno = ConvertNegativeSectors(useLong);
|
errno = ConvertNegativeSectors(useLong);
|
||||||
@@ -374,6 +375,7 @@ public partial class Convert
|
|||||||
if(errno != ErrorNumber.NoError) return errno;
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Enable for Ngcw
|
||||||
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _overflowSectors > 0)
|
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _overflowSectors > 0)
|
||||||
{
|
{
|
||||||
errno = ConvertOverflowSectors(useLong);
|
errno = ConvertOverflowSectors(useLong);
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ public partial class Convert
|
|||||||
case SectorTagType.CdSectorEccP:
|
case SectorTagType.CdSectorEccP:
|
||||||
case SectorTagType.CdSectorEccQ:
|
case SectorTagType.CdSectorEccQ:
|
||||||
case SectorTagType.CdSectorEcc:
|
case SectorTagType.CdSectorEcc:
|
||||||
|
case SectorTagType.DvdSectorCmi:
|
||||||
|
case SectorTagType.DvdSectorTitleKey:
|
||||||
|
case SectorTagType.DvdSectorEdc:
|
||||||
|
case SectorTagType.DvdSectorIed:
|
||||||
|
case SectorTagType.DvdSectorInformation:
|
||||||
|
case SectorTagType.DvdSectorNumber:
|
||||||
// These tags are inline in long sector
|
// These tags are inline in long sector
|
||||||
continue;
|
continue;
|
||||||
case SectorTagType.CdTrackFlags:
|
case SectorTagType.CdTrackFlags:
|
||||||
@@ -329,6 +335,12 @@ public partial class Convert
|
|||||||
case SectorTagType.CdSectorEccP:
|
case SectorTagType.CdSectorEccP:
|
||||||
case SectorTagType.CdSectorEccQ:
|
case SectorTagType.CdSectorEccQ:
|
||||||
case SectorTagType.CdSectorEcc:
|
case SectorTagType.CdSectorEcc:
|
||||||
|
case SectorTagType.DvdSectorCmi:
|
||||||
|
case SectorTagType.DvdSectorTitleKey:
|
||||||
|
case SectorTagType.DvdSectorEdc:
|
||||||
|
case SectorTagType.DvdSectorIed:
|
||||||
|
case SectorTagType.DvdSectorInformation:
|
||||||
|
case SectorTagType.DvdSectorNumber:
|
||||||
// These tags are inline in long sector
|
// These tags are inline in long sector
|
||||||
continue;
|
continue;
|
||||||
case SectorTagType.CdTrackFlags:
|
case SectorTagType.CdTrackFlags:
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ using System.Collections.Generic;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Aaru.CommonTypes;
|
using Aaru.CommonTypes;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
using Aaru.Core.Image.Ngcw;
|
using Aaru.Decoders.Nintendo;
|
||||||
|
using Aaru.Decryption.Ngcw;
|
||||||
|
using NgcwPartitions = Aaru.Decryption.Ngcw.Partitions;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
using Aaru.Localization;
|
using Aaru.Localization;
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ public partial class Convert
|
|||||||
// Parse Wii partition table
|
// Parse Wii partition table
|
||||||
PulseProgress?.Invoke(UI.Ngcw_parsing_partition_table);
|
PulseProgress?.Invoke(UI.Ngcw_parsing_partition_table);
|
||||||
|
|
||||||
_ngcwPartitions = Ngcw.Partitions.ParseWiiPartitions(_inputImage);
|
_ngcwPartitions = NgcwPartitions.ParseWiiPartitions(_inputImage);
|
||||||
|
|
||||||
if(_ngcwPartitions == null)
|
if(_ngcwPartitions == null)
|
||||||
{
|
{
|
||||||
@@ -83,10 +85,10 @@ public partial class Convert
|
|||||||
// Build partition region map
|
// Build partition region map
|
||||||
PulseProgress?.Invoke(UI.Ngcw_building_partition_key_map);
|
PulseProgress?.Invoke(UI.Ngcw_building_partition_key_map);
|
||||||
|
|
||||||
_ngcwRegions = Ngcw.Partitions.BuildRegionMap(_ngcwPartitions);
|
_ngcwRegions = NgcwPartitions.BuildRegionMap(_ngcwPartitions);
|
||||||
|
|
||||||
// Serialize and write partition key map
|
// Serialize and write partition key map
|
||||||
byte[] keyMapData = Ngcw.Partitions.SerializeKeyMap(_ngcwRegions);
|
byte[] keyMapData = NgcwPartitions.SerializeKeyMap(_ngcwRegions);
|
||||||
|
|
||||||
_outputImage.WriteMediaTag(keyMapData, MediaTagType.WiiPartitionKeyMap);
|
_outputImage.WriteMediaTag(keyMapData, MediaTagType.WiiPartitionKeyMap);
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ public partial class Convert
|
|||||||
/// writes with Unencrypted status; plaintext areas use Dumped/Generable.
|
/// writes with Unencrypted status; plaintext areas use Dumped/Generable.
|
||||||
/// Does not copy sector tags, negative sectors, or overflow sectors.
|
/// Does not copy sector tags, negative sectors, or overflow sectors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ErrorNumber ConvertNgcwSectors()
|
ErrorNumber ConvertNgcwSectors(bool useLong)
|
||||||
{
|
{
|
||||||
if(_aborted) return ErrorNumber.NoError;
|
if(_aborted) return ErrorNumber.NoError;
|
||||||
|
|
||||||
@@ -119,7 +121,8 @@ public partial class Convert
|
|||||||
|
|
||||||
if(_mediaType == MediaType.GOD)
|
if(_mediaType == MediaType.GOD)
|
||||||
{
|
{
|
||||||
ErrorNumber errno =
|
ErrorNumber errno = useLong ?
|
||||||
|
ConvertGameCubeSectorsLong(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors) :
|
||||||
ConvertGameCubeSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
|
ConvertGameCubeSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
|
||||||
|
|
||||||
if(errno != ErrorNumber.NoError)
|
if(errno != ErrorNumber.NoError)
|
||||||
@@ -131,7 +134,9 @@ public partial class Convert
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ErrorNumber errno = ConvertWiiSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
|
ErrorNumber errno = useLong ?
|
||||||
|
ConvertWiiSectorsLong(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors) :
|
||||||
|
ConvertWiiSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
|
||||||
|
|
||||||
if(errno != ErrorNumber.NoError)
|
if(errno != ErrorNumber.NoError)
|
||||||
{
|
{
|
||||||
@@ -294,7 +299,7 @@ public partial class Convert
|
|||||||
(long)totalLogicalSectors);
|
(long)totalLogicalSectors);
|
||||||
|
|
||||||
// Check if inside a partition's data area
|
// Check if inside a partition's data area
|
||||||
int inPart = Ngcw.Partitions.FindPartitionAtOffset(_ngcwPartitions, offset);
|
int inPart = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, offset);
|
||||||
|
|
||||||
if(inPart >= 0)
|
if(inPart >= 0)
|
||||||
{
|
{
|
||||||
@@ -747,4 +752,137 @@ public partial class Convert
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GameCube sector conversion pipeline for long sectors.
|
||||||
|
ErrorNumber ConvertGameCubeSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc,
|
||||||
|
ref ulong dataSectors, ref ulong junkSectors)
|
||||||
|
{
|
||||||
|
const int blockSize = Crypto.GROUP_SIZE; // 0x8000 logical (16 × 2048 user bytes)
|
||||||
|
const int sectorsPerBlock = Crypto.LOGICAL_PER_GROUP;
|
||||||
|
const int userSize = Crypto.SECTOR_SIZE;
|
||||||
|
|
||||||
|
// Junk / FST offsets are in logical (user) byte space. User 2048-byte slices match ReadSector (Nintendo main_data at Sector.NintendoMainDataOffset).
|
||||||
|
ErrorNumber probeErr = _inputImage.ReadSectorLong(0, false, out byte[] longProbe, out _);
|
||||||
|
|
||||||
|
if(probeErr != ErrorNumber.NoError || longProbe == null ||
|
||||||
|
longProbe.Length < Sector.NintendoMainDataOffset + userSize)
|
||||||
|
return ErrorNumber.InOutError;
|
||||||
|
|
||||||
|
int longSectorSize = longProbe.Length;
|
||||||
|
|
||||||
|
// Read disc header to get FST info (logical user bytes via ReadSector)
|
||||||
|
byte[] header = ReadNgcwSectors(0, 2); // first 0x1000 bytes (need 0x42C)
|
||||||
|
|
||||||
|
if(header == null || header.Length < 0x42C) return ErrorNumber.InOutError;
|
||||||
|
|
||||||
|
// Read extended header for FST pointers (at 0x424)
|
||||||
|
byte[] extHeader = ReadNgcwSectors(0, (0x440 + userSize - 1) / userSize);
|
||||||
|
|
||||||
|
var fstOffset = BigEndianBitConverter.ToUInt32(extHeader, 0x424);
|
||||||
|
var fstSize = BigEndianBitConverter.ToUInt32(extHeader, 0x428);
|
||||||
|
ulong sysEnd = fstOffset + fstSize;
|
||||||
|
|
||||||
|
// Build FST data map
|
||||||
|
DataRegion[] dataMap = null;
|
||||||
|
|
||||||
|
if(fstSize > 0 && fstSize < 64 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
byte[] fst = ReadNgcwBytes(fstOffset, (int)fstSize);
|
||||||
|
|
||||||
|
if(fst != null) dataMap = DataMap.BuildFromFst(fst, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User-only buffer for LFG / data-region junk detection (same as ConvertGameCubeSectors)
|
||||||
|
var userBlockBuf = new byte[blockSize];
|
||||||
|
var longSectorBufs = new byte[sectorsPerBlock][];
|
||||||
|
var sectorStatuses = new SectorStatus[sectorsPerBlock];
|
||||||
|
|
||||||
|
for(ulong blockOff = 0; blockOff < discSize; blockOff += blockSize)
|
||||||
|
{
|
||||||
|
if(_aborted) break;
|
||||||
|
|
||||||
|
int blockBytes = blockSize;
|
||||||
|
|
||||||
|
if(blockOff + (ulong)blockBytes > discSize) blockBytes = (int)(discSize - blockOff);
|
||||||
|
|
||||||
|
ulong baseSector = blockOff / userSize;
|
||||||
|
|
||||||
|
UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_0_to_1,
|
||||||
|
baseSector,
|
||||||
|
baseSector + sectorsPerBlock),
|
||||||
|
(long)baseSector,
|
||||||
|
(long)totalLogicalSectors);
|
||||||
|
|
||||||
|
// Read long sectors; pack user main data into userBlockBuf; keep full long buffers for output
|
||||||
|
for(var s = 0; s < sectorsPerBlock && s * userSize < blockBytes; s++)
|
||||||
|
{
|
||||||
|
ErrorNumber errno =
|
||||||
|
_inputImage.ReadSectorLong(baseSector + (ulong)s, false, out byte[] sectorData, out _);
|
||||||
|
|
||||||
|
byte[] stored = new byte[longSectorSize];
|
||||||
|
longSectorBufs[s] = stored;
|
||||||
|
|
||||||
|
if(errno != ErrorNumber.NoError || sectorData == null)
|
||||||
|
{
|
||||||
|
Array.Clear(userBlockBuf, s * userSize, userSize);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int copyLong = sectorData.Length < longSectorSize ? sectorData.Length : longSectorSize;
|
||||||
|
Array.Copy(sectorData, 0, stored, 0, copyLong);
|
||||||
|
|
||||||
|
if(sectorData.Length >= Sector.NintendoMainDataOffset + userSize)
|
||||||
|
Array.Copy(sectorData, Sector.NintendoMainDataOffset, userBlockBuf, s * userSize, userSize);
|
||||||
|
else
|
||||||
|
Array.Clear(userBlockBuf, s * userSize, userSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Junk.DetectJunkInBlock(userBlockBuf,
|
||||||
|
blockBytes,
|
||||||
|
blockOff,
|
||||||
|
dataMap,
|
||||||
|
sysEnd,
|
||||||
|
0xFFFF,
|
||||||
|
jc,
|
||||||
|
ref dataSectors,
|
||||||
|
ref junkSectors,
|
||||||
|
sectorStatuses);
|
||||||
|
|
||||||
|
int numSectors = blockBytes / userSize;
|
||||||
|
|
||||||
|
for(var si = 0; si < numSectors; si++)
|
||||||
|
{
|
||||||
|
ulong sector = baseSector + (ulong)si;
|
||||||
|
bool ok = _outputImage.WriteSectorLong(longSectorBufs[si], sector, false, sectorStatuses[si]);
|
||||||
|
|
||||||
|
if(!ok)
|
||||||
|
{
|
||||||
|
if(_force)
|
||||||
|
{
|
||||||
|
ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
|
||||||
|
_outputImage.ErrorMessage,
|
||||||
|
sector));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
|
||||||
|
_outputImage.ErrorMessage,
|
||||||
|
sector));
|
||||||
|
|
||||||
|
return ErrorNumber.WriteError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wii sector conversion pipeline for long sectors.
|
||||||
|
ErrorNumber ConvertWiiSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc, ref ulong dataSectors, ref ulong junkSectors){
|
||||||
|
// TODO: Implement
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -157,6 +157,49 @@ public sealed class Sector
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static readonly byte[] DvdGfExp = CreateDvdGfExpTable();
|
||||||
|
static readonly int[] DvdGfLog = CreateDvdGfLogTable(DvdGfExp);
|
||||||
|
|
||||||
|
static byte[] CreateDvdGfExpTable()
|
||||||
|
{
|
||||||
|
const ushort primitive = 0x11D;
|
||||||
|
byte[] exp = new byte[512];
|
||||||
|
exp[0] = 1;
|
||||||
|
|
||||||
|
for(int i = 1; i < 255; i++)
|
||||||
|
{
|
||||||
|
ushort x = (ushort)(exp[i - 1] << 1);
|
||||||
|
|
||||||
|
if((x & 0x100) != 0) x ^= primitive;
|
||||||
|
|
||||||
|
exp[i] = (byte)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 255; i < 512; i++) exp[i] = exp[i - 255];
|
||||||
|
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int[] CreateDvdGfLogTable(byte[] exp)
|
||||||
|
{
|
||||||
|
int[] log = new int[256];
|
||||||
|
|
||||||
|
for(int i = 0; i < 256; i++) log[i] = -1;
|
||||||
|
|
||||||
|
for(int i = 0; i < 255; i++) log[exp[i]] = i;
|
||||||
|
|
||||||
|
log[0] = -1;
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte DvdGfMul(byte a, byte b)
|
||||||
|
{
|
||||||
|
if(a == 0 || b == 0) return 0;
|
||||||
|
|
||||||
|
return DvdGfExp[DvdGfLog[a] + DvdGfLog[b]];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Store seed and its cipher in cache
|
/// Store seed and its cipher in cache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -207,6 +250,57 @@ public sealed class Sector
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the 16-bit ID Error Detection (IED) for bytes 0-3 of a DVD ROM sector (ID + 3-byte PSN),
|
||||||
|
/// per the ECMA-267 RS remainder step used in <c>gpsxre::dvd::DataFrame::ID::valid()</c> (redumper).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sectorLong">Buffer of the sector (must be at least 4 bytes; first 4 bytes are the ID field).</param>
|
||||||
|
/// <returns>The IED value in the same byte order as stored at sector bytes 4-5 (little-endian uint16 layout).</returns>
|
||||||
|
public static ushort ComputeIed(byte[] sectorLong)
|
||||||
|
{
|
||||||
|
if(sectorLong == null || sectorLong.Length < 4) return 0;
|
||||||
|
|
||||||
|
// G(x) = x^2 + g1*x + g2, with g1 = alpha^0 + alpha^1, g2 = alpha^0 * alpha^1 in GF(2^8)
|
||||||
|
byte g1 = (byte)(1 ^ DvdGfExp[1]);
|
||||||
|
byte g2 = DvdGfExp[1];
|
||||||
|
|
||||||
|
Span<byte> poly = stackalloc byte[6];
|
||||||
|
poly[0] = sectorLong[0];
|
||||||
|
poly[1] = sectorLong[1];
|
||||||
|
poly[2] = sectorLong[2];
|
||||||
|
poly[3] = sectorLong[3];
|
||||||
|
poly[4] = 0;
|
||||||
|
poly[5] = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
byte coef = poly[i];
|
||||||
|
|
||||||
|
if(coef == 0) continue;
|
||||||
|
|
||||||
|
poly[i] = 0;
|
||||||
|
poly[i + 1] ^= DvdGfMul(coef, g1);
|
||||||
|
poly[i + 2] ^= DvdGfMul(coef, g2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ushort)(poly[4] | poly[5] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the IED of a sector is correct (bytes 0-3 vs bytes 4-5, same layout as redumper <c>DataFrame::ID</c>).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sectorLong">Buffer of the sector</param>
|
||||||
|
/// <returns><c>True</c> if IED is correct, <c>False</c> if not</returns>
|
||||||
|
public static bool CheckIed(byte[] sectorLong)
|
||||||
|
{
|
||||||
|
if(sectorLong == null || sectorLong.Length < 6) return false;
|
||||||
|
|
||||||
|
ushort computed = ComputeIed(sectorLong);
|
||||||
|
ushort stored = (ushort)(sectorLong[4] | sectorLong[5] << 8);
|
||||||
|
|
||||||
|
return computed == stored;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests if a seed unscrambles a sector correctly
|
/// Tests if a seed unscrambles a sector correctly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -42,14 +42,22 @@ namespace Aaru.Decoders.Nintendo;
|
|||||||
public sealed class Sector
|
public sealed class Sector
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ECMA-267 <c>main_data</c> offset in OmniDrive 2064-byte Nintendo sectors: DVD XOR applies to 2048 bytes from
|
/// Start of the 2048-byte DVD XOR (scramble) region in a 2064-byte Nintendo sector — same as ECMA-267
|
||||||
/// here (same as standard DVD). Bytes 6-11 (<c>cpr_mai</c>) are not scrambled on media.
|
/// <c>main_data</c> for a standard DVD sector. Nintendo still applies the table to these 2048 bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int NintendoMainDataOffset = 12;
|
public const int NintendoScrambledDataOffset = 12;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Derives the Nintendo descramble key from the first 8 bytes of the cpr_mai region (LBA 0 payload).
|
/// Start of the 2048-byte logical <c>main_data</c> exposed to the game / filesystem in Nintendo GameCube/Wii DVD
|
||||||
/// Used when software-descrambling Nintendo sectors.
|
/// sectors. Unlike ECMA-267 (where <c>main_data</c> begins at byte 12), Nintendo uses byte 6; CPR_MAI and related
|
||||||
|
/// fields follow a different layout than on a standard DVD-ROM. The DVD XOR layer still scrambles 2048 bytes
|
||||||
|
/// starting at <see cref="NintendoScrambledDataOffset" />; bytes 6–11 are not part of that scrambled block.
|
||||||
|
/// </summary>
|
||||||
|
public const int NintendoMainDataOffset = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Derives the Nintendo descramble key from the first 8 bytes at <see cref="NintendoMainDataOffset" /> in the
|
||||||
|
/// LBA 0 sector after descramble (same 8 bytes used for key derivation in the drive/firmware path).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static byte DeriveNintendoKey(byte[] cprMaiFirst8)
|
public static byte DeriveNintendoKey(byte[] cprMaiFirst8)
|
||||||
{
|
{
|
||||||
@@ -76,7 +84,7 @@ public sealed class Sector
|
|||||||
if(sector is not { Length: 2064 }) return ErrorNumber.NotSupported;
|
if(sector is not { Length: 2064 }) return ErrorNumber.NotSupported;
|
||||||
|
|
||||||
int psn = DVD.Sector.GetPsn(sector);
|
int psn = DVD.Sector.GetPsn(sector);
|
||||||
int mainDataStart = NintendoMainDataOffset;
|
int mainDataStart = NintendoScrambledDataOffset;
|
||||||
|
|
||||||
int tableOffset = (int)((nintendoKey ^ (psn >> 4 & 0xF)) * DVD.Sector.Form1DataSize +
|
int tableOffset = (int)((nintendoKey ^ (psn >> 4 & 0xF)) * DVD.Sector.Form1DataSize +
|
||||||
7 * DVD.Sector.Form1DataSize + DVD.Sector.Form1DataSize / 2);
|
7 * DVD.Sector.Form1DataSize + DVD.Sector.Form1DataSize / 2);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<Title>Aaru.Decryption</Title>
|
<Title>Aaru.Decryption</Title>
|
||||||
<Description>Decryption algorithms used by the Aaru Data Preservation Suite.</Description>
|
<Description>Decryption algorithms used by the Aaru Data Preservation Suite.</Description>
|
||||||
<PackageProjectUrl>https://github.com/aaru-dps/</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/aaru-dps/</PackageProjectUrl>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>(MIT AND GPL-3.0-only)</PackageLicenseExpression>
|
||||||
<RepositoryUrl>https://github.com/aaru-dps/Aaru.Decryption</RepositoryUrl>
|
<RepositoryUrl>https://github.com/aaru-dps/Aaru.Decryption</RepositoryUrl>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<NeutralLanguage>en-US</NeutralLanguage>
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
@@ -32,9 +32,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Aaru.CommonTypes\Aaru.CommonTypes.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj"/>
|
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Devices\Aaru.Devices.csproj"/>
|
<ProjectReference Include="..\Aaru.Devices\Aaru.Devices.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Images\Aaru.Images.csproj"/>
|
<ProjectReference Include="..\Aaru.Helpers\Aaru.Helpers.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Filename : Crypto.cs
|
// Filename : Crypto.cs
|
||||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
//
|
//
|
||||||
// Component : Image conversion.
|
// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
|
||||||
//
|
//
|
||||||
// --[ Description ] ----------------------------------------------------------
|
// --[ Description ] ----------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -34,10 +34,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Aaru.Core.Image.Ngcw;
|
namespace Aaru.Decryption.Ngcw;
|
||||||
|
|
||||||
/// <summary>Wii disc encryption helpers.</summary>
|
/// <summary>Wii disc encryption helpers.</summary>
|
||||||
static class Crypto
|
public static class Crypto
|
||||||
{
|
{
|
||||||
/// <summary>Wii physical group size (32 KiB).</summary>
|
/// <summary>Wii physical group size (32 KiB).</summary>
|
||||||
public const int GROUP_SIZE = 0x8000;
|
public const int GROUP_SIZE = 0x8000;
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// Filename : DataMap.cs
|
// Filename : DataMap.cs
|
||||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
//
|
//
|
||||||
// Component : Image conversion.
|
// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
|
||||||
//
|
//
|
||||||
// --[ Description ] ----------------------------------------------------------
|
// --[ Description ] ----------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -34,10 +34,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
|
|
||||||
namespace Aaru.Core.Image.Ngcw;
|
namespace Aaru.Decryption.Ngcw;
|
||||||
|
|
||||||
/// <summary>A contiguous region of file data on disc.</summary>
|
/// <summary>A contiguous region of file data on disc.</summary>
|
||||||
readonly struct DataRegion : IComparable<DataRegion>
|
public readonly struct DataRegion : IComparable<DataRegion>
|
||||||
{
|
{
|
||||||
public readonly ulong Offset;
|
public readonly ulong Offset;
|
||||||
public readonly ulong Length;
|
public readonly ulong Length;
|
||||||
@@ -55,7 +55,7 @@ readonly struct DataRegion : IComparable<DataRegion>
|
|||||||
/// Sorted map of file data regions parsed from a Nintendo GameCube/Wii FST.
|
/// Sorted map of file data regions parsed from a Nintendo GameCube/Wii FST.
|
||||||
/// Used to classify disc sectors as data (file content / system area) or potential junk.
|
/// Used to classify disc sectors as data (file content / system area) or potential junk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static class DataMap
|
public static class DataMap
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build a data region map from an FST (File System Table).
|
/// Build a data region map from an FST (File System Table).
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// Filename : Junk.cs
|
// Filename : Junk.cs
|
||||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
//
|
//
|
||||||
// Component : Image conversion.
|
// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
|
||||||
//
|
//
|
||||||
// --[ Description ] ----------------------------------------------------------
|
// --[ Description ] ----------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -34,10 +34,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
|
|
||||||
namespace Aaru.Core.Image.Ngcw;
|
namespace Aaru.Decryption.Ngcw;
|
||||||
|
|
||||||
/// <summary>In-memory junk map entry.</summary>
|
/// <summary>In-memory junk map entry.</summary>
|
||||||
struct JunkEntry
|
public struct JunkEntry
|
||||||
{
|
{
|
||||||
/// <summary>Disc byte offset where junk starts.</summary>
|
/// <summary>Disc byte offset where junk starts.</summary>
|
||||||
public ulong Offset;
|
public ulong Offset;
|
||||||
@@ -55,7 +55,7 @@ struct JunkEntry
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collects junk entries during conversion, merging contiguous entries with the same seed.
|
/// Collects junk entries during conversion, merging contiguous entries with the same seed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
sealed class JunkCollector
|
public sealed class JunkCollector
|
||||||
{
|
{
|
||||||
public int Count => Entries.Count;
|
public int Count => Entries.Count;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ sealed class JunkCollector
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Junk map serialization/deserialization and block-level junk detection.
|
/// Junk map serialization/deserialization and block-level junk detection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static class Junk
|
public static class Junk
|
||||||
{
|
{
|
||||||
const ushort JUNK_MAP_VERSION = 1;
|
const ushort JUNK_MAP_VERSION = 1;
|
||||||
const int JUNK_MAP_HEADER = 8; // version(2) + count(4) + seed_size(2)
|
const int JUNK_MAP_HEADER = 8; // version(2) + count(4) + seed_size(2)
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// Filename : Lfg.cs
|
// Filename : Lfg.cs
|
||||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
//
|
//
|
||||||
// Component : Image conversion.
|
// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
|
||||||
//
|
//
|
||||||
// --[ Description ] ----------------------------------------------------------
|
// --[ Description ] ----------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -35,10 +35,10 @@ using System;
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Aaru.Core.Image.Ngcw;
|
namespace Aaru.Decryption.Ngcw;
|
||||||
|
|
||||||
/// <summary>Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.</summary>
|
/// <summary>Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.</summary>
|
||||||
static class Lfg
|
public static class Lfg
|
||||||
{
|
{
|
||||||
/// <summary>LFG buffer size (number of uint32 words in state).</summary>
|
/// <summary>LFG buffer size (number of uint32 words in state).</summary>
|
||||||
const int K = 521;
|
const int K = 521;
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
// Filename : Partitions.cs
|
// Filename : Partitions.cs
|
||||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
//
|
//
|
||||||
// Component : Image conversion.
|
// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
|
||||||
//
|
//
|
||||||
// --[ Description ] ----------------------------------------------------------
|
// --[ Description ] ----------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -36,10 +36,10 @@ using Aaru.CommonTypes.Enums;
|
|||||||
using Aaru.CommonTypes.Interfaces;
|
using Aaru.CommonTypes.Interfaces;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
|
|
||||||
namespace Aaru.Core.Image.Ngcw;
|
namespace Aaru.Decryption.Ngcw;
|
||||||
|
|
||||||
/// <summary>In-memory representation of a Wii partition.</summary>
|
/// <summary>In-memory representation of a Wii partition.</summary>
|
||||||
struct WiiPartition
|
public struct WiiPartition
|
||||||
{
|
{
|
||||||
/// <summary>Partition offset on disc.</summary>
|
/// <summary>Partition offset on disc.</summary>
|
||||||
public ulong Offset;
|
public ulong Offset;
|
||||||
@@ -58,7 +58,7 @@ struct WiiPartition
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Wii partition region for the partition key map.</summary>
|
/// <summary>Wii partition region for the partition key map.</summary>
|
||||||
struct WiiPartitionRegion
|
public struct WiiPartitionRegion
|
||||||
{
|
{
|
||||||
/// <summary>First physical sector (0x8000-byte units).</summary>
|
/// <summary>First physical sector (0x8000-byte units).</summary>
|
||||||
public uint StartSector;
|
public uint StartSector;
|
||||||
@@ -73,7 +73,7 @@ struct WiiPartitionRegion
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wii partition table parsing and key map serialization.
|
/// Wii partition table parsing and key map serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static class Partitions
|
public static class Partitions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse the Wii partition table from a source image, extracting all partitions
|
/// Parse the Wii partition table from a source image, extracting all partitions
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
<ProjectReference Include="..\Aaru.Helpers\Aaru.Helpers.csproj"/>
|
<ProjectReference Include="..\Aaru.Helpers\Aaru.Helpers.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Logging\Aaru.Logging.csproj"/>
|
<ProjectReference Include="..\Aaru.Logging\Aaru.Logging.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj"/>
|
<ProjectReference Include="..\Aaru.Decoders\Aaru.Decoders.csproj"/>
|
||||||
|
<ProjectReference Include="..\Aaru.Decryption\Aaru.Decryption.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Filters\Aaru.Filters.csproj"/>
|
<ProjectReference Include="..\Aaru.Filters\Aaru.Filters.csproj"/>
|
||||||
<ProjectReference Include="..\Aaru.Settings\Aaru.Settings.csproj"/>
|
<ProjectReference Include="..\Aaru.Settings\Aaru.Settings.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
14
Aaru.Images/Localization/Localization.Designer.cs
generated
14
Aaru.Images/Localization/Localization.Designer.cs
generated
@@ -1,4 +1,4 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
//
|
//
|
||||||
@@ -5937,5 +5937,17 @@ namespace Aaru.Images {
|
|||||||
return ResourceManager.GetString("WinOnCD_disc_image", resourceCulture);
|
return ResourceManager.GetString("WinOnCD_disc_image", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string Redumper_Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Redumper_Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string Redumper_disc_image {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Redumper_disc_image", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2970,4 +2970,10 @@
|
|||||||
<data name="WinOnCD_disc_image" xml:space="preserve">
|
<data name="WinOnCD_disc_image" xml:space="preserve">
|
||||||
<value>Imagen de disco de WinOnCD</value>
|
<value>Imagen de disco de WinOnCD</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Redumper_Name" xml:space="preserve">
|
||||||
|
<value>Volcado DVD crudo de Redumper</value>
|
||||||
|
</data>
|
||||||
|
<data name="Redumper_disc_image" xml:space="preserve">
|
||||||
|
<value>Imagen de disco DVD crudo de Redumper</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -2980,4 +2980,10 @@
|
|||||||
<data name="WinOnCD_disc_image" xml:space="preserve">
|
<data name="WinOnCD_disc_image" xml:space="preserve">
|
||||||
<value>WinOnCD disc image</value>
|
<value>WinOnCD disc image</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Redumper_Name" xml:space="preserve">
|
||||||
|
<value>Redumper raw DVD dump</value>
|
||||||
|
</data>
|
||||||
|
<data name="Redumper_disc_image" xml:space="preserve">
|
||||||
|
<value>Redumper raw DVD disc image</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
71
Aaru.Images/Redumper/Identify.cs
Normal file
71
Aaru.Images/Redumper/Identify.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Identify.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Identifies Redumper raw DVD dump images.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using Aaru.CommonTypes.Interfaces;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
public sealed partial class Redumper
|
||||||
|
{
|
||||||
|
#region IOpticalMediaImage Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Identify(IFilter imageFilter)
|
||||||
|
{
|
||||||
|
string filename = imageFilter.Filename;
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(filename)) return false;
|
||||||
|
|
||||||
|
string extension = Path.GetExtension(filename)?.ToLower();
|
||||||
|
|
||||||
|
if(extension != ".state") return false;
|
||||||
|
|
||||||
|
string basePath = filename[..^".state".Length];
|
||||||
|
string sdramPath = basePath + ".sdram";
|
||||||
|
|
||||||
|
if(!File.Exists(sdramPath)) return false;
|
||||||
|
|
||||||
|
long stateLength = imageFilter.DataForkLength;
|
||||||
|
long sdramLength = new FileInfo(sdramPath).Length;
|
||||||
|
|
||||||
|
if(sdramLength == 0 || stateLength == 0) return false;
|
||||||
|
|
||||||
|
if(sdramLength % RECORDING_FRAME_SIZE != 0) return false;
|
||||||
|
|
||||||
|
long frameCount = sdramLength / RECORDING_FRAME_SIZE;
|
||||||
|
|
||||||
|
return stateLength == frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
151
Aaru.Images/Redumper/Ngcw.cs
Normal file
151
Aaru.Images/Redumper/Ngcw.cs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Ngcw.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins (Redumper Nintendo GOD/WOD).
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Nintendo DVD descrambling for GameCube/Wii Redumper dumps. Produces
|
||||||
|
// 2064-byte long sectors (and 2048-byte user via ReadSector) matching a
|
||||||
|
// raw dump after the Nintendo layer: Wii AES partition data remains
|
||||||
|
// ciphertext in user sectors until conversion (see ConvertNgcwSectors).
|
||||||
|
// Junk maps, partition key tags, and Wii decrypt are handled when
|
||||||
|
// converting to AaruFormat, not when reading this plugin.
|
||||||
|
//
|
||||||
|
// --[ 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.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Aaru.CommonTypes;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.Decoders.Nintendo;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
public sealed partial class Redumper
|
||||||
|
{
|
||||||
|
const int NGCW_LONG_SECTOR_SIZE = 2064;
|
||||||
|
const int NGCW_SECTORS_PER_GROUP = 16;
|
||||||
|
|
||||||
|
static bool IsNintendoMediaType(MediaType mt) => mt is MediaType.GOD or MediaType.WOD;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Derives the Nintendo disc key from LBA 0 so sectors 16+ can be descrambled.
|
||||||
|
/// Does not parse partitions, junk, or decrypt Wii groups — conversion does that.
|
||||||
|
/// </summary>
|
||||||
|
void TryInitializeNgcwAfterOpen()
|
||||||
|
{
|
||||||
|
_nintendoDerivedKey = null;
|
||||||
|
|
||||||
|
if(!IsNintendoMediaType(_imageInfo.MediaType)) return;
|
||||||
|
|
||||||
|
EnsureNintendoDerivedKeyFromLba0();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Derives the Nintendo key from LBA 0 so sectors 16+ can be descrambled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>True</c> if the Nintendo key was derived successfully, <c>False</c> if not</returns>
|
||||||
|
bool EnsureNintendoDerivedKeyFromLba0()
|
||||||
|
{
|
||||||
|
ErrorNumber errno = ReadSectorLongForNgcw(0, false, out byte[] long0, out _);
|
||||||
|
|
||||||
|
return errno == ErrorNumber.NoError && long0 != null && long0.Length >= NGCW_LONG_SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a sector long for Nintendo descrambling.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sectorAddress">The sector address to read</param>
|
||||||
|
/// <param name="negative">Whether the sector address is negative</param>
|
||||||
|
/// <param name="buffer">The buffer to read the sector into</param>
|
||||||
|
/// <param name="sectorStatus">The status of the sector</param>
|
||||||
|
/// <returns>The error number</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is used to read a sector long for Nintendo descrambling.
|
||||||
|
/// It is used to read the sector long for LBA 0 to derive the Nintendo key.
|
||||||
|
/// </remarks>
|
||||||
|
ErrorNumber ReadSectorLongForNgcw(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
sectorStatus = SectorStatus.NotDumped;
|
||||||
|
|
||||||
|
int lba = negative ? -(int)sectorAddress : (int)sectorAddress;
|
||||||
|
|
||||||
|
long frameIndex = (long)lba - LBA_START;
|
||||||
|
|
||||||
|
if(frameIndex < 0 || frameIndex >= _totalFrames) return ErrorNumber.OutOfRange;
|
||||||
|
|
||||||
|
sectorStatus = MapState(_stateData[frameIndex]);
|
||||||
|
|
||||||
|
byte[] dvdSector = ReadAndFlattenFrame(frameIndex);
|
||||||
|
|
||||||
|
if(dvdSector is null) return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
if(sectorStatus != SectorStatus.Dumped)
|
||||||
|
{
|
||||||
|
buffer = dvdSector;
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!IsNintendoMediaType(_imageInfo.MediaType))
|
||||||
|
{
|
||||||
|
ErrorNumber error = _decoding.Scramble(dvdSector, out byte[] descrambled);
|
||||||
|
|
||||||
|
buffer = error == ErrorNumber.NoError ? descrambled : dvdSector;
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!DescrambleNintendo2064InPlace(dvdSector, lba))
|
||||||
|
{
|
||||||
|
buffer = dvdSector;
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = dvdSector;
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescrambleNintendo2064InPlace(byte[] buffer, int lba)
|
||||||
|
{
|
||||||
|
byte[] one = new byte[NGCW_LONG_SECTOR_SIZE];
|
||||||
|
Array.Copy(buffer, 0, one, 0, NGCW_LONG_SECTOR_SIZE);
|
||||||
|
byte key = lba < NGCW_SECTORS_PER_GROUP ? (byte)0 : (_nintendoDerivedKey ?? (byte)0);
|
||||||
|
|
||||||
|
ErrorNumber error = _nintendoDecoder.Scramble(one, key, out byte[] decoded);
|
||||||
|
|
||||||
|
if(error != ErrorNumber.NoError)
|
||||||
|
{
|
||||||
|
Array.Clear(buffer, 0, NGCW_LONG_SECTOR_SIZE);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(decoded != null) Array.Copy(decoded, 0, buffer, 0, NGCW_LONG_SECTOR_SIZE);
|
||||||
|
|
||||||
|
if(lba == 0 && decoded != null)
|
||||||
|
{
|
||||||
|
byte[] keyMaterial = new byte[8];
|
||||||
|
Array.Copy(decoded, Sector.NintendoMainDataOffset, keyMaterial, 0, 8);
|
||||||
|
_nintendoDerivedKey = Sector.DeriveNintendoKey(keyMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
Aaru.Images/Redumper/Properties.cs
Normal file
120
Aaru.Images/Redumper/Properties.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Properties.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Contains properties for Redumper raw DVD dump images.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Aaru.CommonTypes;
|
||||||
|
using Aaru.CommonTypes.AaruMetadata;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
|
using Partition = Aaru.CommonTypes.Partition;
|
||||||
|
using Track = Aaru.CommonTypes.Structs.Track;
|
||||||
|
using Session = Aaru.CommonTypes.Structs.Session;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
public sealed partial class Redumper
|
||||||
|
{
|
||||||
|
#region IOpticalMediaImage Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
// ReSharper disable once ConvertToAutoProperty
|
||||||
|
public ImageInfo Info => _imageInfo;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => Localization.Redumper_Name;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Guid Id => new("F2D3E4A5-B6C7-4D8E-9F0A-1B2C3D4E5F60");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Author => Authors.RebeccaWallander;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Format => Localization.Redumper_disc_image;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<Partition> Partitions { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<Track> Tracks { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<Session> Sessions { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<DumpHardware> DumpHardware => null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Metadata AaruMetadata => null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<MediaTagType> SupportedMediaTags =>
|
||||||
|
[
|
||||||
|
MediaTagType.DVD_PFI,
|
||||||
|
MediaTagType.DVD_PFI_2ndLayer,
|
||||||
|
MediaTagType.DVD_DMI,
|
||||||
|
MediaTagType.DVD_BCA
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<SectorTagType> SupportedSectorTags =>
|
||||||
|
[
|
||||||
|
SectorTagType.DvdSectorInformation,
|
||||||
|
SectorTagType.DvdSectorNumber,
|
||||||
|
SectorTagType.DvdSectorIed,
|
||||||
|
SectorTagType.DvdSectorCmi,
|
||||||
|
SectorTagType.DvdSectorTitleKey,
|
||||||
|
SectorTagType.DvdSectorEdc
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<MediaType> SupportedMediaTypes =>
|
||||||
|
[
|
||||||
|
MediaType.DVDROM, MediaType.DVDR, MediaType.DVDRDL, MediaType.DVDRW, MediaType.DVDRWDL, MediaType.DVDRAM,
|
||||||
|
MediaType.DVDPR, MediaType.DVDPRDL, MediaType.DVDPRW, MediaType.DVDPRWDL, MediaType.GOD, MediaType.WOD,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions => [];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<string> KnownExtensions => [".state"];
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsWriting => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ErrorMessage { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
469
Aaru.Images/Redumper/Read.cs
Normal file
469
Aaru.Images/Redumper/Read.cs
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Read.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Reads Redumper raw DVD dump images (.sdram + .state).
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Aaru.CommonTypes;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.CommonTypes.Interfaces;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
|
using Aaru.Decoders.DVD;
|
||||||
|
using Aaru.Helpers;
|
||||||
|
using Aaru.Logging;
|
||||||
|
using Partition = Aaru.CommonTypes.Partition;
|
||||||
|
using Track = Aaru.CommonTypes.Structs.Track;
|
||||||
|
using TrackType = Aaru.CommonTypes.Enums.TrackType;
|
||||||
|
using Session = Aaru.CommonTypes.Structs.Session;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
public sealed partial class Redumper
|
||||||
|
{
|
||||||
|
#region IOpticalMediaImage Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber Open(IFilter imageFilter)
|
||||||
|
{
|
||||||
|
string filename = imageFilter.Filename;
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(filename)) return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
string basePath = filename[..^".state".Length];
|
||||||
|
string sdramPath = basePath + ".sdram";
|
||||||
|
|
||||||
|
if(!File.Exists(sdramPath)) return ErrorNumber.NoSuchFile;
|
||||||
|
|
||||||
|
long stateLength = imageFilter.DataForkLength;
|
||||||
|
long sdramLength = new FileInfo(sdramPath).Length;
|
||||||
|
|
||||||
|
if(sdramLength % RECORDING_FRAME_SIZE != 0) return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
_totalFrames = sdramLength / RECORDING_FRAME_SIZE;
|
||||||
|
|
||||||
|
if(stateLength != _totalFrames) return ErrorNumber.InvalidArgument;
|
||||||
|
|
||||||
|
_imageFilter = imageFilter;
|
||||||
|
|
||||||
|
// Read entire state file into memory (1 byte per frame, manageable size)
|
||||||
|
Stream stateStream = imageFilter.GetDataForkStream();
|
||||||
|
_stateData = new byte[stateLength];
|
||||||
|
stateStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
stateStream.EnsureRead(_stateData, 0, (int)stateLength);
|
||||||
|
|
||||||
|
// Open sdram via filter system
|
||||||
|
_sdramFilter = PluginRegister.Singleton.GetFilter(sdramPath);
|
||||||
|
|
||||||
|
if(_sdramFilter is null) return ErrorNumber.NoSuchFile;
|
||||||
|
|
||||||
|
// Compute sector counts
|
||||||
|
// Frames map to physical LBAs: frame[i] → LBA (LBA_START + i)
|
||||||
|
// Negative LBAs: LBA_START .. -1, count = min(-LBA_START, _totalFrames)
|
||||||
|
// Positive LBAs: 0 .. (_totalFrames + LBA_START - 1)
|
||||||
|
long negativeLbaCount = Math.Min(-LBA_START, _totalFrames);
|
||||||
|
long positiveLbaCount = Math.Max(0, _totalFrames + LBA_START);
|
||||||
|
|
||||||
|
_imageInfo.NegativeSectors = (uint)negativeLbaCount;
|
||||||
|
_imageInfo.Sectors = (ulong)positiveLbaCount;
|
||||||
|
_imageInfo.SectorSize = DVD_USER_DATA_SIZE;
|
||||||
|
_imageInfo.ImageSize = _imageInfo.Sectors * DVD_USER_DATA_SIZE;
|
||||||
|
|
||||||
|
_imageInfo.CreationTime = imageFilter.CreationTime;
|
||||||
|
_imageInfo.LastModificationTime = imageFilter.LastWriteTime;
|
||||||
|
_imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename);
|
||||||
|
_imageInfo.MetadataMediaType = MetadataMediaType.OpticalDisc;
|
||||||
|
_imageInfo.HasPartitions = true;
|
||||||
|
_imageInfo.HasSessions = true;
|
||||||
|
|
||||||
|
// Load media tag sidecars
|
||||||
|
_mediaTags = new Dictionary<MediaTagType, byte[]>();
|
||||||
|
LoadMediaTagSidecars(basePath);
|
||||||
|
|
||||||
|
// Determine media type from PFI if available
|
||||||
|
_imageInfo.MediaType = MediaType.DVDROM;
|
||||||
|
|
||||||
|
if(_mediaTags.TryGetValue(MediaTagType.DVD_PFI, out byte[] pfi))
|
||||||
|
{
|
||||||
|
PFI.PhysicalFormatInformation? decodedPfi = PFI.Decode(pfi, _imageInfo.MediaType);
|
||||||
|
|
||||||
|
if(decodedPfi.HasValue)
|
||||||
|
{
|
||||||
|
_imageInfo.MediaType = decodedPfi.Value.DiskCategory switch
|
||||||
|
{
|
||||||
|
DiskCategory.DVDPR => MediaType.DVDPR,
|
||||||
|
DiskCategory.DVDPRDL => MediaType.DVDPRDL,
|
||||||
|
DiskCategory.DVDPRW => MediaType.DVDPRW,
|
||||||
|
DiskCategory.DVDPRWDL => MediaType.DVDPRWDL,
|
||||||
|
DiskCategory.DVDR => decodedPfi.Value.PartVersion >= 6 ? MediaType.DVDRDL : MediaType.DVDR,
|
||||||
|
DiskCategory.DVDRAM => MediaType.DVDRAM,
|
||||||
|
DiskCategory.DVDRW => decodedPfi.Value.PartVersion >= 15 ? MediaType.DVDRWDL : MediaType.DVDRW,
|
||||||
|
DiskCategory.Nintendo => decodedPfi.Value.DiscSize == DVDSize.Eighty
|
||||||
|
? MediaType.GOD
|
||||||
|
: MediaType.WOD,
|
||||||
|
_ => MediaType.DVDROM
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TryInitializeNgcwAfterOpen();
|
||||||
|
|
||||||
|
_imageInfo.ReadableMediaTags = [.._mediaTags.Keys];
|
||||||
|
|
||||||
|
// Sector tags available from DVD RecordingFrame structure
|
||||||
|
_imageInfo.ReadableSectorTags =
|
||||||
|
[
|
||||||
|
SectorTagType.DvdSectorInformation,
|
||||||
|
SectorTagType.DvdSectorNumber,
|
||||||
|
SectorTagType.DvdSectorIed,
|
||||||
|
SectorTagType.DvdSectorCmi,
|
||||||
|
SectorTagType.DvdSectorTitleKey,
|
||||||
|
SectorTagType.DvdSectorEdc
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set up single track and session covering positive LBAs
|
||||||
|
Tracks =
|
||||||
|
[
|
||||||
|
new Track
|
||||||
|
{
|
||||||
|
Sequence = 1,
|
||||||
|
Session = 1,
|
||||||
|
Type = TrackType.Data,
|
||||||
|
StartSector = 0,
|
||||||
|
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
|
||||||
|
Pregap = 0,
|
||||||
|
FileType = "BINARY",
|
||||||
|
Filter = _sdramFilter,
|
||||||
|
File = sdramPath,
|
||||||
|
BytesPerSector = DVD_USER_DATA_SIZE,
|
||||||
|
RawBytesPerSector = DVD_SECTOR_SIZE
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
Sessions =
|
||||||
|
[
|
||||||
|
new Session
|
||||||
|
{
|
||||||
|
Sequence = 1,
|
||||||
|
StartSector = 0,
|
||||||
|
EndSector = _imageInfo.Sectors > 0 ? _imageInfo.Sectors - 1 : 0,
|
||||||
|
StartTrack = 1,
|
||||||
|
EndTrack = 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
Partitions =
|
||||||
|
[
|
||||||
|
new Partition
|
||||||
|
{
|
||||||
|
Sequence = 0,
|
||||||
|
Start = 0,
|
||||||
|
Length = _imageInfo.Sectors,
|
||||||
|
Size = _imageInfo.Sectors * _imageInfo.SectorSize,
|
||||||
|
Offset = 0,
|
||||||
|
Type = "DVD Data"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadMediaTag(MediaTagType tag, out byte[] buffer)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
if(!_mediaTags.TryGetValue(tag, out byte[] data)) return ErrorNumber.NoData;
|
||||||
|
|
||||||
|
buffer = new byte[data.Length];
|
||||||
|
Array.Copy(data, buffer, data.Length);
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
|
||||||
|
{
|
||||||
|
buffer = new byte[DVD_USER_DATA_SIZE];
|
||||||
|
ErrorNumber errno = ReadSectorLong(sectorAddress, negative, out byte[] long_buffer, out sectorStatus);
|
||||||
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
|
|
||||||
|
Array.Copy(long_buffer, Aaru.Decoders.Nintendo.Sector.NintendoMainDataOffset, buffer, 0, DVD_USER_DATA_SIZE);
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||||
|
out SectorStatus[] sectorStatus)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
sectorStatus = null;
|
||||||
|
|
||||||
|
buffer = new byte[length * DVD_USER_DATA_SIZE];
|
||||||
|
sectorStatus = new SectorStatus[length];
|
||||||
|
|
||||||
|
for(uint i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
ulong addr = negative ? sectorAddress - i : sectorAddress + i;
|
||||||
|
ErrorNumber errno = ReadSector(addr, negative, out byte[] sector, out SectorStatus status);
|
||||||
|
|
||||||
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
|
|
||||||
|
Array.Copy(sector, 0, buffer, i * DVD_USER_DATA_SIZE, DVD_USER_DATA_SIZE);
|
||||||
|
sectorStatus[i] = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorLong(ulong sectorAddress, bool negative, out byte[] buffer,
|
||||||
|
out SectorStatus sectorStatus) =>
|
||||||
|
ReadSectorLongForNgcw(sectorAddress, negative, out buffer, out sectorStatus);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorsLong(ulong sectorAddress, bool negative, uint length, out byte[] buffer,
|
||||||
|
out SectorStatus[] sectorStatus)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
sectorStatus = null;
|
||||||
|
|
||||||
|
buffer = new byte[length * DVD_SECTOR_SIZE];
|
||||||
|
sectorStatus = new SectorStatus[length];
|
||||||
|
|
||||||
|
for(uint i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out SectorStatus status);
|
||||||
|
|
||||||
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
|
|
||||||
|
Array.Copy(sector, 0, buffer, i * DVD_SECTOR_SIZE, DVD_SECTOR_SIZE);
|
||||||
|
sectorStatus[i] = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorTag(ulong sectorAddress, bool negative, SectorTagType tag, out byte[] buffer)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
return ReadSectorsTag(sectorAddress, negative, 1, tag, out buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorsTag(ulong sectorAddress, bool negative, uint length, SectorTagType tag,
|
||||||
|
out byte[] buffer)
|
||||||
|
{
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
uint sectorOffset;
|
||||||
|
uint sectorSize;
|
||||||
|
|
||||||
|
switch(tag)
|
||||||
|
{
|
||||||
|
case SectorTagType.DvdSectorInformation:
|
||||||
|
sectorOffset = 0;
|
||||||
|
sectorSize = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SectorTagType.DvdSectorNumber:
|
||||||
|
sectorOffset = 1;
|
||||||
|
sectorSize = 3;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SectorTagType.DvdSectorIed:
|
||||||
|
sectorOffset = 4;
|
||||||
|
sectorSize = 2;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SectorTagType.DvdSectorCmi:
|
||||||
|
sectorOffset = 6;
|
||||||
|
sectorSize = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SectorTagType.DvdSectorTitleKey:
|
||||||
|
sectorOffset = 7;
|
||||||
|
sectorSize = 5;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SectorTagType.DvdSectorEdc:
|
||||||
|
sectorOffset = 2060;
|
||||||
|
sectorSize = 4;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return ErrorNumber.NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = new byte[sectorSize * length];
|
||||||
|
|
||||||
|
for(uint i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
ErrorNumber errno = ReadSectorLong(sectorAddress + i, negative, out byte[] sector, out _);
|
||||||
|
|
||||||
|
if(errno != ErrorNumber.NoError) return errno;
|
||||||
|
|
||||||
|
Array.Copy(sector, sectorOffset, buffer, i * sectorSize, sectorSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorNumber.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSector(ulong sectorAddress, uint track, out byte[] buffer, out SectorStatus sectorStatus) =>
|
||||||
|
ReadSector(sectorAddress, false, out buffer, out sectorStatus);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorLong(ulong sectorAddress, uint track, out byte[] buffer,
|
||||||
|
out SectorStatus sectorStatus) =>
|
||||||
|
ReadSectorLong(sectorAddress, false, out buffer, out sectorStatus);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectors(ulong sectorAddress, uint length, uint track, out byte[] buffer,
|
||||||
|
out SectorStatus[] sectorStatus) =>
|
||||||
|
ReadSectors(sectorAddress, false, length, out buffer, out sectorStatus);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorsLong(ulong sectorAddress, uint length, uint track, out byte[] buffer,
|
||||||
|
out SectorStatus[] sectorStatus) =>
|
||||||
|
ReadSectorsLong(sectorAddress, false, length, out buffer, out sectorStatus);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag, out byte[] buffer) =>
|
||||||
|
ReadSectorTag(sectorAddress, false, tag, out buffer);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ErrorNumber ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag,
|
||||||
|
out byte[] buffer) =>
|
||||||
|
ReadSectorsTag(sectorAddress, false, length, tag, out buffer);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<Track> GetSessionTracks(Session session) => Tracks;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public List<Track> GetSessionTracks(ushort session) => Tracks;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads one RecordingFrame from the .sdram file and flattens it into a 2064-byte DVD sector
|
||||||
|
/// by extracting only the 172-byte main_data portion from each of the 12 rows (discarding PI/PO parity).
|
||||||
|
/// </summary>
|
||||||
|
byte[] ReadAndFlattenFrame(long frameIndex)
|
||||||
|
{
|
||||||
|
Stream stream = _sdramFilter.GetDataForkStream();
|
||||||
|
long offset = frameIndex * RECORDING_FRAME_SIZE;
|
||||||
|
|
||||||
|
if(offset + RECORDING_FRAME_SIZE > stream.Length) return null;
|
||||||
|
|
||||||
|
var frame = new byte[RECORDING_FRAME_SIZE];
|
||||||
|
stream.Seek(offset, SeekOrigin.Begin);
|
||||||
|
stream.EnsureRead(frame, 0, RECORDING_FRAME_SIZE);
|
||||||
|
|
||||||
|
// Flatten: copy 172 main-data bytes from each of the 12 rows into a contiguous 2064-byte buffer
|
||||||
|
var dvdSector = new byte[DVD_SECTOR_SIZE];
|
||||||
|
int rowStride = ROW_MAIN_DATA_SIZE + ROW_PARITY_INNER_SIZE;
|
||||||
|
|
||||||
|
for(int row = 0; row < RECORDING_FRAME_ROWS; row++)
|
||||||
|
Array.Copy(frame, row * rowStride, dvdSector, row * ROW_MAIN_DATA_SIZE, ROW_MAIN_DATA_SIZE);
|
||||||
|
|
||||||
|
return dvdSector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Maps a Redumper state byte to an Aaru SectorStatus.</summary>
|
||||||
|
static SectorStatus MapState(byte state) =>
|
||||||
|
state switch
|
||||||
|
{
|
||||||
|
0 => SectorStatus.NotDumped, // ERROR_SKIP
|
||||||
|
1 => SectorStatus.Errored, // ERROR_C2
|
||||||
|
_ => SectorStatus.Dumped // SUCCESS_C2_OFF (2), SUCCESS_SCSI_OFF (3), SUCCESS (4)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Loads Redumper sidecar files (.physical, .manufacturer, .bca) as media tags.</summary>
|
||||||
|
void LoadMediaTagSidecars(string basePath)
|
||||||
|
{
|
||||||
|
// PFI layer 0: prefer unindexed, fall back to .0.physical
|
||||||
|
LoadScsiSidecar(basePath, ".physical", ".0.physical", MediaTagType.DVD_PFI);
|
||||||
|
|
||||||
|
// PFI layer 1
|
||||||
|
LoadScsiSidecar(basePath, ".1.physical", null, MediaTagType.DVD_PFI_2ndLayer);
|
||||||
|
|
||||||
|
// DMI layer 0: prefer unindexed, fall back to .0.manufacturer
|
||||||
|
LoadScsiSidecar(basePath, ".manufacturer", ".0.manufacturer", MediaTagType.DVD_DMI);
|
||||||
|
|
||||||
|
// BCA (no SCSI header stripping — redumper writes raw BCA data)
|
||||||
|
string bcaPath = basePath + ".bca";
|
||||||
|
|
||||||
|
if(File.Exists(bcaPath))
|
||||||
|
{
|
||||||
|
byte[] bcaData = File.ReadAllBytes(bcaPath);
|
||||||
|
|
||||||
|
if(bcaData.Length > 0)
|
||||||
|
{
|
||||||
|
_mediaTags[MediaTagType.DVD_BCA] = bcaData;
|
||||||
|
AaruLogging.Debug(MODULE_NAME, Localization.Found_media_tag_0, MediaTagType.DVD_BCA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a SCSI READ DVD STRUCTURE response sidecar, strips the 4-byte parameter list header,
|
||||||
|
/// and stores the 2048-byte payload as a media tag.
|
||||||
|
/// </summary>
|
||||||
|
void LoadScsiSidecar(string basePath, string primarySuffix, string fallbackSuffix, MediaTagType tag)
|
||||||
|
{
|
||||||
|
string path = basePath + primarySuffix;
|
||||||
|
|
||||||
|
if(!File.Exists(path))
|
||||||
|
{
|
||||||
|
if(fallbackSuffix is null) return;
|
||||||
|
|
||||||
|
path = basePath + fallbackSuffix;
|
||||||
|
|
||||||
|
if(!File.Exists(path)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = File.ReadAllBytes(path);
|
||||||
|
|
||||||
|
if(data.Length <= SCSI_HEADER_SIZE) return;
|
||||||
|
|
||||||
|
// Strip the 4-byte SCSI parameter list header
|
||||||
|
byte[] payload = new byte[data.Length - SCSI_HEADER_SIZE];
|
||||||
|
Array.Copy(data, SCSI_HEADER_SIZE, payload, 0, payload.Length);
|
||||||
|
|
||||||
|
_mediaTags[tag] = payload;
|
||||||
|
AaruLogging.Debug(MODULE_NAME, Localization.Found_media_tag_0, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Aaru.Images/Redumper/Redumper.cs
Normal file
118
Aaru.Images/Redumper/Redumper.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Redumper.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Manages Redumper raw DVD dump images (.sdram + .state).
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.CommonTypes.Interfaces;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Aaru.CommonTypes.Interfaces.IOpticalMediaImage" />
|
||||||
|
/// <summary>
|
||||||
|
/// Implements reading Redumper raw DVD dump images (.sdram with .state sidecar).
|
||||||
|
/// The .sdram file stores scrambled DVD RecordingFrames (2366 bytes each, including
|
||||||
|
/// inner and outer Reed–Solomon parity). The .state file has one byte per frame
|
||||||
|
/// indicating dump status. The first frame in the file corresponds to physical
|
||||||
|
/// sector number LBA_START (-0x30000 / -196608).
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class Redumper : IOpticalMediaImage
|
||||||
|
{
|
||||||
|
const string MODULE_NAME = "Redumper plugin";
|
||||||
|
|
||||||
|
/// <summary>Size of a single DVD RecordingFrame: 12 rows of (172 main + 10 PI) + 182 PO.</summary>
|
||||||
|
const int RECORDING_FRAME_SIZE = 2366;
|
||||||
|
|
||||||
|
/// <summary>Size of a DVD sector without parity (ID + CPR_MAI + user data + EDC).</summary>
|
||||||
|
const int DVD_SECTOR_SIZE = 2064;
|
||||||
|
|
||||||
|
/// <summary>DVD user data size.</summary>
|
||||||
|
const int DVD_USER_DATA_SIZE = 2048;
|
||||||
|
|
||||||
|
/// <summary>Number of main-data bytes per row in a RecordingFrame.</summary>
|
||||||
|
const int ROW_MAIN_DATA_SIZE = 172;
|
||||||
|
|
||||||
|
/// <summary>Number of inner-parity bytes per row.</summary>
|
||||||
|
const int ROW_PARITY_INNER_SIZE = 10;
|
||||||
|
|
||||||
|
/// <summary>Number of rows in a RecordingFrame.</summary>
|
||||||
|
const int RECORDING_FRAME_ROWS = 12;
|
||||||
|
|
||||||
|
/// <summary>Size of the outer parity block.</summary>
|
||||||
|
const int PARITY_OUTER_SIZE = 182;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First physical LBA stored at file offset 0 in the .sdram/.state files.
|
||||||
|
/// DVD user-data LBA 0 starts at file index -LBA_START (196608).
|
||||||
|
/// </summary>
|
||||||
|
const int LBA_START = -0x30000;
|
||||||
|
|
||||||
|
/// <summary>SCSI READ DVD STRUCTURE parameter list header size (4 bytes).</summary>
|
||||||
|
const int SCSI_HEADER_SIZE = 4;
|
||||||
|
|
||||||
|
readonly Decoders.DVD.Sector _decoding = new();
|
||||||
|
readonly Decoders.Nintendo.Sector _nintendoDecoder = new();
|
||||||
|
|
||||||
|
/// <summary>Derived Nintendo key from LBA 0 so sectors 16+ can be descrambled.</summary>
|
||||||
|
byte? _nintendoDerivedKey;
|
||||||
|
|
||||||
|
IFilter _imageFilter;
|
||||||
|
ImageInfo _imageInfo;
|
||||||
|
Dictionary<MediaTagType, byte[]> _mediaTags;
|
||||||
|
byte[] _stateData;
|
||||||
|
IFilter _sdramFilter;
|
||||||
|
long _totalFrames;
|
||||||
|
|
||||||
|
public Redumper() => _imageInfo = new ImageInfo
|
||||||
|
{
|
||||||
|
ReadableSectorTags = [],
|
||||||
|
ReadableMediaTags = [],
|
||||||
|
HasPartitions = true,
|
||||||
|
HasSessions = true,
|
||||||
|
Version = null,
|
||||||
|
Application = "Redumper",
|
||||||
|
ApplicationVersion = null,
|
||||||
|
Creator = null,
|
||||||
|
Comments = null,
|
||||||
|
MediaManufacturer = null,
|
||||||
|
MediaModel = null,
|
||||||
|
MediaSerialNumber = null,
|
||||||
|
MediaBarcode = null,
|
||||||
|
MediaPartNumber = null,
|
||||||
|
MediaSequence = 0,
|
||||||
|
LastMediaSequence = 0,
|
||||||
|
DriveManufacturer = null,
|
||||||
|
DriveModel = null,
|
||||||
|
DriveSerialNumber = null,
|
||||||
|
DriveFirmwareRevision = null
|
||||||
|
};
|
||||||
|
}
|
||||||
105
Aaru.Images/Redumper/Verify.cs
Normal file
105
Aaru.Images/Redumper/Verify.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : Verify.cs
|
||||||
|
// Author(s) : Rebecca Wallander <sakcheen@gmail.com>
|
||||||
|
//
|
||||||
|
// Component : Disc image plugins.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Verifies Redumper raw DVD dump images.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2026 Rebecca Wallander
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.Decoders.DVD;
|
||||||
|
|
||||||
|
namespace Aaru.Images;
|
||||||
|
|
||||||
|
public sealed partial class Redumper
|
||||||
|
{
|
||||||
|
#region IOpticalMediaImage Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool? VerifySector(ulong sectorAddress)
|
||||||
|
{
|
||||||
|
long frameIndex = (long)sectorAddress - LBA_START;
|
||||||
|
|
||||||
|
if(frameIndex < 0 || frameIndex >= _totalFrames) return null;
|
||||||
|
|
||||||
|
if(MapState(_stateData[frameIndex]) != SectorStatus.Dumped) return null;
|
||||||
|
|
||||||
|
byte[] dvdSector = ReadAndFlattenFrame(frameIndex);
|
||||||
|
|
||||||
|
if(dvdSector is null) return null;
|
||||||
|
|
||||||
|
if(IsNintendoMediaType(_imageInfo.MediaType))
|
||||||
|
{
|
||||||
|
byte[] work = (byte[])dvdSector.Clone();
|
||||||
|
|
||||||
|
if(!DescrambleNintendo2064InPlace(work, (int)sectorAddress)) return null;
|
||||||
|
|
||||||
|
dvdSector = work;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Sector.CheckIed(dvdSector)) return false;
|
||||||
|
|
||||||
|
return Sector.CheckEdc(dvdSector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> failingLbas,
|
||||||
|
out List<ulong> unknownLbas)
|
||||||
|
{
|
||||||
|
failingLbas = [];
|
||||||
|
unknownLbas = [];
|
||||||
|
|
||||||
|
for(ulong i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
bool? result = VerifySector(sectorAddress + i);
|
||||||
|
|
||||||
|
switch(result)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
unknownLbas.Add(sectorAddress + i);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
failingLbas.Add(sectorAddress + i);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(unknownLbas.Count > 0) return null;
|
||||||
|
|
||||||
|
return failingLbas.Count <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> failingLbas,
|
||||||
|
out List<ulong> unknownLbas) =>
|
||||||
|
VerifySectors(sectorAddress, length, out failingLbas, out unknownLbas);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user