Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions BedrockLauncher.Core/BedrockCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ public BedrockCore(CoreOptions options)
/// </summary>
public async Task InitAsync()
{
await Task.Run((() =>
{

if (Options.IsAutoOpenDevelopment)
{
if (!GetWindowsDevelopmentState())
Expand All @@ -69,9 +68,14 @@ await Task.Run((() =>
VCRuntimeHelper.CompleteVCRuntimeAsync(RuntimeInformation.OSArchitecture).Wait();
}
}
}));
}

if (Options.IsAutoCompleteGameInput)
{
await AutoCompleteGameInput();
}

}

/// <summary>
/// Get Windows Development state
/// </summary>
Expand Down Expand Up @@ -239,11 +243,11 @@ await ZipExtractor.ExtractWithProgressAsync(options.FileFullPath, options.Instal
return null;
}
/// <summary>
/// Starts the Minecraft game process based on the specified launch options
/// Launch the Minecraft game process based on the specified launch options
/// </summary>
/// <param name="options">The launch options containing game folder, arguments, and build type</param>
/// <returns>The Process object representing the launched game instance</returns>
public async Task<Process> StartGameAsync(LaunchOptions options)
public async Task<Process> LaunchGameAsync(LaunchOptions options)
{
var process = new Process();
if (options.MinecraftBuildType == MinecraftBuildTypeVersion.GDK)
Expand All @@ -254,6 +258,7 @@ public async Task<Process> 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);
Expand Down Expand Up @@ -323,7 +328,7 @@ public async Task<Process> StartGameAsync(LaunchOptions options)
{
TargetApplicationPackageFamilyName = packageFamily
};
if (options.LaunchArgs != null)
if (options?.LaunchArgs == string.Empty)
{
await Launcher.LaunchUriAsync(new Uri(options.LaunchArgs), options_st);
}
Expand Down Expand Up @@ -399,4 +404,21 @@ public async Task<string> GetPackageUri(BuildInfo buildInfo,Architecture device
throw new BedrockCoreNoAvailbaleVersionUri("There is no available Uri to download");
return await GetPackageUriInside(find.MetaData.Last());
}
/// <summary>
/// Ensures that the GameInput runtime is installed on the system, installing it if necessary.
/// </summary>
/// <remarks>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.</remarks>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task AutoCompleteGameInput()
{
var isMsiInstalled = MsiHelper.IsMsiProductInstalledByGuid("64d0ccb1-329e-d507-0886-47e53d59ae21");
if (!isMsiInstalled)
{
await VCRuntimeHelper.InstallGameInput();
}
return;
}

}
4 changes: 2 additions & 2 deletions BedrockLauncher.Core/BedrockLauncher.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<Version>2.0.0-rc.2-dev</Version>
<Version>2.0.0-dev</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Version>2.0.0-rc.2</Version>
<Version>2.0.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
4 changes: 4 additions & 0 deletions BedrockLauncher.Core/CoreOption/CoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public class CoreOptions
/// Gets a value indicating whether MD5 checksum verification is enabled.
/// </summary>
public bool IsCheckMD5 { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the input is treated as game input for auto-complete operations.
/// </summary>
public bool IsAutoCompleteGameInput { get; set; }
}
32 changes: 3 additions & 29 deletions BedrockLauncher.Core/DependsComplete/VCRuntimeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using BedrockLauncher.Core.UwpRegister;
using Windows.Management.Deployment;
using BedrockLauncher.Core.Utils;

namespace BedrockLauncher.Core.DependsComplete;

Expand Down Expand Up @@ -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);

/// <summary>
/// Use Windows Installer API To Install MSI
/// </summary>
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
Expand All @@ -149,7 +122,8 @@ async Task<byte[]> 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
Expand Down
37 changes: 37 additions & 0 deletions BedrockLauncher.Core/Utils/CheckUwp.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
101 changes: 101 additions & 0 deletions BedrockLauncher.Core/Utils/MsiInstaller.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Silently installs an MSI package
/// </summary>
/// <param name="msiFilePath">Full path to the MSI file</param>
/// <param name="additionalArgs">Additional installation parameters</param>
/// <returns>True if installation succeeded, false otherwise</returns>
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;
}
}

}
31 changes: 21 additions & 10 deletions BedrockLauncher.Core/VersionJsons/SoureJson.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,22 +11,32 @@ public class BuildDatabase

[JsonExtensionData] public Dictionary<string, object> ExtensionData { get; set; } = new();

[JsonIgnore] public Dictionary<string, BuildInfo> Builds => GetBuildsFromExtensionData();
[JsonIgnore] public IAsyncEnumerable<KeyValuePair<string, BuildInfo>> Builds => GetBuildsFromExtensionData();

private Dictionary<string, BuildInfo> GetBuildsFromExtensionData()
private async IAsyncEnumerable<KeyValuePair<string, BuildInfo>> GetBuildsFromExtensionData()
{
var result = new Dictionary<string, BuildInfo>();

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<string, BuildInfo>(jsonProperty.Name, buildInfo);
}

return result;

await Task.Yield();
}


}
}
}
}

Expand Down
16 changes: 14 additions & 2 deletions BedrockLauncher.Core/VersionJsons/VersionsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BuildDatabase>(
stream,
BuildDatabaseContext.Default.BuildDatabase,
cancellationToken);

return builds;
}
}
Expand Down
Loading
Loading