mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-05 21:44:05 +00:00
Start adding NGCW dumping
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// /***************************************************************************
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
@@ -348,10 +348,7 @@ partial class Dump
|
||||
|
||||
if(nintendoPfi is { DiskCategory: DiskCategory.Nintendo, PartVersion: 15 })
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Localization.Core
|
||||
.Dumping_Nintendo_GameCube_or_Wii_discs_is_not_yet_implemented);
|
||||
|
||||
return;
|
||||
dskType = nintendoPfi.Value.DiscSize == DVDSize.Eighty ? MediaType.GOD : MediaType.WOD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.AaruMetadata;
|
||||
@@ -337,6 +338,30 @@ partial class Dump
|
||||
}
|
||||
}
|
||||
|
||||
bool ngcwMode = dskType is MediaType.GOD or MediaType.WOD;
|
||||
|
||||
if(ngcwMode)
|
||||
{
|
||||
if(!scsiReader.OmniDriveReadRaw)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Dumping_Nintendo_GameCube_or_Wii_discs_is_not_yet_implemented);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(outputFormat.Format != "Aaru")
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(string.Format(Localization.Core.Output_format_does_not_support_0,
|
||||
MediaTagType.NgcwJunkMap));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(blocksToRead > 16) blocksToRead = 16;
|
||||
}
|
||||
|
||||
scsiReader.OmniDriveNintendoMode = ngcwMode;
|
||||
|
||||
ret = true;
|
||||
|
||||
foreach(MediaTagType tag in mediaTags.Keys.Where(tag => !outputFormat.SupportedMediaTags.Contains(tag)))
|
||||
@@ -406,7 +431,9 @@ partial class Dump
|
||||
((dskType >= MediaType.DVDROM && dskType <= MediaType.DVDDownload)
|
||||
|| dskType == MediaType.PS2DVD
|
||||
|| dskType == MediaType.PS3DVD
|
||||
|| dskType == MediaType.Nuon))
|
||||
|| dskType == MediaType.Nuon
|
||||
|| dskType == MediaType.GOD
|
||||
|| dskType == MediaType.WOD))
|
||||
nominalNegativeSectors = Math.Min(nominalNegativeSectors, DvdLeadinSectors);
|
||||
|
||||
mediaTags.TryGetValue(MediaTagType.BD_DI, out byte[] di);
|
||||
@@ -783,6 +810,9 @@ partial class Dump
|
||||
|
||||
var newTrim = false;
|
||||
|
||||
if(ngcwMode && !InitializeNgcwContext(dskType, scsiReader, outputFormat))
|
||||
return;
|
||||
|
||||
if(mediaTags.TryGetValue(MediaTagType.DVD_CMI, out byte[] cmi) &&
|
||||
Settings.Settings.Current.EnableDecryption &&
|
||||
_titleKeys &&
|
||||
@@ -938,6 +968,8 @@ partial class Dump
|
||||
|
||||
#endregion Error handling
|
||||
|
||||
if(ngcwMode) FinalizeNgcwContext(outputFormat);
|
||||
|
||||
if(opticalDisc)
|
||||
{
|
||||
foreach(KeyValuePair<MediaTagType, byte[]> tag in mediaTags)
|
||||
|
||||
@@ -308,46 +308,56 @@ partial class Dump
|
||||
|
||||
if(scsiReader.ReadBuffer3CReadRaw || scsiReader.OmniDriveReadRaw || scsiReader.HldtstReadRaw)
|
||||
{
|
||||
var cmi = new byte[1];
|
||||
|
||||
byte[] key = buffer.Skip(7).Take(5).ToArray();
|
||||
|
||||
if(key.All(static k => k == 0))
|
||||
if(_ngcwEnabled)
|
||||
{
|
||||
outputFormat.WriteSectorTag(new byte[5], badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
|
||||
MarkTitleKeyDumped(badSector);
|
||||
if(TransformNgcwLongSectors(scsiReader, buffer, badSector, 1, out SectorStatus[] statuses))
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, statuses[0]);
|
||||
else
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.NotDumped);
|
||||
}
|
||||
else
|
||||
{
|
||||
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(badSector);
|
||||
var cmi = new byte[1];
|
||||
|
||||
cmi[0] = buffer[6];
|
||||
}
|
||||
byte[] key = buffer.Skip(7).Take(5).ToArray();
|
||||
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(badSector,
|
||||
false,
|
||||
1,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
if(key.All(static k => k == 0))
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core
|
||||
.Error_retrieving_title_key_for_sector_0,
|
||||
badSector));
|
||||
outputFormat.WriteSectorTag(new byte[5], badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
|
||||
MarkTitleKeyDumped(badSector);
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi);
|
||||
}
|
||||
{
|
||||
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(badSector);
|
||||
|
||||
_resume.BadBlocks.Remove(badSector);
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.Dumped);
|
||||
cmi[0] = buffer[6];
|
||||
}
|
||||
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(badSector,
|
||||
false,
|
||||
1,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core
|
||||
.Error_retrieving_title_key_for_sector_0,
|
||||
badSector));
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi);
|
||||
}
|
||||
|
||||
_resume.BadBlocks.Remove(badSector);
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.Dumped);
|
||||
}
|
||||
}
|
||||
else
|
||||
outputFormat.WriteSector(buffer, badSector, false, SectorStatus.Dumped);
|
||||
|
||||
606
Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
Normal file
606
Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
Normal file
@@ -0,0 +1,606 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Ngcw.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// --[ Description ] ----------------------------------------------------------
|
||||
//
|
||||
// NGCW (GameCube/Wii) helpers for OmniDrive raw DVD dumping.
|
||||
//
|
||||
// --[ License ] --------------------------------------------------------------
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// Copyright © 2020-2026 Rebecca Wallander
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aaru.CommonTypes;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.Core.Image.Ngcw;
|
||||
using Aaru.Decoders.Nintendo;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Localization;
|
||||
using NgcwPartitions = Aaru.Core.Image.Ngcw.Partitions;
|
||||
|
||||
namespace Aaru.Core.Devices.Dumping;
|
||||
|
||||
partial class Dump
|
||||
{
|
||||
const int NGCW_LONG_SECTOR_SIZE = 2064;
|
||||
const int NGCW_PAYLOAD_OFFSET = 6;
|
||||
const int NGCW_SECTOR_SIZE = 2048;
|
||||
const int NGCW_SECTORS_PER_GROUP = 16;
|
||||
|
||||
bool _ngcwEnabled;
|
||||
MediaType _ngcwMediaType;
|
||||
JunkCollector _ngcwJunkCollector;
|
||||
List<WiiPartition> _ngcwPartitions;
|
||||
DataRegion[][] _ngcwPartDataMaps;
|
||||
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)
|
||||
{
|
||||
UpdateStatus?.Invoke(UI.Ngcw_nintendo_software_descramble);
|
||||
|
||||
if(!EnsureNintendoDerivedKeyFromLba0(scsiReader)) return false;
|
||||
}
|
||||
|
||||
if(dskType == MediaType.GOD)
|
||||
return InitializeGameCubeContext(scsiReader);
|
||||
|
||||
return InitializeWiiContext(scsiReader, outputFormat);
|
||||
}
|
||||
|
||||
void FinalizeNgcwContext(IWritableImage outputFormat)
|
||||
{
|
||||
if(!_ngcwEnabled || _ngcwJunkCollector is null || _ngcwJunkCollector.Count == 0) return;
|
||||
|
||||
byte[] junkMapData = Junk.Serialize(_ngcwJunkCollector.Entries);
|
||||
outputFormat.WriteMediaTag(junkMapData, MediaTagType.NgcwJunkMap);
|
||||
UpdateStatus?.Invoke(string.Format(UI.Ngcw_stored_junk_map_0_entries_1_bytes,
|
||||
_ngcwJunkCollector.Count,
|
||||
junkMapData.Length));
|
||||
}
|
||||
|
||||
bool TransformNgcwLongSectors(Reader scsiReader, byte[] longBuffer, ulong startSector, uint sectors,
|
||||
out SectorStatus[] statuses)
|
||||
{
|
||||
statuses = new SectorStatus[sectors];
|
||||
|
||||
if(!_ngcwEnabled)
|
||||
{
|
||||
for(int i = 0; i < sectors; i++) statuses[i] = SectorStatus.Dumped;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(_omniDriveNintendoSoftwareDescramble)
|
||||
DescrambleNintendoLongBuffer(longBuffer, startSector, sectors);
|
||||
|
||||
if(_ngcwMediaType == MediaType.GOD)
|
||||
return TransformGameCubeLongSectors(longBuffer, startSector, sectors, statuses);
|
||||
|
||||
return TransformWiiLongSectors(scsiReader, longBuffer, startSector, sectors, statuses);
|
||||
}
|
||||
|
||||
bool InitializeGameCubeContext(Reader scsiReader)
|
||||
{
|
||||
byte[] extHeader = ReadDiscBytesFromDevice(scsiReader, 0, 0x440);
|
||||
|
||||
if(extHeader == null || extHeader.Length < 0x42C)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Unable_to_read_medium);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint fstOffset = BigEndianBitConverter.ToUInt32(extHeader, 0x424);
|
||||
uint fstSize = BigEndianBitConverter.ToUInt32(extHeader, 0x428);
|
||||
_ngcwGcSysEnd = fstOffset + fstSize;
|
||||
|
||||
if(fstSize > 0 && fstSize < 64 * 1024 * 1024)
|
||||
{
|
||||
byte[] fst = ReadDiscBytesFromDevice(scsiReader, fstOffset, (int)fstSize);
|
||||
|
||||
if(fst != null) _ngcwGcDataMap = DataMap.BuildFromFst(fst, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InitializeWiiContext(Reader scsiReader, IWritableImage outputFormat)
|
||||
{
|
||||
UpdateStatus?.Invoke(UI.Ngcw_parsing_partition_table);
|
||||
_ngcwPartitions = ParseWiiPartitionsFromDevice(scsiReader);
|
||||
|
||||
if(_ngcwPartitions == null)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Unable_to_read_medium);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WiiPartitionRegion[] regions = NgcwPartitions.BuildRegionMap(_ngcwPartitions);
|
||||
byte[] keyMapData = NgcwPartitions.SerializeKeyMap(regions);
|
||||
|
||||
outputFormat.WriteMediaTag(keyMapData, MediaTagType.WiiPartitionKeyMap);
|
||||
UpdateStatus?.Invoke(UI.Ngcw_written_partition_key_map);
|
||||
|
||||
BuildWiiPartitionFstMaps(scsiReader);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuildWiiPartitionFstMaps(Reader scsiReader)
|
||||
{
|
||||
if(_ngcwPartitions == null || _ngcwPartitions.Count == 0) return;
|
||||
|
||||
_ngcwPartDataMaps = new DataRegion[_ngcwPartitions.Count][];
|
||||
_ngcwPartSysEnd = new ulong[_ngcwPartitions.Count];
|
||||
|
||||
for(int p = 0; p < _ngcwPartitions.Count; p++)
|
||||
{
|
||||
byte[] encGrp0 = ReadRawPayloadSectors(scsiReader, _ngcwPartitions[p].DataOffset / NGCW_SECTOR_SIZE, 16);
|
||||
|
||||
if(encGrp0 == null || encGrp0.Length < Crypto.GROUP_SIZE) continue;
|
||||
|
||||
byte[] hb0 = new byte[Crypto.GROUP_HASH_SIZE];
|
||||
byte[] gd0 = new byte[Crypto.GROUP_DATA_SIZE];
|
||||
Crypto.DecryptGroup(_ngcwPartitions[p].TitleKey, encGrp0, hb0, gd0);
|
||||
|
||||
uint fstOffset = BigEndianBitConverter.ToUInt32(gd0, 0x424) << 2;
|
||||
uint fstSize = BigEndianBitConverter.ToUInt32(gd0, 0x428) << 2;
|
||||
|
||||
_ngcwPartSysEnd[p] = fstOffset + fstSize;
|
||||
|
||||
if(fstSize == 0 || fstSize >= 64 * 1024 * 1024) continue;
|
||||
|
||||
byte[] fstBuffer = new byte[fstSize];
|
||||
uint fstRead = 0;
|
||||
bool ok = true;
|
||||
|
||||
while(fstRead < fstSize)
|
||||
{
|
||||
ulong logicalOffset = fstOffset + fstRead;
|
||||
ulong groupIndex = logicalOffset / Crypto.GROUP_DATA_SIZE;
|
||||
int groupOffset = (int)(logicalOffset % Crypto.GROUP_DATA_SIZE);
|
||||
ulong discOffset = _ngcwPartitions[p].DataOffset + groupIndex * Crypto.GROUP_SIZE;
|
||||
|
||||
byte[] encGroup = ReadRawPayloadSectors(scsiReader, discOffset / NGCW_SECTOR_SIZE, 16);
|
||||
|
||||
if(encGroup == null || encGroup.Length < Crypto.GROUP_SIZE)
|
||||
{
|
||||
ok = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] hb = new byte[Crypto.GROUP_HASH_SIZE];
|
||||
byte[] gd = new byte[Crypto.GROUP_DATA_SIZE];
|
||||
Crypto.DecryptGroup(_ngcwPartitions[p].TitleKey, encGroup, hb, gd);
|
||||
|
||||
int available = Crypto.GROUP_DATA_SIZE - groupOffset;
|
||||
int chunk = fstSize - (int)fstRead < available ? (int)(fstSize - fstRead) : available;
|
||||
Array.Copy(gd, groupOffset, fstBuffer, fstRead, chunk);
|
||||
fstRead += (uint)chunk;
|
||||
}
|
||||
|
||||
if(ok) _ngcwPartDataMaps[p] = DataMap.BuildFromFst(fstBuffer, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool TransformGameCubeLongSectors(byte[] longBuffer, ulong startSector, uint sectors, SectorStatus[] statuses)
|
||||
{
|
||||
byte[] payload = new byte[sectors * NGCW_SECTOR_SIZE];
|
||||
|
||||
for(uint i = 0; i < sectors; i++)
|
||||
Array.Copy(longBuffer, i * NGCW_LONG_SECTOR_SIZE + NGCW_PAYLOAD_OFFSET, payload, i * NGCW_SECTOR_SIZE,
|
||||
NGCW_SECTOR_SIZE);
|
||||
|
||||
ulong dataSectors = 0;
|
||||
ulong junkSectors = 0;
|
||||
Junk.DetectJunkInBlock(payload,
|
||||
payload.Length,
|
||||
startSector * NGCW_SECTOR_SIZE,
|
||||
_ngcwGcDataMap,
|
||||
_ngcwGcSysEnd,
|
||||
0xFFFF,
|
||||
_ngcwJunkCollector,
|
||||
ref dataSectors,
|
||||
ref junkSectors,
|
||||
statuses);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransformWiiLongSectors(Reader scsiReader, byte[] longBuffer, ulong startSector, uint sectors, SectorStatus[] statuses)
|
||||
{
|
||||
ulong discOffset = startSector * NGCW_SECTOR_SIZE;
|
||||
int partIndex = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, discOffset);
|
||||
|
||||
if(partIndex < 0)
|
||||
{
|
||||
byte[] payload = new byte[sectors * NGCW_SECTOR_SIZE];
|
||||
|
||||
for(uint i = 0; i < sectors; i++)
|
||||
Array.Copy(longBuffer, i * NGCW_LONG_SECTOR_SIZE + NGCW_PAYLOAD_OFFSET, payload, i * NGCW_SECTOR_SIZE,
|
||||
NGCW_SECTOR_SIZE);
|
||||
|
||||
ulong dataSectors = 0;
|
||||
ulong junkSectors = 0;
|
||||
Junk.DetectJunkInBlock(payload,
|
||||
payload.Length,
|
||||
discOffset,
|
||||
null,
|
||||
0x50000,
|
||||
0xFFFF,
|
||||
_ngcwJunkCollector,
|
||||
ref dataSectors,
|
||||
ref junkSectors,
|
||||
statuses);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ulong groupStartOffset = _ngcwPartitions[partIndex].DataOffset +
|
||||
(discOffset - _ngcwPartitions[partIndex].DataOffset) / Crypto.GROUP_SIZE * Crypto.GROUP_SIZE;
|
||||
|
||||
byte[] groupPayload;
|
||||
|
||||
if(sectors == NGCW_SECTORS_PER_GROUP && discOffset == groupStartOffset)
|
||||
{
|
||||
groupPayload = new byte[Crypto.GROUP_SIZE];
|
||||
|
||||
for(uint i = 0; i < sectors; i++)
|
||||
Array.Copy(longBuffer, i * NGCW_LONG_SECTOR_SIZE + NGCW_PAYLOAD_OFFSET, groupPayload, i * NGCW_SECTOR_SIZE,
|
||||
NGCW_SECTOR_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupPayload = ReadRawPayloadSectors(scsiReader, groupStartOffset / NGCW_SECTOR_SIZE, NGCW_SECTORS_PER_GROUP);
|
||||
|
||||
if(groupPayload == null || groupPayload.Length < Crypto.GROUP_SIZE) return false;
|
||||
}
|
||||
|
||||
byte[] hashBlock = new byte[Crypto.GROUP_HASH_SIZE];
|
||||
byte[] groupData = new byte[Crypto.GROUP_DATA_SIZE];
|
||||
Crypto.DecryptGroup(_ngcwPartitions[partIndex].TitleKey, groupPayload, hashBlock, groupData);
|
||||
|
||||
ulong groupNumber = (groupStartOffset - _ngcwPartitions[partIndex].DataOffset) / Crypto.GROUP_SIZE;
|
||||
ulong logicalOffset = groupNumber * Crypto.GROUP_DATA_SIZE;
|
||||
bool[] sectorIsData = new bool[NGCW_SECTORS_PER_GROUP];
|
||||
int userDataCount = 0;
|
||||
|
||||
for(ulong off = 0; off < Crypto.GROUP_DATA_SIZE; off += NGCW_SECTOR_SIZE)
|
||||
{
|
||||
ulong chunk = Crypto.GROUP_DATA_SIZE - off;
|
||||
|
||||
if(chunk > NGCW_SECTOR_SIZE) chunk = NGCW_SECTOR_SIZE;
|
||||
|
||||
if(logicalOffset + off < _ngcwPartSysEnd[partIndex])
|
||||
sectorIsData[userDataCount] = true;
|
||||
else if(_ngcwPartDataMaps[partIndex] != null)
|
||||
{
|
||||
sectorIsData[userDataCount] = DataMap.IsDataRegion(_ngcwPartDataMaps[partIndex], logicalOffset + off, chunk);
|
||||
}
|
||||
else
|
||||
sectorIsData[userDataCount] = true;
|
||||
|
||||
userDataCount++;
|
||||
}
|
||||
|
||||
ulong blockPhase = logicalOffset % Crypto.GROUP_SIZE;
|
||||
ulong block2Start = blockPhase > 0 ? Crypto.GROUP_SIZE - blockPhase : Crypto.GROUP_DATA_SIZE;
|
||||
if(block2Start > Crypto.GROUP_DATA_SIZE) block2Start = Crypto.GROUP_DATA_SIZE;
|
||||
|
||||
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 < userDataCount; s++)
|
||||
{
|
||||
if(sectorIsData[s]) continue;
|
||||
|
||||
ulong sectorOffset = (ulong)s * NGCW_SECTOR_SIZE;
|
||||
bool inBlock2 = sectorOffset >= block2Start;
|
||||
|
||||
if(inBlock2 && haveSeed2) continue;
|
||||
if(!inBlock2 && haveSeed1) continue;
|
||||
|
||||
int available = Crypto.GROUP_DATA_SIZE - (int)sectorOffset;
|
||||
int dataOffset = (int)((logicalOffset + sectorOffset) % Crypto.GROUP_SIZE);
|
||||
|
||||
if(available < Lfg.MIN_SEED_DATA_BYTES) continue;
|
||||
|
||||
uint[] destination = inBlock2 ? seed2 : seed1;
|
||||
int matched = Lfg.GetSeed(groupData.AsSpan((int)sectorOffset, available), dataOffset, destination);
|
||||
|
||||
if(matched > 0)
|
||||
{
|
||||
if(inBlock2)
|
||||
haveSeed2 = true;
|
||||
else
|
||||
haveSeed1 = true;
|
||||
}
|
||||
|
||||
if(haveSeed1 && haveSeed2) break;
|
||||
}
|
||||
|
||||
byte[] decryptedGroup = new byte[Crypto.GROUP_SIZE];
|
||||
Array.Copy(hashBlock, 0, decryptedGroup, 0, Crypto.GROUP_HASH_SIZE);
|
||||
|
||||
for(int s = 0; s < userDataCount; s++)
|
||||
{
|
||||
ulong off = (ulong)s * NGCW_SECTOR_SIZE;
|
||||
int chunk = Crypto.GROUP_DATA_SIZE - (int)off;
|
||||
int outOff = Crypto.GROUP_HASH_SIZE + (int)off;
|
||||
|
||||
if(chunk > NGCW_SECTOR_SIZE) chunk = NGCW_SECTOR_SIZE;
|
||||
|
||||
if(sectorIsData[s])
|
||||
{
|
||||
Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool inBlock2 = off >= block2Start;
|
||||
bool haveSeed = inBlock2 ? haveSeed2 : haveSeed1;
|
||||
uint[] seed = inBlock2 ? seed2 : seed1;
|
||||
|
||||
if(!haveSeed)
|
||||
{
|
||||
Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
uint[] lfgBuffer = new uint[Lfg.MIN_SEED_DATA_BYTES / sizeof(uint)];
|
||||
uint[] seedCopy = new uint[Lfg.SEED_SIZE];
|
||||
int position = 0;
|
||||
byte[] expectedData = new byte[NGCW_SECTOR_SIZE];
|
||||
Array.Copy(seed, seedCopy, Lfg.SEED_SIZE);
|
||||
Lfg.SetSeed(lfgBuffer, seedCopy);
|
||||
|
||||
int advance = (int)((logicalOffset + off) % Crypto.GROUP_SIZE);
|
||||
|
||||
if(advance > 0)
|
||||
{
|
||||
byte[] discard = new byte[4096];
|
||||
int remain = advance;
|
||||
|
||||
while(remain > 0)
|
||||
{
|
||||
int step = remain > discard.Length ? discard.Length : remain;
|
||||
Lfg.GetBytes(lfgBuffer, ref position, discard, 0, step);
|
||||
remain -= step;
|
||||
}
|
||||
}
|
||||
|
||||
Lfg.GetBytes(lfgBuffer, ref position, expectedData, 0, chunk);
|
||||
|
||||
if(groupData.AsSpan((int)off, chunk).SequenceEqual(expectedData.AsSpan(0, chunk)))
|
||||
{
|
||||
Array.Clear(decryptedGroup, outOff, chunk);
|
||||
_ngcwJunkCollector.Add(groupStartOffset + Crypto.GROUP_HASH_SIZE + off, (ulong)chunk, (ushort)partIndex, seed);
|
||||
}
|
||||
else
|
||||
Array.Copy(groupData, (int)off, decryptedGroup, outOff, chunk);
|
||||
}
|
||||
|
||||
for(uint i = 0; i < sectors; i++)
|
||||
{
|
||||
ulong absoluteSector = startSector + i;
|
||||
int groupIndex = (int)(absoluteSector - groupStartOffset / NGCW_SECTOR_SIZE);
|
||||
if(groupIndex < 0 || groupIndex >= NGCW_SECTORS_PER_GROUP) continue;
|
||||
|
||||
Array.Copy(decryptedGroup,
|
||||
groupIndex * NGCW_SECTOR_SIZE,
|
||||
longBuffer,
|
||||
i * NGCW_LONG_SECTOR_SIZE + NGCW_PAYLOAD_OFFSET,
|
||||
NGCW_SECTOR_SIZE);
|
||||
statuses[i] = SectorStatus.Unencrypted;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnsureNintendoDerivedKeyFromLba0(Reader scsiReader)
|
||||
{
|
||||
if(!_omniDriveNintendoSoftwareDescramble || _nintendoDerivedDiscKey.HasValue) return true;
|
||||
|
||||
bool sense = scsiReader.ReadBlock(out byte[] raw, 0, out _, out _, out _);
|
||||
|
||||
if(sense || _dev.Error || raw == null || raw.Length < NGCW_LONG_SECTOR_SIZE)
|
||||
{
|
||||
StoppingErrorMessage?.Invoke(Localization.Core.Unable_to_read_medium);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = _nintendoSectorDecoder.Scramble(raw, 0, out byte[] descrambled);
|
||||
|
||||
if(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);
|
||||
UpdateStatus?.Invoke(string.Format(UI.Ngcw_nintendo_derived_key_0, _nintendoDerivedDiscKey.Value));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DescrambleNintendoLongBuffer(byte[] longBuffer, ulong startSector, uint sectors)
|
||||
{
|
||||
if(!_omniDriveNintendoSoftwareDescramble) return;
|
||||
|
||||
for(uint i = 0; i < sectors; i++)
|
||||
DescrambleNintendo2064At(longBuffer, (int)(i * NGCW_LONG_SECTOR_SIZE), startSector + i);
|
||||
}
|
||||
|
||||
void 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);
|
||||
|
||||
var error = _nintendoSectorDecoder.Scramble(one, key, out byte[] decoded);
|
||||
|
||||
if(error != ErrorNumber.NoError)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
List<WiiPartition> ParseWiiPartitionsFromDevice(Reader scsiReader)
|
||||
{
|
||||
byte[] partitionTable = ReadDiscBytesFromDevice(scsiReader, 0x40000, 32);
|
||||
|
||||
if(partitionTable == null) return null;
|
||||
|
||||
List<WiiPartition> partitions = new List<WiiPartition>();
|
||||
uint[] counts = new uint[4];
|
||||
uint[] offsets = new uint[4];
|
||||
|
||||
for(int t = 0; t < 4; t++)
|
||||
{
|
||||
counts[t] = BigEndianBitConverter.ToUInt32(partitionTable, t * 8);
|
||||
offsets[t] = BigEndianBitConverter.ToUInt32(partitionTable, t * 8 + 4);
|
||||
}
|
||||
|
||||
for(int t = 0; t < 4; t++)
|
||||
{
|
||||
if(counts[t] == 0) continue;
|
||||
|
||||
ulong tableOffset = (ulong)offsets[t] << 2;
|
||||
int tableSize = (int)counts[t] * 8;
|
||||
byte[] tableData = ReadDiscBytesFromDevice(scsiReader, tableOffset, tableSize);
|
||||
|
||||
if(tableData == null) return null;
|
||||
|
||||
for(uint p = 0; p < counts[t]; p++)
|
||||
{
|
||||
ulong partitionOffset = (ulong)BigEndianBitConverter.ToUInt32(tableData, (int)p * 8) << 2;
|
||||
uint partType = BigEndianBitConverter.ToUInt32(tableData, (int)p * 8 + 4);
|
||||
byte[] ticket = ReadDiscBytesFromDevice(scsiReader, partitionOffset, 0x2A4);
|
||||
|
||||
if(ticket == null) return null;
|
||||
|
||||
byte[] titleKey = Crypto.DecryptTitleKey(ticket);
|
||||
byte[] header = ReadDiscBytesFromDevice(scsiReader, partitionOffset + 0x2B8, 8);
|
||||
|
||||
if(header == null) return null;
|
||||
|
||||
ulong dataOffset = partitionOffset + ((ulong)BigEndianBitConverter.ToUInt32(header, 0) << 2);
|
||||
ulong dataSize = (ulong)BigEndianBitConverter.ToUInt32(header, 4) << 2;
|
||||
|
||||
partitions.Add(new WiiPartition
|
||||
{
|
||||
Offset = partitionOffset,
|
||||
DataOffset = dataOffset,
|
||||
DataSize = dataSize,
|
||||
Type = partType,
|
||||
TitleKey = titleKey
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return partitions;
|
||||
}
|
||||
|
||||
byte[] ReadDiscBytesFromDevice(Reader scsiReader, ulong byteOffset, int length)
|
||||
{
|
||||
byte[] result = new byte[length];
|
||||
int read = 0;
|
||||
|
||||
while(read < length)
|
||||
{
|
||||
ulong sector = (byteOffset + (ulong)read) / NGCW_SECTOR_SIZE;
|
||||
int sectorOff = (int)((byteOffset + (ulong)read) % NGCW_SECTOR_SIZE);
|
||||
int chunk = NGCW_SECTOR_SIZE - sectorOff;
|
||||
|
||||
if(chunk > length - read) chunk = length - read;
|
||||
|
||||
bool sense = scsiReader.ReadBlock(out byte[] rawSector, sector, out _, out _, out _);
|
||||
|
||||
if(sense || _dev.Error || rawSector == null || rawSector.Length < NGCW_PAYLOAD_OFFSET + NGCW_SECTOR_SIZE)
|
||||
return null;
|
||||
|
||||
if(_omniDriveNintendoSoftwareDescramble && rawSector.Length >= NGCW_LONG_SECTOR_SIZE)
|
||||
DescrambleNintendo2064At(rawSector, 0, sector);
|
||||
|
||||
Array.Copy(rawSector, NGCW_PAYLOAD_OFFSET + sectorOff, result, read, chunk);
|
||||
read += chunk;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
byte[] ReadRawPayloadSectors(Reader scsiReader, ulong startSector, uint count)
|
||||
{
|
||||
bool sense = scsiReader.ReadBlocks(out byte[] rawBuffer, startSector, count, out _, out _, out _);
|
||||
|
||||
if(sense || _dev.Error || rawBuffer == null) return null;
|
||||
|
||||
if(_omniDriveNintendoSoftwareDescramble && rawBuffer.Length >= count * NGCW_LONG_SECTOR_SIZE)
|
||||
{
|
||||
for(uint i = 0; i < count; i++)
|
||||
DescrambleNintendo2064At(rawBuffer, (int)(i * NGCW_LONG_SECTOR_SIZE), startSector + i);
|
||||
}
|
||||
|
||||
byte[] payload = new byte[count * NGCW_SECTOR_SIZE];
|
||||
|
||||
for(uint i = 0; i < count; i++)
|
||||
Array.Copy(rawBuffer, i * NGCW_LONG_SECTOR_SIZE + NGCW_PAYLOAD_OFFSET, payload, i * NGCW_SECTOR_SIZE,
|
||||
NGCW_SECTOR_SIZE);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
@@ -209,55 +209,82 @@ partial class Dump
|
||||
|
||||
_writeStopwatch.Restart();
|
||||
|
||||
byte[] tmpBuf;
|
||||
var cmi = new byte[blocksToRead];
|
||||
|
||||
for(uint j = 0; j < blocksToRead; j++)
|
||||
if(_ngcwEnabled)
|
||||
{
|
||||
byte[] key = buffer.Skip((int)(2064 * j + 7)).Take(5).ToArray();
|
||||
|
||||
if(key.All(static k => k == 0))
|
||||
if(!TransformNgcwLongSectors(scsiReader, buffer, i, blocksToRead, out SectorStatus[] statuses))
|
||||
{
|
||||
outputFormat.WriteSectorTag(new byte[5], i + j, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
if(_stopOnError) return;
|
||||
|
||||
MarkTitleKeyDumped(i + j);
|
||||
outputFormat.WriteSectorsLong(new byte[blockSize * _skip],
|
||||
i,
|
||||
false,
|
||||
_skip,
|
||||
Enumerable.Repeat(SectorStatus.NotDumped, (int)_skip).ToArray());
|
||||
|
||||
continue;
|
||||
}
|
||||
for(ulong b = i; b < i + _skip; b++) _resume.BadBlocks.Add(b);
|
||||
|
||||
CSS.DecryptTitleKey(discKey, key, out tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, i + j, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(i + j);
|
||||
|
||||
if(_storeEncrypted) continue;
|
||||
|
||||
cmi[j] = buffer[2064 * j + 6];
|
||||
}
|
||||
|
||||
// Todo: Flag in the outputFormat that a sector has been decrypted
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(i,
|
||||
false,
|
||||
blocksToRead,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_title_key_for_sector_0,
|
||||
i));
|
||||
mhddLog.Write(i, cmdDuration < 500 ? 65535 : cmdDuration, _skip);
|
||||
ibgLog.Write(i, 0);
|
||||
AaruLogging.WriteLine(Localization.Core.Skipping_0_blocks_from_errored_block_1, _skip, i);
|
||||
i += _skip - blocksToRead;
|
||||
newTrim = true;
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi, blocksToRead);
|
||||
{
|
||||
outputFormat.WriteSectorsLong(buffer, i, false, blocksToRead, statuses);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var cmi = new byte[blocksToRead];
|
||||
|
||||
outputFormat.WriteSectorsLong(buffer,
|
||||
i,
|
||||
false,
|
||||
blocksToRead,
|
||||
Enumerable.Repeat(SectorStatus.Dumped, (int)blocksToRead).ToArray());
|
||||
for (uint j = 0; j < blocksToRead; j++)
|
||||
{
|
||||
byte[] key = buffer.Skip((int)(2064 * j + 7)).Take(5).ToArray();
|
||||
|
||||
if(key.All(static k => k == 0))
|
||||
{
|
||||
outputFormat.WriteSectorTag(new byte[5], i + j, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
|
||||
MarkTitleKeyDumped(i + j);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, i + j, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(i + j);
|
||||
|
||||
if(_storeEncrypted) continue;
|
||||
|
||||
cmi[j] = buffer[2064 * j + 6];
|
||||
}
|
||||
|
||||
// Todo: Flag in the outputFormat that a sector has been decrypted
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(i,
|
||||
false,
|
||||
blocksToRead,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_title_key_for_sector_0,
|
||||
i));
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi, blocksToRead);
|
||||
}
|
||||
|
||||
outputFormat.WriteSectorsLong(buffer,
|
||||
i,
|
||||
false,
|
||||
blocksToRead,
|
||||
Enumerable.Repeat(SectorStatus.Dumped, (int)blocksToRead).ToArray());
|
||||
}
|
||||
|
||||
imageWriteDuration += _writeStopwatch.Elapsed.TotalSeconds;
|
||||
extents.Add(i, blocksToRead, true);
|
||||
|
||||
@@ -101,45 +101,55 @@ partial class Dump
|
||||
|
||||
if(scsiReader.ReadBuffer3CReadRaw || scsiReader.OmniDriveReadRaw || scsiReader.HldtstReadRaw)
|
||||
{
|
||||
var cmi = new byte[1];
|
||||
|
||||
byte[] key = buffer.Skip(7).Take(5).ToArray();
|
||||
|
||||
if(key.All(static k => k == 0))
|
||||
if(_ngcwEnabled)
|
||||
{
|
||||
outputFormat.WriteSectorTag(new byte[5], badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
|
||||
MarkTitleKeyDumped(badSector);
|
||||
if(TransformNgcwLongSectors(scsiReader, buffer, badSector, 1, out SectorStatus[] statuses))
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, statuses[0]);
|
||||
else
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.NotDumped);
|
||||
}
|
||||
else
|
||||
{
|
||||
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(badSector);
|
||||
var cmi = new byte[1];
|
||||
|
||||
cmi[0] = buffer[6];
|
||||
}
|
||||
byte[] key = buffer.Skip(7).Take(5).ToArray();
|
||||
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(badSector,
|
||||
false,
|
||||
1,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
if(key.All(static k => k == 0))
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_title_key_for_sector_0,
|
||||
badSector));
|
||||
outputFormat.WriteSectorTag(new byte[5], badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
|
||||
MarkTitleKeyDumped(badSector);
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi);
|
||||
}
|
||||
{
|
||||
CSS.DecryptTitleKey(discKey, key, out byte[] tmpBuf);
|
||||
outputFormat.WriteSectorTag(tmpBuf, badSector, false, SectorTagType.DvdTitleKeyDecrypted);
|
||||
MarkTitleKeyDumped(badSector);
|
||||
|
||||
_resume.BadBlocks.Remove(badSector);
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.Dumped);
|
||||
cmi[0] = buffer[6];
|
||||
}
|
||||
|
||||
if(!_storeEncrypted)
|
||||
{
|
||||
ErrorNumber errno =
|
||||
outputFormat.ReadSectorsTag(badSector,
|
||||
false,
|
||||
1,
|
||||
SectorTagType.DvdTitleKeyDecrypted,
|
||||
out byte[] titleKey);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
ErrorMessage?.Invoke(string.Format(Localization.Core.Error_retrieving_title_key_for_sector_0,
|
||||
badSector));
|
||||
}
|
||||
else
|
||||
buffer = CSS.DecryptSectorLong(buffer, titleKey, cmi);
|
||||
}
|
||||
|
||||
_resume.BadBlocks.Remove(badSector);
|
||||
outputFormat.WriteSectorLong(buffer, badSector, false, SectorStatus.Dumped);
|
||||
}
|
||||
}
|
||||
else
|
||||
outputFormat.WriteSector(buffer, badSector, false, SectorStatus.Dumped);
|
||||
|
||||
@@ -77,6 +77,8 @@ sealed partial class Reader
|
||||
internal uint PhysicalBlockSize { get; private set; }
|
||||
internal uint LongBlockSize { 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>
|
||||
internal bool OmniDriveNintendoMode { get; set; }
|
||||
internal bool CanSeek => _ataSeek || _seek6 || _seek10;
|
||||
internal bool CanSeekLba => _ataSeekLba || _seek6 || _seek10;
|
||||
|
||||
|
||||
@@ -588,11 +588,12 @@ sealed partial class Reader
|
||||
ReadBuffer3CReadRaw =
|
||||
!_dev.ReadBuffer3CRawDvd(out _, out senseBuf, 0, 1, _timeout, out _, layerbreak, otp);
|
||||
|
||||
// Try OmniDrive on drives with OmniDrive firmware
|
||||
// Try OmniDrive on drives with OmniDrive firmware (standard descramble=1 and Nintendo descramble=0)
|
||||
if(_dev.IsOmniDriveFirmware())
|
||||
{
|
||||
OmniDriveReadRaw =
|
||||
!_dev.OmniDriveReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _);
|
||||
bool omniStandardOk = !_dev.OmniDriveReadRawDvd(out _, out senseBuf, 0, 1, _timeout, out _);
|
||||
bool omniNintendoOk = !_dev.OmniDriveReadNintendoDvd(out _, out senseBuf, 0, 1, _timeout, out _);
|
||||
OmniDriveReadRaw = omniStandardOk || omniNintendoOk;
|
||||
}
|
||||
|
||||
if(HldtstReadRaw || _plextorReadRaw || ReadBuffer3CReadRaw || OmniDriveReadRaw)
|
||||
@@ -848,12 +849,21 @@ sealed partial class Reader
|
||||
else if(OmniDriveReadRaw)
|
||||
{
|
||||
uint lba = negative ? (uint)(-(long)block) : (uint)block;
|
||||
sense = _dev.OmniDriveReadRawDvd(out buffer,
|
||||
out senseBuf,
|
||||
lba,
|
||||
count,
|
||||
_timeout,
|
||||
out duration);
|
||||
|
||||
if(OmniDriveNintendoMode)
|
||||
sense = _dev.OmniDriveReadNintendoDvd(out buffer,
|
||||
out senseBuf,
|
||||
lba,
|
||||
count,
|
||||
_timeout,
|
||||
out duration);
|
||||
else
|
||||
sense = _dev.OmniDriveReadRawDvd(out buffer,
|
||||
out senseBuf,
|
||||
lba,
|
||||
count,
|
||||
_timeout,
|
||||
out duration);
|
||||
}
|
||||
else if(ReadBuffer3CReadRaw)
|
||||
{
|
||||
|
||||
@@ -41,8 +41,11 @@ namespace Aaru.Decoders.Nintendo;
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
public sealed class Sector
|
||||
{
|
||||
/// <summary>Offset of main user data in a Nintendo (GameCube/Wii) DVD sector (bytes 6-2053).</summary>
|
||||
public const int NintendoMainDataOffset = 6;
|
||||
/// <summary>
|
||||
/// ECMA-267 <c>main_data</c> offset in OmniDrive 2064-byte Nintendo sectors: DVD XOR applies to 2048 bytes from
|
||||
/// here (same as standard DVD). Bytes 6-11 (<c>cpr_mai</c>) are not scrambled on media.
|
||||
/// </summary>
|
||||
public const int NintendoMainDataOffset = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Derives the Nintendo descramble key from the first 8 bytes of the cpr_mai region (LBA 0 payload).
|
||||
|
||||
@@ -41,6 +41,45 @@ namespace Aaru.Devices;
|
||||
|
||||
public partial class Device
|
||||
{
|
||||
enum OmniDriveDiscType
|
||||
{
|
||||
CD = 0,
|
||||
DVD = 1,
|
||||
BD = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes byte 1 of the OmniDrive READ CDB to match redumper's <c>CDB12_ReadOmniDrive</c>
|
||||
/// (<c>scsi/mmc.ixx</c>): <c>disc_type</c> :2 (LSB), <c>raw_addressing</c> :1, <c>fua</c> :1, <c>descramble</c> :1, reserved :3.
|
||||
/// </summary>
|
||||
/// <param name="discType">0 = CD, 1 = DVD, 2 = BD (redumper <c>OmniDrive_DiscType</c>).</param>
|
||||
static byte EncodeOmniDriveReadCdb1(OmniDriveDiscType discType, bool rawAddressing, bool fua, bool descramble)
|
||||
{
|
||||
int d = (byte)discType & 3;
|
||||
int r = rawAddressing ? 1 : 0;
|
||||
int f = fua ? 1 : 0;
|
||||
int s = descramble ? 1 : 0;
|
||||
|
||||
return (byte)(d | (r << 2) | (f << 3) | (s << 4));
|
||||
}
|
||||
|
||||
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[10] = 0; // subchannels=NONE, c2=0
|
||||
cdb[11] = 0; // control
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the drive has OmniDrive firmware by inspecting INQUIRY Reserved5 (bytes 74+) for "OmniDrive",
|
||||
/// matching redumper's is_omnidrive_firmware behaviour.
|
||||
@@ -77,27 +116,19 @@ public partial class Device
|
||||
/// <param name="transferLength">Number of 2064-byte sectors to read.</param>
|
||||
/// <param name="timeout">Timeout in seconds.</param>
|
||||
/// <param name="duration">Duration in milliseconds it took for the device to execute the command.</param>
|
||||
/// <param name="fua">Set to <c>true</c> if the command should use FUA.</param>
|
||||
/// <param name="descramble">Set to <c>true</c> if the data should be descrambled by the device.</param>
|
||||
public bool OmniDriveReadRawDvd(out byte[] buffer, out ReadOnlySpan<byte> senseBuffer, uint lba, uint transferLength,
|
||||
uint timeout, out double duration)
|
||||
uint timeout, out double duration, bool fua = false, bool descramble = true)
|
||||
{
|
||||
senseBuffer = SenseBuffer;
|
||||
Span<byte> cdb = CdbBuffer[..12];
|
||||
cdb.Clear();
|
||||
|
||||
buffer = new byte[2064 * transferLength];
|
||||
|
||||
cdb[0] = (byte)ScsiCommands.ReadOmniDrive;
|
||||
cdb[1] = 0x11; // disc_type=1 (DVD), raw_addressing=0 (LBA), fua=0, descramble=1
|
||||
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
|
||||
FillOmniDriveReadDvdCdb(cdb,
|
||||
lba,
|
||||
transferLength,
|
||||
EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, fua, descramble));
|
||||
|
||||
LastError = SendScsiCommand(cdb, ref buffer, timeout, ScsiDirection.In, out duration, out bool sense);
|
||||
|
||||
@@ -109,4 +140,32 @@ public partial class Device
|
||||
|
||||
return sense;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="descramble">Drive-side DVD descramble (redumper raw DVD uses <c>false</c>).</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)
|
||||
{
|
||||
senseBuffer = SenseBuffer;
|
||||
Span<byte> cdb = CdbBuffer[..12];
|
||||
buffer = new byte[2064 * transferLength];
|
||||
|
||||
FillOmniDriveReadDvdCdb(cdb,
|
||||
lba,
|
||||
transferLength,
|
||||
EncodeOmniDriveReadCdb1(OmniDriveDiscType.DVD, false, false, descramble));
|
||||
|
||||
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.
|
||||
Error = LastError != 0;
|
||||
|
||||
AaruLogging.Debug(SCSI_MODULE_NAME, "OmniDrive READ NINTENDO DVD took {0} ms", duration);
|
||||
|
||||
return sense;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,6 +925,18 @@
|
||||
<data name="Dumping_Nintendo_GameCube_or_Wii_discs_is_not_yet_implemented" xml:space="preserve">
|
||||
<value>[red]El volcado de discos de Nintendo GameCube o Wii no está implementado todavía.[/]</value>
|
||||
</data>
|
||||
<data name="Nintendo_OmniDrive_dump_requires_Aaru_format" xml:space="preserve">
|
||||
<value>[red]El volcado de discos de Nintendo GameCube o Wii con OmniDrive requiere formato de salida Aaru.[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_parsing_partition_table" xml:space="preserve">
|
||||
<value>[slateblue1]Analizando tabla de particiones de Wii...[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_wrote_partition_key_map" xml:space="preserve">
|
||||
<value>[slateblue1]Se guardó el mapa de claves de partición de Wii.[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_wrote_junk_map_0_entries" xml:space="preserve">
|
||||
<value>[slateblue1]Se guardó el mapa de basura de Nintendo con [lime]{0}[/] entradas.[/]</value>
|
||||
</data>
|
||||
<data name="Dumping_progress_log" xml:space="preserve">
|
||||
<value>################# Registro de progreso del volcado #################</value>
|
||||
</data>
|
||||
|
||||
@@ -1395,6 +1395,18 @@
|
||||
<data name="Dumping_Nintendo_GameCube_or_Wii_discs_is_not_yet_implemented" xml:space="preserve">
|
||||
<value>[red]Dumping Nintendo GameCube or Wii discs is not yet implemented.[/]</value>
|
||||
</data>
|
||||
<data name="Nintendo_OmniDrive_dump_requires_Aaru_format" xml:space="preserve">
|
||||
<value>[red]Dumping Nintendo GameCube or Wii discs with OmniDrive requires Aaru output format.[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_parsing_partition_table" xml:space="preserve">
|
||||
<value>[slateblue1]Parsing Wii partition table...[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_wrote_partition_key_map" xml:space="preserve">
|
||||
<value>[slateblue1]Stored Wii partition key map.[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_dump_wrote_junk_map_0_entries" xml:space="preserve">
|
||||
<value>[slateblue1]Stored Nintendo junk map with [lime]{0}[/] entries.[/]</value>
|
||||
</data>
|
||||
<data name="Reading_Disc_Manufacturing_Information" xml:space="preserve">
|
||||
<value>[slateblue1]Reading [italic]Disc Manufacturing Information[/][/]</value>
|
||||
</data>
|
||||
|
||||
14
Aaru.Localization/UI.Designer.cs
generated
14
Aaru.Localization/UI.Designer.cs
generated
@@ -1,4 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
@@ -1983,6 +1983,18 @@ namespace Aaru.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
public static string Ngcw_nintendo_software_descramble {
|
||||
get {
|
||||
return ResourceManager.GetString("Ngcw_nintendo_software_descramble", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Ngcw_nintendo_derived_key_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("Ngcw_nintendo_derived_key_0", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string PS3_disc_key_resolved_from_0 {
|
||||
get {
|
||||
return ResourceManager.GetString("PS3_disc_key_resolved_from_0", resourceCulture);
|
||||
|
||||
@@ -663,6 +663,12 @@
|
||||
</data>
|
||||
<data name="Ngcw_disc_number_0" xml:space="preserve">
|
||||
<value>[slateblue1]Número de disco: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_software_descramble" xml:space="preserve">
|
||||
<value>[slateblue1]Descifrado por software de Nintendo activado (OmniDrive en bruto, descramble=0).[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||
<value>[slateblue1]Clave de disco Nintendo derivada: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||
<value>[slateblue1]Clave de disco PS3 resuelta desde [aqua]{0}[/].[/]</value>
|
||||
|
||||
@@ -1002,6 +1002,12 @@ In you are unsure, please press N to not continue.</value>
|
||||
</data>
|
||||
<data name="Ngcw_disc_number_0" xml:space="preserve">
|
||||
<value>[slateblue1]Disc number: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_software_descramble" xml:space="preserve">
|
||||
<value>[slateblue1]Nintendo software descrambling enabled (OmniDrive raw, descramble=0).[/]</value>
|
||||
</data>
|
||||
<data name="Ngcw_nintendo_derived_key_0" xml:space="preserve">
|
||||
<value>[slateblue1]Derived Nintendo disc key: [green]{0}[/].[/]</value>
|
||||
</data>
|
||||
<data name="PS3_disc_key_resolved_from_0" xml:space="preserve">
|
||||
<value>[slateblue1]PS3 disc key resolved from [aqua]{0}[/].[/]</value>
|
||||
|
||||
Reference in New Issue
Block a user