diff --git a/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs b/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
index 3e119bb13..76f4f2e37 100644
--- a/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
+++ b/Aaru.Core/Devices/Dumping/Sbc/Ngcw.cs
@@ -35,11 +35,11 @@ using System.Linq;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
-using Aaru.Core.Image.Ngcw;
+using Aaru.Decryption.Ngcw;
using Aaru.Decoders.Nintendo;
using Aaru.Helpers;
using Aaru.Localization;
-using NgcwPartitions = Aaru.Core.Image.Ngcw.Partitions;
+using NgcwPartitions = Aaru.Decryption.Ngcw.Partitions;
namespace Aaru.Core.Devices.Dumping;
diff --git a/Aaru.Core/Image/Convert/Convert.cs b/Aaru.Core/Image/Convert/Convert.cs
index 43ff920b9..acefaf57a 100644
--- a/Aaru.Core/Image/Convert/Convert.cs
+++ b/Aaru.Core/Image/Convert/Convert.cs
@@ -287,7 +287,7 @@ public partial class Convert
}
}
- errno = ConvertNgcwSectors();
+ errno = ConvertNgcwSectors(useLong);
if(errno != ErrorNumber.NoError) return errno;
}
@@ -367,6 +367,7 @@ public partial class Convert
}
}
+ // TODO: Enable for Ngcw
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _negativeSectors > 0)
{
errno = ConvertNegativeSectors(useLong);
@@ -374,6 +375,7 @@ public partial class Convert
if(errno != ErrorNumber.NoError) return errno;
}
+ // TODO: Enable for Ngcw
if(!isPs3Conversion && !isWiiuConversion && !isNgcwConversion && _overflowSectors > 0)
{
errno = ConvertOverflowSectors(useLong);
diff --git a/Aaru.Core/Image/Convert/Edge.cs b/Aaru.Core/Image/Convert/Edge.cs
index 5b9e7dfa4..08dbab8f5 100644
--- a/Aaru.Core/Image/Convert/Edge.cs
+++ b/Aaru.Core/Image/Convert/Edge.cs
@@ -135,6 +135,12 @@ public partial class Convert
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
+ case SectorTagType.DvdSectorCmi:
+ case SectorTagType.DvdSectorTitleKey:
+ case SectorTagType.DvdSectorEdc:
+ case SectorTagType.DvdSectorIed:
+ case SectorTagType.DvdSectorInformation:
+ case SectorTagType.DvdSectorNumber:
// These tags are inline in long sector
continue;
case SectorTagType.CdTrackFlags:
@@ -329,6 +335,12 @@ public partial class Convert
case SectorTagType.CdSectorEccP:
case SectorTagType.CdSectorEccQ:
case SectorTagType.CdSectorEcc:
+ case SectorTagType.DvdSectorCmi:
+ case SectorTagType.DvdSectorTitleKey:
+ case SectorTagType.DvdSectorEdc:
+ case SectorTagType.DvdSectorIed:
+ case SectorTagType.DvdSectorInformation:
+ case SectorTagType.DvdSectorNumber:
// These tags are inline in long sector
continue;
case SectorTagType.CdTrackFlags:
diff --git a/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs b/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
index c095c36a0..264722eb6 100644
--- a/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
+++ b/Aaru.Core/Image/Convert/Ngcw/ConvertNgcw.cs
@@ -37,7 +37,9 @@ using System.Collections.Generic;
using System.Text;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
-using Aaru.Core.Image.Ngcw;
+using Aaru.Decoders.Nintendo;
+using Aaru.Decryption.Ngcw;
+using NgcwPartitions = Aaru.Decryption.Ngcw.Partitions;
using Aaru.Helpers;
using Aaru.Localization;
@@ -68,7 +70,7 @@ public partial class Convert
// Parse Wii partition table
PulseProgress?.Invoke(UI.Ngcw_parsing_partition_table);
- _ngcwPartitions = Ngcw.Partitions.ParseWiiPartitions(_inputImage);
+ _ngcwPartitions = NgcwPartitions.ParseWiiPartitions(_inputImage);
if(_ngcwPartitions == null)
{
@@ -83,10 +85,10 @@ public partial class Convert
// Build partition region map
PulseProgress?.Invoke(UI.Ngcw_building_partition_key_map);
- _ngcwRegions = Ngcw.Partitions.BuildRegionMap(_ngcwPartitions);
+ _ngcwRegions = NgcwPartitions.BuildRegionMap(_ngcwPartitions);
// Serialize and write partition key map
- byte[] keyMapData = Ngcw.Partitions.SerializeKeyMap(_ngcwRegions);
+ byte[] keyMapData = NgcwPartitions.SerializeKeyMap(_ngcwRegions);
_outputImage.WriteMediaTag(keyMapData, MediaTagType.WiiPartitionKeyMap);
@@ -104,7 +106,7 @@ public partial class Convert
/// writes with Unencrypted status; plaintext areas use Dumped/Generable.
/// Does not copy sector tags, negative sectors, or overflow sectors.
///
- ErrorNumber ConvertNgcwSectors()
+ ErrorNumber ConvertNgcwSectors(bool useLong)
{
if(_aborted) return ErrorNumber.NoError;
@@ -119,7 +121,8 @@ public partial class Convert
if(_mediaType == MediaType.GOD)
{
- ErrorNumber errno =
+ ErrorNumber errno = useLong ?
+ ConvertGameCubeSectorsLong(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors) :
ConvertGameCubeSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
if(errno != ErrorNumber.NoError)
@@ -131,7 +134,9 @@ public partial class Convert
}
else
{
- ErrorNumber errno = ConvertWiiSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
+ ErrorNumber errno = useLong ?
+ ConvertWiiSectorsLong(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors) :
+ ConvertWiiSectors(discSize, totalLogicalSectors, jc, ref dataSectors, ref junkSectors);
if(errno != ErrorNumber.NoError)
{
@@ -294,7 +299,7 @@ public partial class Convert
(long)totalLogicalSectors);
// Check if inside a partition's data area
- int inPart = Ngcw.Partitions.FindPartitionAtOffset(_ngcwPartitions, offset);
+ int inPart = NgcwPartitions.FindPartitionAtOffset(_ngcwPartitions, offset);
if(inPart >= 0)
{
@@ -747,4 +752,137 @@ public partial class Convert
return result;
}
+
+ /// GameCube sector conversion pipeline for long sectors.
+ ErrorNumber ConvertGameCubeSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc,
+ ref ulong dataSectors, ref ulong junkSectors)
+ {
+ const int blockSize = Crypto.GROUP_SIZE; // 0x8000 logical (16 × 2048 user bytes)
+ const int sectorsPerBlock = Crypto.LOGICAL_PER_GROUP;
+ const int userSize = Crypto.SECTOR_SIZE;
+
+ // Junk / FST offsets are in logical (user) byte space. User 2048-byte slices match ReadSector (Nintendo main_data at Sector.NintendoMainDataOffset).
+ ErrorNumber probeErr = _inputImage.ReadSectorLong(0, false, out byte[] longProbe, out _);
+
+ if(probeErr != ErrorNumber.NoError || longProbe == null ||
+ longProbe.Length < Sector.NintendoMainDataOffset + userSize)
+ return ErrorNumber.InOutError;
+
+ int longSectorSize = longProbe.Length;
+
+ // Read disc header to get FST info (logical user bytes via ReadSector)
+ byte[] header = ReadNgcwSectors(0, 2); // first 0x1000 bytes (need 0x42C)
+
+ if(header == null || header.Length < 0x42C) return ErrorNumber.InOutError;
+
+ // Read extended header for FST pointers (at 0x424)
+ byte[] extHeader = ReadNgcwSectors(0, (0x440 + userSize - 1) / userSize);
+
+ var fstOffset = BigEndianBitConverter.ToUInt32(extHeader, 0x424);
+ var fstSize = BigEndianBitConverter.ToUInt32(extHeader, 0x428);
+ ulong sysEnd = fstOffset + fstSize;
+
+ // Build FST data map
+ DataRegion[] dataMap = null;
+
+ if(fstSize > 0 && fstSize < 64 * 1024 * 1024)
+ {
+ byte[] fst = ReadNgcwBytes(fstOffset, (int)fstSize);
+
+ if(fst != null) dataMap = DataMap.BuildFromFst(fst, 0, 0);
+ }
+
+ // User-only buffer for LFG / data-region junk detection (same as ConvertGameCubeSectors)
+ var userBlockBuf = new byte[blockSize];
+ var longSectorBufs = new byte[sectorsPerBlock][];
+ var sectorStatuses = new SectorStatus[sectorsPerBlock];
+
+ for(ulong blockOff = 0; blockOff < discSize; blockOff += blockSize)
+ {
+ if(_aborted) break;
+
+ int blockBytes = blockSize;
+
+ if(blockOff + (ulong)blockBytes > discSize) blockBytes = (int)(discSize - blockOff);
+
+ ulong baseSector = blockOff / userSize;
+
+ UpdateProgress?.Invoke(string.Format(UI.Converting_sectors_0_to_1,
+ baseSector,
+ baseSector + sectorsPerBlock),
+ (long)baseSector,
+ (long)totalLogicalSectors);
+
+ // Read long sectors; pack user main data into userBlockBuf; keep full long buffers for output
+ for(var s = 0; s < sectorsPerBlock && s * userSize < blockBytes; s++)
+ {
+ ErrorNumber errno =
+ _inputImage.ReadSectorLong(baseSector + (ulong)s, false, out byte[] sectorData, out _);
+
+ byte[] stored = new byte[longSectorSize];
+ longSectorBufs[s] = stored;
+
+ if(errno != ErrorNumber.NoError || sectorData == null)
+ {
+ Array.Clear(userBlockBuf, s * userSize, userSize);
+
+ continue;
+ }
+
+ int copyLong = sectorData.Length < longSectorSize ? sectorData.Length : longSectorSize;
+ Array.Copy(sectorData, 0, stored, 0, copyLong);
+
+ if(sectorData.Length >= Sector.NintendoMainDataOffset + userSize)
+ Array.Copy(sectorData, Sector.NintendoMainDataOffset, userBlockBuf, s * userSize, userSize);
+ else
+ Array.Clear(userBlockBuf, s * userSize, userSize);
+ }
+
+ Junk.DetectJunkInBlock(userBlockBuf,
+ blockBytes,
+ blockOff,
+ dataMap,
+ sysEnd,
+ 0xFFFF,
+ jc,
+ ref dataSectors,
+ ref junkSectors,
+ sectorStatuses);
+
+ int numSectors = blockBytes / userSize;
+
+ for(var si = 0; si < numSectors; si++)
+ {
+ ulong sector = baseSector + (ulong)si;
+ bool ok = _outputImage.WriteSectorLong(longSectorBufs[si], sector, false, sectorStatuses[si]);
+
+ if(!ok)
+ {
+ if(_force)
+ {
+ ErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_continuing,
+ _outputImage.ErrorMessage,
+ sector));
+ }
+ else
+ {
+ StoppingErrorMessage?.Invoke(string.Format(UI.Error_0_writing_sector_1_not_continuing,
+ _outputImage.ErrorMessage,
+ sector));
+
+ return ErrorNumber.WriteError;
+ }
+ }
+ }
+ }
+
+ return ErrorNumber.NoError;
+ }
+
+ /// Wii sector conversion pipeline for long sectors.
+ ErrorNumber ConvertWiiSectorsLong(ulong discSize, ulong totalLogicalSectors, JunkCollector jc, ref ulong dataSectors, ref ulong junkSectors){
+ // TODO: Implement
+ return ErrorNumber.NoError;
+ }
+
}
\ No newline at end of file
diff --git a/Aaru.Decoders/DVD/Sector.cs b/Aaru.Decoders/DVD/Sector.cs
index c6a858185..56d963e29 100644
--- a/Aaru.Decoders/DVD/Sector.cs
+++ b/Aaru.Decoders/DVD/Sector.cs
@@ -157,6 +157,49 @@ public sealed class Sector
return ret;
}
+ static readonly byte[] DvdGfExp = CreateDvdGfExpTable();
+ static readonly int[] DvdGfLog = CreateDvdGfLogTable(DvdGfExp);
+
+ static byte[] CreateDvdGfExpTable()
+ {
+ const ushort primitive = 0x11D;
+ byte[] exp = new byte[512];
+ exp[0] = 1;
+
+ for(int i = 1; i < 255; i++)
+ {
+ ushort x = (ushort)(exp[i - 1] << 1);
+
+ if((x & 0x100) != 0) x ^= primitive;
+
+ exp[i] = (byte)x;
+ }
+
+ for(int i = 255; i < 512; i++) exp[i] = exp[i - 255];
+
+ return exp;
+ }
+
+ static int[] CreateDvdGfLogTable(byte[] exp)
+ {
+ int[] log = new int[256];
+
+ for(int i = 0; i < 256; i++) log[i] = -1;
+
+ for(int i = 0; i < 255; i++) log[exp[i]] = i;
+
+ log[0] = -1;
+
+ return log;
+ }
+
+ static byte DvdGfMul(byte a, byte b)
+ {
+ if(a == 0 || b == 0) return 0;
+
+ return DvdGfExp[DvdGfLog[a] + DvdGfLog[b]];
+ }
+
///
/// Store seed and its cipher in cache
///
@@ -207,6 +250,57 @@ public sealed class Sector
return true;
}
+ ///
+ /// Computes the 16-bit ID Error Detection (IED) for bytes 0-3 of a DVD ROM sector (ID + 3-byte PSN),
+ /// per the ECMA-267 RS remainder step used in gpsxre::dvd::DataFrame::ID::valid() (redumper).
+ ///
+ /// Buffer of the sector (must be at least 4 bytes; first 4 bytes are the ID field).
+ /// The IED value in the same byte order as stored at sector bytes 4-5 (little-endian uint16 layout).
+ public static ushort ComputeIed(byte[] sectorLong)
+ {
+ if(sectorLong == null || sectorLong.Length < 4) return 0;
+
+ // G(x) = x^2 + g1*x + g2, with g1 = alpha^0 + alpha^1, g2 = alpha^0 * alpha^1 in GF(2^8)
+ byte g1 = (byte)(1 ^ DvdGfExp[1]);
+ byte g2 = DvdGfExp[1];
+
+ Span poly = stackalloc byte[6];
+ poly[0] = sectorLong[0];
+ poly[1] = sectorLong[1];
+ poly[2] = sectorLong[2];
+ poly[3] = sectorLong[3];
+ poly[4] = 0;
+ poly[5] = 0;
+
+ for(int i = 0; i < 4; i++)
+ {
+ byte coef = poly[i];
+
+ if(coef == 0) continue;
+
+ poly[i] = 0;
+ poly[i + 1] ^= DvdGfMul(coef, g1);
+ poly[i + 2] ^= DvdGfMul(coef, g2);
+ }
+
+ return (ushort)(poly[4] | poly[5] << 8);
+ }
+
+ ///
+ /// Check if the IED of a sector is correct (bytes 0-3 vs bytes 4-5, same layout as redumper DataFrame::ID).
+ ///
+ /// Buffer of the sector
+ /// True if IED is correct, False if not
+ public static bool CheckIed(byte[] sectorLong)
+ {
+ if(sectorLong == null || sectorLong.Length < 6) return false;
+
+ ushort computed = ComputeIed(sectorLong);
+ ushort stored = (ushort)(sectorLong[4] | sectorLong[5] << 8);
+
+ return computed == stored;
+ }
+
///
/// Tests if a seed unscrambles a sector correctly
///
diff --git a/Aaru.Decoders/Nintendo/Sector.cs b/Aaru.Decoders/Nintendo/Sector.cs
index 27c7582b6..40e8bc332 100644
--- a/Aaru.Decoders/Nintendo/Sector.cs
+++ b/Aaru.Decoders/Nintendo/Sector.cs
@@ -42,14 +42,22 @@ namespace Aaru.Decoders.Nintendo;
public sealed class Sector
{
///
- /// ECMA-267 main_data offset in OmniDrive 2064-byte Nintendo sectors: DVD XOR applies to 2048 bytes from
- /// here (same as standard DVD). Bytes 6-11 (cpr_mai) are not scrambled on media.
+ /// Start of the 2048-byte DVD XOR (scramble) region in a 2064-byte Nintendo sector — same as ECMA-267
+ /// main_data for a standard DVD sector. Nintendo still applies the table to these 2048 bytes.
///
- public const int NintendoMainDataOffset = 12;
+ public const int NintendoScrambledDataOffset = 12;
///
- /// Derives the Nintendo descramble key from the first 8 bytes of the cpr_mai region (LBA 0 payload).
- /// Used when software-descrambling Nintendo sectors.
+ /// Start of the 2048-byte logical main_data exposed to the game / filesystem in Nintendo GameCube/Wii DVD
+ /// sectors. Unlike ECMA-267 (where main_data begins at byte 12), Nintendo uses byte 6; CPR_MAI and related
+ /// fields follow a different layout than on a standard DVD-ROM. The DVD XOR layer still scrambles 2048 bytes
+ /// starting at ; bytes 6–11 are not part of that scrambled block.
+ ///
+ public const int NintendoMainDataOffset = 6;
+
+ ///
+ /// Derives the Nintendo descramble key from the first 8 bytes at in the
+ /// LBA 0 sector after descramble (same 8 bytes used for key derivation in the drive/firmware path).
///
public static byte DeriveNintendoKey(byte[] cprMaiFirst8)
{
@@ -76,7 +84,7 @@ public sealed class Sector
if(sector is not { Length: 2064 }) return ErrorNumber.NotSupported;
int psn = DVD.Sector.GetPsn(sector);
- int mainDataStart = NintendoMainDataOffset;
+ int mainDataStart = NintendoScrambledDataOffset;
int tableOffset = (int)((nintendoKey ^ (psn >> 4 & 0xF)) * DVD.Sector.Form1DataSize +
7 * DVD.Sector.Form1DataSize + DVD.Sector.Form1DataSize / 2);
diff --git a/Aaru.Decryption/Aaru.Decryption.csproj b/Aaru.Decryption/Aaru.Decryption.csproj
index d09a8596d..6285b9124 100644
--- a/Aaru.Decryption/Aaru.Decryption.csproj
+++ b/Aaru.Decryption/Aaru.Decryption.csproj
@@ -8,7 +8,7 @@
Aaru.Decryption
Decryption algorithms used by the Aaru Data Preservation Suite.
https://github.com/aaru-dps/
- MIT
+ (MIT AND GPL-3.0-only)
https://github.com/aaru-dps/Aaru.Decryption
true
en-US
@@ -32,9 +32,10 @@
+
-
+
diff --git a/Aaru.Core/Image/Convert/Ngcw/Crypto.cs b/Aaru.Decryption/Ngcw/Crypto.cs
similarity index 97%
rename from Aaru.Core/Image/Convert/Ngcw/Crypto.cs
rename to Aaru.Decryption/Ngcw/Crypto.cs
index 2cce0cf4d..4ef08ed78 100644
--- a/Aaru.Core/Image/Convert/Ngcw/Crypto.cs
+++ b/Aaru.Decryption/Ngcw/Crypto.cs
@@ -5,7 +5,7 @@
// Filename : Crypto.cs
// Author(s) : Natalia Portillo
//
-// Component : Image conversion.
+// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
//
// --[ Description ] ----------------------------------------------------------
//
@@ -34,10 +34,10 @@
using System;
using System.Security.Cryptography;
-namespace Aaru.Core.Image.Ngcw;
+namespace Aaru.Decryption.Ngcw;
/// Wii disc encryption helpers.
-static class Crypto
+public static class Crypto
{
/// Wii physical group size (32 KiB).
public const int GROUP_SIZE = 0x8000;
diff --git a/Aaru.Core/Image/Convert/Ngcw/DataMap.cs b/Aaru.Decryption/Ngcw/DataMap.cs
similarity index 96%
rename from Aaru.Core/Image/Convert/Ngcw/DataMap.cs
rename to Aaru.Decryption/Ngcw/DataMap.cs
index c3683100b..a1726016a 100644
--- a/Aaru.Core/Image/Convert/Ngcw/DataMap.cs
+++ b/Aaru.Decryption/Ngcw/DataMap.cs
@@ -5,7 +5,7 @@
// Filename : DataMap.cs
// Author(s) : Natalia Portillo
//
-// Component : Image conversion.
+// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
//
// --[ Description ] ----------------------------------------------------------
//
@@ -34,10 +34,10 @@ using System;
using System.Collections.Generic;
using Aaru.Helpers;
-namespace Aaru.Core.Image.Ngcw;
+namespace Aaru.Decryption.Ngcw;
/// A contiguous region of file data on disc.
-readonly struct DataRegion : IComparable
+public readonly struct DataRegion : IComparable
{
public readonly ulong Offset;
public readonly ulong Length;
@@ -55,7 +55,7 @@ readonly struct DataRegion : IComparable
/// Sorted map of file data regions parsed from a Nintendo GameCube/Wii FST.
/// Used to classify disc sectors as data (file content / system area) or potential junk.
///
-static class DataMap
+public static class DataMap
{
///
/// Build a data region map from an FST (File System Table).
diff --git a/Aaru.Core/Image/Convert/Ngcw/Junk.cs b/Aaru.Decryption/Ngcw/Junk.cs
similarity index 98%
rename from Aaru.Core/Image/Convert/Ngcw/Junk.cs
rename to Aaru.Decryption/Ngcw/Junk.cs
index 6116f6371..8677edaed 100644
--- a/Aaru.Core/Image/Convert/Ngcw/Junk.cs
+++ b/Aaru.Decryption/Ngcw/Junk.cs
@@ -5,7 +5,7 @@
// Filename : Junk.cs
// Author(s) : Natalia Portillo
//
-// Component : Image conversion.
+// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
//
// --[ Description ] ----------------------------------------------------------
//
@@ -34,10 +34,10 @@ using System;
using System.Collections.Generic;
using Aaru.CommonTypes.Enums;
-namespace Aaru.Core.Image.Ngcw;
+namespace Aaru.Decryption.Ngcw;
/// In-memory junk map entry.
-struct JunkEntry
+public struct JunkEntry
{
/// Disc byte offset where junk starts.
public ulong Offset;
@@ -55,7 +55,7 @@ struct JunkEntry
///
/// Collects junk entries during conversion, merging contiguous entries with the same seed.
///
-sealed class JunkCollector
+public sealed class JunkCollector
{
public int Count => Entries.Count;
@@ -110,7 +110,7 @@ sealed class JunkCollector
///
/// Junk map serialization/deserialization and block-level junk detection.
///
-static class Junk
+public static class Junk
{
const ushort JUNK_MAP_VERSION = 1;
const int JUNK_MAP_HEADER = 8; // version(2) + count(4) + seed_size(2)
diff --git a/Aaru.Core/Image/Convert/Ngcw/Lfg.cs b/Aaru.Decryption/Ngcw/Lfg.cs
similarity index 98%
rename from Aaru.Core/Image/Convert/Ngcw/Lfg.cs
rename to Aaru.Decryption/Ngcw/Lfg.cs
index 5ff29f212..267ff6c5b 100644
--- a/Aaru.Core/Image/Convert/Ngcw/Lfg.cs
+++ b/Aaru.Decryption/Ngcw/Lfg.cs
@@ -5,7 +5,7 @@
// Filename : Lfg.cs
// Author(s) : Natalia Portillo
//
-// Component : Image conversion.
+// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
//
// --[ Description ] ----------------------------------------------------------
//
@@ -35,10 +35,10 @@ using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
-namespace Aaru.Core.Image.Ngcw;
+namespace Aaru.Decryption.Ngcw;
/// Lagged Fibonacci Generator for Nintendo GameCube/Wii junk fill.
-static class Lfg
+public static class Lfg
{
/// LFG buffer size (number of uint32 words in state).
const int K = 521;
diff --git a/Aaru.Core/Image/Convert/Ngcw/Partitions.cs b/Aaru.Decryption/Ngcw/Partitions.cs
similarity index 97%
rename from Aaru.Core/Image/Convert/Ngcw/Partitions.cs
rename to Aaru.Decryption/Ngcw/Partitions.cs
index 460cdd42f..ce3187968 100644
--- a/Aaru.Core/Image/Convert/Ngcw/Partitions.cs
+++ b/Aaru.Decryption/Ngcw/Partitions.cs
@@ -5,7 +5,7 @@
// Filename : Partitions.cs
// Author(s) : Natalia Portillo
//
-// Component : Image conversion.
+// Component : Aaru.Decryption.Ngcw (GPL-3.0-or-later).
//
// --[ Description ] ----------------------------------------------------------
//
@@ -36,10 +36,10 @@ using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Helpers;
-namespace Aaru.Core.Image.Ngcw;
+namespace Aaru.Decryption.Ngcw;
/// In-memory representation of a Wii partition.
-struct WiiPartition
+public struct WiiPartition
{
/// Partition offset on disc.
public ulong Offset;
@@ -58,7 +58,7 @@ struct WiiPartition
}
/// Wii partition region for the partition key map.
-struct WiiPartitionRegion
+public struct WiiPartitionRegion
{
/// First physical sector (0x8000-byte units).
public uint StartSector;
@@ -73,7 +73,7 @@ struct WiiPartitionRegion
///
/// Wii partition table parsing and key map serialization.
///
-static class Partitions
+public static class Partitions
{
///
/// Parse the Wii partition table from a source image, extracting all partitions
diff --git a/Aaru.Images/Aaru.Images.csproj b/Aaru.Images/Aaru.Images.csproj
index ff1493067..12b18caa4 100644
--- a/Aaru.Images/Aaru.Images.csproj
+++ b/Aaru.Images/Aaru.Images.csproj
@@ -1,4 +1,4 @@
-
+
2.0
@@ -50,6 +50,7 @@
+
diff --git a/Aaru.Images/Localization/Localization.Designer.cs b/Aaru.Images/Localization/Localization.Designer.cs
index 305b8dfa4..f36992867 100644
--- a/Aaru.Images/Localization/Localization.Designer.cs
+++ b/Aaru.Images/Localization/Localization.Designer.cs
@@ -1,4 +1,4 @@
-//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
//
// This code was generated by a tool.
//
@@ -5937,5 +5937,17 @@ namespace Aaru.Images {
return ResourceManager.GetString("WinOnCD_disc_image", resourceCulture);
}
}
+
+ internal static string Redumper_Name {
+ get {
+ return ResourceManager.GetString("Redumper_Name", resourceCulture);
+ }
+ }
+
+ internal static string Redumper_disc_image {
+ get {
+ return ResourceManager.GetString("Redumper_disc_image", resourceCulture);
+ }
+ }
}
}
diff --git a/Aaru.Images/Localization/Localization.es.resx b/Aaru.Images/Localization/Localization.es.resx
index 84f6bf467..0504c5d39 100644
--- a/Aaru.Images/Localization/Localization.es.resx
+++ b/Aaru.Images/Localization/Localization.es.resx
@@ -2970,4 +2970,10 @@
Imagen de disco de WinOnCD
+
+ Volcado DVD crudo de Redumper
+
+
+ Imagen de disco DVD crudo de Redumper
+
\ No newline at end of file
diff --git a/Aaru.Images/Localization/Localization.resx b/Aaru.Images/Localization/Localization.resx
index 3bf5aecaa..a9e6434d1 100644
--- a/Aaru.Images/Localization/Localization.resx
+++ b/Aaru.Images/Localization/Localization.resx
@@ -2980,4 +2980,10 @@
WinOnCD disc image
+
+ Redumper raw DVD dump
+
+
+ Redumper raw DVD disc image
+
\ No newline at end of file
diff --git a/Aaru.Images/Redumper/Identify.cs b/Aaru.Images/Redumper/Identify.cs
new file mode 100644
index 000000000..8532661b9
--- /dev/null
+++ b/Aaru.Images/Redumper/Identify.cs
@@ -0,0 +1,71 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Identify.cs
+// Author(s) : Rebecca Wallander
+//
+// Component : Disc image plugins.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Identifies Redumper raw DVD dump images.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2026 Rebecca Wallander
+// ****************************************************************************/
+
+using System.IO;
+using Aaru.CommonTypes.Interfaces;
+
+namespace Aaru.Images;
+
+public sealed partial class Redumper
+{
+#region IOpticalMediaImage Members
+
+ ///
+ public bool Identify(IFilter imageFilter)
+ {
+ string filename = imageFilter.Filename;
+
+ if(string.IsNullOrEmpty(filename)) return false;
+
+ string extension = Path.GetExtension(filename)?.ToLower();
+
+ if(extension != ".state") return false;
+
+ string basePath = filename[..^".state".Length];
+ string sdramPath = basePath + ".sdram";
+
+ if(!File.Exists(sdramPath)) return false;
+
+ long stateLength = imageFilter.DataForkLength;
+ long sdramLength = new FileInfo(sdramPath).Length;
+
+ if(sdramLength == 0 || stateLength == 0) return false;
+
+ if(sdramLength % RECORDING_FRAME_SIZE != 0) return false;
+
+ long frameCount = sdramLength / RECORDING_FRAME_SIZE;
+
+ return stateLength == frameCount;
+ }
+
+#endregion
+}
\ No newline at end of file
diff --git a/Aaru.Images/Redumper/Ngcw.cs b/Aaru.Images/Redumper/Ngcw.cs
new file mode 100644
index 000000000..9def68195
--- /dev/null
+++ b/Aaru.Images/Redumper/Ngcw.cs
@@ -0,0 +1,151 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Ngcw.cs
+// Author(s) : Rebecca Wallander
+//
+// Component : Disc image plugins (Redumper Nintendo GOD/WOD).
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Nintendo DVD descrambling for GameCube/Wii Redumper dumps. Produces
+// 2064-byte long sectors (and 2048-byte user via ReadSector) matching a
+// raw dump after the Nintendo layer: Wii AES partition data remains
+// ciphertext in user sectors until conversion (see ConvertNgcwSectors).
+// Junk maps, partition key tags, and Wii decrypt are handled when
+// converting to AaruFormat, not when reading this plugin.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2026 Rebecca Wallander
+// ****************************************************************************/
+
+using System;
+using Aaru.CommonTypes;
+using Aaru.CommonTypes.Enums;
+using Aaru.Decoders.Nintendo;
+
+namespace Aaru.Images;
+
+public sealed partial class Redumper
+{
+ const int NGCW_LONG_SECTOR_SIZE = 2064;
+ const int NGCW_SECTORS_PER_GROUP = 16;
+
+ static bool IsNintendoMediaType(MediaType mt) => mt is MediaType.GOD or MediaType.WOD;
+
+ ///
+ /// Derives the Nintendo disc key from LBA 0 so sectors 16+ can be descrambled.
+ /// Does not parse partitions, junk, or decrypt Wii groups — conversion does that.
+ ///
+ void TryInitializeNgcwAfterOpen()
+ {
+ _nintendoDerivedKey = null;
+
+ if(!IsNintendoMediaType(_imageInfo.MediaType)) return;
+
+ EnsureNintendoDerivedKeyFromLba0();
+ }
+
+ ///
+ /// Derives the Nintendo key from LBA 0 so sectors 16+ can be descrambled.
+ ///
+ /// True if the Nintendo key was derived successfully, False if not
+ bool EnsureNintendoDerivedKeyFromLba0()
+ {
+ ErrorNumber errno = ReadSectorLongForNgcw(0, false, out byte[] long0, out _);
+
+ return errno == ErrorNumber.NoError && long0 != null && long0.Length >= NGCW_LONG_SECTOR_SIZE;
+ }
+
+ ///
+ /// Reads a sector long for Nintendo descrambling.
+ ///
+ /// The sector address to read
+ /// Whether the sector address is negative
+ /// The buffer to read the sector into
+ /// The status of the sector
+ /// The error number
+ ///
+ /// This method is used to read a sector long for Nintendo descrambling.
+ /// It is used to read the sector long for LBA 0 to derive the Nintendo key.
+ ///
+ ErrorNumber ReadSectorLongForNgcw(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus)
+ {
+ buffer = null;
+ sectorStatus = SectorStatus.NotDumped;
+
+ int lba = negative ? -(int)sectorAddress : (int)sectorAddress;
+
+ long frameIndex = (long)lba - LBA_START;
+
+ if(frameIndex < 0 || frameIndex >= _totalFrames) return ErrorNumber.OutOfRange;
+
+ sectorStatus = MapState(_stateData[frameIndex]);
+
+ byte[] dvdSector = ReadAndFlattenFrame(frameIndex);
+
+ if(dvdSector is null) return ErrorNumber.InvalidArgument;
+
+ if(sectorStatus != SectorStatus.Dumped)
+ {
+ buffer = dvdSector;
+
+ return ErrorNumber.NoError;
+ }
+
+ if(!IsNintendoMediaType(_imageInfo.MediaType))
+ {
+ ErrorNumber error = _decoding.Scramble(dvdSector, out byte[] descrambled);
+
+ buffer = error == ErrorNumber.NoError ? descrambled : dvdSector;
+
+ return ErrorNumber.NoError;
+ }
+
+ if(!DescrambleNintendo2064InPlace(dvdSector, lba))
+ {
+ buffer = dvdSector;
+
+ return ErrorNumber.NoError;
+ }
+
+ buffer = dvdSector;
+
+ return ErrorNumber.NoError;
+ }
+
+ bool DescrambleNintendo2064InPlace(byte[] buffer, int lba)
+ {
+ byte[] one = new byte[NGCW_LONG_SECTOR_SIZE];
+ Array.Copy(buffer, 0, one, 0, NGCW_LONG_SECTOR_SIZE);
+ byte key = lba < NGCW_SECTORS_PER_GROUP ? (byte)0 : (_nintendoDerivedKey ?? (byte)0);
+
+ ErrorNumber error = _nintendoDecoder.Scramble(one, key, out byte[] decoded);
+
+ if(error != ErrorNumber.NoError)
+ {
+ Array.Clear(buffer, 0, NGCW_LONG_SECTOR_SIZE);
+
+ return false;
+ }
+
+ if(decoded != null) Array.Copy(decoded, 0, buffer, 0, NGCW_LONG_SECTOR_SIZE);
+
+ if(lba == 0 && decoded != null)
+ {
+ byte[] keyMaterial = new byte[8];
+ Array.Copy(decoded, Sector.NintendoMainDataOffset, keyMaterial, 0, 8);
+ _nintendoDerivedKey = Sector.DeriveNintendoKey(keyMaterial);
+ }
+
+ return true;
+ }
+}
diff --git a/Aaru.Images/Redumper/Properties.cs b/Aaru.Images/Redumper/Properties.cs
new file mode 100644
index 000000000..5e926f3bc
--- /dev/null
+++ b/Aaru.Images/Redumper/Properties.cs
@@ -0,0 +1,120 @@
+// /***************************************************************************
+// Aaru Data Preservation Suite
+// ----------------------------------------------------------------------------
+//
+// Filename : Properties.cs
+// Author(s) : Rebecca Wallander
+//
+// Component : Disc image plugins.
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Contains properties for Redumper raw DVD dump images.
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright © 2011-2026 Rebecca Wallander
+// ****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using Aaru.CommonTypes;
+using Aaru.CommonTypes.AaruMetadata;
+using Aaru.CommonTypes.Enums;
+using Aaru.CommonTypes.Structs;
+using Partition = Aaru.CommonTypes.Partition;
+using Track = Aaru.CommonTypes.Structs.Track;
+using Session = Aaru.CommonTypes.Structs.Session;
+
+namespace Aaru.Images;
+
+public sealed partial class Redumper
+{
+ #region IOpticalMediaImage Members
+
+ ///
+ // ReSharper disable once ConvertToAutoProperty
+ public ImageInfo Info => _imageInfo;
+
+ ///
+ public string Name => Localization.Redumper_Name;
+
+ ///
+ public Guid Id => new("F2D3E4A5-B6C7-4D8E-9F0A-1B2C3D4E5F60");
+
+ ///
+ public string Author => Authors.RebeccaWallander;
+
+ ///
+ public string Format => Localization.Redumper_disc_image;
+
+ ///
+ public List Partitions { get; private set; }
+
+ ///
+ public List