mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-05 21:44:05 +00:00
Dump NGCW
This commit is contained in:
@@ -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
|
||||
/// <param name="dimensions">Dimensions of graph in pixels for a square</param>
|
||||
/// <param name="paranoia">Check sectors integrity before writing to image</param>
|
||||
/// <param name="cureParanoia">Try to fix sectors integrity</param>
|
||||
/// <param name="bypassWiiDecryption">When dumping Wii (WOD), skip partition AES decryption and store encrypted data</param>
|
||||
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<string, string> 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();
|
||||
|
||||
@@ -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<WiiPartition> 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++)
|
||||
|
||||
@@ -79,6 +79,12 @@ sealed partial class Reader
|
||||
internal bool CanReadRaw { get; private set; }
|
||||
/// <summary>When true with OmniDrive raw reads, use descramble=0 and software Nintendo descrambling (GameCube/Wii).</summary>
|
||||
internal bool OmniDriveNintendoMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal byte? NintendoDerivedDiscKey { get; set; }
|
||||
internal bool CanSeek => _ataSeek || _seek6 || _seek10;
|
||||
internal bool CanSeekLba => _ataSeekLba || _seek6 || _seek10;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -271,6 +271,216 @@ public partial class Convert
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Fill a 32 KiB Wii encrypted group from 2048-byte logical ReadSector slices.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read one encrypted Wii group from long sectors: copy main_data into <paramref name="encGrp" /> and
|
||||
/// keep full long buffers for output (one ReadSectorLong per logical sector).
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classify FST regions, run LFG junk detection on decrypted user data, and build the 32 KiB
|
||||
/// plaintext group (hash + user, junk zeroed).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Wii sector conversion pipeline.</summary>
|
||||
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
|
||||
/// <summary>Wii sector conversion pipeline for long sectors (main_data + framing).</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -301,6 +301,21 @@ public sealed class Sector
|
||||
return computed == stored;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the IED of sectors is correct
|
||||
/// </summary>
|
||||
/// <param name="sectorLong">Buffer of the sector</param>
|
||||
/// <param name="transferLength">The number of sectors to check</param>
|
||||
/// <returns><c>True</c> if IED is correct, <c>False</c> if not</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a seed unscrambles a sector correctly
|
||||
/// </summary>
|
||||
|
||||
@@ -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<byte> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads raw Nintendo GameCube/Wii DVD sectors (2064 bytes) on OmniDrive. Default matches redumper raw DVD
|
||||
/// (<c>descramble</c> 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 <paramref name="derivedDiscKey" /> (from LBA 0 CPR_MAI), or 0 if not yet
|
||||
/// derived.
|
||||
/// </summary>
|
||||
/// <param name="descramble">Drive-side DVD descramble (redumper raw DVD uses <c>false</c>).</param>
|
||||
/// <param name="derivedDiscKey">Disc key from LBA 0 (0–15); <c>null</c> until the host has read and derived it.</param>
|
||||
/// <returns><c>true</c> if the command failed and <paramref name="senseBuffer" /> contains the sense buffer.</returns>
|
||||
public bool OmniDriveReadNintendoDvd(out byte[] buffer, out ReadOnlySpan<byte> 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<byte> 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);
|
||||
|
||||
@@ -753,7 +753,8 @@ public sealed partial class MediaDumpViewModel : ViewModelBase
|
||||
true,
|
||||
1080,
|
||||
Paranoia,
|
||||
CureParanoia);
|
||||
CureParanoia,
|
||||
false);
|
||||
|
||||
new Thread(DoWork).Start();
|
||||
}
|
||||
|
||||
6
Aaru.Localization/UI.Designer.cs
generated
6
Aaru.Localization/UI.Designer.cs
generated
@@ -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);
|
||||
|
||||
@@ -669,6 +669,9 @@
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||
<value>[slateblue1]Clave de disco Nintendo derivada: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_wii_dump_bypass_decryption" xml:space="preserve">
|
||||
<value>[slateblue1]Omisión del descifrado de particiones Wii: guardando datos de partición cifrados tal como se leen.[/]</value>
|
||||
</data>
|
||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||
<value>[slateblue1]Clave de disco PS3 resuelta desde [aqua]{0}[/].[/]</value>
|
||||
|
||||
@@ -971,7 +971,7 @@ In you are unsure, please press N to not continue.</value>
|
||||
<value>Skip Wii U disc encryption processing during conversion.</value>
|
||||
</data>
|
||||
<data name="Bypass_Wii_decryption_help" xml:space="preserve">
|
||||
<value>Skip Wii disc encryption processing during conversion.</value>
|
||||
<value>Skip Wii disc encryption processing during conversion or dump.</value>
|
||||
</data>
|
||||
<data name="Ngcw_parsing_partition_table" xml:space="preserve">
|
||||
<value>[slateblue1]Parsing Wii partition table...[/]</value>
|
||||
@@ -1008,6 +1008,9 @@ In you are unsure, please press N to not continue.</value>
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||
<value>[slateblue1]Derived Nintendo disc key: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_wii_dump_bypass_decryption" xml:space="preserve">
|
||||
<value>[slateblue1]Wii partition decryption bypass: storing encrypted partition data as read.[/]</value>
|
||||
</data>
|
||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||
<value>[slateblue1]PS3 disc key resolved from [aqua]{0}[/].[/]</value>
|
||||
|
||||
@@ -116,6 +116,7 @@ sealed class DumpMediaCommand : Command<DumpMediaCommand.Settings>
|
||||
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<DumpMediaCommand.Settings>
|
||||
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<DumpMediaCommand.Settings>
|
||||
[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)]
|
||||
|
||||
Reference in New Issue
Block a user