NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsModcryptTransform.cs
1using System.Security.Cryptography;
2
3#pragma warning disable CA5358 // CTR mode requires raw AES block encryption; ECB is not used to encrypt payload blocks.
4
5namespace NdsForge;
6
8internal sealed class NdsModcryptTransform : IDisposable
9{
11 private readonly Aes _aes;
13 private readonly ICryptoTransform _encryptor;
15 private readonly byte[] _aesKey;
17 private readonly byte[] _counter;
19 private readonly byte[] _aesCounter = new byte[NdsModcrypt.BlockSize];
21 private readonly byte[] _keyStream = new byte[NdsModcrypt.BlockSize];
23 private int _keyStreamIndex = NdsModcrypt.BlockSize;
25 private bool _disposed;
26
31 internal NdsModcryptTransform(ReadOnlySpan<byte> key, ReadOnlySpan<byte> initialCounter, long byteOffset)
32 {
33 _aes = Aes.Create();
34 _aes.Mode = CipherMode.ECB;
35 _aes.Padding = PaddingMode.None;
36 _aesKey = key.ToArray();
37 Array.Reverse(_aesKey);
38 _aes.Key = _aesKey;
39 _encryptor = _aes.CreateEncryptor();
40 _counter = initialCounter.ToArray();
41 AddBlocks(_counter, (ulong)(byteOffset / NdsModcrypt.BlockSize));
42 int skip = (int)(byteOffset % NdsModcrypt.BlockSize);
43 if (skip != 0)
44 {
45 GenerateKeyStream();
46 _keyStreamIndex = skip;
47 }
48 }
49
53 internal void Transform(ReadOnlySpan<byte> source, Span<byte> destination)
54 {
55 ObjectDisposedException.ThrowIf(_disposed, this);
56 if (destination.Length < source.Length)
57 {
58 throw new ArgumentException("The modcrypt destination is shorter than the source.", nameof(destination));
59 }
60
61 for (int index = 0; index < source.Length; index++)
62 {
63 if (_keyStreamIndex == NdsModcrypt.BlockSize)
64 {
65 GenerateKeyStream();
66 _keyStreamIndex = 0;
67 }
68
69 destination[index] = (byte)(source[index] ^ _keyStream[_keyStreamIndex++]);
70 }
71 }
72
74 public void Dispose()
75 {
76 if (_disposed)
77 {
78 return;
79 }
80
81 _encryptor.Dispose();
82 _aes.Dispose();
83 CryptographicOperations.ZeroMemory(_aesKey);
84 CryptographicOperations.ZeroMemory(_counter);
85 CryptographicOperations.ZeroMemory(_aesCounter);
86 CryptographicOperations.ZeroMemory(_keyStream);
87 _disposed = true;
88 }
89
91 private void GenerateKeyStream()
92 {
93 for (int index = 0; index < _counter.Length; index++)
94 {
95 _aesCounter[index] = _counter[^(index + 1)];
96 }
97
98 _ = _encryptor.TransformBlock(_aesCounter, 0, _aesCounter.Length, _keyStream, 0);
99 Array.Reverse(_keyStream);
100 Increment(_counter);
101 }
102
106 private static void AddBlocks(Span<byte> counter, ulong blocks)
107 {
108 ulong carry = blocks;
109 for (int index = 0; index < counter.Length && carry != 0; index++)
110 {
111 ulong sum = counter[index] + (carry & 0xFF);
112 counter[index] = (byte)sum;
113 carry = (carry >> 8) + (sum >> 8);
114 }
115
116 if (carry != 0)
117 {
118 throw new ArgumentOutOfRangeException(nameof(blocks), "The byte offset overflows the 128-bit modcrypt counter.");
119 }
120 }
121
124 private static void Increment(Span<byte> counter)
125 {
126 for (int index = 0; index < counter.Length; index++)
127 {
128 counter[index]++;
129 if (counter[index] != 0)
130 {
131 return;
132 }
133 }
134 }
135}
136
137#pragma warning restore CA5358