1using System.Buffers.Binary;
6internal static class NdsKey1Cipher
9 public const uint DestroyedId = 0xE7FFDEFF;
11 private const uint SecureId0 = 0x72636E65;
13 private const uint SecureId1 = 0x6A624F79;
20 public static byte[] Encrypt(ReadOnlySpan<byte> area, uint gameCode, NdsKey1KeyTable keyTable)
23 if (ReadWord(area, 0) != DestroyedId || ReadWord(area, 4) != DestroyedId)
25 throw new InvalidDataException(
"Decrypted secure-area bytes must begin with two 0xE7FFDEFF marker words.");
28 byte[] output = area.ToArray();
29 (uint[] words, uint[] argument) = InitializeLevel1(keyTable, gameCode);
30 argument[1] = unchecked(argument[1] << 1);
32 InitializeLevel2(words, argument);
33 for (
int offset = 8; offset < 0x800; offset += 8)
35 EncryptStoredBlock(words, output, offset);
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);
51 public static byte[] Decrypt(ReadOnlySpan<byte> area, uint gameCode, NdsKey1KeyTable keyTable)
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);
59 InitializeLevel2(words, argument);
60 DecryptStoredBlock(words, output, 0);
61 if (ReadWord(output, 0) != SecureId0 || ReadWord(output, 4) != SecureId1)
63 throw new InvalidDataException(
"The supplied KEY1 table and game code do not recover a secure-area identifier.");
66 WriteWord(output, 0, DestroyedId);
67 WriteWord(output, 4, DestroyedId);
68 for (
int offset = 8; offset < 0x800; offset += 8)
70 DecryptStoredBlock(words, output, offset);
80 private static (uint[] Words, uint[] Argument) InitializeLevel1(NdsKey1KeyTable keyTable, uint gameCode)
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);
92 private static void InitializeLevel2(uint[] words, uint[] argument)
94 EncryptBlock(words, ref argument[2], ref argument[1]);
95 EncryptBlock(words, ref argument[1], ref argument[0]);
96 UpdateSchedule(words, argument);
102 private static void UpdateSchedule(uint[] words, uint[] argument)
104 Span<byte> argumentBytes = stackalloc
byte[12];
105 for (
int index = 0; index < argument.Length; index++)
107 BinaryPrimitives.WriteUInt32LittleEndian(argumentBytes[(index * 4)..], argument[index]);
110 for (
int round = 0; round < 18; round++)
113 for (
int index = 0; index < 4; index++)
115 value = unchecked((value << 8) | argumentBytes[((round * 4) + index) & 7]);
118 words[round] ^= value;
123 for (
int index = 0; index < words.Length; index += 2)
125 EncryptBlock(words, ref first, ref second);
126 words[index] = first;
127 words[index + 1] = second;
135 private static void EncryptBlock(uint[] words, ref uint first, ref uint second)
139 for (
int round = 0; round < 16; round++)
141 uint c = words[round] ^ a;
142 a = b ^ Lookup(words, c);
146 second = a ^ words[16];
147 first = b ^ words[17];
154 private static void DecryptBlock(uint[] words, ref uint first, ref uint second)
158 for (
int round = 17; round > 1; round--)
160 uint c = words[round] ^ a;
161 a = b ^ Lookup(words, c);
165 first = b ^ words[0];
166 second = a ^ words[1];
173 private static uint Lookup(uint[] words, uint value)
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)));
186 private static void EncryptStoredBlock(uint[] words, Span<byte> data,
int offset)
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);
199 private static void DecryptStoredBlock(uint[] words, Span<byte> data,
int offset)
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);
209 private static uint ReadWord(ReadOnlySpan<byte> data,
int offset) =>
210 BinaryPrimitives.ReadUInt32LittleEndian(data[offset..]);
213 private static void WriteWord(Span<byte> data,
int offset, uint value) =>
214 BinaryPrimitives.WriteUInt32LittleEndian(data[offset..], value);
217 private static void ValidateArea(ReadOnlySpan<byte> area)
219 if (area.Length != NdsSecureArea.ByteLength)
221 throw new ArgumentException($
"A secure area must contain exactly 0x{NdsSecureArea.ByteLength:X} bytes.", nameof(area));