From 4e778bc837310fddece02caa72f63c14f340727b Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 30 Sep 2025 18:09:17 -0400 Subject: [PATCH] Sync back fixes from IO --- NDecrypt.Core/CommonOperations.cs | 127 +++++++++++++++++++--------- NDecrypt.Core/ExtensionAttribute.cs | 9 ++ NDecrypt.Core/PartitionKeys.cs | 24 +++--- NDecrypt.Core/ThreeDSTool.cs | 8 +- 4 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 NDecrypt.Core/ExtensionAttribute.cs diff --git a/NDecrypt.Core/CommonOperations.cs b/NDecrypt.Core/CommonOperations.cs index 4d8df2a..b22fb17 100644 --- a/NDecrypt.Core/CommonOperations.cs +++ b/NDecrypt.Core/CommonOperations.cs @@ -126,48 +126,74 @@ namespace NDecrypt.Core #endregion + // TODO: Remove when IO updated #region Byte Arrays /// /// Add an integer value to a number represented by a byte array /// - /// Byte array to add to + /// Byte array to add to /// Amount to add /// Byte array representing the new value - public static byte[] Add(byte[] input, uint add) + /// Assumes array values are in big-endian format + public static byte[] Add(this byte[] self, uint add) { + // If nothing is being added, just return + if (add == 0) + return self; + + // Get the big-endian representation of the value byte[] addBytes = BitConverter.GetBytes(add); Array.Reverse(addBytes); + + // Pad the array out to 16 bytes byte[] paddedBytes = new byte[16]; Array.Copy(addBytes, 0, paddedBytes, 12, 4); - return Add(input, paddedBytes); + + // If the input is empty, just return the added value + if (self.Length == 0) + return paddedBytes; + + return self.Add(paddedBytes); } /// /// Add two numbers represented by byte arrays /// - /// Byte array to add to - /// Amount to add + /// Byte array to add to + /// Amount to add /// Byte array representing the new value - public static byte[] Add(byte[] left, byte[] right) + /// Assumes array values are in big-endian format + public static byte[] Add(this byte[] self, byte[] add) { - int addBytes = Math.Min(left.Length, right.Length); - int outLength = Math.Max(left.Length, right.Length); + // If either input is empty + if (self.Length == 0 && add.Length == 0) + return []; + else if (self.Length > 0 && add.Length == 0) + return self; + else if (self.Length == 0 && add.Length > 0) + return add; + // Setup the output array + int outLength = Math.Max(self.Length, add.Length); byte[] output = new byte[outLength]; + // Loop adding with carry uint carry = 0; - for (int i = addBytes - 1; i >= 0; i--) + for (int i = 0; i < outLength; i++) { - uint addValue = (uint)(left[i] + right[i]) + carry; - output[i] = (byte)addValue; - carry = addValue >> 8; - } + int selfIndex = self.Length - i - 1; + uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u; - if (outLength != addBytes && left.Length == outLength) - Array.Copy(left, addBytes, output, addBytes, outLength - addBytes); - else if (outLength != addBytes && right.Length == outLength) - Array.Copy(right, addBytes, output, addBytes, outLength - addBytes); + int addIndex = add.Length - i - 1; + uint addValue = addIndex >= 0 ? add[addIndex] : 0u; + + uint next = selfValue + addValue + carry; + carry = next >> 8; + + int outputIndex = output.Length - i - 1; + output[outputIndex] = (byte)(next & 0xFF); + } return output; } @@ -175,16 +201,23 @@ namespace NDecrypt.Core /// /// Perform a rotate left on a byte array /// - /// Byte array value to rotate - /// Number of bits to rotate + /// Byte array value to rotate + /// Number of bits to rotate /// Rotated byte array value - public static byte[] RotateLeft(byte[] val, int r_bits) + /// Assumes array values are in big-endian format + public static byte[] RotateLeft(this byte[] self, int numBits) { - byte[] output = new byte[val.Length]; - Array.Copy(val, output, output.Length); + // If either input is empty + if (self.Length == 0) + return []; + else if (numBits == 0) + return self; + + byte[] output = new byte[self.Length]; + Array.Copy(self, output, output.Length); // Shift by bytes - while (r_bits >= 8) + while (numBits >= 8) { byte temp = output[0]; for (int i = 0; i < output.Length - 1; i++) @@ -193,13 +226,13 @@ namespace NDecrypt.Core } output[output.Length - 1] = temp; - r_bits -= 8; + numBits -= 8; } // Shift by bits - if (r_bits > 0) + if (numBits > 0) { - byte bitMask = (byte)(8 - r_bits), carry, wrap = 0; + byte bitMask = (byte)(8 - numBits), carry, wrap = 0; for (int i = 0; i < output.Length; i++) { carry = (byte)((255 << bitMask & output[i]) >> bitMask); @@ -213,7 +246,7 @@ namespace NDecrypt.Core output[i - 1] |= carry; // Shift the current bits - output[i] <<= r_bits; + output[i] <<= numBits; } // Make sure the wrap happens @@ -226,24 +259,38 @@ namespace NDecrypt.Core /// /// XOR two numbers represented by byte arrays /// - /// Byte array to XOR to - /// Amount to XOR + /// Byte array to XOR to + /// Amount to XOR /// Byte array representing the new value - public static byte[] Xor(byte[] left, byte[] right) + /// Assumes array values are in big-endian format + public static byte[] Xor(this byte[] self, byte[] xor) { - int xorBytes = Math.Min(left.Length, right.Length); - int outLength = Math.Max(left.Length, right.Length); + // If either input is empty + if (self.Length == 0 && xor.Length == 0) + return []; + else if (self.Length > 0 && xor.Length == 0) + return self; + else if (self.Length == 0 && xor.Length > 0) + return xor; + // Setup the output array + int outLength = Math.Max(self.Length, xor.Length); byte[] output = new byte[outLength]; - for (int i = 0; i < xorBytes; i++) - { - output[i] = (byte)(left[i] ^ right[i]); - } - if (outLength != xorBytes && left.Length == outLength) - Array.Copy(left, xorBytes, output, xorBytes, outLength - xorBytes); - else if (outLength != xorBytes && right.Length == outLength) - Array.Copy(right, xorBytes, output, xorBytes, outLength - xorBytes); + // Loop XOR + for (int i = 0; i < outLength; i++) + { + int selfIndex = self.Length - i - 1; + uint selfValue = selfIndex >= 0 ? self[selfIndex] : 0u; + + int xorIndex = xor.Length - i - 1; + uint xorValue = xorIndex >= 0 ? xor[xorIndex] : 0u; + + uint next = selfValue ^ xorValue; + + int outputIndex = output.Length - i - 1; + output[outputIndex] = (byte)(next & 0xFF); + } return output; } diff --git a/NDecrypt.Core/ExtensionAttribute.cs b/NDecrypt.Core/ExtensionAttribute.cs new file mode 100644 index 0000000..b40aaca --- /dev/null +++ b/NDecrypt.Core/ExtensionAttribute.cs @@ -0,0 +1,9 @@ +#if NET20 + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class ExtensionAttribute : Attribute {} +} + +#endif diff --git a/NDecrypt.Core/PartitionKeys.cs b/NDecrypt.Core/PartitionKeys.cs index f60628c..47e342d 100644 --- a/NDecrypt.Core/PartitionKeys.cs +++ b/NDecrypt.Core/PartitionKeys.cs @@ -61,10 +61,10 @@ namespace NDecrypt.Core // Set the standard normal key values NormalKey = new byte[16]; - NormalKey2C = RotateLeft(KeyX2C, 2); - NormalKey2C = Xor(NormalKey2C, KeyY); - NormalKey2C = Add(NormalKey2C, args.AESHardwareConstant); - NormalKey2C = RotateLeft(NormalKey2C, 87); + NormalKey2C = KeyX2C.RotateLeft(2); + NormalKey2C = NormalKey2C.Xor(KeyY); + NormalKey2C = NormalKey2C.Add(add: args.AESHardwareConstant); + NormalKey2C = NormalKey2C.RotateLeft(87); // Special case for zero-key #if NET20 || NET35 @@ -104,10 +104,10 @@ namespace NDecrypt.Core } // Set the normal key based on the new KeyX value - NormalKey = RotateLeft(KeyX, 2); - NormalKey = Xor(NormalKey, KeyY); - NormalKey = Add(NormalKey, args.AESHardwareConstant); - NormalKey = RotateLeft(NormalKey, 87); + NormalKey = KeyX.RotateLeft(2); + NormalKey = NormalKey.Xor(KeyY); + NormalKey = NormalKey.Add(args.AESHardwareConstant); + NormalKey = NormalKey.RotateLeft(87); } /// @@ -129,10 +129,10 @@ namespace NDecrypt.Core // Encrypting RomFS for partitions 1 and up always use Key0x2C KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C; - NormalKey = RotateLeft(KeyX, 2); - NormalKey = Xor(NormalKey, KeyY); - NormalKey = Add(NormalKey, _decryptArgs.AESHardwareConstant); - NormalKey = RotateLeft(NormalKey, 87); + NormalKey = KeyX.RotateLeft(2); + NormalKey = NormalKey.Xor(KeyY); + NormalKey = NormalKey.Add(_decryptArgs.AESHardwareConstant); + NormalKey = NormalKey.RotateLeft(87); } } } \ No newline at end of file diff --git a/NDecrypt.Core/ThreeDSTool.cs b/NDecrypt.Core/ThreeDSTool.cs index 74b61c9..50da692 100644 --- a/NDecrypt.Core/ThreeDSTool.cs +++ b/NDecrypt.Core/ThreeDSTool.cs @@ -264,7 +264,7 @@ namespace NDecrypt.Core // Create the ExeFS AES cipher for this partition uint ctroffsetE = cart.MediaUnitSize / 0x10; - byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE); + byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE); var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset); // Setup and perform the decryption @@ -361,7 +361,7 @@ namespace NDecrypt.Core // Create the ExeFS AES ciphers for this partition uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10; - byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset); + byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset); var firstCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader); var secondCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader); @@ -690,7 +690,7 @@ namespace NDecrypt.Core // Create the ExeFS AES cipher for this partition uint ctroffsetE = cart.MediaUnitSize / 0x10; - byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE); + byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE); var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset); // Setup and perform the encryption @@ -784,7 +784,7 @@ namespace NDecrypt.Core // Create the ExeFS AES ciphers for this partition uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10; - byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset); + byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset); var firstCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader); var secondCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);