1using System.Buffers.Binary;
23 ArgumentNullException.ThrowIfNull(image);
30 if (image.
Length < region.End)
36 using Stream stream = image.
OpenRead(region);
37 stream.ReadExactly(data);
48 ReadOnlySpan<byte> area,
54 uint first = BinaryPrimitives.ReadUInt32LittleEndian(area);
55 uint second = BinaryPrimitives.ReadUInt32LittleEndian(area[4..]);
57 if (first == 0 && second == 0)
62 if (first == NdsKey1Cipher.DestroyedId && second == NdsKey1Cipher.DestroyedId)
64 ushort? calculated = keyTable is
null
70 if (keyTable is not
null)
74 _ = NdsKey1Cipher.Decrypt(area, ParseGameCode(gameCode), keyTable);
81 catch (InvalidDataException)
101 ArgumentNullException.ThrowIfNull(keyTable);
102 return NdsKey1Cipher.Encrypt(area, ParseGameCode(gameCode), keyTable);
112 ArgumentNullException.ThrowIfNull(keyTable);
113 return NdsKey1Cipher.Decrypt(area, ParseGameCode(gameCode), keyTable);
132 CancellationToken cancellationToken =
default) =>
133 TransformAsync(source, destination, gameCode, keyTable, encrypt:
true, cancellationToken);
150 CancellationToken cancellationToken =
default) =>
151 TransformAsync(source, destination, gameCode, keyTable, encrypt:
false, cancellationToken);
160 private static async ValueTask TransformAsync(
166 CancellationToken cancellationToken)
168 ArgumentNullException.ThrowIfNull(source);
169 ArgumentNullException.ThrowIfNull(destination);
172 throw new ArgumentException(
"The secure-area source must be readable.", nameof(source));
175 if (!destination.CanWrite)
177 throw new ArgumentException(
"The secure-area destination must be writable.", nameof(destination));
181 await source.ReadExactlyAsync(input, cancellationToken).ConfigureAwait(
false);
182 byte[] output = encrypt
183 ?
Encrypt(input, gameCode, keyTable)
184 :
Decrypt(input, gameCode, keyTable);
185 await destination.WriteAsync(output, cancellationToken).ConfigureAwait(
false);
191 private static uint ParseGameCode(
string gameCode)
193 ArgumentNullException.ThrowIfNull(gameCode);
194 if (gameCode.Length != 4 || gameCode.Any(
static value => value is <
' ' or >
'~'))
196 throw new ArgumentException(
"A KEY1 game code must contain exactly four printable ASCII characters.", nameof(gameCode));
199 Span<byte> bytes = stackalloc
byte[4];
200 Encoding.ASCII.GetBytes(gameCode, bytes);
201 return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
205 private static void ValidateArea(ReadOnlySpan<byte> area)
209 throw new ArgumentException($
"A secure area must contain exactly 0x{ByteLength:X} bytes.", nameof(area));
Calculates checksums used by Nintendo DS image structures.
static ushort ComputeCrc16(ReadOnlySpan< byte > data, ushort seed=ushort.MaxValue)
Calculates the CRC-16 used by Nintendo DS headers and banners.
Provides structured, random-access inspection of a Nintendo DS-family image.
long Length
Reports physical source bytes, which may exceed the header's used-ROM size because cartridges are cap...
NdsHeader Header
Preserves both typed DS/DSi fields and the raw bytes required for checksums and lossless edits.
Stream OpenRead(NdsRegion region)
Opens a read-only stream over a validated image region.
Holds the 18 round words and four 256-word substitution boxes consumed by the DS KEY1 algorithm....
Identifies a bounded range of bytes in an image.
Reports secure-area presence, encryption state, and checksum evidence without modifying image bytes.
Provides pure inspection and KEY1 transformations for the conventional 0x4000-0x7FFF cartridge interv...
static byte[] Encrypt(ReadOnlySpan< byte > area, string gameCode, NdsKey1KeyTable keyTable)
Encrypts a decrypted interval for the supplied product identity and returns an independent copy.
static ValueTask DecryptAsync(Stream source, Stream destination, string gameCode, NdsKey1KeyTable keyTable, CancellationToken cancellationToken=default)
Reads one encrypted interval and emits a verified decrypted copy without closing caller-owned streams...
static byte[] Decrypt(ReadOnlySpan< byte > area, string gameCode, NdsKey1KeyTable keyTable)
Decrypts a key-verifiable interval and replaces its secure identifier with conventional destroyed mar...
const int ByteLength
CRC-covered interval length; KEY1 itself transforms only the first 0x800 bytes.
static NdsSecureAreaInspection Inspect(NdsImage image, NdsKey1KeyTable? keyTable=null)
Inspects a loaded image and verifies encrypted state or CRC only when enough explicit key material ex...
static NdsSecureAreaInspection Inspect(ReadOnlySpan< byte > area, string gameCode, ushort storedCrc, NdsKey1KeyTable? keyTable=null)
Classifies one isolated interval using caller-supplied identity and stored checksum context.
const int Offset
Absolute image offset at which the cartridge security interval begins.
static ValueTask EncryptAsync(Stream source, Stream destination, string gameCode, NdsKey1KeyTable keyTable, CancellationToken cancellationToken=default)
Reads one interval at the source's current position, encrypts it, and writes exactly 16 KiB without c...
NdsSecureAreaState
Classifies the cartridge security interval without conflating encrypted bytes with malformed metadata...