Skip to content

Instantly share code, notes, and snippets.

@Aetopia
Last active February 14, 2026 06:42
Show Gist options
  • Select an option

  • Save Aetopia/afc4b44df5a98e5f2e74e6dea246d60a to your computer and use it in GitHub Desktop.

Select an option

Save Aetopia/afc4b44df5a98e5f2e74e6dea246d60a to your computer and use it in GitHub Desktop.
Purely managed implementation for launching Minecraft: Bedrock Edition on Windows.
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;
}
}
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