mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-05 21:44:17 +00:00
Dump NGCW
This commit is contained in:
@@ -104,6 +104,7 @@ public partial class Dump
|
|||||||
readonly Stopwatch _speedStopwatch;
|
readonly Stopwatch _speedStopwatch;
|
||||||
readonly bool _stopOnError;
|
readonly bool _stopOnError;
|
||||||
readonly bool _storeEncrypted;
|
readonly bool _storeEncrypted;
|
||||||
|
readonly bool _bypassWiiDecryption;
|
||||||
readonly DumpSubchannel _subchannel;
|
readonly DumpSubchannel _subchannel;
|
||||||
readonly bool _titleKeys;
|
readonly bool _titleKeys;
|
||||||
readonly bool _trim;
|
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="dimensions">Dimensions of graph in pixels for a square</param>
|
||||||
/// <param name="paranoia">Check sectors integrity before writing to image</param>
|
/// <param name="paranoia">Check sectors integrity before writing to image</param>
|
||||||
/// <param name="cureParanoia">Try to fix sectors integrity</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,
|
public Dump(bool doResume, Device dev, string devicePath, IBaseWritableImage outputPlugin, ushort retryPasses,
|
||||||
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, Encoding encoding,
|
bool force, bool dumpRaw, bool persistent, bool stopOnError, Resume resume, Encoding encoding,
|
||||||
string outputPrefix, string outputPath, Dictionary<string, string> formatOptions, Metadata preSidecar,
|
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 fixSubchannel, bool fixSubchannelCrc, bool skipCdireadyHole, ErrorLog errorLog,
|
||||||
bool generateSubchannels, uint maximumReadable, bool useBufferedReads, bool storeEncrypted,
|
bool generateSubchannels, uint maximumReadable, bool useBufferedReads, bool storeEncrypted,
|
||||||
bool titleKeys, uint ignoreCdrRunOuts, bool createGraph, uint dimensions, bool paranoia,
|
bool titleKeys, uint ignoreCdrRunOuts, bool createGraph, uint dimensions, bool paranoia,
|
||||||
bool cureParanoia)
|
bool cureParanoia, bool bypassWiiDecryption)
|
||||||
{
|
{
|
||||||
_doResume = doResume;
|
_doResume = doResume;
|
||||||
_dev = dev;
|
_dev = dev;
|
||||||
@@ -223,6 +225,7 @@ public partial class Dump
|
|||||||
_dimensions = dimensions;
|
_dimensions = dimensions;
|
||||||
_paranoia = paranoia;
|
_paranoia = paranoia;
|
||||||
_cureParanoia = cureParanoia;
|
_cureParanoia = cureParanoia;
|
||||||
|
_bypassWiiDecryption = bypassWiiDecryption;
|
||||||
_dumpStopwatch = new Stopwatch();
|
_dumpStopwatch = new Stopwatch();
|
||||||
_sidecarStopwatch = new Stopwatch();
|
_sidecarStopwatch = new Stopwatch();
|
||||||
_speedStopwatch = new Stopwatch();
|
_speedStopwatch = new Stopwatch();
|
||||||
|
|||||||
@@ -58,21 +58,17 @@ partial class Dump
|
|||||||
ulong[] _ngcwPartSysEnd;
|
ulong[] _ngcwPartSysEnd;
|
||||||
DataRegion[] _ngcwGcDataMap;
|
DataRegion[] _ngcwGcDataMap;
|
||||||
ulong _ngcwGcSysEnd;
|
ulong _ngcwGcSysEnd;
|
||||||
|
|
||||||
bool _omniDriveNintendoSoftwareDescramble;
|
|
||||||
byte? _nintendoDerivedDiscKey;
|
byte? _nintendoDerivedDiscKey;
|
||||||
readonly Aaru.Decoders.Nintendo.Sector _nintendoSectorDecoder = new Aaru.Decoders.Nintendo.Sector();
|
|
||||||
|
|
||||||
bool InitializeNgcwContext(MediaType dskType, Reader scsiReader, IWritableImage outputFormat)
|
bool InitializeNgcwContext(MediaType dskType, Reader scsiReader, IWritableImage outputFormat)
|
||||||
{
|
{
|
||||||
_ngcwEnabled = dskType is MediaType.GOD or MediaType.WOD;
|
_ngcwEnabled = dskType is MediaType.GOD or MediaType.WOD;
|
||||||
_ngcwMediaType = dskType;
|
_ngcwMediaType = dskType;
|
||||||
_ngcwJunkCollector = new JunkCollector();
|
_ngcwJunkCollector = new JunkCollector();
|
||||||
_omniDriveNintendoSoftwareDescramble = scsiReader.OmniDriveNintendoMode;
|
|
||||||
|
|
||||||
if(!_ngcwEnabled) return true;
|
if(!_ngcwEnabled) return true;
|
||||||
|
|
||||||
if(_omniDriveNintendoSoftwareDescramble)
|
if(scsiReader.OmniDriveNintendoMode)
|
||||||
{
|
{
|
||||||
UpdateStatus?.Invoke(UI.Ngcw_nintendo_software_descramble);
|
UpdateStatus?.Invoke(UI.Ngcw_nintendo_software_descramble);
|
||||||
|
|
||||||
@@ -108,10 +104,6 @@ partial class Dump
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_omniDriveNintendoSoftwareDescramble &&
|
|
||||||
!DescrambleNintendoLongBuffer(longBuffer, startSector, sectors))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(_ngcwMediaType == MediaType.GOD)
|
if(_ngcwMediaType == MediaType.GOD)
|
||||||
return TransformGameCubeLongSectors(longBuffer, startSector, sectors, statuses);
|
return TransformGameCubeLongSectors(longBuffer, startSector, sectors, statuses);
|
||||||
|
|
||||||
@@ -155,6 +147,15 @@ partial class Dump
|
|||||||
return false;
|
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);
|
WiiPartitionRegion[] regions = NgcwPartitions.BuildRegionMap(_ngcwPartitions);
|
||||||
byte[] keyMapData = NgcwPartitions.SerializeKeyMap(regions);
|
byte[] keyMapData = NgcwPartitions.SerializeKeyMap(regions);
|
||||||
|
|
||||||
@@ -253,6 +254,13 @@ partial class Dump
|
|||||||
ulong discOffset = startSector * NGCW_SECTOR_SIZE;
|
ulong discOffset = startSector * NGCW_SECTOR_SIZE;
|
||||||
int partIndex = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, discOffset);
|
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)
|
if(partIndex < 0)
|
||||||
{
|
{
|
||||||
byte[] payload = new byte[sectors * NGCW_SECTOR_SIZE];
|
byte[] payload = new byte[sectors * NGCW_SECTOR_SIZE];
|
||||||
@@ -443,7 +451,9 @@ partial class Dump
|
|||||||
|
|
||||||
bool EnsureNintendoDerivedKeyFromLba0(Reader scsiReader)
|
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 _);
|
bool sense = scsiReader.ReadBlock(out byte[] raw, 0, out _, out _, out _);
|
||||||
|
|
||||||
@@ -454,63 +464,15 @@ partial class Dump
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorNumber errno = _nintendoSectorDecoder.Scramble(raw, 0, out byte[] descrambled);
|
byte[] keyMaterial = new byte[8];
|
||||||
|
Array.Copy(raw, Sector.NintendoMainDataOffset, keyMaterial, 0, 8);
|
||||||
if(errno != ErrorNumber.NoError || descrambled == null)
|
_nintendoDerivedDiscKey = Sector.DeriveNintendoKey(keyMaterial);
|
||||||
{
|
scsiReader.NintendoDerivedDiscKey = _nintendoDerivedDiscKey;
|
||||||
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);
|
|
||||||
UpdateStatus?.Invoke(string.Format(UI.Ngcw_nintendo_derived_key_0, _nintendoDerivedDiscKey.Value));
|
UpdateStatus?.Invoke(string.Format(UI.Ngcw_nintendo_derived_key_0, _nintendoDerivedDiscKey.Value));
|
||||||
|
|
||||||
return true;
|
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)
|
List<WiiPartition> ParseWiiPartitionsFromDevice(Reader scsiReader)
|
||||||
{
|
{
|
||||||
byte[] partitionTable = ReadDiscBytesFromDevice(scsiReader, 0x40000, 32);
|
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)
|
if(sense || _dev.Error || rawSector == null || rawSector.Length < NGCW_PAYLOAD_OFFSET + NGCW_SECTOR_SIZE)
|
||||||
return null;
|
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);
|
Array.Copy(rawSector, NGCW_PAYLOAD_OFFSET + sectorOff, result, read, chunk);
|
||||||
read += chunk;
|
read += chunk;
|
||||||
}
|
}
|
||||||
@@ -604,15 +560,6 @@ partial class Dump
|
|||||||
|
|
||||||
if(sense || _dev.Error || rawBuffer == null) return null;
|
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];
|
byte[] payload = new byte[count * NGCW_SECTOR_SIZE];
|
||||||
|
|
||||||
for(uint i = 0; i < count; i++)
|
for(uint i = 0; i < count; i++)
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ sealed partial class Reader
|
|||||||
internal bool CanReadRaw { get; private set; }
|
internal bool CanReadRaw { get; private set; }
|
||||||
/// <summary>When true with OmniDrive raw reads, use descramble=0 and software Nintendo descrambling (GameCube/Wii).</summary>
|
/// <summary>When true with OmniDrive raw reads, use descramble=0 and software Nintendo descrambling (GameCube/Wii).</summary>
|
||||||
internal bool OmniDriveNintendoMode { get; set; }
|
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 CanSeek => _ataSeek || _seek6 || _seek10;
|
||||||
internal bool CanSeekLba => _ataSeekLba || _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)
|
// Try OmniDrive on drives with OmniDrive firmware (standard descramble=1 and Nintendo descramble=0)
|
||||||
if(_dev.IsOmniDriveFirmware())
|
if(_dev.IsOmniDriveFirmware())
|
||||||
{
|
{
|
||||||
bool omniStandardOk = !_dev.OmniDriveReadRawDvd(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 _);
|
bool omniNintendoOk = !_dev.OmniDriveReadNintendoDvd(out _, out senseBuf, 0, 1, _timeout, out _, true, true);
|
||||||
OmniDriveReadRaw = omniStandardOk || omniNintendoOk;
|
OmniDriveReadRaw = omniStandardOk || omniNintendoOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,7 +856,10 @@ sealed partial class Reader
|
|||||||
lba,
|
lba,
|
||||||
count,
|
count,
|
||||||
_timeout,
|
_timeout,
|
||||||
out duration);
|
out duration,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
NintendoDerivedDiscKey);
|
||||||
else
|
else
|
||||||
sense = _dev.OmniDriveReadRawDvd(out buffer,
|
sense = _dev.OmniDriveReadRawDvd(out buffer,
|
||||||
out senseBuf,
|
out senseBuf,
|
||||||
|
|||||||
@@ -271,6 +271,216 @@ public partial class Convert
|
|||||||
return ErrorNumber.NoError;
|
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>
|
/// <summary>Wii sector conversion pipeline.</summary>
|
||||||
ErrorNumber ConvertWiiSectors(ulong discSize, ulong totalLogicalSectors, JunkCollector jc, ref ulong dataSectors,
|
ErrorNumber ConvertWiiSectors(ulong discSize, ulong totalLogicalSectors, JunkCollector jc, ref ulong dataSectors,
|
||||||
ref ulong junkSectors)
|
ref ulong junkSectors)
|
||||||
@@ -307,162 +517,16 @@ public partial class Convert
|
|||||||
ulong groupDiscOff = _ngcwPartitions[inPart].DataOffset +
|
ulong groupDiscOff = _ngcwPartitions[inPart].DataOffset +
|
||||||
(offset - _ngcwPartitions[inPart].DataOffset) / groupSize * groupSize;
|
(offset - _ngcwPartitions[inPart].DataOffset) / groupSize * groupSize;
|
||||||
|
|
||||||
// Read encrypted group
|
|
||||||
var encGrp = new byte[groupSize];
|
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 hashBlock = new byte[hashSize];
|
||||||
var groupData = new byte[groupDataSize];
|
var groupData = new byte[groupDataSize];
|
||||||
Crypto.DecryptGroup(_ngcwPartitions[inPart].TitleKey, encGrp, hashBlock, groupData);
|
Crypto.DecryptGroup(_ngcwPartitions[inPart].TitleKey, encGrp, hashBlock, groupData);
|
||||||
|
|
||||||
// Classify user data sectors
|
byte[] decryptedGroup =
|
||||||
ulong groupNum = (groupDiscOff - _ngcwPartitions[inPart].DataOffset) / groupSize;
|
ProcessWiiDecryptedGroup(inPart, groupDiscOff, hashBlock, groupData, jc, ref dataSectors,
|
||||||
ulong logicalOffset = groupNum * groupDataSize;
|
ref junkSectors);
|
||||||
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write all 16 sectors as SectorStatusUnencrypted
|
// Write all 16 sectors as SectorStatusUnencrypted
|
||||||
for(var s = 0; s < sectorsPerBlock; s++)
|
for(var s = 0; s < sectorsPerBlock; s++)
|
||||||
@@ -879,9 +943,194 @@ public partial class Convert
|
|||||||
return ErrorNumber.NoError;
|
return ErrorNumber.NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wii sector conversion pipeline for long sectors.
|
/// <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){
|
ErrorNumber ConvertWiiSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc,
|
||||||
// TODO: Implement
|
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;
|
return ErrorNumber.NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,21 @@ public sealed class Sector
|
|||||||
return computed == stored;
|
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>
|
/// <summary>
|
||||||
/// Tests if a seed unscrambles a sector correctly
|
/// Tests if a seed unscrambles a sector correctly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -33,14 +33,17 @@
|
|||||||
// ****************************************************************************/
|
// ****************************************************************************/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using Aaru.CommonTypes.Enums;
|
||||||
using Aaru.CommonTypes.Structs.Devices.SCSI;
|
using Aaru.CommonTypes.Structs.Devices.SCSI;
|
||||||
using Aaru.Logging;
|
using Aaru.Logging;
|
||||||
using Aaru.Decoders.DVD;
|
using Aaru.Decoders.DVD;
|
||||||
|
using NintendoSector = Aaru.Decoders.Nintendo.Sector;
|
||||||
|
|
||||||
namespace Aaru.Devices;
|
namespace Aaru.Devices;
|
||||||
|
|
||||||
public partial class Device
|
public partial class Device
|
||||||
{
|
{
|
||||||
|
readonly NintendoSector _nintendoSectorDecoder = new NintendoSector();
|
||||||
enum OmniDriveDiscType
|
enum OmniDriveDiscType
|
||||||
{
|
{
|
||||||
CD = 0,
|
CD = 0,
|
||||||
@@ -66,16 +69,16 @@ public partial class Device
|
|||||||
static void FillOmniDriveReadDvdCdb(Span<byte> cdb, uint lba, uint transferLength, byte cdbByte1)
|
static void FillOmniDriveReadDvdCdb(Span<byte> cdb, uint lba, uint transferLength, byte cdbByte1)
|
||||||
{
|
{
|
||||||
cdb.Clear();
|
cdb.Clear();
|
||||||
cdb[0] = (byte)ScsiCommands.ReadOmniDrive;
|
cdb[0] = (byte)ScsiCommands.ReadOmniDrive;
|
||||||
cdb[1] = cdbByte1;
|
cdb[1] = cdbByte1;
|
||||||
cdb[2] = (byte)((lba >> 24) & 0xFF);
|
cdb[2] = (byte)((lba >> 24) & 0xFF);
|
||||||
cdb[3] = (byte)((lba >> 16) & 0xFF);
|
cdb[3] = (byte)((lba >> 16) & 0xFF);
|
||||||
cdb[4] = (byte)((lba >> 8) & 0xFF);
|
cdb[4] = (byte)((lba >> 8) & 0xFF);
|
||||||
cdb[5] = (byte)(lba & 0xFF);
|
cdb[5] = (byte)(lba & 0xFF);
|
||||||
cdb[6] = (byte)((transferLength >> 24) & 0xFF);
|
cdb[6] = (byte)((transferLength >> 24) & 0xFF);
|
||||||
cdb[7] = (byte)((transferLength >> 16) & 0xFF);
|
cdb[7] = (byte)((transferLength >> 16) & 0xFF);
|
||||||
cdb[8] = (byte)((transferLength >> 8) & 0xFF);
|
cdb[8] = (byte)((transferLength >> 8) & 0xFF);
|
||||||
cdb[9] = (byte)(transferLength & 0xFF);
|
cdb[9] = (byte)(transferLength & 0xFF);
|
||||||
cdb[10] = 0; // subchannels=NONE, c2=0
|
cdb[10] = 0; // subchannels=NONE, c2=0
|
||||||
cdb[11] = 0; // control
|
cdb[11] = 0; // control
|
||||||
}
|
}
|
||||||
@@ -89,20 +92,20 @@ public partial class Device
|
|||||||
{
|
{
|
||||||
bool sense = ScsiInquiry(out byte[] buffer, out _, Timeout, out _);
|
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);
|
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;
|
return false;
|
||||||
|
|
||||||
byte[] reserved5 = inquiry.Value.Reserved5;
|
byte[] reserved5 = inquiry.Value.Reserved5;
|
||||||
byte[] omnidrive = [0x4F, 0x6D, 0x6E, 0x69, 0x44, 0x72, 0x69, 0x76, 0x65]; // "OmniDrive"
|
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++)
|
for (int i = 0; i < omnidrive.Length; i++)
|
||||||
if(reserved5[i] != omnidrive[i])
|
if (reserved5[i] != omnidrive[i])
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -131,8 +134,11 @@ public partial class Device
|
|||||||
EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, fua, descramble));
|
EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, fua, descramble));
|
||||||
|
|
||||||
LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense);
|
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;
|
Error = LastError != 0;
|
||||||
|
|
||||||
@@ -142,13 +148,16 @@ public partial class Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads raw Nintendo GameCube/Wii DVD sectors (2064 bytes) on OmniDrive. Default matches redumper raw DVD
|
/// Reads Nintendo GameCube/Wii DVD sectors (2064 bytes) on OmniDrive. The drive returns DVD-layer data with
|
||||||
/// (<c>descramble</c> off); use software descramble via Aaru.Decoders.Nintendo.Sector when needed.
|
/// 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>
|
/// </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>
|
/// <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,
|
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;
|
senseBuffer = SenseBuffer;
|
||||||
Span<byte> cdb = CdbBuffer[..12];
|
Span<byte> cdb = CdbBuffer[..12];
|
||||||
@@ -157,11 +166,55 @@ public partial class Device
|
|||||||
FillOmniDriveReadDvdCdb(cdb,
|
FillOmniDriveReadDvdCdb(cdb,
|
||||||
lba,
|
lba,
|
||||||
transferLength,
|
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);
|
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;
|
Error = LastError != 0;
|
||||||
|
|
||||||
AaruLogging.Debug(SCSI_MODULE_NAME, "OmniDrive READ NINTENDO DVD took {0} ms", duration);
|
AaruLogging.Debug(SCSI_MODULE_NAME, "OmniDrive READ NINTENDO DVD took {0} ms", duration);
|
||||||
|
|||||||
@@ -753,7 +753,8 @@ public sealed partial class MediaDumpViewModel : ViewModelBase
|
|||||||
true,
|
true,
|
||||||
1080,
|
1080,
|
||||||
Paranoia,
|
Paranoia,
|
||||||
CureParanoia);
|
CureParanoia,
|
||||||
|
false);
|
||||||
|
|
||||||
new Thread(DoWork).Start();
|
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 {
|
public static string PS3_disc_key_resolved_from_0 {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("PS3_disc_key_resolved_from_0", resourceCulture);
|
return ResourceManager.GetString("PS3_disc_key_resolved_from_0", resourceCulture);
|
||||||
|
|||||||
@@ -669,6 +669,9 @@
|
|||||||
</data>
|
</data>
|
||||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||||
<value>[slateblue1]Clave de disco Nintendo derivada: [green]{0}[/].[/]</value>
|
<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>
|
||||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||||
<value>[slateblue1]Clave de disco PS3 resuelta desde [aqua]{0}[/].[/]</value>
|
<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>
|
<value>Skip Wii U disc encryption processing during conversion.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Bypass_Wii_decryption_help" xml:space="preserve">
|
<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>
|
||||||
<data name="Ngcw_parsing_partition_table" xml:space="preserve">
|
<data name="Ngcw_parsing_partition_table" xml:space="preserve">
|
||||||
<value>[slateblue1]Parsing Wii partition table...[/]</value>
|
<value>[slateblue1]Parsing Wii partition table...[/]</value>
|
||||||
@@ -1008,6 +1008,9 @@ In you are unsure, please press N to not continue.</value>
|
|||||||
</data>
|
</data>
|
||||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||||
<value>[slateblue1]Derived Nintendo disc key: [green]{0}[/].[/]</value>
|
<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>
|
||||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||||
<value>[slateblue1]PS3 disc key resolved from [aqua]{0}[/].[/]</value>
|
<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, "--max-blocks={0}", maxBlocks);
|
||||||
AaruLogging.Debug(MODULE_NAME, "--use-buffered-reads={0}", settings.UseBufferedReads);
|
AaruLogging.Debug(MODULE_NAME, "--use-buffered-reads={0}", settings.UseBufferedReads);
|
||||||
AaruLogging.Debug(MODULE_NAME, "--store-encrypted={0}", settings.StoreEncrypted);
|
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, "--title-keys={0}", settings.TitleKeys);
|
||||||
AaruLogging.Debug(MODULE_NAME, "--ignore-cdr-runouts={0}", settings.IgnoreCdrRunOuts);
|
AaruLogging.Debug(MODULE_NAME, "--ignore-cdr-runouts={0}", settings.IgnoreCdrRunOuts);
|
||||||
AaruLogging.Debug(MODULE_NAME, "--create-graph={0}", settings.CreateGraph);
|
AaruLogging.Debug(MODULE_NAME, "--create-graph={0}", settings.CreateGraph);
|
||||||
@@ -526,7 +527,8 @@ sealed class DumpMediaCommand : Command<DumpMediaCommand.Settings>
|
|||||||
settings.CreateGraph,
|
settings.CreateGraph,
|
||||||
(uint)settings.Dimensions,
|
(uint)settings.Dimensions,
|
||||||
settings.Paranoia,
|
settings.Paranoia,
|
||||||
settings.CureParanoia);
|
settings.CureParanoia,
|
||||||
|
settings.BypassWiiDecryption);
|
||||||
|
|
||||||
AnsiConsole.Progress()
|
AnsiConsole.Progress()
|
||||||
.AutoClear(true)
|
.AutoClear(true)
|
||||||
@@ -757,6 +759,10 @@ sealed class DumpMediaCommand : Command<DumpMediaCommand.Settings>
|
|||||||
[CommandOption("--store-encrypted")]
|
[CommandOption("--store-encrypted")]
|
||||||
[DefaultValue(true)]
|
[DefaultValue(true)]
|
||||||
public bool StoreEncrypted { get; init; }
|
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))]
|
[LocalizedDescription(nameof(UI.Try_to_read_the_title_keys_from_CSS_DVDs))]
|
||||||
[CommandOption("--title-keys")]
|
[CommandOption("--title-keys")]
|
||||||
[DefaultValue(true)]
|
[DefaultValue(true)]
|
||||||
|
|||||||
Reference in New Issue
Block a user