diff --git a/src/LibHac/Common/Buffer.cs b/src/LibHac/Common/Buffer.cs
new file mode 100644
index 00000000..9e8880d6
--- /dev/null
+++ b/src/LibHac/Common/Buffer.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace LibHac.Common
+{
+ ///
+ /// Represents a buffer of 16 bytes.
+ /// Contains functions that assist with common operations on small buffers.
+ ///
+ [DebuggerDisplay("{ToString()}")]
+ [StructLayout(LayoutKind.Sequential, Size = 16)]
+ public struct Buffer16
+ {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
+
+ public byte this[int i]
+ {
+ get => Bytes[i];
+ set => Bytes[i] = value;
+ }
+
+ public Span Bytes => SpanHelpers.AsByteSpan(ref this);
+
+ // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Span(in Buffer16 value)
+ {
+ return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlySpan(in Buffer16 value)
+ {
+ return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T As() where T : unmanaged
+ {
+ if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf())
+ {
+ throw new ArgumentException();
+ }
+
+ return ref MemoryMarshal.GetReference(AsSpan());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AsSpan() where T : unmanaged
+ {
+ return SpanHelpers.AsSpan(ref this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged
+ {
+ return SpanHelpers.AsReadOnlySpan(ref Unsafe.AsRef(in this));
+ }
+
+ public override string ToString()
+ {
+ return Bytes.ToHexString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LibHac/Common/SpanHelpers.cs b/src/LibHac/Common/SpanHelpers.cs
index ce8aabd4..aee202ff 100644
--- a/src/LibHac/Common/SpanHelpers.cs
+++ b/src/LibHac/Common/SpanHelpers.cs
@@ -25,11 +25,51 @@ namespace LibHac.Common
return CreateSpan(ref reference, 1);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateSpan(ref Unsafe.As(ref reference),
+ Unsafe.SizeOf() / Unsafe.SizeOf());
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span AsByteSpan(ref T reference) where T : unmanaged
{
- Span span = AsSpan(ref reference);
- return MemoryMarshal.Cast(span);
+ return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NETCOREAPP
+ public static ReadOnlySpan CreateReadOnlySpan(ref T reference, int length)
+ {
+ return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
+ }
+#else
+ public static unsafe ReadOnlySpan CreateReadOnlySpan(ref T reference, int length)
+ {
+ return new ReadOnlySpan(Unsafe.AsPointer(ref reference), length);
+ }
+#endif
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlySpan(ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref reference, 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlySpan(ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As(ref reference),
+ Unsafe.SizeOf() / Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlyByteSpan(ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As(ref reference), Unsafe.SizeOf());
}
}
}
diff --git a/src/LibHac/Crypto2/Aes.cs b/src/LibHac/Crypto2/Aes.cs
index e2e8cc2c..e433f901 100644
--- a/src/LibHac/Crypto2/Aes.cs
+++ b/src/LibHac/Crypto2/Aes.cs
@@ -8,6 +8,9 @@ namespace LibHac.Crypto2
{
public static class AesCrypto
{
+ public const int KeySize128 = 0x10;
+ public const int BlockSize = 0x10;
+
public static bool IsAesNiSupported()
{
#if HAS_INTRINSICS
@@ -77,5 +80,29 @@ namespace LibHac.Crypto2
#endif
return new AesCtrEncryptor(key, iv);
}
+
+ public static ICipher CreateXtsDecryptor(ReadOnlySpan key1, ReadOnlySpan key2,
+ ReadOnlySpan iv, bool preferDotNetCrypto = false)
+ {
+#if HAS_INTRINSICS
+ if (IsAesNiSupported() && !preferDotNetCrypto)
+ {
+ return new AesXtsCipherHw(key1, key2, iv, true);
+ }
+#endif
+ return new AesXtsCipher(key1, key2, iv, true);
+ }
+
+ public static ICipher CreateXtsEncryptor(ReadOnlySpan key1, ReadOnlySpan key2,
+ ReadOnlySpan iv, bool preferDotNetCrypto = false)
+ {
+#if HAS_INTRINSICS
+ if (IsAesNiSupported() && !preferDotNetCrypto)
+ {
+ return new AesXtsCipherHw(key1, key2, iv, false);
+ }
+#endif
+ return new AesXtsCipher(key1, key2, iv, false);
+ }
}
}
diff --git a/src/LibHac/Crypto2/AesCbcModeHw.cs b/src/LibHac/Crypto2/AesCbcModeHw.cs
index 4bb0311f..4735befd 100644
--- a/src/LibHac/Crypto2/AesCbcModeHw.cs
+++ b/src/LibHac/Crypto2/AesCbcModeHw.cs
@@ -24,32 +24,24 @@ namespace LibHac.Crypto2
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan input, Span output)
{
- ReadOnlySpan> keys = _aesCore.RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
+ int blockCount = Math.Min(input.Length, output.Length) >> 4;
- Vector128 b = _iv;
-
- for (int i = 0; i < inBlocks.Length; i++)
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
+
+ Vector128 iv = _iv;
+
+ for (int i = 0; i < blockCount; i++)
{
- b = Sse2.Xor(b, inBlocks[i]);
+ iv = _aesCore.EncryptBlock(Sse2.Xor(iv, inBlock));
- b = Sse2.Xor(b, keys[0]);
- b = Aes.Encrypt(b, keys[1]);
- b = Aes.Encrypt(b, keys[2]);
- b = Aes.Encrypt(b, keys[3]);
- b = Aes.Encrypt(b, keys[4]);
- b = Aes.Encrypt(b, keys[5]);
- b = Aes.Encrypt(b, keys[6]);
- b = Aes.Encrypt(b, keys[7]);
- b = Aes.Encrypt(b, keys[8]);
- b = Aes.Encrypt(b, keys[9]);
- b = Aes.EncryptLast(b, keys[10]);
+ outBlock = iv;
- outBlocks[i] = b;
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
}
- _iv = b;
+ _iv = iv;
}
}
@@ -70,32 +62,22 @@ namespace LibHac.Crypto2
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan input, Span output)
{
- ReadOnlySpan> keys = _aesCore.RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
+ int blockCount = Math.Min(input.Length, output.Length) >> 4;
+
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
Vector128 iv = _iv;
- for (int i = 0; i < inBlocks.Length; i++)
+ for (int i = 0; i < blockCount; i++)
{
- Vector128 b = inBlocks[i];
- Vector128 nextIv = b;
+ Vector128 decBeforeIv = _aesCore.DecryptBlock(inBlock);
+ outBlock = Sse2.Xor(decBeforeIv, iv);
- b = Sse2.Xor(b, keys[10]);
- b = Aes.Decrypt(b, keys[9]);
- b = Aes.Decrypt(b, keys[8]);
- b = Aes.Decrypt(b, keys[7]);
- b = Aes.Decrypt(b, keys[6]);
- b = Aes.Decrypt(b, keys[5]);
- b = Aes.Decrypt(b, keys[4]);
- b = Aes.Decrypt(b, keys[3]);
- b = Aes.Decrypt(b, keys[2]);
- b = Aes.Decrypt(b, keys[1]);
- b = Aes.DecryptLast(b, keys[0]);
+ iv = inBlock;
- b = Sse2.Xor(b, iv);
- iv = nextIv;
- outBlocks[i] = b;
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
}
_iv = iv;
diff --git a/src/LibHac/Crypto2/AesCoreNi.cs b/src/LibHac/Crypto2/AesCoreNi.cs
index 5e547c67..57aabc7d 100644
--- a/src/LibHac/Crypto2/AesCoreNi.cs
+++ b/src/LibHac/Crypto2/AesCoreNi.cs
@@ -15,67 +15,90 @@ namespace LibHac.Crypto2
private Vector128 _roundKeys;
+ public AesCoreNi(ReadOnlySpan key, bool isDecrypting)
+ {
+ _roundKeys = default;
+
+ KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
+ }
+
public void Initialize(ReadOnlySpan key, bool isDecrypting)
{
- KeyExpansion(key, RoundKeys, isDecrypting);
+ KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
}
- public Span> RoundKeys =>
- MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount);
+ public readonly ReadOnlySpan> RoundKeys =>
+ MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _roundKeys), RoundKeyCount);
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public void Encrypt(ReadOnlySpan input, Span output)
+ public readonly void Encrypt(ReadOnlySpan input, Span output)
{
- ReadOnlySpan> keys = RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
+ int blockCount = Math.Min(input.Length, output.Length) >> 4;
- for (int i = 0; i < inBlocks.Length; i++)
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
+
+ for (int i = 0; i < blockCount; i++)
{
- Vector128 b = inBlocks[i];
+ outBlock = EncryptBlock(inBlock);
- b = Sse2.Xor(b, keys[0]);
- b = Aes.Encrypt(b, keys[1]);
- b = Aes.Encrypt(b, keys[2]);
- b = Aes.Encrypt(b, keys[3]);
- b = Aes.Encrypt(b, keys[4]);
- b = Aes.Encrypt(b, keys[5]);
- b = Aes.Encrypt(b, keys[6]);
- b = Aes.Encrypt(b, keys[7]);
- b = Aes.Encrypt(b, keys[8]);
- b = Aes.Encrypt(b, keys[9]);
- b = Aes.EncryptLast(b, keys[10]);
-
- outBlocks[i] = b;
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public void Decrypt(ReadOnlySpan input, Span output)
+ public readonly void Decrypt(ReadOnlySpan input, Span output)
+ {
+ int blockCount = Math.Min(input.Length, output.Length) >> 4;
+
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ outBlock = DecryptBlock(inBlock);
+
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly Vector128 EncryptBlock(Vector128 input)
{
ReadOnlySpan> keys = RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
- for (int i = 0; i < inBlocks.Length; i++)
- {
- Vector128 b = inBlocks[i];
+ Vector128 b = Sse2.Xor(input, keys[0]);
+ b = Aes.Encrypt(b, keys[1]);
+ b = Aes.Encrypt(b, keys[2]);
+ b = Aes.Encrypt(b, keys[3]);
+ b = Aes.Encrypt(b, keys[4]);
+ b = Aes.Encrypt(b, keys[5]);
+ b = Aes.Encrypt(b, keys[6]);
+ b = Aes.Encrypt(b, keys[7]);
+ b = Aes.Encrypt(b, keys[8]);
+ b = Aes.Encrypt(b, keys[9]);
+ return Aes.EncryptLast(b, keys[10]);
+ }
- b = Sse2.Xor(b, keys[10]);
- b = Aes.Decrypt(b, keys[9]);
- b = Aes.Decrypt(b, keys[8]);
- b = Aes.Decrypt(b, keys[7]);
- b = Aes.Decrypt(b, keys[6]);
- b = Aes.Decrypt(b, keys[5]);
- b = Aes.Decrypt(b, keys[4]);
- b = Aes.Decrypt(b, keys[3]);
- b = Aes.Decrypt(b, keys[2]);
- b = Aes.Decrypt(b, keys[1]);
- b = Aes.DecryptLast(b, keys[0]);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly Vector128 DecryptBlock(Vector128 input)
+ {
+ ReadOnlySpan> keys = RoundKeys;
- outBlocks[i] = b;
- }
+ Vector128 b = Sse2.Xor(input, keys[10]);
+ b = Aes.Decrypt(b, keys[9]);
+ b = Aes.Decrypt(b, keys[8]);
+ b = Aes.Decrypt(b, keys[7]);
+ b = Aes.Decrypt(b, keys[6]);
+ b = Aes.Decrypt(b, keys[5]);
+ b = Aes.Decrypt(b, keys[4]);
+ b = Aes.Decrypt(b, keys[3]);
+ b = Aes.Decrypt(b, keys[2]);
+ b = Aes.Decrypt(b, keys[1]);
+ return Aes.DecryptLast(b, keys[0]);
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
diff --git a/src/LibHac/Crypto2/AesCtrModeHw.cs b/src/LibHac/Crypto2/AesCtrModeHw.cs
index 1329c80f..f5e57ad1 100644
--- a/src/LibHac/Crypto2/AesCtrModeHw.cs
+++ b/src/LibHac/Crypto2/AesCtrModeHw.cs
@@ -23,42 +23,35 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan input, Span output)
{
- ReadOnlySpan> keys = _aesCore.RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
+ int blockCount = Math.Min(input.Length, output.Length) >> 4;
+
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
Vector128 byteSwapMask = Vector128.Create((ulong)0x706050403020100, 0x8090A0B0C0D0E0F).AsByte();
Vector128 inc = Vector128.Create((ulong)0, 1);
- var iv = _iv;
+ Vector128 iv = _iv;
Vector128 bSwappedIv = Ssse3.Shuffle(iv, byteSwapMask).AsUInt64();
- for (int i = 0; i < inBlocks.Length; i++)
+ for (int i = 0; i < blockCount; i++)
{
- Vector128 b = Sse2.Xor(iv, keys[0]);
- b = Aes.Encrypt(b, keys[1]);
- b = Aes.Encrypt(b, keys[2]);
- b = Aes.Encrypt(b, keys[3]);
- b = Aes.Encrypt(b, keys[4]);
- b = Aes.Encrypt(b, keys[5]);
- b = Aes.Encrypt(b, keys[6]);
- b = Aes.Encrypt(b, keys[7]);
- b = Aes.Encrypt(b, keys[8]);
- b = Aes.Encrypt(b, keys[9]);
- b = Aes.EncryptLast(b, keys[10]);
-
- outBlocks[i] = Sse2.Xor(inBlocks[i], b);
+ Vector128 encIv = _aesCore.EncryptBlock(iv);
+ outBlock = Sse2.Xor(inBlock, encIv);
// Increase the counter
bSwappedIv = Sse2.Add(bSwappedIv, inc);
iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask);
+
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
}
_iv = iv;
if ((input.Length & 0xF) != 0)
{
- EncryptCtrPartialBlock(input.Slice(inBlocks.Length * 0x10), output.Slice(outBlocks.Length * 0x10));
+ EncryptCtrPartialBlock(input.Slice(blockCount * 0x10), output.Slice(blockCount * 0x10));
}
}
diff --git a/src/LibHac/Crypto2/AesEcbMode.cs b/src/LibHac/Crypto2/AesEcbMode.cs
index bd43f33a..48dda455 100644
--- a/src/LibHac/Crypto2/AesEcbMode.cs
+++ b/src/LibHac/Crypto2/AesEcbMode.cs
@@ -1,10 +1,12 @@
using System;
+using System.Buffers;
using System.Security.Cryptography;
namespace LibHac.Crypto2
{
public class AesEcbEncryptor : ICipher
{
+ private const int BufferRentThreshold = 1024;
private ICryptoTransform _encryptor;
public AesEcbEncryptor(ReadOnlySpan key)
@@ -21,16 +23,34 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan input, Span output)
{
- var outputBuffer = new byte[input.Length];
+ if (input.Length < BufferRentThreshold)
+ {
+ var outputBuffer = new byte[input.Length];
+ input.CopyTo(outputBuffer);
- _encryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
+ _encryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
- outputBuffer.CopyTo(output);
+ outputBuffer.CopyTo(output);
+ }
+ else
+ {
+ byte[] outputBuffer = ArrayPool.Shared.Rent(input.Length);
+ try
+ {
+ input.CopyTo(outputBuffer);
+
+ _encryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
+
+ outputBuffer.CopyTo(output);
+ }
+ finally { ArrayPool.Shared.Return(outputBuffer); }
+ }
}
}
public class AesEcbDecryptor : ICipher
{
+ private const int BufferRentThreshold = 1024;
private ICryptoTransform _decryptor;
public AesEcbDecryptor(ReadOnlySpan key)
@@ -47,11 +67,28 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan input, Span output)
{
- var outputBuffer = new byte[input.Length];
+ if (input.Length < BufferRentThreshold)
+ {
+ var outputBuffer = new byte[input.Length];
+ input.CopyTo(outputBuffer);
- _decryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
+ _decryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
- outputBuffer.CopyTo(output);
+ outputBuffer.CopyTo(output);
+ }
+ else
+ {
+ byte[] outputBuffer = ArrayPool.Shared.Rent(input.Length);
+ try
+ {
+ input.CopyTo(outputBuffer);
+
+ _decryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
+
+ outputBuffer.CopyTo(output);
+ }
+ finally { ArrayPool.Shared.Return(outputBuffer); }
+ }
}
}
}
diff --git a/src/LibHac/Crypto2/AesEcbModeHw.cs b/src/LibHac/Crypto2/AesEcbModeHw.cs
index b257c932..c673e9b9 100644
--- a/src/LibHac/Crypto2/AesEcbModeHw.cs
+++ b/src/LibHac/Crypto2/AesEcbModeHw.cs
@@ -1,10 +1,5 @@
-
-
-#if HAS_INTRINSICS
+#if HAS_INTRINSICS
using System;
-using System.Runtime.Intrinsics;
-using System.Runtime.InteropServices;
-using System.Runtime.Intrinsics.X86;
namespace LibHac.Crypto2
{
@@ -20,28 +15,7 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan input, Span output)
{
- ReadOnlySpan> keys = _aesCore.RoundKeys;
- ReadOnlySpan> inBlocks = MemoryMarshal.Cast>(input);
- Span> outBlocks = MemoryMarshal.Cast>(output);
-
- for (int i = 0; i < inBlocks.Length; i++)
- {
- Vector128 b = inBlocks[i];
-
- b = Sse2.Xor(b, keys[0]);
- b = Aes.Encrypt(b, keys[1]);
- b = Aes.Encrypt(b, keys[2]);
- b = Aes.Encrypt(b, keys[3]);
- b = Aes.Encrypt(b, keys[4]);
- b = Aes.Encrypt(b, keys[5]);
- b = Aes.Encrypt(b, keys[6]);
- b = Aes.Encrypt(b, keys[7]);
- b = Aes.Encrypt(b, keys[8]);
- b = Aes.Encrypt(b, keys[9]);
- b = Aes.EncryptLast(b, keys[10]);
-
- outBlocks[i] = b;
- }
+ _aesCore.Encrypt(input, output);
}
}
diff --git a/src/LibHac/Crypto2/AesXtsMode.cs b/src/LibHac/Crypto2/AesXtsMode.cs
new file mode 100644
index 00000000..d2e84f3f
--- /dev/null
+++ b/src/LibHac/Crypto2/AesXtsMode.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using LibHac.Common;
+
+namespace LibHac.Crypto2
+{
+ public class AesXtsCipher : ICipher
+ {
+ private ICipher _dataCipher;
+ private ICipher _tweakCipher;
+ private Buffer16 _iv;
+ private bool _decrypting;
+
+ public AesXtsCipher(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool decrypting)
+ {
+ Debug.Assert(key1.Length == AesCrypto.KeySize128);
+ Debug.Assert(key2.Length == AesCrypto.KeySize128);
+ Debug.Assert(iv.Length == AesCrypto.KeySize128);
+
+ if (decrypting)
+ {
+ _dataCipher = new AesEcbDecryptor(key1);
+ }
+ else
+ {
+ _dataCipher = new AesEcbEncryptor(key1);
+ }
+
+ _tweakCipher = new AesEcbEncryptor(key2);
+
+ _iv = new Buffer16();
+ iv.CopyTo(_iv);
+
+ _decrypting = decrypting;
+ }
+
+ public void Encrypt(ReadOnlySpan input, Span output)
+ {
+ int length = Math.Min(input.Length, output.Length);
+ int blockCount = length >> 4;
+ int leftover = length & 0xF;
+
+ // Data units must be at least 1 block long.
+ if (length < AesCrypto.BlockSize)
+ throw new ArgumentException();
+
+ var tweak = new Buffer16();
+
+ _tweakCipher.Transform(_iv, tweak);
+
+ byte[] tweakBufferRented = ArrayPool.Shared.Rent(blockCount * AesCrypto.BlockSize);
+ try
+ {
+ Span tweakBuffer = tweakBufferRented.AsSpan(0, blockCount * AesCrypto.BlockSize);
+ tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast(tweakBuffer));
+
+ Util.XorArrays(output, input, tweakBuffer);
+ _dataCipher.Transform(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
+ Util.XorArrays(output, output, tweakBuffer);
+ }
+ finally { ArrayPool.Shared.Return(tweakBufferRented); }
+
+ if (leftover != 0)
+ {
+ ref Buffer16 inBlock =
+ ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount);
+
+ ref Buffer16 outBlock =
+ ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount);
+
+ ref Buffer16 prevOutBlock = ref Unsafe.Subtract(ref outBlock, 1);
+
+ var tmp = new Buffer16();
+
+ for (int i = 0; i < leftover; i++)
+ {
+ outBlock[i] = prevOutBlock[i];
+ tmp[i] = inBlock[i];
+ }
+
+ for (int i = leftover; i < AesCrypto.BlockSize; i++)
+ {
+ tmp[i] = prevOutBlock[i];
+ }
+
+ XorBuffer(ref tmp, ref tmp, ref tweak);
+ _dataCipher.Transform(tmp, tmp);
+ XorBuffer(ref prevOutBlock, ref tmp, ref tweak);
+ }
+ }
+
+ public void Decrypt(ReadOnlySpan input, Span output)
+ {
+ int length = Math.Min(input.Length, output.Length);
+ int blockCount = length >> 4;
+ int leftover = length & 0xF;
+
+ // Data units must be at least 1 block long.
+ if (length < AesCrypto.BlockSize)
+ throw new ArgumentException();
+
+ if (leftover != 0) blockCount--;
+
+ var tweak = new Buffer16();
+
+ _tweakCipher.Transform(_iv, tweak);
+
+ if (blockCount > 0)
+ {
+ byte[] tweakBufferRented = ArrayPool.Shared.Rent(blockCount * AesCrypto.BlockSize);
+ try
+ {
+ Span tweakBuffer = tweakBufferRented.AsSpan(0, blockCount * AesCrypto.BlockSize);
+ tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast(tweakBuffer));
+
+ Util.XorArrays(output, input, tweakBuffer);
+ _dataCipher.Transform(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
+ Util.XorArrays(output, output, tweakBuffer);
+ }
+ finally { ArrayPool.Shared.Return(tweakBufferRented); }
+ }
+
+ if (leftover != 0)
+ {
+ Buffer16 finalTweak = tweak;
+ Gf128Mul(ref finalTweak);
+
+ ref Buffer16 inBlock =
+ ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(input)), blockCount);
+
+ ref Buffer16 outBlock =
+ ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(output)), blockCount);
+
+ var tmp = new Buffer16();
+
+ XorBuffer(ref tmp, ref inBlock, ref finalTweak);
+ _dataCipher.Transform(tmp, tmp);
+ XorBuffer(ref outBlock, ref tmp, ref finalTweak);
+
+ ref Buffer16 finalOutBlock = ref Unsafe.Add(ref outBlock, 1);
+ ref Buffer16 finalInBlock = ref Unsafe.Add(ref inBlock, 1);
+
+ for (int i = 0; i < leftover; i++)
+ {
+ finalOutBlock[i] = outBlock[i];
+ tmp[i] = finalInBlock[i];
+ }
+
+ for (int i = leftover; i < AesCrypto.BlockSize; i++)
+ {
+ tmp[i] = outBlock[i];
+ }
+
+ XorBuffer(ref tmp, ref tmp, ref tweak);
+ _dataCipher.Transform(tmp, tmp);
+ XorBuffer(ref outBlock, ref tmp, ref tweak);
+ }
+ }
+
+ public void Transform(ReadOnlySpan input, Span output)
+ {
+ if (_decrypting)
+ {
+ Decrypt(input, output);
+ }
+ else
+ {
+ Encrypt(input, output);
+ }
+ }
+
+ private static Buffer16 FillTweakBuffer(Buffer16 initialTweak, Span tweakBuffer)
+ {
+ for (int i = 0; i < tweakBuffer.Length; i++)
+ {
+ tweakBuffer[i] = initialTweak;
+ Gf128Mul(ref initialTweak);
+ }
+
+ return initialTweak;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Gf128Mul(ref Buffer16 buffer)
+ {
+ Span b = buffer.AsSpan();
+
+ ulong tt = (ulong)((long)b[1] >> 63) & 0x87;
+
+ b[1] = (b[1] << 1) | (b[0] >> 63);
+ b[0] = (b[0] << 1) ^ tt;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void XorBuffer(ref Buffer16 output, ref Buffer16 input1, ref Buffer16 input2)
+ {
+ Span outputS = output.AsSpan();
+ Span input1S = input1.AsSpan();
+ Span input2S = input2.AsSpan();
+
+ outputS[0] = input1S[0] ^ input2S[0];
+ outputS[1] = input1S[1] ^ input2S[1];
+ }
+ }
+}
diff --git a/src/LibHac/Crypto2/AesXtsModeHw.cs b/src/LibHac/Crypto2/AesXtsModeHw.cs
new file mode 100644
index 00000000..1a67d077
--- /dev/null
+++ b/src/LibHac/Crypto2/AesXtsModeHw.cs
@@ -0,0 +1,186 @@
+#if HAS_INTRINSICS
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using LibHac.Common;
+
+namespace LibHac.Crypto2
+{
+ public class AesXtsCipherHw : ICipher
+ {
+ private AesCoreNi _dataAesCore;
+ private AesCoreNi _tweakAesCore;
+ private Vector128 _iv;
+ private bool _decrypting;
+
+ public AesXtsCipherHw(ReadOnlySpan key1, ReadOnlySpan key2, ReadOnlySpan iv, bool decrypting)
+ {
+ Debug.Assert(key1.Length == AesCrypto.KeySize128);
+ Debug.Assert(key2.Length == AesCrypto.KeySize128);
+ Debug.Assert(iv.Length == AesCrypto.KeySize128);
+
+ _dataAesCore = new AesCoreNi();
+ _dataAesCore.Initialize(key1, decrypting);
+
+ _tweakAesCore = new AesCoreNi();
+ _tweakAesCore.Initialize(key2, false);
+
+ _iv = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(iv));
+
+ _decrypting = decrypting;
+ }
+
+ public void Encrypt(ReadOnlySpan input, Span output)
+ {
+ int length = Math.Min(input.Length, output.Length);
+ int blockCount = length >> 4;
+ int leftover = length & 0xF;
+
+ Debug.Assert(blockCount > 0);
+
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
+
+ Vector128 mask = Vector128.Create(0x87, 1).AsByte();
+
+ Vector128 tweak = _tweakAesCore.EncryptBlock(_iv);
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ Vector128 tmp = Sse2.Xor(inBlock, tweak);
+ tmp = _dataAesCore.EncryptBlock(tmp);
+ outBlock = Sse2.Xor(tmp, tweak);
+
+ tweak = Gf128Mul(tweak, mask);
+
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
+ }
+
+ if (leftover != 0)
+ {
+ EncryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, leftover);
+ }
+ }
+
+ public void Decrypt(ReadOnlySpan input, Span output)
+ {
+ int length = Math.Min(input.Length, output.Length);
+ int blockCount = length >> 4;
+ int leftover = length & 0xF;
+
+ Debug.Assert(blockCount > 0);
+
+ if (leftover != 0) blockCount--;
+
+ ref Vector128 inBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ ref Vector128 outBlock = ref Unsafe.As>(ref MemoryMarshal.GetReference(output));
+
+ Vector128 mask = Vector128.Create(0x87, 1).AsByte();
+
+ Vector128 tweak = _tweakAesCore.EncryptBlock(_iv);
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ Vector128 tmp = Sse2.Xor(inBlock, tweak);
+ tmp = _dataAesCore.DecryptBlock(tmp);
+ outBlock = Sse2.Xor(tmp, tweak);
+
+ tweak = Gf128Mul(tweak, mask);
+
+ inBlock = ref Unsafe.Add(ref inBlock, 1);
+ outBlock = ref Unsafe.Add(ref outBlock, 1);
+ }
+
+ if (leftover != 0)
+ {
+ DecryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, mask, leftover);
+ }
+ }
+
+ // ReSharper disable once RedundantAssignment
+ private void DecryptPartialFinalBlock(ref Vector128 input, ref Vector128 output,
+ Vector128 tweak, Vector128 mask, int finalBlockLength)
+ {
+ Vector128 finalTweak = Gf128Mul(tweak, mask);
+
+ Vector128 tmp = Sse2.Xor(input, finalTweak);
+ tmp = _dataAesCore.DecryptBlock(tmp);
+ output = Sse2.Xor(tmp, finalTweak);
+
+ var x = new Buffer16();
+ ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output);
+ ref Buffer16 nextInBuf = ref Unsafe.As, Buffer16>(ref Unsafe.Add(ref input, 1));
+ ref Buffer16 nextOutBuf = ref Unsafe.As, Buffer16>(ref Unsafe.Add(ref output, 1));
+
+ for (int i = 0; i < finalBlockLength; i++)
+ {
+ nextOutBuf[i] = outBuf[i];
+ x[i] = nextInBuf[i];
+ }
+
+ for (int i = finalBlockLength; i < 16; i++)
+ {
+ x[i] = outBuf[i];
+ }
+
+ tmp = Sse2.Xor(x.As>(), tweak);
+ tmp = _dataAesCore.DecryptBlock(tmp);
+ output = Sse2.Xor(tmp, tweak);
+ }
+
+ private void EncryptPartialFinalBlock(ref Vector128 input, ref Vector128 output,
+ Vector128 tweak, int finalBlockLength)
+ {
+ ref Vector128 prevOutBlock = ref Unsafe.Subtract(ref output, 1);
+
+ var x = new Buffer16();
+ ref Buffer16 outBuf = ref Unsafe.As, Buffer16>(ref output);
+ ref Buffer16 inBuf = ref Unsafe.As, Buffer16>(ref input);
+ ref Buffer16 prevOutBuf = ref Unsafe.As, Buffer16>(ref prevOutBlock);
+
+ for (int i = 0; i < finalBlockLength; i++)
+ {
+ outBuf[i] = prevOutBuf[i];
+ x[i] = inBuf[i];
+ }
+
+ for (int i = finalBlockLength; i < 16; i++)
+ {
+ x[i] = prevOutBuf[i];
+ }
+
+ Vector128 tmp = Sse2.Xor(x.As>(), tweak);
+ tmp = _dataAesCore.EncryptBlock(tmp);
+ prevOutBlock = Sse2.Xor(tmp, tweak);
+ }
+
+ public void Transform(ReadOnlySpan input, Span output)
+ {
+ if (_decrypting)
+ {
+ Decrypt(input, output);
+ }
+ else
+ {
+ Encrypt(input, output);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Gf128Mul(Vector128 iv, Vector128 mask)
+ {
+ Vector128 tmp1 = Sse2.Add(iv.AsUInt64(), iv.AsUInt64()).AsByte();
+
+ Vector128 tmp2 = Sse2.Shuffle(iv.AsInt32(), 0x13).AsByte();
+ tmp2 = Sse2.ShiftRightArithmetic(tmp2.AsInt32(), 31).AsByte();
+ tmp2 = Sse2.And(mask, tmp2);
+
+ return Sse2.Xor(tmp1, tmp2);
+ }
+ }
+}
+#endif
diff --git a/src/LibHac/FsService/SaveDataIndexer.cs b/src/LibHac/FsService/SaveDataIndexer.cs
index 77810790..e9001f11 100644
--- a/src/LibHac/FsService/SaveDataIndexer.cs
+++ b/src/LibHac/FsService/SaveDataIndexer.cs
@@ -546,6 +546,7 @@ namespace LibHac.FsService
private void CloseReader(SaveDataInfoReader reader)
{
+ // ReSharper disable once RedundantAssignment
bool wasRemoved = OpenedReaders.Remove(reader);
Debug.Assert(wasRemoved);
diff --git a/src/LibHac/Util.cs b/src/LibHac/Util.cs
index ea20c212..ea8a7232 100644
--- a/src/LibHac/Util.cs
+++ b/src/LibHac/Util.cs
@@ -145,13 +145,13 @@ namespace LibHac
return true;
}
- public static void XorArrays(Span transformData, Span xorData)
+ public static void XorArrays(Span transformData, ReadOnlySpan xorData)
{
int sisdStart = 0;
if (Vector.IsHardwareAccelerated)
{
Span> dataVec = MemoryMarshal.Cast>(transformData);
- Span> xorVec = MemoryMarshal.Cast>(xorData);
+ ReadOnlySpan> xorVec = MemoryMarshal.Cast>(xorData);
sisdStart = dataVec.Length * Vector.Count;
for (int i = 0; i < dataVec.Length; i++)
@@ -166,6 +166,33 @@ namespace LibHac
}
}
+ public static void XorArrays(Span output, ReadOnlySpan input1, ReadOnlySpan input2)
+ {
+ int length = Math.Min(input1.Length, input2.Length);
+
+ int sisdStart = 0;
+ if (Vector.IsHardwareAccelerated)
+ {
+ int lengthVec = length / Vector.Count;
+
+ Span> outputVec = MemoryMarshal.Cast>(output);
+ ReadOnlySpan> input1Vec = MemoryMarshal.Cast>(input1);
+ ReadOnlySpan> input2Vec = MemoryMarshal.Cast>(input2);
+
+ sisdStart = lengthVec * Vector.Count;
+
+ for (int i = 0; i < lengthVec; i++)
+ {
+ outputVec[i] = input1Vec[i] ^ input2Vec[i];
+ }
+ }
+
+ for (int i = sisdStart; i < length; i++)
+ {
+ output[i] = (byte)(input1[i] ^ input2[i]);
+ }
+ }
+
public static void CopyStream(this Stream input, Stream output, long length, IProgressReport progress = null)
{
const int bufferSize = 0x8000;
diff --git a/src/hactoolnet/ProcessBench.cs b/src/hactoolnet/ProcessBench.cs
index f01727ea..36fb78f8 100644
--- a/src/hactoolnet/ProcessBench.cs
+++ b/src/hactoolnet/ProcessBench.cs
@@ -116,7 +116,8 @@ namespace hactoolnet
logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s");
}
- private static void RunCipherBenchmark(Func cipherNet, Func cipherLibHac, string label, IProgressReport logger)
+ private static void RunCipherBenchmark(Func cipherNet, Func cipherLibHac, bool benchBlocked,
+ string label, IProgressReport logger)
{
var srcData = new byte[Size];
@@ -131,14 +132,23 @@ namespace hactoolnet
if (AesCrypto.IsAesNiSupported()) CipherBenchmark(srcData, dstDataLh, cipherLibHac, Iterations, "LibHac impl: ", logger);
CipherBenchmark(srcData, dstDataNet, cipherNet, Iterations, ".NET impl: ", logger);
- if (AesCrypto.IsAesNiSupported()) CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, "LibHac impl (blocked): ", logger);
- CipherBenchmarkBlocked(srcData, dstDataBlockedNet, cipherNet, Iterations / 5, ".NET impl (blocked): ", logger);
+ if (benchBlocked)
+ {
+ if (AesCrypto.IsAesNiSupported())
+ CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, "LibHac impl (blocked): ", logger);
+
+ CipherBenchmarkBlocked(srcData, dstDataBlockedNet, cipherNet, Iterations / 5, ".NET impl (blocked): ", logger);
+ }
if (AesCrypto.IsAesNiSupported())
{
logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataNet)}");
- logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedLh)}");
- logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedNet)}");
+
+ if (benchBlocked)
+ {
+ logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedLh)}");
+ logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedNet)}");
+ }
}
}
@@ -197,12 +207,12 @@ namespace hactoolnet
Func