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
3 changes: 2 additions & 1 deletion BedrockLauncher.Core/BedrockCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@
config.PackagePath = Path.Combine(options.InstallDstFolder, "AppxManifest.xml");
config.CancellationToken = options.CancellationToken.GetValueOrDefault();
config.Timeout = new TimeSpan(0, 3, 0);
config.DeploymentOptions = is_installed ? DeploymentOptions.DevelopmentMode | DeploymentOptions.ForceUpdateFromAnyVersion
config.DeploymentOptions = is_installed
? DeploymentOptions.DevelopmentMode | DeploymentOptions.ForceUpdateFromAnyVersion
: DeploymentOptions.DevelopmentMode;
config.ProgressCallback = options.DeployProgress;

Expand All @@ -252,7 +253,7 @@

if (processes.Length > 0)
{
return processes

Check warning on line 256 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference return.

Check warning on line 256 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference return.
.Where(p => p.StartTime > startTime)
.OrderBy(p => (p.StartTime - startTime).TotalMilliseconds)
.FirstOrDefault();
Expand All @@ -262,7 +263,7 @@
await Task.Delay(200);
}

return null;

Check warning on line 266 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference return.

Check warning on line 266 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference return.
}
private static DateTime GetStartTimeSafe(Process proc)
{
Expand Down Expand Up @@ -303,7 +304,7 @@
UseShellExecute = true
});

Process minecraftProcess = null;

Check warning on line 307 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Converting null literal or possible null value to non-nullable type.


for (int i = 0; i < 10; i++)
Expand Down Expand Up @@ -417,7 +418,7 @@
processes = processes.Concat(process_oldVersion).ToArray();
process = processes.OrderBy(p => p.StartTime).Last();
}
return process;

Check warning on line 421 in BedrockLauncher.Core/BedrockCore.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference return.
}
/// <summary>
/// Remove Uwp Minecraft Game
Expand Down
2 changes: 1 addition & 1 deletion BedrockLauncher.Core/BedrockLauncher.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<Version>2.0.4.1-dev</Version>
<Version>2.0.4.2</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
Expand Down
312 changes: 181 additions & 131 deletions BedrockLauncher.Core/UwpRegister/UwpRegister.cs
Original file line number Diff line number Diff line change
@@ -1,137 +1,187 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Management.Deployment;

namespace BedrockLauncher.Core.UwpRegister;

