NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsKey1Cipher.cs
1using System.Buffers.Binary;
2
3namespace NdsForge;
4
6internal static class NdsKey1Cipher
7{
9 public const uint DestroyedId = 0xE7FFDEFF;
11 private const uint SecureId0 = 0x72636E65;
13 private const uint SecureId1 = 0x6A624F79;
14
20 public static byte[] Encrypt(ReadOnlySpan<byte> area, uint gameCode, NdsKey1KeyTable keyTable)
21 {
22 ValidateArea(area);
23 if (ReadWord(area, 0) != DestroyedId || ReadWord(area, 4) != DestroyedId)
24 {
25 throw new InvalidDataException("Decrypted secure-area bytes must begin with two 0xE7FFDEFF marker words.");
26 }
27
28 byte[] output = area.ToArray();
29 (uint[] words, uint[] argument) = InitializeLevel1(keyTable, gameCode);
30 argument[1] = unchecked(argument[1] << 1);
31 argument[2] >>= 1;
32 InitializeLevel2(words, argument);
33 for (int offset = 8; offset < 0x800; offset += 8)
34 {
35 EncryptStoredBlock(words, output, offset);
36 }
37
38 WriteWord(output, 0, SecureId0);
39 WriteWord(output, 4, SecureId1);
40 EncryptStoredBlock(words, output, 0);
41 (words, _) = InitializeLevel1(keyTable, gameCode);
42 EncryptStoredBlock(words, output, 0);
43 return output;
44 }
45
51 public static byte[] Decrypt(ReadOnlySpan<byte> area, uint gameCode, NdsKey1KeyTable keyTable)
52 {
53 ValidateArea(area);
54 byte[] output = area.ToArray();
55 (uint[] words, uint[] argument) = InitializeLevel1(keyTable, gameCode);
56 DecryptStoredBlock(words, output, 0);
57 argument[1] = unchecked(argument[1] << 1);
58 argument[2] >>= 1;
59 InitializeLevel2(words, argument);
60 DecryptStoredBlock(words, output, 0);
61 if (ReadWord(output, 0) != SecureId0 || ReadWord(output, 4) != SecureId1)
62 {
63 throw new InvalidDataException("The supplied KEY1 table and game code do not recover a secure-area identifier.");
64 }
65
66 WriteWord(output, 0, DestroyedId);
67 WriteWord(output, 4, DestroyedId);
68 for (int offset = 8; offset < 0x800; offset += 8)
69 {
70 DecryptStoredBlock(words, output, offset);
71 }
72
73 return output;
74 }
75
80 private static (uint[] Words, uint[] Argument) InitializeLevel1(NdsKey1KeyTable keyTable, uint gameCode)
81 {
82 uint[] words = keyTable.CreateWorkingWords();
83 uint[] argument = [gameCode, gameCode >> 1, unchecked(gameCode << 1)];
84 InitializeLevel2(words, argument);
85 InitializeLevel2(words, argument);
86 return (words, argument);
87 }
88
92 private static void InitializeLevel2(uint[] words, uint[] argument)
93 {
94 EncryptBlock(words, ref argument[2], ref argument[1]);
95 EncryptBlock(words, ref argument[1], ref argument[0]);
96 UpdateSchedule(words, argument);
97 }
98
102 private static void UpdateSchedule(uint[] words, uint[] argument)
103 {
104 Span<byte> argumentBytes = stackalloc byte[12];
105 for (int index = 0; index < argument.Length; index++)
106 {
107 BinaryPrimitives.WriteUInt32LittleEndian(argumentBytes[(index * 4)..], argument[index]);
108 }
109
110 for (int round = 0; round < 18; round++)
111 {
112 uint value = 0;
113 for (int index = 0; index < 4; index++)
114 {
115 value = unchecked((value << 8) | argumentBytes[((round * 4) + index) & 7]);
116 }
117
118 words[round] ^= value;
119 }
120
121 uint first = 0;
122 uint second = 0;
123 for (int index = 0; index < words.Length; index += 2)
124 {
125 EncryptBlock(words, ref first, ref second);
126 words[index] = first;
127 words[index + 1] = second;
128 }
129 }
130
135 private static void EncryptBlock(uint[] words, ref uint first, ref uint second)
136 {
137 uint a = first;
138 uint b = second;
139 for (int round = 0; round < 16; round++)
140 {
141 uint c = words[round] ^ a;
142 a = b ^ Lookup(words, c);
143 b = c;
144 }
145
146 second = a ^ words[16];
147 first = b ^ words[17];
148 }
149
154 private static void DecryptBlock(uint[] words, ref uint first, ref uint second)
155 {
156 uint a = first;
157 uint b = second;
158 for (int round = 17; round > 1; round--)
159 {
160 uint c = words[round] ^ a;
161 a = b ^ Lookup(words, c);
162 b = c;
163 }
164
165 first = b ^ words[0];
166 second = a ^ words[1];
167 }
168
173 private static uint Lookup(uint[] words, uint value)
174 {
175 uint a = words[18 + (value >> 24)];
176 uint b = words[18 + 256 + ((value >> 16) & 0xFF)];
177 uint c = words[18 + 512 + ((value >> 8) & 0xFF)];
178 uint d = words[18 + 768 + (value & 0xFF)];
179 return unchecked(d + (c ^ (b + a)));
180 }
181
186 private static void EncryptStoredBlock(uint[] words, Span<byte> data, int offset)
187 {
188 uint first = ReadWord(data, offset + 4);
189 uint second = ReadWord(data, offset);
190 EncryptBlock(words, ref first, ref second);
191 WriteWord(data, offset + 4, first);
192 WriteWord(data, offset, second);
193 }
194
199 private static void DecryptStoredBlock(uint[] words, Span<byte> data, int offset)
200 {
201 uint first = ReadWord(data, offset + 4);
202 uint second = ReadWord(data, offset);
203 DecryptBlock(words, ref first, ref second);
204 WriteWord(data, offset + 4, first);
205 WriteWord(data, offset, second);
206 }
207
209 private static uint ReadWord(ReadOnlySpan<byte> data, int offset) =>
210 BinaryPrimitives.ReadUInt32LittleEndian(data[offset..]);
211
213 private static void WriteWord(Span<byte> data, int offset, uint value) =>
214 BinaryPrimitives.WriteUInt32LittleEndian(data[offset..], value);
215
217 private static void ValidateArea(ReadOnlySpan<byte> area)
218 {
219 if (area.Length != NdsSecureArea.ByteLength)
220 {
221 throw new ArgumentException($"A secure area must contain exactly 0x{NdsSecureArea.ByteLength:X} bytes.", nameof(area));
222 }
223 }
224}