Last active
February 14, 2026 06:42
-
-
Save Aetopia/afc4b44df5a98e5f2e74e6dea246d60a to your computer and use it in GitHub Desktop.
Purely managed implementation for launching Minecraft: Bedrock Edition on Windows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.ComponentModel; | |
| using System.Diagnostics; | |
| using System.IO; | |
| using System.Management.Automation; | |
| using System.Management.Automation.Runspaces; | |
| using System.Runtime.InteropServices; | |
| using System.Threading.Tasks; | |
| using Windows.System.Diagnostics; | |
| namespace Basalt.Launcher.Runtime.Game; | |
| sealed class MinecraftGDK : Minecraft | |
| { | |
| static readonly InitialSessionState s_async = InitialSessionState.Create(); | |
| static MinecraftGDK() | |
| { | |
| s_async.ImportPSModule(["Appx"]); | |
| s_async.ThreadOptions = PSThreadOptions.UseNewThread; | |
| } | |
| static async Task<uint?> GetProcessIdAsync() | |
| { | |
| await Task.Yield(); | |
| foreach (var process in ProcessDiagnosticInfo.GetForProcesses()) | |
| { | |
| if (!process.ExecutableFileName.Equals("Minecraft.Windows.exe", StringComparison.OrdinalIgnoreCase)) | |
| continue; | |
| foreach (var app in process.GetAppDiagnosticInfos()) | |
| { | |
| if (app.AppInfo.PackageFamilyName.Equals(PackageFamilyName, StringComparison.OrdinalIgnoreCase)) | |
| return process.ProcessId; | |
| } | |
| } | |
| return null; | |
| } | |
| protected override async Task<uint?> ActivateAsync() | |
| { | |
| if (await GetProcessIdAsync() is { } processId) | |
| return processId; | |
| if (!IsInstalled) | |
| throw new InvalidOperationException(); | |
| var path = Path.Combine(Package.InstalledPath, "Minecraft.Windows.exe"); | |
| if (!File.Exists(path)) throw new FileNotFoundException(null, path); | |
| using var powershell = PowerShell.Create(s_async); | |
| powershell.AddCommand("Invoke-CommandInDesktopPackage"); | |
| powershell.AddParameter("Command", path); | |
| powershell.AddParameter("AppId", "Game"); | |
| powershell.AddParameter("PackageFamilyName", PackageFamilyName); | |
| await Task.Factory.FromAsync(powershell.BeginInvoke(), _ => powershell.EndInvoke(_)); | |
| return await GetProcessIdAsync(); | |
| } | |
| public override async Task<uint?> LaunchAsync(bool initialized) | |
| { | |
| if (await GetProcessIdAsync() is { } currentProcessId) | |
| { | |
| using var currentProcess = Process.GetProcessById((int)currentProcessId); | |
| if (currentProcess.MainWindowHandle > (nint)0) return (uint)currentProcess.Id; | |
| } | |
| if (await ActivateAsync() is not { } activatedProcessId) | |
| return null; | |
| TaskCompletionSource<bool> tcs = new(); | |
| var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Minecraft Bedrock\Users"); | |
| using var activatedProcess = Process.GetProcessById((int)activatedProcessId); | |
| activatedProcess.EnableRaisingEvents = true; | |
| var directory = Directory.CreateDirectory(path); | |
| using FileSystemWatcher watcher = new(directory.FullName, initialized ? "*resource_init_lock" : "*menu_load_lock") | |
| { | |
| InternalBufferSize = 0, | |
| EnableRaisingEvents = true, | |
| IncludeSubdirectories = true, | |
| NotifyFilter = NotifyFilters.FileName, | |
| }; | |
| watcher.Deleted += (_, _) => tcs.TrySetResult(true); | |
| activatedProcess.Exited += (_, _) => tcs.TrySetResult(false); | |
| return await tcs.Task ? (uint)activatedProcess.Id : null; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Diagnostics; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Management.Automation; | |
| using System.Threading.Tasks; | |
| using Windows.ApplicationModel; | |
| using Windows.Management.Core; | |
| using Windows.System; | |
| namespace Basalt.Launcher.Runtime.Game; | |
| sealed class MinecraftUWP : Minecraft | |
| { | |
| const string AppUserModelId = $"{PackageFamilyName}!App"; | |
| public override async Task<uint?> LaunchAsync(bool initialized) | |
| { | |
| if (GetProcess() is { } currentProcess) | |
| return currentProcess.ProcessId; | |
| if (await ActivateAsync() is not { } processId) | |
| return null; | |
| var path = ApplicationDataManager.CreateForPackageFamily(PackageFamilyName).LocalFolder.Path; | |
| path = Path.Combine(path, @"games\com.mojang\minecraftpe"); | |
| using var activatedProcess = Process.GetProcessById((int)processId); | |
| activatedProcess.EnableRaisingEvents = true; | |
| using FileSystemWatcher watcher = new(path, initialized ? "*resource_init_lock" : "*menu_load_lock") | |
| { | |
| InternalBufferSize = 0, | |
| EnableRaisingEvents = true, | |
| IncludeSubdirectories = true, | |
| NotifyFilter = NotifyFilters.FileName, | |
| }; | |
| TaskCompletionSource<bool> tcs = new(); | |
| watcher.Deleted += (_, _) => tcs.TrySetResult(true); | |
| activatedProcess.Exited += (_, _) => tcs.TrySetResult(false); | |
| return await tcs.Task ? processId : null; | |
| } | |
| protected override async Task<uint?> ActivateAsync() | |
| { | |
| var result = await (await AppDiagnosticInfo.RequestInfoForAppAsync(AppUserModelId))[0].LaunchAsync(); | |
| if (result.ExtendedError is { }) | |
| throw result.ExtendedError; | |
| foreach (var process in result.AppResourceGroupInfo.GetProcessDiagnosticInfos()) | |
| { | |
| if (process.ExecutableFileName.Equals("Minecraft.Windows.exe", StringComparison.OrdinalIgnoreCase)) | |
| return process.ProcessId; | |
| } | |
| return null; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment