NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsModcrypt.cs
1using System.Buffers;
2
3namespace NdsForge;
4
9public static class NdsModcrypt
10{
12 public const int BlockSize = 16;
14 private const int StreamBufferSize = 64 * 1024;
15
22 public static byte[] Transform(
23 ReadOnlySpan<byte> data,
24 NdsModcryptContext context,
25 NdsModcryptArea area,
26 long byteOffset = 0)
27 {
28 ArgumentNullException.ThrowIfNull(context);
29 return Transform(data, context.Key.Span, context.GetCounter(area).Span, byteOffset);
30 }
31
38 public static byte[] Transform(
39 ReadOnlySpan<byte> data,
40 ReadOnlySpan<byte> key,
41 ReadOnlySpan<byte> initialCounter,
42 long byteOffset = 0)
43 {
44 ValidateArguments(key, initialCounter, byteOffset);
45 byte[] output = new byte[data.Length];
46 using var transform = new NdsModcryptTransform(key, initialCounter, byteOffset);
47 transform.Transform(data, output);
48 return output;
49 }
50
63 public static async ValueTask TransformAsync(
64 Stream source,
65 Stream destination,
66 long length,
67 NdsModcryptContext context,
68 NdsModcryptArea area,
69 long byteOffset = 0,
70 CancellationToken cancellationToken = default)
71 {
72 ArgumentNullException.ThrowIfNull(context);
73 await TransformAsync(
74 source,
75 destination,
76 length,
77 context.Key,
78 context.GetCounter(area),
79 byteOffset,
80 cancellationToken).ConfigureAwait(false);
81 }
82
95 public static async ValueTask TransformAsync(
96 Stream source,
97 Stream destination,
98 long length,
99 ReadOnlyMemory<byte> key,
100 ReadOnlyMemory<byte> initialCounter,
101 long byteOffset = 0,
102 CancellationToken cancellationToken = default)
103 {
104 ArgumentNullException.ThrowIfNull(source);
105 ArgumentNullException.ThrowIfNull(destination);
106 if (!source.CanRead)
107 {
108 throw new ArgumentException("The modcrypt source must be readable.", nameof(source));
109 }
110
111 if (!destination.CanWrite)
112 {
113 throw new ArgumentException("The modcrypt destination must be writable.", nameof(destination));
114 }
115
116 if (ReferenceEquals(source, destination))
117 {
118 throw new ArgumentException("Streaming modcrypt requires distinct source and destination streams.", nameof(destination));
119 }
120
121 if (length < 0)
122 {
123 throw new ArgumentOutOfRangeException(nameof(length), "The transform length cannot be negative.");
124 }
125
126 ValidateArguments(key.Span, initialCounter.Span, byteOffset);
127 byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamBufferSize);
128 try
129 {
130 using var transform = new NdsModcryptTransform(key.Span, initialCounter.Span, byteOffset);
131 long remaining = length;
132 while (remaining != 0)
133 {
134 cancellationToken.ThrowIfCancellationRequested();
135 int count = (int)Math.Min(remaining, StreamBufferSize);
136 await source.ReadExactlyAsync(buffer.AsMemory(0, count), cancellationToken).ConfigureAwait(false);
137 transform.Transform(buffer.AsSpan(0, count), buffer.AsSpan(0, count));
138 await destination.WriteAsync(buffer.AsMemory(0, count), cancellationToken).ConfigureAwait(false);
139 remaining -= count;
140 }
141 }
142 finally
143 {
144 ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
145 }
146 }
147
152 private static void ValidateArguments(ReadOnlySpan<byte> key, ReadOnlySpan<byte> initialCounter, long byteOffset)
153 {
154 if (key.Length != BlockSize)
155 {
156 throw new ArgumentException("A modcrypt AES key must contain exactly sixteen bytes.", nameof(key));
157 }
158
159 if (initialCounter.Length != BlockSize)
160 {
161 throw new ArgumentException("A modcrypt initial counter must contain exactly sixteen bytes.", nameof(initialCounter));
162 }
163
164 if (byteOffset < 0)
165 {
166 throw new ArgumentOutOfRangeException(nameof(byteOffset), "A modcrypt slice offset cannot be negative.");
167 }
168 }
169}
Captures the AES-128 normal key and both HMAC-derived initial counters needed to transform DSi modcry...
Applies the DSi AES-CTR transform with little-endian counter advancement. Encryption and decryption a...
static byte[] Transform(ReadOnlySpan< byte > data, ReadOnlySpan< byte > key, ReadOnlySpan< byte > initialCounter, long byteOffset=0)
Transforms an independent buffer using explicit AES normal-key and initial-counter bytes.
static byte[] Transform(ReadOnlySpan< byte > data, NdsModcryptContext context, NdsModcryptArea area, long byteOffset=0)
Transforms an independent buffer using one area from an immutable header-derived context.
const int BlockSize
Defines the AES-128 key, counter, and keystream block width imposed by the cartridge format.
static async ValueTask TransformAsync(Stream source, Stream destination, long length, ReadOnlyMemory< byte > key, ReadOnlyMemory< byte > initialCounter, long byteOffset=0, CancellationToken cancellationToken=default)
Transforms an exact stream interval with explicit normal-key and counter material....
static async ValueTask TransformAsync(Stream source, Stream destination, long length, NdsModcryptContext context, NdsModcryptArea area, long byteOffset=0, CancellationToken cancellationToken=default)
Transforms exactly length bytes without closing either stream. If a late source truncation occurs,...
NdsModcryptArea
Identifies which DSi modcrypt interval and HMAC-derived initial counter a transformation uses.