From a9b766d14df80c1ab65a816d8453f704534dedee Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 13 May 2026 01:59:30 +0900 Subject: [PATCH] =?UTF-8?q?installer:=20=EB=A7=88=EC=9D=B8=ED=81=AC?= =?UTF-8?q?=EB=9E=98=ED=94=84=ED=8A=B8=20=EB=9F=B0=EC=B2=98=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EC=A0=84=EB=9E=B5=20=EC=9E=AC=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=E2=80=94=20Win32/MSIX=20=EC=A7=81=EC=A0=91=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EC=9A=B0=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit minecraft:// URL 스킴이 핸들러가 깨졌거나 비어 있을 때 MS Store 로 폴백되어 실제 런처가 안 떠는 케이스 대응. 실행 순서를 아래로 변경: 1) Win32 설치판 직접 spawn (Program Files / Xbox / portable) 2) App Execution Alias(Minecraft.exe / MinecraftLauncher.exe, reparse point 이므로 cmd /c start 경유) 3) explorer.exe shell:AppsFolder\\Microsoft.4297127D64EC6_8wekyb3d8bbwe!Minecraft 로 MSIX(Microsoft Store) 런처 직접 호출 4) 마지막 수단: minecraft:// URL 스킴 --- src/installer/main.ts | 87 +++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/src/installer/main.ts b/src/installer/main.ts index 72877a3..e428d09 100644 --- a/src/installer/main.ts +++ b/src/installer/main.ts @@ -1379,39 +1379,78 @@ ipcMain.handle('finish:startServer', async () => { }) ipcMain.handle('finish:startLauncher', async () => { - // 1순위: minecraft:// URL 스킴. UWP(Microsoft Store) / Win32 / Xbox 앱 어떤 형태로 설치돼 - // 있어도 OS의 등록된 프로토콜 핸들러가 처리하므로 가장 견고하다. - try { - sendLog('마인크래프트 런처 실행 요청(URL 스킴 minecraft://)...') - await shell.openExternal('minecraft://') - sendLog('마인크래프트 런처 실행 요청 완료.') + // 마인크래프트 런처는 두 가지 형태로 배포된다: + // 1) Win32 설치판: C:\Program Files (x86)\Minecraft Launcher\MinecraftLauncher.exe 등 + // 2) MSIX(Microsoft Store) 앱: PackageFamilyName=Microsoft.4297127D64EC6_8wekyb3d8bbwe, AppId=Minecraft + // - 일반 .exe 가 아니라 shell:AppsFolder\! 또는 App Execution Alias 로만 띄울 수 있음. + // minecraft:// URL 스킴은 런처가 핸들러로 등록되어 있어야만 동작하고, 등록이 깨지거나 비어 있으면 + // MS Store 로 폴백되므로 가장 마지막 시도로 미룬다. + if (process.platform !== 'win32') { + try { + await shell.openExternal('minecraft://') + sendLog('마인크래프트 런처 실행 요청 완료(URL 스킴, 비-Windows).') + } catch (err) { + sendLog(`런처 실행 실패: ${(err as Error).message}`) + } return - } catch (err) { - sendLog(`URL 스킴 실행 실패: ${(err as Error).message}. 직접 경로 탐색으로 폴백합니다.`) } - // 2순위: 알려진 설치 경로 탐색. 구버전 .exe 설치판 / Xbox 앱 설치 위치까지 커버. const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)' const programFiles = process.env['ProgramFiles'] ?? 'C:\\Program Files' const localAppData = process.env['LOCALAPPDATA'] ?? path.join(os.homedir(), 'AppData', 'Local') - const candidates = [ - path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'), - path.join(programFiles, 'Minecraft Launcher', 'MinecraftLauncher.exe'), - path.join(programFilesX86, 'Minecraft', 'MinecraftLauncher.exe'), - path.join(programFiles, 'Minecraft', 'MinecraftLauncher.exe'), - 'C:\\XboxGames\\Minecraft Launcher\\Content\\Minecraft.exe', - path.join(localAppData, 'Programs', 'minecraft-launcher', 'MinecraftLauncher.exe') + + type LauncherCandidate = { label: string; path: string; viaShell: boolean } + const candidates: LauncherCandidate[] = [ + // Win32 설치판 — 실행 파일 직접 spawn. + { label: 'Win32 설치(Program Files (x86))', path: path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'), viaShell: false }, + { label: 'Win32 설치(Program Files)', path: path.join(programFiles, 'Minecraft Launcher', 'MinecraftLauncher.exe'), viaShell: false }, + { label: 'Win32 설치(legacy Minecraft 폴더)', path: path.join(programFilesX86, 'Minecraft', 'MinecraftLauncher.exe'), viaShell: false }, + { label: 'Win32 설치(legacy Minecraft 폴더)', path: path.join(programFiles, 'Minecraft', 'MinecraftLauncher.exe'), viaShell: false }, + { label: 'Xbox / Game Pass', path: 'C:\\XboxGames\\Minecraft Launcher\\Content\\Minecraft.exe', viaShell: false }, + { label: 'npm/portable', path: path.join(localAppData, 'Programs', 'minecraft-launcher', 'MinecraftLauncher.exe'), viaShell: false }, + // App Execution Alias(MS Store 설치 시 자동 생성, reparse point 라 cmd /c start 로 띄워야 안정적). + { label: 'App Execution Alias(Minecraft.exe)', path: path.join(localAppData, 'Microsoft', 'WindowsApps', 'Minecraft.exe'), viaShell: true }, + { label: 'App Execution Alias(MinecraftLauncher.exe)', path: path.join(localAppData, 'Microsoft', 'WindowsApps', 'MinecraftLauncher.exe'), viaShell: true } ] - const target = candidates.find((candidate) => { - try { return fs.existsSync(candidate) } catch { return false } - }) - if (target) { - spawn(target, [], { detached: true, stdio: 'ignore' }).unref() - sendLog(`마인크래프트 런처 실행: ${target}`) - return + + for (const cand of candidates) { + let exists = false + try { exists = fs.existsSync(cand.path) } catch { exists = false } + if (!exists) continue + try { + if (cand.viaShell) { + sendLog(`마인크래프트 런처 실행(${cand.label}, 셸 경유): ${cand.path}`) + spawn('cmd.exe', ['/c', 'start', '', cand.path], { detached: true, stdio: 'ignore' }).unref() + } else { + sendLog(`마인크래프트 런처 실행(${cand.label}): ${cand.path}`) + spawn(cand.path, [], { detached: true, stdio: 'ignore' }).unref() + } + return + } catch (err) { + sendLog(`${cand.path} 실행 실패: ${(err as Error).message}`) + } } - sendLog('Minecraft Launcher를 찾을 수 없습니다. Microsoft Store에서 "Minecraft Launcher"를 설치하거나 minecraft.net에서 받은 뒤 직접 실행해 주세요.') + // MSIX 앱 직접 실행: shell:AppsFolder\!. + // 마인크래프트 런처(Java) PFN: Microsoft.4297127D64EC6_8wekyb3d8bbwe, AppId: Minecraft. + try { + const aumid = 'shell:AppsFolder\\Microsoft.4297127D64EC6_8wekyb3d8bbwe!Minecraft' + sendLog(`AppsFolder 로 MS Store 런처 실행 시도: ${aumid}`) + spawn('explorer.exe', [aumid], { detached: true, stdio: 'ignore' }).unref() + return + } catch (err) { + sendLog(`AppsFolder 실행 실패: ${(err as Error).message}`) + } + + // 마지막 수단: minecraft:// URL 스킴. 런처가 없으면 MS Store 가 열린다. + try { + sendLog('마지막 시도: minecraft:// URL 스킴 (런처가 없으면 MS Store 가 열릴 수 있음).') + await shell.openExternal('minecraft://') + } catch (err) { + sendLog(`URL 스킴 실행 실패: ${(err as Error).message}.`) + } + + sendLog('Minecraft Launcher 실행 시도가 모두 실패했습니다. minecraft.net 또는 Microsoft Store 에서 "Minecraft Launcher" 를 설치한 뒤 다시 시도해 주세요.') }) ipcMain.handle('app:quit', () => {