public class DeploymentOptionsConfig
{
/// <summary>
/// Gets or sets the file system path to the package.
/// </summary>
public string PackagePath { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the options used to configure deployment behavior.
/// </summary>
public DeploymentOptions DeploymentOptions { get; set; }

/// <summary>
/// Gets or sets the cancellation token that is used to observe cancellation requests for the associated operation.
/// </summary>
/// <remarks>
/// Assign a cancellation token to enable cooperative cancellation of the operation. If no token is
/// provided, the operation cannot be cancelled through this property.
/// </remarks>
public CancellationToken CancellationToken { get; set; } = default;

/// <summary>
/// Progress callback
/// </summary>
public IProgress<DeploymentProgress>? ProgressCallback { get; set; }

/// <summary>
/// Gets or sets the maximum duration to wait before the operation times out.
/// </summary>
public TimeSpan? Timeout { get; set; }
}

public class UwpRegister
namespace BedrockLauncher.Core.UwpRegister
{
/// <summary>
/// Registers an appx package with flexible configuration
/// </summary>
/// <param name="config">Deployment configuration options</param>
/// <returns>Deployment result containing operation status</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PackageManager))]
public static async Task<DeploymentResult> RegisterAppxAsync(DeploymentOptionsConfig config)
{
ValidateConfig(config);
var manager = new PackageManager();
var asyncOperation = manager.RegisterPackageAsync(
new Uri(config.PackagePath),
null,
config.DeploymentOptions | DeploymentOptions.DevelopmentMode);

return await ExecuteWithTimeout(asyncOperation, config);
}

/// <summary>
/// Adds a framework appx package with flexible configuration
/// </summary>
/// <param name="config">Deployment configuration options</param>
/// <returns>Deployment result containing operation status</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PackageManager))]
public static async Task<DeploymentResult> AddAppxAsync(DeploymentOptionsConfig config)
{
ValidateConfig(config);

var packageManager = new PackageManager();
var asyncOperation = packageManager.AddPackageAsync(
new Uri(config.PackagePath),
null,
config.DeploymentOptions);

return await ExecuteWithTimeout(asyncOperation, config);
}

private static async Task<DeploymentResult> ExecuteWithTimeout(
IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> asyncOperation,
DeploymentOptionsConfig config)
{
if (config.Timeout.HasValue)
{
using var timeoutCts = new CancellationTokenSource(config.Timeout.Value);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
config.CancellationToken, timeoutCts.Token);

return await asyncOperation.AsTask(linkedCts.Token, config.ProgressCallback);
}

return await asyncOperation.AsTask(config.CancellationToken, config.ProgressCallback);
}
public static bool CheckForPackageVersion(string packageName, string version)
{
PackageManager packageManager = new PackageManager();

foreach (var package in packageManager.FindPackages())
{
if (package.Id.Name == packageName)
{
var currentVersion = $"{package.Id.Version.Major}.{package.Id.Version.Minor}.{package.Id.Version.Build}.{package.Id.Version.Revision}";

if (currentVersion == version)
{
return true;
}
}
}

return false;
}
public static bool IsPackageInstalled(string packageName)
{
if (string.IsNullOrWhiteSpace(packageName))
throw new ArgumentException("Package name cannot be null or empty", nameof(packageName));

PackageManager packageManager = new PackageManager();

foreach (var package in packageManager.FindPackages())
{
if (package.Id.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}
private static void ValidateConfig(DeploymentOptionsConfig config)
{
if (string.IsNullOrWhiteSpace(config.PackagePath))
throw new ArgumentException("Package path cannot be null or empty", nameof(config));

if (!Uri.TryCreate(config.PackagePath, UriKind.Absolute, out _))
throw new ArgumentException("Package path must be a valid absolute URI", nameof(config));
}
public class DeploymentOptionsConfig
{
/// <summary>
/// Gets or sets the file system path to the package.
/// </summary>
public string PackagePath { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the options used to configure deployment behavior.
/// </summary>
public DeploymentOptions DeploymentOptions { get; set; }

/// <summary>
/// Gets or sets the cancellation token that is used to observe cancellation requests for the associated operation.
/// </summary>
/// <remarks>
/// Assign a cancellation token to enable cooperative cancellation of the operation. If no token is
/// provided, the operation cannot be cancelled through this property.
/// </remarks>
public CancellationToken CancellationToken { get; set; } = default;

/// <summary>
/// Progress callback
/// </summary>
public IProgress<DeploymentProgress>? ProgressCallback { get; set; }

/// <summary>
/// Gets or sets the maximum duration to wait before the operation times out.
/// </summary>
public TimeSpan? Timeout { get; set; }
}

public class UwpRegister
{
/// <summary>
/// Registers an appx package with flexible configuration (no admin required)
/// </summary>
/// <param name="config">Deployment configuration options</param>
/// <returns>Deployment result containing operation status</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PackageManager))]
public static async Task<DeploymentResult> RegisterAppxAsync(DeploymentOptionsConfig config)
{
ValidateConfig(config);

// 处理包路径,确保是文件夹路径
string packageFolderPath = config.PackagePath;
if (packageFolderPath.StartsWith("file://"))
{
packageFolderPath = new Uri(packageFolderPath).LocalPath;
}

// 移除签名文件(如果存在)
RemoveSignatureFile(packageFolderPath);

var manager = new PackageManager();
var asyncOperation = manager.RegisterPackageAsync(
new Uri(config.PackagePath),
null,
config.DeploymentOptions | DeploymentOptions.DevelopmentMode);

return await ExecuteWithTimeout(asyncOperation, config);
}

/// <summary>
/// Adds a framework appx package with flexible configuration
/// </summary>
/// <param name="config">Deployment configuration options</param>
/// <returns>Deployment result containing operation status</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PackageManager))]
public static async Task<DeploymentResult> AddAppxAsync(DeploymentOptionsConfig config)
{
ValidateConfig(config);

// 处理包路径,确保是文件夹路径
string packageFolderPath = config.PackagePath;
if (packageFolderPath.StartsWith("file://"))
{
packageFolderPath = new Uri(packageFolderPath).LocalPath;
}

// 移除签名文件(如果存在)
RemoveSignatureFile(packageFolderPath);

var packageManager = new PackageManager();
var asyncOperation = packageManager.AddPackageAsync(
new Uri(config.PackagePath),
null,
config.DeploymentOptions | DeploymentOptions.DevelopmentMode);

return await ExecuteWithTimeout(asyncOperation, config);
}

/// <summary>
/// Removes the signature file from the package folder
/// </summary>
/// <param name="packageFolderPath">Path to the unpacked package folder</param>
private static void RemoveSignatureFile(string packageFolderPath)
{
try
{
string signaturePath = Path.Combine(packageFolderPath, "AppxSignature.p7x");
if (File.Exists(signaturePath))
{
File.Delete(signaturePath);
}
}
catch (Exception ex)
{
// 记录错误但继续执行
Console.WriteLine($"Error removing signature file: {ex.Message}");
}
}

private static async Task<DeploymentResult> ExecuteWithTimeout(
IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> asyncOperation,
DeploymentOptionsConfig config)
{
if (config.Timeout.HasValue)
{
using var timeoutCts = new CancellationTokenSource(config.Timeout.Value);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
config.CancellationToken, timeoutCts.Token);

return await asyncOperation.AsTask(linkedCts.Token, config.ProgressCallback);
}

return await asyncOperation.AsTask(config.CancellationToken, config.ProgressCallback);
}

public static bool CheckForPackageVersion(string packageName, string version)
{
PackageManager packageManager = new PackageManager();

foreach (var package in packageManager.FindPackages())
{
if (package.Id.Name == packageName)
{
var currentVersion = $"{package.Id.Version.Major}.{package.Id.Version.Minor}.{package.Id.Version.Build}.{package.Id.Version.Revision}";

if (currentVersion == version)
{
return true;
}
}
}

return false;
}

public static bool IsPackageInstalled(string packageName)
{
if (string.IsNullOrWhiteSpace(packageName))
throw new ArgumentException("Package name cannot be null or empty", nameof(packageName));

PackageManager packageManager = new PackageManager();

foreach (var package in packageManager.FindPackages())
{
if (package.Id.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}

private static void ValidateConfig(DeploymentOptionsConfig config)
{
if (string.IsNullOrWhiteSpace(config.PackagePath))
throw new ArgumentException("Package path cannot be null or empty", nameof(config));

if (!Uri.TryCreate(config.PackagePath, UriKind.Absolute, out _))
throw new ArgumentException("Package path must be a valid absolute URI", nameof(config));
}
}
}
Loading