installer: 마인크래프트 런처 실행 전략 재정렬 — Win32/MSIX 직접 실행 우선

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 스킴
This commit is contained in:
2026-05-13 01:59:30 +09:00
parent 99ed5076c1
commit a9b766d14d

View File

@@ -1379,39 +1379,78 @@ ipcMain.handle('finish:startServer', async () => {
}) })
ipcMain.handle('finish:startLauncher', async () => { ipcMain.handle('finish:startLauncher', async () => {
// 1순위: minecraft:// URL 스킴. UWP(Microsoft Store) / Win32 / Xbox 앱 어떤 형태로 설치돼 // 마인크래프트 런처는 두 가지 형태로 배포된다:
// 있어도 OS의 등록된 프로토콜 핸들러가 처리하므로 가장 견고하다. // 1) Win32 설치판: C:\Program Files (x86)\Minecraft Launcher\MinecraftLauncher.exe 등
try { // 2) MSIX(Microsoft Store) 앱: PackageFamilyName=Microsoft.4297127D64EC6_8wekyb3d8bbwe, AppId=Minecraft
sendLog('마인크래프트 런처 실행 요청(URL 스킴 minecraft://)...') // - 일반 .exe 가 아니라 shell:AppsFolder\<PFN>!<AppId> 또는 App Execution Alias 로만 띄울 수 있음.
await shell.openExternal('minecraft://') // minecraft:// URL 스킴은 런처가 핸들러로 등록되어 있어야만 동작하고, 등록이 깨지거나 비어 있으면
sendLog('마인크래프트 런처 실행 요청 완료.') // MS Store 로 폴백되므로 가장 마지막 시도로 미룬다.
if (process.platform !== 'win32') {
try {
await shell.openExternal('minecraft://')
sendLog('마인크래프트 런처 실행 요청 완료(URL 스킴, 비-Windows).')
} catch (err) {
sendLog(`런처 실행 실패: ${(err as Error).message}`)
}
return return
} catch (err) {
sendLog(`URL 스킴 실행 실패: ${(err as Error).message}. 직접 경로 탐색으로 폴백합니다.`)
} }
// 2순위: 알려진 설치 경로 탐색. 구버전 .exe 설치판 / Xbox 앱 설치 위치까지 커버.
const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)' const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)'
const programFiles = process.env['ProgramFiles'] ?? 'C:\\Program Files' const programFiles = process.env['ProgramFiles'] ?? 'C:\\Program Files'
const localAppData = process.env['LOCALAPPDATA'] ?? path.join(os.homedir(), 'AppData', 'Local') const localAppData = process.env['LOCALAPPDATA'] ?? path.join(os.homedir(), 'AppData', 'Local')
const candidates = [
path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'), type LauncherCandidate = { label: string; path: string; viaShell: boolean }
path.join(programFiles, 'Minecraft Launcher', 'MinecraftLauncher.exe'), const candidates: LauncherCandidate[] = [
path.join(programFilesX86, 'Minecraft', 'MinecraftLauncher.exe'), // Win32 설치판 — 실행 파일 직접 spawn.
path.join(programFiles, 'Minecraft', 'MinecraftLauncher.exe'), { label: 'Win32 설치(Program Files (x86))', path: path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'), viaShell: false },
'C:\\XboxGames\\Minecraft Launcher\\Content\\Minecraft.exe', { label: 'Win32 설치(Program Files)', path: path.join(programFiles, 'Minecraft Launcher', 'MinecraftLauncher.exe'), viaShell: false },
path.join(localAppData, 'Programs', 'minecraft-launcher', 'MinecraftLauncher.exe') { 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 } for (const cand of candidates) {
}) let exists = false
if (target) { try { exists = fs.existsSync(cand.path) } catch { exists = false }
spawn(target, [], { detached: true, stdio: 'ignore' }).unref() if (!exists) continue
sendLog(`마인크래프트 런처 실행: ${target}`) try {
return 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\<PackageFamilyName>!<AppId>.
// 마인크래프트 런처(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', () => { ipcMain.handle('app:quit', () => {