diff --git a/package-lock.json b/package-lock.json index fa94c49..5e2b9a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aeromc", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aeromc", - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "dependencies": { "archiver": "^7.0.1", diff --git a/package.json b/package.json index 2e10e05..a3f3bd9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aeromc", "productName": "AeroMC", - "version": "1.0.5", + "version": "1.0.6", "description": "AeroMC – Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)", "homepage": "https://m-viper.de", "main": "src/main.js", diff --git a/src/bedrock.js b/src/bedrock.js index 6939aa6..eaca930 100644 --- a/src/bedrock.js +++ b/src/bedrock.js @@ -14,7 +14,12 @@ const path = require('path'); const { execFile, spawn } = require('child_process'); const PKG_FAMILY = 'Microsoft.MinecraftUWP_8wekyb3d8bbwe'; -const APP_ID = PKG_FAMILY + '!App'; +// Der Teil nach "!" (Application Id) ist NICHT stabil – Microsoft hat ihn +// schon mindestens einmal geändert (früher "App", inzwischen "Game"). +// Deshalb zur Laufzeit über Get-StartApps auflösen statt fest zu verdrahten; +// dieser Wert dient nur als letzter Rückfallwert, falls die Auflösung +// fehlschlägt (z. B. PowerShell blockiert). +const FALLBACK_APP_ID = PKG_FAMILY + '!Game'; function comMojangDir() { return path.join(process.env.LOCALAPPDATA || '', 'Packages', PKG_FAMILY, @@ -49,30 +54,87 @@ async function detect() { // Start // --------------------------------------------------------------------------- +let cachedAppId = null; + +/* + * Ermittelt die aktuell gültige AUMID über Get-StartApps – das ist derselbe + * Weg, den auch das Startmenü nutzt, und bildet daher immer die Application + * Id ab, die Windows gerade tatsächlich zum Starten akzeptiert. + */ +async function resolveAppId() { + if (cachedAppId) return cachedAppId; + const out = await runPwsh( + `(Get-StartApps | Where-Object { $_.AppID -like '${PKG_FAMILY}*' } ` + + `| Select-Object -First 1 -ExpandProperty AppID)`); + cachedAppId = out || FALLBACK_APP_ID; + return cachedAppId; +} + /* * Startet Bedrock direkt. * Wichtig: Der AppsFolder-Pfad darf NICHT in Anführungszeichen stehen – sonst * öffnet der Explorer nur ein Ordnerfenster. Daher windowsVerbatimArguments. */ -function launch() { - const target = 'shell:appsFolder\\' + APP_ID; +async function launch() { + const appId = await resolveAppId(); + const target = 'shell:appsFolder\\' + appId; + // Wichtig: Es darf pro Klick nur EINE Aktivierung tatsächlich lostreten. + // Die App-Aktivierung lässt sich nicht abbrechen – wenn wir bei einer + // Verzögerung (Xbox-Dienste, Kaltstart) automatisch einen zweiten Weg + // probieren, laufen im Zweifel beide an und Bedrock öffnet sich doppelt. + // Die anderen Wege dienen daher nur als Rückfall, falls das Spawnen + // selbst scheitert (Programm nicht gefunden o. Ä.), nicht als + // Reaktion auf einen bloß langsamen Start. const versuche = [ - // 1) genau der Aufruf, der sich unter Windows als zuverlässig erwiesen hat + // 1) explorer direkt, ohne zusätzliche Anführungszeichen – entspricht + // exakt dem manuellen "explorer.exe shell:appsFolder\...", der + // nachweislich zuverlässig funktioniert + ['explorer.exe', [target], { windowsVerbatimArguments: true }], + // 2) über die Eingabeaufforderung + ['cmd', ['/c', 'start', '', target], { windowsVerbatimArguments: true }], + // 3) Rückfallweg über eine versteckte PowerShell – aus einem + // windowless/detached Kindprozess heraus offenbar nicht überall + // zuverlässig, daher nur letzter Versuch ['powershell', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', `Start-Process explorer.exe -ArgumentList '${target}'`], {}], - // 2) explorer direkt, ohne zusätzliche Anführungszeichen - ['explorer.exe', [target], { windowsVerbatimArguments: true }], - // 3) über die Eingabeaufforderung - ['cmd', ['/c', 'start', '', target], { windowsVerbatimArguments: true }], ]; + let methode = null; for (const [cmd, args, extra] of versuche) { try { const p = spawn(cmd, args, Object.assign({ detached: true, stdio: 'ignore', windowsHide: true }, extra)); p.unref(); - return { ok: true, method: cmd }; - } catch { /* nächsten Weg versuchen */ } + methode = cmd; + break; // nur diesen einen Weg tatsächlich auslösen + } catch { /* Spawnen selbst fehlgeschlagen – nächsten Weg versuchen */ } + } + if (!methode) return { ok: false, message: 'Bedrock konnte nicht gestartet werden.' }; + // Aktivierung kann dauern (v. a. beim ersten Start nach längerer Pause) – + // hier nur zur ehrlichen Rückmeldung abwarten, OHNE bei Zeitüberschreitung + // einen weiteren Versuch zu starten (siehe Hinweis oben). + const bestaetigt = await waitForBedrockProcess(15000); + return { ok: true, method: methode, confirmed: bestaetigt }; +} + +function isBedrockRunning() { + return new Promise((resolve) => { + execFile('tasklist', ['/FI', 'IMAGENAME eq Minecraft.Windows.exe', '/FO', 'CSV', '/NH'], + { windowsHide: true, timeout: 5000 }, + (err, stdout) => resolve(!err && /Minecraft\.Windows\.exe/i.test(String(stdout || '')))); + }); +} + +function warten(ms) { + return new Promise((resolve) => { setTimeout(resolve, ms); }); +} + +async function waitForBedrockProcess(timeoutMs) { + const start = Date.now(); + // sofort prüfen (falls schon ein Fenster lief) und danach im Kurztakt + for (;;) { + if (await isBedrockRunning()) return true; + if (Date.now() - start >= timeoutMs) return false; + await warten(500); } - return { ok: false, message: 'Bedrock konnte nicht gestartet werden.' }; } // --------------------------------------------------------------------------- diff --git a/src/index.html b/src/index.html index df9d50b..fd7cd5d 100644 --- a/src/index.html +++ b/src/index.html @@ -10,7 +10,7 @@