diff --git a/BedrockLauncher.Core/BedrockCore.cs b/BedrockLauncher.Core/BedrockCore.cs index 374aa9c..894fa1b 100644 --- a/BedrockLauncher.Core/BedrockCore.cs +++ b/BedrockLauncher.Core/BedrockCore.cs @@ -51,8 +51,7 @@ public BedrockCore(CoreOptions options) /// public async Task InitAsync() { - await Task.Run((() => - { + if (Options.IsAutoOpenDevelopment) { if (!GetWindowsDevelopmentState()) @@ -69,9 +68,14 @@ await Task.Run((() => VCRuntimeHelper.CompleteVCRuntimeAsync(RuntimeInformation.OSArchitecture).Wait(); } } - })); - } + if (Options.IsAutoCompleteGameInput) + { + await AutoCompleteGameInput(); + } + + } + /// /// Get Windows Development state /// @@ -239,11 +243,11 @@ await ZipExtractor.ExtractWithProgressAsync(options.FileFullPath, options.Instal return null; } /// - /// Starts the Minecraft game process based on the specified launch options + /// Launch the Minecraft game process based on the specified launch options /// /// The launch options containing game folder, arguments, and build type /// The Process object representing the launched game instance - public async Task StartGameAsync(LaunchOptions options) + public async Task LaunchGameAsync(LaunchOptions options) { var process = new Process(); if (options.MinecraftBuildType == MinecraftBuildTypeVersion.GDK) @@ -254,6 +258,7 @@ public async Task StartGameAsync(LaunchOptions options) info.Arguments = options.LaunchArgs; info.UseShellExecute = false; info.CreateNoWindow = true; + info.WorkingDirectory = options.GameFolder; process.StartInfo = info; process.Start(); options.Progress?.Report(LaunchState.Launched); @@ -323,7 +328,7 @@ public async Task StartGameAsync(LaunchOptions options) { TargetApplicationPackageFamilyName = packageFamily }; - if (options.LaunchArgs != null) + if (options?.LaunchArgs == string.Empty) { await Launcher.LaunchUriAsync(new Uri(options.LaunchArgs), options_st); } @@ -399,4 +404,21 @@ public async Task GetPackageUri(BuildInfo buildInfo,Architecture device throw new BedrockCoreNoAvailbaleVersionUri("There is no available Uri to download"); return await GetPackageUriInside(find.MetaData.Last()); } + /// + /// Ensures that the GameInput runtime is installed on the system, installing it if necessary. + /// + /// This method checks for the presence of the GameInput runtime using its MSI product identifier. If + /// the runtime is not installed, it initiates the installation process. Callers can await the returned task to ensure + /// the operation completes before proceeding. + /// A task that represents the asynchronous operation. + public async Task AutoCompleteGameInput() + { + var isMsiInstalled = MsiHelper.IsMsiProductInstalledByGuid("64d0ccb1-329e-d507-0886-47e53d59ae21"); + if (!isMsiInstalled) + { + await VCRuntimeHelper.InstallGameInput(); + } + return; + } + } \ No newline at end of file diff --git a/BedrockLauncher.Core/BedrockLauncher.Core.csproj b/BedrockLauncher.Core/BedrockLauncher.Core.csproj index 3c839a7..2fd6634 100644 --- a/BedrockLauncher.Core/BedrockLauncher.Core.csproj +++ b/BedrockLauncher.Core/BedrockLauncher.Core.csproj @@ -20,11 +20,11 @@ true - 2.0.0-rc.2-dev + 2.0.0-dev - 2.0.0-rc.2 + 2.0.0 diff --git a/BedrockLauncher.Core/CoreOption/CoreOptions.cs b/BedrockLauncher.Core/CoreOption/CoreOptions.cs index 65f61b4..4579cbd 100644 --- a/BedrockLauncher.Core/CoreOption/CoreOptions.cs +++ b/BedrockLauncher.Core/CoreOption/CoreOptions.cs @@ -19,4 +19,8 @@ public class CoreOptions /// Gets a value indicating whether MD5 checksum verification is enabled. /// public bool IsCheckMD5 { get; set; } + /// + /// Gets or sets a value indicating whether the input is treated as game input for auto-complete operations. + /// + public bool IsAutoCompleteGameInput { get; set; } } \ No newline at end of file diff --git a/BedrockLauncher.Core/DependsComplete/VCRuntimeHelper.cs b/BedrockLauncher.Core/DependsComplete/VCRuntimeHelper.cs index 39c3240..25a54d3 100644 --- a/BedrockLauncher.Core/DependsComplete/VCRuntimeHelper.cs +++ b/BedrockLauncher.Core/DependsComplete/VCRuntimeHelper.cs @@ -5,6 +5,7 @@ using System.Text; using BedrockLauncher.Core.UwpRegister; using Windows.Management.Deployment; +using BedrockLauncher.Core.Utils; namespace BedrockLauncher.Core.DependsComplete; @@ -106,34 +107,6 @@ await UwpRegister.UwpRegister.AddAppxAsync(new DeploymentOptionsConfig } } } - [DllImport("msi.dll", CharSet = CharSet.Unicode)] - static extern Int32 MsiInstallProduct(string szPackagePath, string szCommandLine); - - [DllImport("msi.dll", CharSet = CharSet.Unicode)] - static extern Int32 MsiConfigureProduct(string szProduct, int iInstallLevel, int eInstallState); - - [DllImport("msi.dll", SetLastError = true)] - static extern int MsiGetProductInfo(string productCode, string property, - [Out] StringBuilder valueBuf, ref int len); - - /// - /// Use Windows Installer API To Install MSI - /// - private static bool InstallUsingMsiApi(string msiPath) - { - try - { - string commandLine = "ACTION=INSTALL REBOOT=ReallySuppress UILevel=2"; - - int result = MsiInstallProduct(msiPath, commandLine); - - return result == 0; // ERROR_SUCCESS - } - catch - { - return false; - } - } public static async Task InstallGameInput() { try @@ -149,7 +122,8 @@ async Task DownloadPackageAsync(string uri) var packages = await DownloadPackageAsync(VCUri.GameInputRedist); var fileName = Path.GetTempFileName()+".msi"; - _ = InstallUsingMsiApi(fileName); + await File.WriteAllBytesAsync(fileName, packages); + _ = MsiHelper.InstallMsiSilently(fileName); } } catch diff --git a/BedrockLauncher.Core/Utils/CheckUwp.cs b/BedrockLauncher.Core/Utils/CheckUwp.cs new file mode 100644 index 0000000..2a5362f --- /dev/null +++ b/BedrockLauncher.Core/Utils/CheckUwp.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Windows.Management.Deployment; + +namespace BedrockLauncher.Core.Utils +{ + public static class CheckUwp + { + public static bool IsUwpPackageInstalled(string packageFamilyName) + { + if (string.IsNullOrWhiteSpace(packageFamilyName)) + throw new ArgumentException("PackageFamilyName can't be empty", nameof(packageFamilyName)); + + try + { + var packageManager = new PackageManager(); + + var packages = packageManager.FindPackagesForUser(string.Empty); + + foreach (var package in packages) + { + if (package.Id.FamilyName.Equals(packageFamilyName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + catch + { + throw; + } + } + } +} diff --git a/BedrockLauncher.Core/Utils/MsiInstaller.cs b/BedrockLauncher.Core/Utils/MsiInstaller.cs new file mode 100644 index 0000000..b2138ad --- /dev/null +++ b/BedrockLauncher.Core/Utils/MsiInstaller.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32; + +namespace BedrockLauncher.Core.Utils +{ + public static class MsiHelper + { + /// + /// Silently installs an MSI package + /// + /// Full path to the MSI file + /// Additional installation parameters + /// True if installation succeeded, false otherwise + public static int InstallMsiSilently(string msiFilePath, string additionalArgs = "") + { + try + { + // Validate that the file exists + if (!File.Exists(msiFilePath)) + { + throw new IOException($"Error: MSI file not found '{msiFilePath}'"); + } + + // Use msiexec.exe to install + Process process = new Process(); + process.StartInfo.FileName = "msiexec.exe"; + + // Key parameters: + // /i - Perform installation + // /qn - Completely silent, no UI + // /norestart - Do not restart after installation + // /l*v - Log verbose output (optional, for debugging) + string arguments = $"/i \"{msiFilePath}\" /qn /norestart {additionalArgs}"; + + process.StartInfo.Arguments = arguments; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; // Don't create a window + process.StartInfo.RedirectStandardOutput = false; // No output needed + + process.Start(); + process.WaitForExit(); // Wait for installation to complete + + // Check exit code + return process.ExitCode; + + } + catch (Exception ex) + { + throw; + } + } + public static bool IsMsiProductInstalledByGuid(string productGuid) + { + + string[] registryPaths = { + @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", + @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" + }; + + using (RegistryKey localMachine = Registry.LocalMachine) + { + foreach (string registryPath in registryPaths) + { + using (RegistryKey key = localMachine.OpenSubKey(registryPath)) + { + if (key == null) continue; + + foreach (string subKeyName in key.GetSubKeyNames()) + { + using (RegistryKey subKey = key.OpenSubKey(subKeyName)) + { + // 检查ProductCode + string productCode = subKey?.GetValue("ProductCode") as string; + if (!string.IsNullOrEmpty(productCode) && + productCode.Equals(productGuid, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // 或者检查UninstallString中是否包含ProductCode + string uninstallString = subKey?.GetValue("UninstallString") as string; + if (!string.IsNullOrEmpty(uninstallString) && + uninstallString.Contains(productGuid, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + } + } + + return false; + } + } + +} diff --git a/BedrockLauncher.Core/VersionJsons/SoureJson.cs b/BedrockLauncher.Core/VersionJsons/SoureJson.cs index a3acf4f..a9f2e1e 100644 --- a/BedrockLauncher.Core/VersionJsons/SoureJson.cs +++ b/BedrockLauncher.Core/VersionJsons/SoureJson.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using BedrockLauncher.Core; using BedrockLauncher.Core.SoureGenerate; @@ -10,22 +11,32 @@ public class BuildDatabase [JsonExtensionData] public Dictionary ExtensionData { get; set; } = new(); - [JsonIgnore] public Dictionary Builds => GetBuildsFromExtensionData(); + [JsonIgnore] public IAsyncEnumerable> Builds => GetBuildsFromExtensionData(); - private Dictionary GetBuildsFromExtensionData() + private async IAsyncEnumerable> GetBuildsFromExtensionData() { - var result = new Dictionary(); - foreach (var (key, value) in ExtensionData) + { if (value is JsonElement element && element.ValueKind == JsonValueKind.Object) { - var buildInfo = JsonSerializer.Deserialize( - element.GetRawText(), - BuildDatabaseContext.Default.DictionaryStringBuildInfo); - if (buildInfo != null) result = buildInfo; - } + foreach (var jsonProperty in element.EnumerateObject()) + { + var buildInfo = JsonSerializer.Deserialize( + jsonProperty.Value.GetRawText(), + BuildDatabaseContext.Default.BuildInfo); + + if (buildInfo != null) + { + yield return new KeyValuePair(jsonProperty.Name, buildInfo); + } - return result; + + await Task.Yield(); + } + + + } + } } } diff --git a/BedrockLauncher.Core/VersionJsons/VersionsHelper.cs b/BedrockLauncher.Core/VersionJsons/VersionsHelper.cs index eb5d137..bcfb4d6 100644 --- a/BedrockLauncher.Core/VersionJsons/VersionsHelper.cs +++ b/BedrockLauncher.Core/VersionJsons/VersionsHelper.cs @@ -25,8 +25,20 @@ public static class VersionsHelper { using (var client = new HttpClient()) { - var data = await client.GetStringAsync(httpAddress, cancellationToken); - var builds = JsonSerializer.Deserialize(data, BuildDatabaseContext.Default.BuildDatabase); + var response = await client.GetAsync( + httpAddress, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + response.EnsureSuccessStatusCode(); + + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); + + var builds = await JsonSerializer.DeserializeAsync( + stream, + BuildDatabaseContext.Default.BuildDatabase, + cancellationToken); + return builds; } } diff --git a/CoreTest/LaunchTest.cs b/CoreTest/LaunchTest.cs index 0c427be..54e21cb 100644 --- a/CoreTest/LaunchTest.cs +++ b/CoreTest/LaunchTest.cs @@ -1,5 +1,6 @@ using BedrockLauncher.Core; using BedrockLauncher.Core.CoreOption; +using BedrockLauncher.Core.DependsComplete; namespace CoreTest; @@ -13,11 +14,24 @@ public void Test() bedrockCore.InitAsync().Wait(); var launchOptions = new LaunchOptions() { - GameFolder = Path.GetFullPath("C:\\Users\\Administrator\\AppData\\Roaming\\RoundStudio\\BedrockBoot\\Bedrock_Data\\bedrock_versions\\1.21.120202"), - MinecraftBuildType = MinecraftBuildTypeVersion.UWP, - GameType = MinecraftGameTypeVersion.Preview, - LaunchArgs = "minecraft://creator/?Editor=true" + GameFolder = Path.GetFullPath("D:\\Windows11\\newdesk\\Code\\bedrock_versions\\1.21.13101"), + MinecraftBuildType = MinecraftBuildTypeVersion.GDK, + GameType = MinecraftGameTypeVersion.Release, + LaunchArgs = null }; - bedrockCore.StartGameAsync(launchOptions).Wait(); + bedrockCore.LaunchGameAsync(launchOptions).Wait(); + } + [TestMethod] + public void MsiTest() + { + var bedrockCore = new BedrockCore(); + var installGameInput = VCRuntimeHelper.InstallGameInput(); + installGameInput.Wait(); + } + [TestMethod] + public void GameInputInstallTest() + { + var bedrockCore = new BedrockCore(); + bedrockCore.AutoCompleteGameInput().Wait(); } } diff --git a/CoreTest/UriGetTest.cs b/CoreTest/UriGetTest.cs index 00208cc..79ccfc1 100644 --- a/CoreTest/UriGetTest.cs +++ b/CoreTest/UriGetTest.cs @@ -8,12 +8,18 @@ namespace CoreTest; public class UriGetTest { [TestMethod] - public void Test() + public async Task TestAsync() { var bedrockCore = new BedrockCore(); bedrockCore.InitAsync().Wait(); var buildDatabaseAsync = VersionsHelper.GetBuildDatabaseAsync("https://data.mcappx.com/v2/bedrock.json").Result; - var result = bedrockCore.GetPackageUri(buildDatabaseAsync.Builds["1.21.114"],Architecture.X64).Result; + BuildInfo build = null; + await foreach (var kvp in buildDatabaseAsync.Builds) + { + if (kvp.Key == "1.21.131") + build = kvp.Value; + } + var result = bedrockCore.GetPackageUri(build,Architecture.X64).Result; Console.WriteLine(result); } }