diff --git a/Aaru.Core/Devices/Dumping/Dump.cs b/Aaru.Core/Devices/Dumping/Dump.cs
index b592cc97d..08b6e0a9e 100644
--- a/Aaru.Core/Devices/Dumping/Dump.cs
+++ b/Aaru.Core/Devices/Dumping/Dump.cs
@@ -104,6 +104,7 @@ public partial class Dump
readonly Stopwatch _speedStopwatch;
readonly bool _stopOnError;
readonly bool _storeEncrypted;
+ readonly bool _bypassWiiDecryption;
readonly DumpSubchannel _subchannel;
readonly bool _titleKeys;
readonly bool _trim;
@@ -171,6 +172,7 @@ public partial class Dump
/// Dimensions of graph in pixels for a square
/// Check sectors integrity before writing to image
/// Try to fix sectors integrity
+ /// When dumping Wii (WOD), skip partition AES decryption and store encrypted data
public Dump(bool doResume, Device dev, string devicePath, IBaseWritableImage outputPlugin, ushort retryPasses,
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, Encoding encoding,
string outputPrefix, string outputPath, Dictionary formatOptions, Metadata preSidecar,
@@ -179,7 +181,7 @@ public partial class Dump
bool fixSubchannel, bool fixSubchannelCrc, bool skipCdireadyHole, ErrorLog errorLog,
bool generateSubchannels, uint maximumReadable, bool useBufferedReads, bool storeEncrypted,
bool titleKeys, uint ignoreCdrRunOuts, bool createGraph, uint dimensions, bool paranoia,
- bool cureParanoia)
+ bool cureParanoia, bool bypassWiiDecryption)
{
_doResume = doResume;
_dev = dev;
@@ -223,6 +225,7 @@ public partial class Dump
_dimensions = dimensions;
_paranoia = paranoia;
_cureParanoia = cureParanoia;
+ _bypassWiiDecryption = bypassWiiDecryption;
_dumpStopwatch = new Stopwatch();
_sidecarStopwatch = new Stopwatch();
_speedStopwatch = new Stopwatch();
diff --git a/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs b/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
index 76f4f2e37..eebfe0490 100644
--- a/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
+++ b/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
@@ -58,21 +58,17 @@ partial class Dump
ulong[] _ngcwPartSysEnd;
DataRegion[] _ngcwGcDataMap;
ulong _ngcwGcSysEnd;
-
- bool _omniDriveNintendoSoftwareDescramble;
byte? _nintendoDerivedDiscKey;
- readonly Aaru.Decoders.Nintendo.Sector _nintendoSectorDecoder = new Aaru.Decoders.Nintendo.Sector();
bool InitializeNgcwContext(MediaType dskType, Reader scsiReader, IWritableImage outputFormat)
{
_ngcwEnabled = dskType is MediaType.GOD or MediaType.WOD;
_ngcwMediaType = dskType;
_ngcwJunkCollector = new JunkCollector();
- _omniDriveNintendoSoftwareDescramble = scsiReader.OmniDriveNintendoMode;
if(!_ngcwEnabled) return true;
- if(_omniDriveNintendoSoftwareDescramble)
+ if(scsiReader.OmniDriveNintendoMode)
{
UpdateStatus?.Invoke(UI.Ngcw_nintendo_software_descramble);
@@ -108,10 +104,6 @@ partial class Dump
return true;
}
- if(_omniDriveNintendoSoftwareDescramble &&
- !DescrambleNintendoLongBuffer(longBuffer, startSector, sectors))
- return false;
-
if(_ngcwMediaType == MediaType.GOD)
return TransformGameCubeLongSectors(longBuffer, startSector, sectors, statuses);
@@ -155,6 +147,15 @@ partial class Dump
return false;
}
+ UpdateStatus?.Invoke(string.Format(UI.Ngcw_found_0_partitions, _ngcwPartitions.Count));
+
+ if(_bypassWiiDecryption)
+ {
+ UpdateStatus?.Invoke(UI.Ngcw_wii_dump_bypass_decryption);
+
+ return true;
+ }
+
WiiPartitionRegion[] regions = NgcwPartitions.BuildRegionMap(_ngcwPartitions);
byte[] keyMapData = NgcwPartitions.SerializeKeyMap(regions);
@@ -253,6 +254,13 @@ partial class Dump
ulong discOffset = startSector * NGCW_SECTOR_SIZE;
int partIndex = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, discOffset);
+ if(_bypassWiiDecryption && partIndex >= 0)
+ {
+ for(int i = 0; i < sectors; i++) statuses[i] = SectorStatus.Encrypted;
+
+ return true;
+ }
+
if(partIndex < 0)
{
byte[] payload = new byte[sectors * NGCW_SECTOR_SIZE];
@@ -443,7 +451,9 @@ partial class Dump
bool EnsureNintendoDerivedKeyFromLba0(Reader scsiReader)
{
- if(!_omniDriveNintendoSoftwareDescramble || _nintendoDerivedDiscKey.HasValue) return true;
+ if(_nintendoDerivedDiscKey.HasValue) return true;
+
+ if(!scsiReader.OmniDriveNintendoMode) return true;
bool sense = scsiReader.ReadBlock(out byte[] raw, 0, out _, out _, out _);
@@ -454,63 +464,15 @@ partial class Dump
return false;
}
- ErrorNumber errno = _nintendoSectorDecoder.Scramble(raw, 0, out byte[] descrambled);
-
- if(errno != ErrorNumber.NoError || descrambled == null)
- {
- StoppingErrorMessage?.Invoke(Localization.Core.Unable_to_read_medium);
-
- return false;
- }
-
- byte[] cprMai8 = new byte[8];
- Array.Copy(descrambled, 6, cprMai8, 0, 8);
- _nintendoDerivedDiscKey = Sector.DeriveNintendoKey(cprMai8);
+ byte[] keyMaterial = new byte[8];
+ Array.Copy(raw, Sector.NintendoMainDataOffset, keyMaterial, 0, 8);
+ _nintendoDerivedDiscKey = Sector.DeriveNintendoKey(keyMaterial);
+ scsiReader.NintendoDerivedDiscKey = _nintendoDerivedDiscKey;
UpdateStatus?.Invoke(string.Format(UI.Ngcw_nintendo_derived_key_0, _nintendoDerivedDiscKey.Value));
return true;
}
- bool DescrambleNintendoLongBuffer(byte[] longBuffer, ulong startSector, uint sectors)
- {
- if(!_omniDriveNintendoSoftwareDescramble) return true;
-
- for(uint i = 0; i < sectors; i++)
- {
- if(!DescrambleNintendo2064At(longBuffer, (int)(i * NGCW_LONG_SECTOR_SIZE), startSector + i))
- return false;
- }
-
- return true;
- }
-
- bool DescrambleNintendo2064At(byte[] buffer, int offset, ulong lba)
- {
- byte[] one = new byte[NGCW_LONG_SECTOR_SIZE];
- Array.Copy(buffer, offset, one, 0, NGCW_LONG_SECTOR_SIZE);
- byte key = lba < NGCW_SECTORS_PER_GROUP ? (byte)0 : (_nintendoDerivedDiscKey ?? (byte)0);
-
- ErrorNumber error = _nintendoSectorDecoder.Scramble(one, key, out byte[] decoded);
-
- if(error != ErrorNumber.NoError)
- {
- Array.Clear(buffer, offset, NGCW_LONG_SECTOR_SIZE);
-
- return false;
- }
-
- if(decoded != null) Array.Copy(decoded, 0, buffer, offset, NGCW_LONG_SECTOR_SIZE);
-
- if(lba == 0 && decoded != null)
- {
- byte[] cprMai8 = new byte[8];
- Array.Copy(decoded, 6, cprMai8, 0, 8);
- _nintendoDerivedDiscKey = Sector.DeriveNintendoKey(cprMai8);
- }
-
- return true;
- }
-
List ParseWiiPartitionsFromDevice(Reader scsiReader)
{
byte[] partitionTable = ReadDiscBytesFromDevice(scsiReader, 0x40000, 32);
@@ -585,12 +547,6 @@ partial class Dump
if(sense || _dev.Error || rawSector == null || rawSector.Length < NGCW_PAYLOAD_OFFSET + NGCW_SECTOR_SIZE)
return null;
- if(_omniDriveNintendoSoftwareDescramble && rawSector.Length >= NGCW_LONG_SECTOR_SIZE)
- {
- if(!DescrambleNintendo2064At(rawSector, 0, sector))
- return null;
- }
-
Array.Copy(rawSector, NGCW_PAYLOAD_OFFSET + sectorOff, result, read, chunk);
read += chunk;
}
@@ -604,15 +560,6 @@ partial class Dump
if(sense || _dev.Error || rawBuffer == null) return null;
- if(_omniDriveNintendoSoftwareDescramble && rawBuffer.Length >= count * NGCW_LONG_SECTOR_SIZE)
- {
- for(uint i = 0; i < count; i++)
- {
- if(!DescrambleNintendo2064At(rawBuffer, (int)(i * NGCW_LONG_SECTOR_SIZE), startSector + i))
- return null;
- }
- }
-
byte[] payload = new byte[count * NGCW_SECTOR_SIZE];
for(uint i = 0; i < count; i++)
diff --git a/Aaru.Core/Devices/Reader.cs b/Aaru.Core/Devices/Reader.cs
index e3389052a..8e6dcb93a 100644
--- a/Aaru.Core/Devices/Reader.cs
+++ b/Aaru.Core/Devices/Reader.cs
@@ -79,6 +79,12 @@ sealed partial class Reader
internal bool CanReadRaw { get; private set; }
/// When true with OmniDrive raw reads, use descramble=0 and software Nintendo descrambling (GameCube/Wii).
internal bool OmniDriveNintendoMode { get; set; }
+
+ ///
+ /// Disc-wide Nintendo XOR key (0–15) from LBA 0 CPR_MAI, set after the dump pipeline reads LBA 0. Used for
+ /// OmniDrive Nintendo reads at LBAs ≥ 16; null until derived.
+ ///
+ internal byte? NintendoDerivedDiscKey { get; set; }
internal bool CanSeek => _ataSeek || _seek6 || _seek10;
internal bool CanSeekLba => _ataSeekLba || _seek6 || _seek10;
diff --git a/Aaru.Core/Devices/ReaderSCSI.cs b/Aaru.Core/Devices/ReaderSCSI.cs
index 58a72c3ed..667653c61 100644
--- a/Aaru.Core/Devices/ReaderSCSI.cs
+++ b/Aaru.Core/Devices/ReaderSCSI.cs
@@ -591,8 +591,8 @@ sealed partial class Reader
// Try OmniDrive on drives with OmniDrive firmware (standard descramble=1 and Nintendo descramble=0)
if(_dev.IsOmniDriveFirmware())
{
- bool omniStandardOk = !_dev.OmniDriveReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _);
- bool omniNintendoOk = !_dev.OmniDriveReadNintendoDvd(out _, out senseBuf, 0, 1, _timeout, out _);
+ bool omniStandardOk = !_dev.OmniDriveReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _, true, true);
+ bool omniNintendoOk = !_dev.OmniDriveReadNintendoDvd(out _, out senseBuf, 0, 1, _timeout, out _, true, true);
OmniDriveReadRaw = omniStandardOk || omniNintendoOk;
}
@@ -856,7 +856,10 @@ sealed partial class Reader
lba,
count,
_timeout,
- out duration);
+ out duration,
+ false,
+ true,
+ NintendoDerivedDiscKey);
else
sense = _dev.OmniDriveReadRawDvd(out buffer,
out senseBuf,
diff --git a/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs b/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
index 264722eb6..38903edd0 100644
--- a/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
+++ b/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
@@ -271,6 +271,216 @@ public partial class Convert
return ErrorNumber.NoError;
}
+ /// Fill a 32 KiB Wii encrypted group from 2048-byte logical ReadSector slices.
+ void FillWiiEncryptedGroupFromShortSectors(ulong firstLogicalSector, byte[] encGrp)
+ {
+ const int sectorSize = Crypto.SECTOR_SIZE;
+ const int sectorsPerBlock = Crypto.LOGICAL_PER_GROUP;
+
+ for(int s = 0; s < sectorsPerBlock; s++)
+ {
+ ulong sec = firstLogicalSector + (ulong)s;
+ ErrorNumber errno = _inputImage.ReadSector(sec, false, out byte[] sd, out _);
+
+ if(errno != ErrorNumber.NoError || sd == null)
+ Array.Clear(encGrp, s * sectorSize, sectorSize);
+ else
+ Array.Copy(sd, 0, encGrp, s * sectorSize, sectorSize);
+ }
+ }
+
+ ///
+ /// Read one encrypted Wii group from long sectors: copy main_data into and
+ /// keep full long buffers for output (one ReadSectorLong per logical sector).
+ ///
+ void ReadWiiEncryptedGroupFromLongSectors(ulong firstLogicalSector, byte[] encGrp, byte[][] longSectorBuffers,
+ int longSectorSize)
+ {
+ const int sectorSize = Crypto.SECTOR_SIZE;
+ const int sectorsPerBlock = Crypto.LOGICAL_PER_GROUP;
+
+ for(int s = 0; s < sectorsPerBlock; s++)
+ {
+ ulong sec = firstLogicalSector + (ulong)s;
+ ErrorNumber errno = _inputImage.ReadSectorLong(sec, false, out byte[] sd, out _);
+
+ byte[] stored = longSectorBuffers[s];
+
+ if(stored == null || stored.Length != longSectorSize)
+ {
+ stored = new byte[longSectorSize];
+ longSectorBuffers[s] = stored;
+ }
+
+ if(errno != ErrorNumber.NoError || sd == null)
+ {
+ Array.Clear(encGrp, s * sectorSize, sectorSize);
+ Array.Clear(stored, 0, longSectorSize);
+
+ continue;
+ }
+
+ int copyLong = sd.Length < longSectorSize ? sd.Length : longSectorSize;
+ Array.Copy(sd, 0, stored, 0, copyLong);
+
+ if(copyLong < longSectorSize)
+ Array.Clear(stored, copyLong, longSectorSize - copyLong);
+
+ if(sd.Length >= Sector.NintendoMainDataOffset + sectorSize)
+ Array.Copy(sd, Sector.NintendoMainDataOffset, encGrp, s * sectorSize, sectorSize);
+ else
+ Array.Clear(encGrp, s * sectorSize, sectorSize);
+ }
+ }
+
+ ///
+ /// Classify FST regions, run LFG junk detection on decrypted user data, and build the 32 KiB
+ /// plaintext group (hash + user, junk zeroed).
+ ///
+ byte[] ProcessWiiDecryptedGroup(int inPart, ulong groupDiscOff, byte[] hashBlock, byte[] groupData,
+ JunkCollector jc, ref ulong dataSectors, ref ulong junkSectors)
+ {
+ const int groupSize = Crypto.GROUP_SIZE;
+ const int hashSize = Crypto.GROUP_HASH_SIZE;
+ const int groupDataSize = Crypto.GROUP_DATA_SIZE;
+ const int sectorSize = Crypto.SECTOR_SIZE;
+
+ ulong groupNum = (groupDiscOff - _ngcwPartitions[inPart].DataOffset) / groupSize;
+ ulong logicalOffset = groupNum * groupDataSize;
+
+ bool[] sectorIsData = new bool[16];
+ int udCount = 0;
+
+ for(ulong off = 0; off < groupDataSize; off += sectorSize)
+ {
+ ulong chunk = groupDataSize - off;
+
+ if(chunk > sectorSize) chunk = sectorSize;
+
+ if(logicalOffset + off < _ngcwPartSysEnd[inPart])
+ sectorIsData[udCount] = true;
+ else if(_ngcwPartDataMaps[inPart] != null)
+ {
+ sectorIsData[udCount] = DataMap.IsDataRegion(_ngcwPartDataMaps[inPart],
+ logicalOffset + off,
+ chunk);
+ }
+ else
+ sectorIsData[udCount] = true;
+
+ udCount++;
+ }
+
+ ulong blockPhase = logicalOffset % groupSize;
+ ulong block2Start = blockPhase > 0 ? groupSize - blockPhase : groupDataSize;
+
+ if(block2Start > groupDataSize) block2Start = groupDataSize;
+
+ bool haveSeed1 = false;
+ uint[] seed1 = new uint[Lfg.SEED_SIZE];
+ bool haveSeed2 = false;
+ uint[] seed2 = new uint[Lfg.SEED_SIZE];
+
+ for(int s = 0; s < udCount; s++)
+ {
+ if(sectorIsData[s]) continue;
+
+ ulong soff = (ulong)s * sectorSize;
+ bool inBlock2 = soff >= block2Start;
+
+ if(inBlock2 && haveSeed2) continue;
+ if(!inBlock2 && haveSeed1) continue;
+
+ int avail = (int)(groupDataSize - soff);
+ int doff = (int)((logicalOffset + soff) % groupSize);
+
+ if(avail < Lfg.MIN_SEED_DATA_BYTES) continue;
+
+ uint[] dst = inBlock2 ? seed2 : seed1;
+ int m = Lfg.GetSeed(groupData.AsSpan((int)soff, avail), doff, dst);
+
+ if(m > 0)
+ {
+ if(inBlock2)
+ haveSeed2 = true;
+ else
+ haveSeed1 = true;
+ }
+
+ if(haveSeed1 && haveSeed2) break;
+ }
+
+ byte[] decryptedGroup = new byte[groupSize];
+ Array.Copy(hashBlock, 0, decryptedGroup, 0, hashSize);
+
+ for(int s = 0; s < udCount; s++)
+ {
+ ulong off = (ulong)s * sectorSize;
+ int chunk = groupDataSize - (int)off;
+ int outOff = hashSize + (int)off;
+
+ if(chunk > sectorSize) chunk = sectorSize;
+
+ if(sectorIsData[s])
+ {
+ Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
+ dataSectors++;
+
+ continue;
+ }
+
+ bool inBlock2 = off >= block2Start;
+ bool haveSeed = inBlock2 ? haveSeed2 : haveSeed1;
+ uint[] theSeed = inBlock2 ? seed2 : seed1;
+
+ if(!haveSeed)
+ {
+ Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
+ dataSectors++;
+
+ continue;
+ }
+
+ uint[] lfgBuffer = new uint[Lfg.MIN_SEED_DATA_BYTES / sizeof(uint)];
+ uint[] seedCopy = new uint[Lfg.SEED_SIZE];
+ Array.Copy(theSeed, seedCopy, Lfg.SEED_SIZE);
+ Lfg.SetSeed(lfgBuffer, seedCopy);
+ int positionBytes = 0;
+
+ int adv = (int)((logicalOffset + off) % groupSize);
+
+ if(adv > 0)
+ {
+ byte[] discard = new byte[4096];
+ int rem = adv;
+
+ while(rem > 0)
+ {
+ int step = rem > discard.Length ? discard.Length : rem;
+ Lfg.GetBytes(lfgBuffer, ref positionBytes, discard, 0, step);
+ rem -= step;
+ }
+ }
+
+ byte[] expected = new byte[sectorSize];
+ Lfg.GetBytes(lfgBuffer, ref positionBytes, expected, 0, chunk);
+
+ if(groupData.AsSpan((int)off, chunk).SequenceEqual(expected.AsSpan(0, chunk)))
+ {
+ Array.Clear(decryptedGroup, outOff, chunk);
+ jc.Add(groupDiscOff + hashSize + off, (ulong)chunk, (ushort)inPart, theSeed);
+ junkSectors++;
+ }
+ else
+ {
+ Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
+ dataSectors++;
+ }
+ }
+
+ return decryptedGroup;
+ }
+
/// Wii sector conversion pipeline.
ErrorNumber ConvertWiiSectors(ulong discSize, ulong totalLogicalSectors, JunkCollector jc, ref ulong dataSectors,
ref ulong junkSectors)
@@ -307,162 +517,16 @@ public partial class Convert
ulong groupDiscOff = _ngcwPartitions[inPart].DataOffset +
(offset - _ngcwPartitions[inPart].DataOffset) / groupSize * groupSize;
- // Read encrypted group
var encGrp = new byte[groupSize];
+ FillWiiEncryptedGroupFromShortSectors(groupDiscOff / sectorSize, encGrp);
- for(var s = 0; s < sectorsPerBlock; s++)
- {
- ulong sec = groupDiscOff / sectorSize + (ulong)s;
- ErrorNumber errno = _inputImage.ReadSector(sec, false, out byte[] sd, out _);
-
- if(errno != ErrorNumber.NoError || sd == null)
- Array.Clear(encGrp, s * sectorSize, sectorSize);
- else
- Array.Copy(sd, 0, encGrp, s * sectorSize, sectorSize);
- }
-
- // Decrypt
var hashBlock = new byte[hashSize];
var groupData = new byte[groupDataSize];
Crypto.DecryptGroup(_ngcwPartitions[inPart].TitleKey, encGrp, hashBlock, groupData);
- // Classify user data sectors
- ulong groupNum = (groupDiscOff - _ngcwPartitions[inPart].DataOffset) / groupSize;
- ulong logicalOffset = groupNum * groupDataSize;
-
- var sectorIsData = new bool[16];
- var udCount = 0;
-
- for(ulong off = 0; off < groupDataSize; off += sectorSize)
- {
- ulong chunk = groupDataSize - off;
-
- if(chunk > sectorSize) chunk = sectorSize;
-
- if(logicalOffset + off < _ngcwPartSysEnd[inPart])
- sectorIsData[udCount] = true;
- else if(_ngcwPartDataMaps[inPart] != null)
- {
- sectorIsData[udCount] = DataMap.IsDataRegion(_ngcwPartDataMaps[inPart],
- logicalOffset + off,
- chunk);
- }
- else
- sectorIsData[udCount] = true;
-
- udCount++;
- }
-
- // Extract LFG seeds (up to 2 per group for block boundaries)
- ulong blockPhase = logicalOffset % groupSize;
- ulong block2Start = blockPhase > 0 ? groupSize - blockPhase : groupDataSize;
-
- if(block2Start > groupDataSize) block2Start = groupDataSize;
-
- var haveSeed1 = false;
- var seed1 = new uint[Lfg.SEED_SIZE];
- var haveSeed2 = false;
- var seed2 = new uint[Lfg.SEED_SIZE];
-
- for(var s = 0; s < udCount; s++)
- {
- if(sectorIsData[s]) continue;
-
- ulong soff = (ulong)s * sectorSize;
- bool inBlock2 = soff >= block2Start;
-
- if(inBlock2 && haveSeed2) continue;
- if(!inBlock2 && haveSeed1) continue;
-
- var avail = (int)(groupDataSize - soff);
- var doff = (int)((logicalOffset + soff) % groupSize);
-
- if(avail < Lfg.MIN_SEED_DATA_BYTES) continue;
-
- uint[] dst = inBlock2 ? seed2 : seed1;
- int m = Lfg.GetSeed(groupData.AsSpan((int)soff, avail), doff, dst);
-
- if(m > 0)
- {
- if(inBlock2)
- haveSeed2 = true;
- else
- haveSeed1 = true;
- }
-
- if(haveSeed1 && haveSeed2) break;
- }
-
- // Build decrypted group: hash_block + processed user_data
- var decryptedGroup = new byte[groupSize];
- Array.Copy(hashBlock, 0, decryptedGroup, 0, hashSize);
-
- for(var s = 0; s < udCount; s++)
- {
- ulong off = (ulong)s * sectorSize;
- int chunk = groupDataSize - (int)off;
- int outOff = hashSize + (int)off;
-
- if(chunk > sectorSize) chunk = sectorSize;
-
- if(sectorIsData[s])
- {
- Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
- dataSectors++;
-
- continue;
- }
-
- bool inBlock2 = off >= block2Start;
- bool haveSeed = inBlock2 ? haveSeed2 : haveSeed1;
- uint[] theSeed = inBlock2 ? seed2 : seed1;
-
- if(!haveSeed)
- {
- Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
- dataSectors++;
-
- continue;
- }
-
- // Verify sector against LFG
- var lfgBuffer = new uint[Lfg.MIN_SEED_DATA_BYTES / sizeof(uint)];
- var seedCopy = new uint[Lfg.SEED_SIZE];
- Array.Copy(theSeed, seedCopy, Lfg.SEED_SIZE);
- Lfg.SetSeed(lfgBuffer, seedCopy);
- var positionBytes = 0;
-
- var adv = (int)((logicalOffset + off) % groupSize);
-
- if(adv > 0)
- {
- var discard = new byte[4096];
- int rem = adv;
-
- while(rem > 0)
- {
- int step = rem > discard.Length ? discard.Length : rem;
- Lfg.GetBytes(lfgBuffer, ref positionBytes, discard, 0, step);
- rem -= step;
- }
- }
-
- var expected = new byte[sectorSize];
- Lfg.GetBytes(lfgBuffer, ref positionBytes, expected, 0, chunk);
-
- if(groupData.AsSpan((int)off, chunk).SequenceEqual(expected.AsSpan(0, chunk)))
- {
- // Junk — zero it out, record in junk map
- Array.Clear(decryptedGroup, outOff, chunk);
- jc.Add(groupDiscOff + hashSize + off, (ulong)chunk, (ushort)inPart, theSeed);
- junkSectors++;
- }
- else
- {
- Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
- dataSectors++;
- }
- }
+ byte[] decryptedGroup =
+ ProcessWiiDecryptedGroup(inPart, groupDiscOff, hashBlock, groupData, jc, ref dataSectors,
+ ref junkSectors);
// Write all 16 sectors as SectorStatusUnencrypted
for(var s = 0; s < sectorsPerBlock; s++)
@@ -879,9 +943,194 @@ public partial class Convert
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
+ /// Wii sector conversion pipeline for long sectors (main_data + framing).
+ ErrorNumber ConvertWiiSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc,
+ ref ulong dataSectors, ref ulong junkSectors)
+ {
+ const int groupSize = Crypto.GROUP_SIZE;
+ const int hashSize = Crypto.GROUP_HASH_SIZE;
+ const int groupDataSize = Crypto.GROUP_DATA_SIZE;
+ const int sectorSize = Crypto.SECTOR_SIZE;
+ const int sectorsPerBlock = Crypto.LOGICAL_PER_GROUP;
+
+ ErrorNumber probeErr = _inputImage.ReadSectorLong(0, false, out byte[] longProbe, out _);
+
+ if(probeErr != ErrorNumber.NoError || longProbe == null ||
+ longProbe.Length < Sector.NintendoMainDataOffset + sectorSize)
+ return ErrorNumber.InOutError;
+
+ int longSectorSize = longProbe.Length;
+
+ BuildWiiPartitionFstMaps();
+
+ var longSectorBufs = new byte[sectorsPerBlock][];
+
+ ulong offset = 0;
+
+ while(offset < discSize)
+ {
+ if(_aborted) break;
+
+ ulong baseSector = offset / sectorSize;
+
+ UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_0_to_1,
+ baseSector,
+ baseSector + sectorsPerBlock),
+ (long)baseSector,
+ (long)totalLogicalSectors);
+
+ int inPart = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, offset);
+
+ if(inPart >= 0)
+ {
+ ulong groupDiscOff = _ngcwPartitions[inPart].DataOffset +
+ (offset - _ngcwPartitions[inPart].DataOffset) / groupSize * groupSize;
+
+ var encGrp = new byte[groupSize];
+ ReadWiiEncryptedGroupFromLongSectors(groupDiscOff / sectorSize, encGrp, longSectorBufs, longSectorSize);
+
+ var hashBlock = new byte[hashSize];
+ var groupData = new byte[groupDataSize];
+ Crypto.DecryptGroup(_ngcwPartitions[inPart].TitleKey, encGrp, hashBlock, groupData);
+
+ byte[] decryptedGroup =
+ ProcessWiiDecryptedGroup(inPart, groupDiscOff, hashBlock, groupData, jc, ref dataSectors,
+ ref junkSectors);
+
+ for(int s = 0; s < sectorsPerBlock; s++)
+ {
+ int dataOff = s * sectorSize;
+ int outOff = hashSize + dataOff;
+
+ if(dataOff >= groupDataSize)
+ {
+ Array.Clear(longSectorBufs[s], Sector.NintendoMainDataOffset, sectorSize);
+
+ continue;
+ }
+
+ int copyLen = groupDataSize - dataOff;
+
+ if(copyLen > sectorSize)
+ copyLen = sectorSize;
+
+ Array.Copy(decryptedGroup, outOff, longSectorBufs[s], Sector.NintendoMainDataOffset, copyLen);
+
+ if(copyLen < sectorSize)
+ Array.Clear(longSectorBufs[s], Sector.NintendoMainDataOffset + copyLen, sectorSize - copyLen);
+ }
+
+ for(int s = 0; s < sectorsPerBlock; s++)
+ {
+ ulong sector = groupDiscOff / sectorSize + (ulong)s;
+ bool ok = _outputImage.WriteSectorLong(longSectorBufs[s],
+ sector,
+ false,
+ SectorStatus.Unencrypted);
+
+ 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;
+ }
+ }
+ }
+
+ offset = groupDiscOff + groupSize;
+ }
+ else
+ {
+ const int blockSize = groupSize;
+ ulong alignedOff = offset & ~(ulong)(blockSize - 1);
+
+ int blockBytes = blockSize;
+
+ if(alignedOff + (ulong)blockBytes > discSize) blockBytes = (int)(discSize - alignedOff);
+
+ var blockBuf = new byte[blockSize];
+
+ for(int s = 0; s < sectorsPerBlock && s * sectorSize < blockBytes; s++)
+ {
+ ulong sec = alignedOff / sectorSize + (ulong)s;
+ ErrorNumber errno = _inputImage.ReadSectorLong(sec, false, out byte[] sd, out _);
+
+ byte[] stored = new byte[longSectorSize];
+ longSectorBufs[s] = stored;
+
+ if(errno != ErrorNumber.NoError || sd == null)
+ {
+ Array.Clear(blockBuf, s * sectorSize, sectorSize);
+ Array.Clear(stored, 0, longSectorSize);
+
+ continue;
+ }
+
+ int copyLong = sd.Length < longSectorSize ? sd.Length : longSectorSize;
+ Array.Copy(sd, 0, stored, 0, copyLong);
+
+ if(copyLong < longSectorSize)
+ Array.Clear(stored, copyLong, longSectorSize - copyLong);
+
+ if(sd.Length >= Sector.NintendoMainDataOffset + sectorSize)
+ Array.Copy(sd, Sector.NintendoMainDataOffset, blockBuf, s * sectorSize, sectorSize);
+ else
+ Array.Clear(blockBuf, s * sectorSize, sectorSize);
+ }
+
+ var sectorStatuses = new SectorStatus[sectorsPerBlock];
+
+ Junk.DetectJunkInBlock(blockBuf,
+ blockBytes,
+ alignedOff,
+ null,
+ 0x50000,
+ 0xFFFF,
+ jc,
+ ref dataSectors,
+ ref junkSectors,
+ sectorStatuses);
+
+ int numSectors = blockBytes / sectorSize;
+
+ for(int si = 0; si < numSectors; si++)
+ {
+ ulong sector = alignedOff / sectorSize + (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;
+ }
+ }
+ }
+
+ offset = alignedOff + (ulong)blockBytes;
+ }
+ }
+
return ErrorNumber.NoError;
}
diff --git a/Aaru.Decoders/DVD/Sector.cs b/Aaru.Decoders/DVD/Sector.cs
index 56d963e29..63033a30f 100644
--- a/Aaru.Decoders/DVD/Sector.cs
+++ b/Aaru.Decoders/DVD/Sector.cs
@@ -301,6 +301,21 @@ public sealed class Sector
return computed == stored;
}
+ ///
+ /// Check if the IED of sectors is correct
+ ///
+ /// Buffer of the sector
+ /// The number of sectors to check
+ /// True if IED is correct, False if not
+ public static bool CheckIed(byte[] sectorLong, uint transferLength)
+ {
+ for(uint i = 0; i < transferLength; i++)
+ {
+ if(!CheckIed(sectorLong.Skip((int)(i * 2064)).Take(2064).ToArray())) return false;
+ }
+ return true;
+ }
+
///
/// Tests if a seed unscrambles a sector correctly
///
diff --git a/Aaru.Devices/Device/ScsiCommands/OmniDrive.cs b/Aaru.Devices/Device/ScsiCommands/OmniDrive.cs
index dec0d670c..b28f984b1 100644
--- a/Aaru.Devices/Device/ScsiCommands/OmniDrive.cs
+++ b/Aaru.Devices/Device/ScsiCommands/OmniDrive.cs
@@ -33,14 +33,17 @@
// ****************************************************************************/
using System;
+using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs.Devices.SCSI;
using Aaru.Logging;
using Aaru.Decoders.DVD;
+using NintendoSector = Aaru.Decoders.Nintendo.Sector;
namespace Aaru.Devices;
public partial class Device
{
+ readonly NintendoSector _nintendoSectorDecoder = new NintendoSector();
enum OmniDriveDiscType
{
CD = 0,
@@ -66,16 +69,16 @@ public partial class Device
static void FillOmniDriveReadDvdCdb(Span cdb, uint lba, uint transferLength, byte cdbByte1)
{
cdb.Clear();
- cdb[0] = (byte)ScsiCommands.ReadOmniDrive;
- cdb[1] = cdbByte1;
- cdb[2] = (byte)((lba >> 24) & 0xFF);
- cdb[3] = (byte)((lba >> 16) & 0xFF);
- cdb[4] = (byte)((lba >> 8) & 0xFF);
- cdb[5] = (byte)(lba & 0xFF);
- cdb[6] = (byte)((transferLength >> 24) & 0xFF);
- cdb[7] = (byte)((transferLength >> 16) & 0xFF);
- cdb[8] = (byte)((transferLength >> 8) & 0xFF);
- cdb[9] = (byte)(transferLength & 0xFF);
+ cdb[0] = (byte)ScsiCommands.ReadOmniDrive;
+ cdb[1] = cdbByte1;
+ cdb[2] = (byte)((lba >> 24) & 0xFF);
+ cdb[3] = (byte)((lba >> 16) & 0xFF);
+ cdb[4] = (byte)((lba >> 8) & 0xFF);
+ cdb[5] = (byte)(lba & 0xFF);
+ cdb[6] = (byte)((transferLength >> 24) & 0xFF);
+ cdb[7] = (byte)((transferLength >> 16) & 0xFF);
+ cdb[8] = (byte)((transferLength >> 8) & 0xFF);
+ cdb[9] = (byte)(transferLength & 0xFF);
cdb[10] = 0; // subchannels=NONE, c2=0
cdb[11] = 0; // control
}
@@ -89,20 +92,20 @@ public partial class Device
{
bool sense = ScsiInquiry(out byte[] buffer, out _, Timeout, out _);
- if(sense || buffer == null) return false;
+ if (sense || buffer == null) return false;
Inquiry? inquiry = Inquiry.Decode(buffer);
- if(!inquiry.HasValue || inquiry.Value.Reserved5 == null || inquiry.Value.Reserved5.Length < 11)
+ if (!inquiry.HasValue || inquiry.Value.Reserved5 == null || inquiry.Value.Reserved5.Length < 11)
return false;
byte[] reserved5 = inquiry.Value.Reserved5;
byte[] omnidrive = [0x4F, 0x6D, 0x6E, 0x69, 0x44, 0x72, 0x69, 0x76, 0x65]; // "OmniDrive"
- if(reserved5.Length < omnidrive.Length) return false;
+ if (reserved5.Length < omnidrive.Length) return false;
- for(int i = 0; i < omnidrive.Length; i++)
- if(reserved5[i] != omnidrive[i])
+ for (int i = 0; i < omnidrive.Length; i++)
+ if (reserved5[i] != omnidrive[i])
return false;
return true;
@@ -131,8 +134,11 @@ public partial class Device
EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, fua, descramble));
LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense);
-
- if(!Sector.CheckEdc(buffer, transferLength)) return true;
+
+ if (!Sector.CheckIed(buffer, transferLength)) return true;
+
+ if(descramble)
+ if (!Sector.CheckEdc(buffer, transferLength)) return true;
Error = LastError != 0;
@@ -142,13 +148,16 @@ public partial class Device
}
///
- /// Reads raw Nintendo GameCube/Wii DVD sectors (2064 bytes) on OmniDrive. Default matches redumper raw DVD
- /// (descramble off); use software descramble via Aaru.Decoders.Nintendo.Sector when needed.
+ /// Reads Nintendo GameCube/Wii DVD sectors (2064 bytes) on OmniDrive. The drive returns DVD-layer data with
+ /// drive-side DVD descramble off; this method applies per-sector Nintendo XOR descramble and returns the result.
+ /// LBAs 0–15 use key 0; LBAs ≥ 16 use (from LBA 0 CPR_MAI), or 0 if not yet
+ /// derived.
///
- /// Drive-side DVD descramble (redumper raw DVD uses false).
+ /// Disc key from LBA 0 (0–15); null until the host has read and derived it.
/// true if the command failed and contains the sense buffer.
public bool OmniDriveReadNintendoDvd(out byte[] buffer, out ReadOnlySpan senseBuffer, uint lba, uint transferLength,
- uint timeout, out double duration, bool descramble = false)
+ uint timeout, out double duration, bool fua = false, bool descramble = true,
+ byte? derivedDiscKey = null)
{
senseBuffer = SenseBuffer;
Span cdb = CdbBuffer[..12];
@@ -157,11 +166,55 @@ public partial class Device
FillOmniDriveReadDvdCdb(cdb,
lba,
transferLength,
- EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, false, descramble));
+ EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, fua, false));
LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense);
- // Scrambled Nintendo sectors do not pass standard DVD EDC until software-descrambled.
+ if(!Sector.CheckIed(buffer, transferLength)) return true;
+
+ if(descramble) {
+ const int sectorBytes = 2064;
+ byte[] outBuf = new byte[sectorBytes * transferLength];
+
+ if(lba < 16 && lba + transferLength > 16) {
+ for(uint i = 0; i < transferLength; i++)
+ {
+ var slice = new byte[sectorBytes];
+ Array.Copy(buffer, i * sectorBytes, slice, 0, sectorBytes);
+ uint absLba = lba + i;
+ byte key = absLba < 16 ? (byte)0 : (derivedDiscKey ?? (byte)0);
+
+ ErrorNumber errno = _nintendoSectorDecoder.Scramble(slice, key, out byte[] descrambled);
+
+ if(errno != ErrorNumber.NoError || descrambled == null)
+ {
+ LastError = (int)errno;
+ Error = true;
+
+ return true;
+ }
+
+ Array.Copy(descrambled, 0, outBuf, i * sectorBytes, sectorBytes);
+ }
+ } else {
+ ErrorNumber errno = _nintendoSectorDecoder.Scramble(
+ buffer,
+ transferLength,
+ lba < 16 || lba > 0xffffff ? (byte)0 : (derivedDiscKey ?? (byte)0),
+ out outBuf);
+
+ if(errno != ErrorNumber.NoError || outBuf == null)
+ {
+ LastError = (int)errno;
+ Error = true;
+
+ return true;
+ }
+ }
+
+ buffer = outBuf;
+ }
+
Error = LastError != 0;
AaruLogging.Debug(SCSI_MODULE_NAME, "OmniDrive READ NINTENDO DVD took {0} ms", duration);
diff --git a/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs b/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
index efde3ef7f..4227b60cb 100644
--- a/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
+++ b/Aaru.Gui/ViewModels/Windows/MediaDumpViewModel.cs
@@ -753,7 +753,8 @@ public sealed partial class MediaDumpViewModel : ViewModelBase
true,
1080,
Paranoia,
- CureParanoia);
+ CureParanoia,
+ false);
new Thread(DoWork).Start();
}
diff --git a/Aaru.Localization/UI.Designer.cs b/Aaru.Localization/UI.Designer.cs
index 15672f15f..d8d2164c6 100644
--- a/Aaru.Localization/UI.Designer.cs
+++ b/Aaru.Localization/UI.Designer.cs
@@ -1995,6 +1995,12 @@ namespace Aaru.Localization {
}
}
+ public static string Ngcw_wii_dump_bypass_decryption {
+ get {
+ return ResourceManager.GetString("Ngcw_wii_dump_bypass_decryption", resourceCulture);
+ }
+ }
+
public static string PS3_disc_key_resolved_from_0 {
get {
return ResourceManager.GetString("PS3_disc_key_resolved_from_0", resourceCulture);
diff --git a/Aaru.Localization/UI.es.resx b/Aaru.Localization/UI.es.resx
index 211104bac..47fc9cd65 100644
--- a/Aaru.Localization/UI.es.resx
+++ b/Aaru.Localization/UI.es.resx
@@ -669,6 +669,9 @@
[slateblue1]Clave de disco Nintendo derivada: [green]{0}[/].[/]
+
+
+ [slateblue1]Omisión del descifrado de particiones Wii: guardando datos de partición cifrados tal como se leen.[/]
[slateblue1]Clave de disco PS3 resuelta desde [aqua]{0}[/].[/]
diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx
index f39e0c4a6..3311592bb 100644
--- a/Aaru.Localization/UI.resx
+++ b/Aaru.Localization/UI.resx
@@ -971,7 +971,7 @@ In you are unsure, please press N to not continue.
Skip Wii U disc encryption processing during conversion.
- Skip Wii disc encryption processing during conversion.
+ Skip Wii disc encryption processing during conversion or dump.
[slateblue1]Parsing Wii partition table...[/]
@@ -1008,6 +1008,9 @@ In you are unsure, please press N to not continue.
[slateblue1]Derived Nintendo disc key: [green]{0}[/].[/]
+
+
+ [slateblue1]Wii partition decryption bypass: storing encrypted partition data as read.[/]
[slateblue1]PS3 disc key resolved from [aqua]{0}[/].[/]
diff --git a/Aaru/Commands/Media/Dump.cs b/Aaru/Commands/Media/Dump.cs
index 374aa505c..151d09372 100644
--- a/Aaru/Commands/Media/Dump.cs
+++ b/Aaru/Commands/Media/Dump.cs
@@ -116,6 +116,7 @@ sealed class DumpMediaCommand : Command
AaruLogging.Debug(MODULE_NAME, "--max-blocks={0}", maxBlocks);
AaruLogging.Debug(MODULE_NAME, "--use-buffered-reads={0}", settings.UseBufferedReads);
AaruLogging.Debug(MODULE_NAME, "--store-encrypted={0}", settings.StoreEncrypted);
+ AaruLogging.Debug(MODULE_NAME, "--bypass-wii-decryption={0}", settings.BypassWiiDecryption);
AaruLogging.Debug(MODULE_NAME, "--title-keys={0}", settings.TitleKeys);
AaruLogging.Debug(MODULE_NAME, "--ignore-cdr-runouts={0}", settings.IgnoreCdrRunOuts);
AaruLogging.Debug(MODULE_NAME, "--create-graph={0}", settings.CreateGraph);
@@ -526,7 +527,8 @@ sealed class DumpMediaCommand : Command
settings.CreateGraph,
(uint)settings.Dimensions,
settings.Paranoia,
- settings.CureParanoia);
+ settings.CureParanoia,
+ settings.BypassWiiDecryption);
AnsiConsole.Progress()
.AutoClear(true)
@@ -757,6 +759,10 @@ sealed class DumpMediaCommand : Command
[CommandOption("--store-encrypted")]
[DefaultValue(true)]
public bool StoreEncrypted { get; init; }
+ [LocalizedDescription(nameof(UI.Bypass_Wii_decryption_help))]
+ [CommandOption("--bypass-wii-decryption")]
+ [DefaultValue(false)]
+ public bool BypassWiiDecryption { get; init; }
[LocalizedDescription(nameof(UI.Try_to_read_the_title_keys_from_CSS_DVDs))]
[CommandOption("--title-keys")]
[DefaultValue(true)]