diff --git a/BedrockLauncher.Core/BedrockCore.cs b/BedrockLauncher.Core/BedrockCore.cs
index ca96be8..1426cce 100644
--- a/BedrockLauncher.Core/BedrockCore.cs
+++ b/BedrockLauncher.Core/BedrockCore.cs
@@ -189,7 +189,7 @@ await Task.Run((async () =>
MinecraftGameTypeVersion.Beta => _DEFINE_REF2.pre,
_=>null
});
- var msiXvdDecoder = new MsiXVDDecoder(cik);
+ var msiXvdDecoder = new MsiXVDDecoder(cik, options.UseHardwareDecode);
var msiXvdStream = new MsiXVDStream(options.FileFullPath);
msiXvdStream.Parse();
options.InstallStates?.Report(InstallStates.Extracting);
diff --git a/BedrockLauncher.Core/BedrockLauncher.Core.csproj b/BedrockLauncher.Core/BedrockLauncher.Core.csproj
index b893b52..c860bf5 100644
--- a/BedrockLauncher.Core/BedrockLauncher.Core.csproj
+++ b/BedrockLauncher.Core/BedrockLauncher.Core.csproj
@@ -20,11 +20,11 @@
true
- 2.0.4.4-dev
+ 2.0.4.5-dev
- 2.0.4.4
+ 2.0.4.5
diff --git a/BedrockLauncher.Core/CoreOption/LocalGamePackageOptions.cs b/BedrockLauncher.Core/CoreOption/LocalGamePackageOptions.cs
index 9790b42..b8029a1 100644
--- a/BedrockLauncher.Core/CoreOption/LocalGamePackageOptions.cs
+++ b/BedrockLauncher.Core/CoreOption/LocalGamePackageOptions.cs
@@ -60,6 +60,13 @@ public class LocalGamePackageOptions
/// Title Name only being used When it is in Uwp mode
///
public string? GameName;
+
+ ///
+ /// Gets or sets whether to use hardware-accelerated AES-NI/SSE2 decoding for GDK packages.
+ /// Set to false to use software-based AES decryption (compatible with CPUs lacking AES-NI support).
+ /// Default is true (hardware decode).
+ ///
+ public bool UseHardwareDecode { get; set; } = true;
}
///
diff --git a/BedrockLauncher.Core/GdkDecode/MsiXVDDecoder.cs b/BedrockLauncher.Core/GdkDecode/MsiXVDDecoder.cs
index 3bdd250..e3ea6be 100644
--- a/BedrockLauncher.Core/GdkDecode/MsiXVDDecoder.cs
+++ b/BedrockLauncher.Core/GdkDecode/MsiXVDDecoder.cs
@@ -2,20 +2,59 @@
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
+using CryptAes = System.Security.Cryptography.Aes;
+using SysCrypt = System.Security.Cryptography;
namespace BedrockLauncher.Core.GdkDecode;
-public class MsiXVDDecoder
+public class MsiXVDDecoder : IDisposable
{
public KeySinagl d;
public KeySinagl t;
+ private readonly bool _useHardware;
+ private readonly CryptAes? _swAesD;
+ private readonly CryptAes? _swAesT;
- public MsiXVDDecoder(CikKey key)
+ public MsiXVDDecoder(CikKey key) : this(key, true)
{
- d.Init(key.DKey,true);
- t.Init(key.TKey,false);
}
+ public MsiXVDDecoder(CikKey key, bool useHardware)
+ {
+ _useHardware = useHardware;
+ if (useHardware)
+ {
+ d.Init(key.DKey, true);
+ t.Init(key.TKey, false);
+ _swAesD = null;
+ _swAesT = null;
+ }
+ else
+ {
+ _swAesD = CryptAes.Create();
+ _swAesD.KeySize = 128;
+ _swAesD.Key = key.DKey;
+ _swAesD.Mode = SysCrypt.CipherMode.ECB;
+ _swAesD.Padding = SysCrypt.PaddingMode.None;
+
+ _swAesT = CryptAes.Create();
+ _swAesT.KeySize = 128;
+ _swAesT.Key = key.TKey;
+ _swAesT.Mode = SysCrypt.CipherMode.ECB;
+ _swAesT.Padding = SysCrypt.PaddingMode.None;
+ }
+ }
+
+ public int Decrypt(ReadOnlySpan input, Span output, ReadOnlySpan tweakIv)
+ {
+ if (_useHardware)
+ return DecryptHardware(input, output, tweakIv);
+ else
+ return DecryptSoftware(input, output, tweakIv);
+ }
+
+ #region Hardware Decode (AES-NI / SSE2)
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128 Gf128Mul(Vector128 iv, Vector128 mask)
{
@@ -26,7 +65,7 @@ private static Vector128 Gf128Mul(Vector128 iv, Vector128 mask
return Sse2.Xor(tmp1, tmp2);
}
- public int Decrypt(ReadOnlySpan input, Span output, ReadOnlySpan tweakIv)
+ private int DecryptHardware(ReadOnlySpan input, Span output, ReadOnlySpan tweakIv)
{
if (tweakIv.Length < 16)
return 0;
@@ -134,10 +173,10 @@ public int Decrypt(ReadOnlySpan input, Span output, ReadOnlySpan in0,
Vector128 in1,
Vector128 in2,
@@ -268,4 +307,108 @@ public void DecryptBlocks8(
out7 = Aes.DecryptLast(b7, key);
}
+ #endregion
+
+ #region Software Decode (managed AES)
+
+ private int DecryptSoftware(ReadOnlySpan input, Span output, ReadOnlySpan tweakIv)
+ {
+ if (tweakIv.Length < 16)
+ return 0;
+
+ int length = Math.Min(input.Length, output.Length);
+ if (length == 0)
+ return 0;
+
+ int remainingBlocks = length >> 4;
+ int leftover = length & 0xF;
+
+ if (leftover != 0)
+ remainingBlocks--;
+
+ if (remainingBlocks <= 0 && leftover == 0)
+ return 0;
+
+ byte[] tweak = new byte[16];
+ _swAesT!.EncryptEcb(tweakIv[..16], tweak, SysCrypt.PaddingMode.None);
+
+ Span tempIn = stackalloc byte[16];
+ Span tempOut = stackalloc byte[16];
+ int offset = 0;
+
+ while (remainingBlocks > 0)
+ {
+ XorBytes(input.Slice(offset, 16), tweak, tempIn);
+ _swAesD.DecryptEcb(tempIn, tempOut, SysCrypt.PaddingMode.None);
+ XorBytes(tempOut, tweak, output.Slice(offset, 16));
+ Gf128MulSoftware(tweak);
+ offset += 16;
+ remainingBlocks--;
+ }
+
+ if (leftover != 0)
+ {
+ byte[] finalTweak = tweak[..]; // copy: finalTweak = Gf128Mul(tweak)
+ Gf128MulSoftware(finalTweak);
+
+ XorBytes(input.Slice(offset, 16), finalTweak, tempIn);
+ _swAesD.DecryptEcb(tempIn, tempOut, SysCrypt.PaddingMode.None);
+ XorBytes(tempOut, finalTweak, output.Slice(offset, 16));
+
+ Span currentOut = output.Slice(offset, 16);
+ ReadOnlySpan nextIn = input.Slice(offset + 16, leftover);
+ Span nextOut = output.Slice(offset + 16, leftover);
+
+ Span temp = stackalloc byte[16];
+
+ for (int i = 0; i < leftover; i++)
+ {
+ nextOut[i] = currentOut[i];
+ temp[i] = nextIn[i];
+ }
+
+ for (int i = leftover; i < 16; i++)
+ {
+ temp[i] = currentOut[i];
+ }
+
+ XorBytes(temp, tweak, tempIn);
+ _swAesD.DecryptEcb(tempIn, tempOut, SysCrypt.PaddingMode.None);
+ XorBytes(tempOut, tweak, output.Slice(offset, 16));
+ }
+
+ return length;
+ }
+
+ private static void XorBytes(ReadOnlySpan a, ReadOnlySpan b, Span result)
+ {
+ for (int i = 0; i < 16; i++)
+ result[i] = (byte)(a[i] ^ b[i]);
+ }
+
+ private static void Gf128MulSoftware(Span iv)
+ {
+ int carryBit127 = (iv[15] >> 7) & 1;
+ int carryBit63 = (iv[7] >> 7) & 1;
+
+ for (int i = 7; i > 0; i--)
+ iv[i] = (byte)((iv[i] << 1) | (iv[i - 1] >> 7));
+ iv[0] <<= 1;
+
+ for (int i = 15; i > 8; i--)
+ iv[i] = (byte)((iv[i] << 1) | (iv[i - 1] >> 7));
+ iv[8] <<= 1;
+
+ if (carryBit63 != 0) iv[8] ^= 0x01;
+ if (carryBit127 != 0) iv[0] ^= 0x87;
+ }
+
+ #endregion
+
+ public void Dispose()
+ {
+ _swAesD?.Dispose();
+ _swAesT?.Dispose();
+ GC.SuppressFinalize(this);
+ }
}
\ No newline at end of file
diff --git a/TestProject/Program.cs b/TestProject/Program.cs
index 50b31f2..3499d31 100644
--- a/TestProject/Program.cs
+++ b/TestProject/Program.cs
@@ -1,6 +1,7 @@
using BedrockLauncher.Core;
using BedrockLauncher.Core.CoreOption;
-using System.Diagnostics;
+using Windows.Management.Deployment;
+using BedrockLauncher.Core.Utils;
namespace TestProject
{
@@ -10,15 +11,23 @@ static void Main(string[] args)
{
var bedrockCore = new BedrockCore();
bedrockCore.InitAsync().Wait();
- var launchOptions = new LaunchOptions()
+
+ bedrockCore.InstallPackageAsync(new LocalGamePackageOptions()
{
- GameFolder = "D:\\BedrockBoot\\bedrock_versions\\1.21.11401",
- GameType = MinecraftGameTypeVersion.Release,
- MinecraftBuildType = MinecraftBuildTypeVersion.UWP,
- LaunchArgs = "minecraft://creator/?Editor=true"
- };
- var process = bedrockCore.LaunchGameAsync(launchOptions).Result;
- Console.WriteLine(process.Id);
+ FileFullPath = @"D:\BedrockBoot\version_save\1.26.2.insPack",
+ Type = MinecraftBuildTypeVersion.GDK,
+ GameTypeVersion = MinecraftGameTypeVersion.Release,
+ InstallDstFolder = Path.GetFullPath(@"E:\GDK_TestInstall\1.26_2"),
+ UseHardwareDecode = false,
+ DeployProgress = new Progress(p =>
+ Console.WriteLine($"Deploy: {p.percentage}%")),
+ ExtractionProgress = new Progress(p =>
+ Console.WriteLine($"Extract: [{p.CurrentCount}/{p.TotalCount}] {p.FileName}")),
+ InstallStates = new Progress(s =>
+ Console.WriteLine($"State: {s}")),
+ }).Wait();
+
+ Console.WriteLine("Install done.");
}
}
}