diff --git a/src/main/main.ts b/src/main/main.ts index d9884ce..56bfbc0 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -31,7 +31,7 @@ const CLIENT_PROCESS_NAMES = [ const SERVER_PROCESS_NAMES = [ "Aki.Server.exe", - "SPT.Server.exe" + "SPT.Server.exe" ]; const GAME_PROCESS_NAMES = [...CLIENT_PROCESS_NAMES, ...SERVER_PROCESS_NAMES]; @@ -247,28 +247,28 @@ const checkGameRunning = async (): Promise<{ running: boolean; serverRunning: bo resolve({ running: false, serverRunning: false, clientRunning: false, processes: [] }); return; } - + const output = stdout.toString().toLowerCase(); const runningProcesses: string[] = []; let serverRunning = false; let clientRunning = false; - + for (const procName of GAME_PROCESS_NAMES) { - if (output.includes(procName.toLowerCase())) { - runningProcesses.push(procName); - if (SERVER_PROCESS_NAMES.includes(procName)) { - serverRunning = true; - } else if (CLIENT_PROCESS_NAMES.includes(procName)) { - clientRunning = true; - } - } + if (output.includes(procName.toLowerCase())) { + runningProcesses.push(procName); + if (SERVER_PROCESS_NAMES.includes(procName)) { + serverRunning = true; + } else if (CLIENT_PROCESS_NAMES.includes(procName)) { + clientRunning = true; + } + } } - resolve({ - running: runningProcesses.length > 0, - serverRunning, - clientRunning, - processes: runningProcesses + resolve({ + running: runningProcesses.length > 0, + serverRunning, + clientRunning, + processes: runningProcesses }); }); }); @@ -670,7 +670,8 @@ const findSptInstallPath = async ( }; const resolveSptInstall = async (): Promise => { - if (process.platform !== "win32") { + const isDevUi = process.env.VITE_DEV_UI === "1"; + if (process.platform !== "win32" && !isDevUi) { return { ok: false, checkedAt: Date.now(), @@ -956,7 +957,7 @@ const syncDirectories = async ( await moveDir(sourcePath, destPath, preservePrefixes, targetDir); } } - + // Final progress event for completion const result = { ok: true, command: "copy", args: [] }; steps?.push({ @@ -1007,11 +1008,79 @@ const runModSync = async ( const installGitTools = async ( onProgress?: (step: InstallStep) => void, ): Promise => { + const isDevUi = process.env.VITE_DEV_UI === "1"; + if (process.platform !== "win32") { + if (!isDevUi) { + return { + ok: false, + error: "Windows에서만 자동 설치가 가능합니다.", + steps: [], + }; + } + + // Mac/Linux Dev UI Simulation + const steps: InstallStep[] = []; + const totalSteps = 3; + const emitProgress = ( + name: string, + current: number, + status: InstallStep["status"], + ) => { + const percent = Math.round((current / totalSteps) * 100); + onProgress?.({ + name, + ok: status !== "error", + status, + progress: { current, total: totalSteps, percent }, + result: { ok: true, command: "simulated", args: [] } + }); + }; + + emitProgress("winget 확인", 1, "running"); + await new Promise(r => setTimeout(r, 500)); + const wingetStep: InstallStep = { + name: "winget 확인", + ok: true, + status: "done", + progress: { current: 1, total: totalSteps, percent: 33 }, + result: { ok: true, command: "simulated", args: [] } + }; + steps.push(wingetStep); + onProgress?.(wingetStep); + + emitProgress("Git 설치", 2, "running"); + await new Promise(r => setTimeout(r, 800)); + const gitInstallStep: InstallStep = { + name: "Git 설치", + ok: true, + status: "done", + progress: { current: 2, total: totalSteps, percent: 67 }, + result: { ok: true, command: "simulated", args: [] } + }; + steps.push(gitInstallStep); + onProgress?.(gitInstallStep); + + emitProgress("Git LFS 설치", 3, "running"); + await new Promise(r => setTimeout(r, 800)); + const lfsInstallStep: InstallStep = { + name: "Git LFS 설치", + ok: true, + status: "done", + progress: { current: 3, total: totalSteps, percent: 100 }, + result: { ok: true, command: "simulated", args: [] } + }; + steps.push(lfsInstallStep); + onProgress?.(lfsInstallStep); + return { - ok: false, - error: "Windows에서만 자동 설치가 가능합니다.", - steps: [], + ok: true, + steps, + check: { + git: { ok: true, command: "git", version: "simulated" }, + lfs: { ok: true, command: "git", lfs: "simulated" } as any, + checkedAt: Date.now() + } }; } @@ -1123,7 +1192,7 @@ const installGitTools = async ( "--accept-source-agreements", "--accept-package-agreements", "--silent", - "--disable-interactivity" + "--disable-interactivity" ], INSTALL_TIMEOUT_MS, ); @@ -1428,7 +1497,7 @@ app.whenReady().then(() => { ipcMain.handle("spt:launchGame", async (event) => { const mainWindow = BrowserWindow.fromWebContents(event.sender); const installInfo = await resolveSptInstall(); - + if (!installInfo.path) { return { ok: false, error: "no_spt_path" }; } @@ -1436,13 +1505,13 @@ app.whenReady().then(() => { // Double check running processes const running = await checkGameRunning(); if (running.clientRunning) { - return { ok: false, error: "already_running", details: running.processes }; + return { ok: false, error: "already_running", details: running.processes }; } // Double check mod version (optional but safer) const modStatus = await getModVersionStatus(); if (!modStatus.ok) { - return { ok: false, error: "mod_verification_failed" }; + return { ok: false, error: "mod_verification_failed" }; } // Launch Server if not running (Local only) @@ -1450,45 +1519,45 @@ app.whenReady().then(() => { const isLocal = serverUrl.hostname === "127.0.0.1" || serverUrl.hostname === "localhost"; if (isLocal && !running.serverRunning) { - let serverExePath: string | undefined; - for (const exe of SERVER_PROCESS_NAMES) { - // Check direct path or SPT subdirectory - const direct = path.join(installInfo.path, exe); - if (await pathExists(direct)) { - serverExePath = direct; - break; - } - const nested = path.join(installInfo.path, "SPT", exe); - if (await pathExists(nested)) { - serverExePath = nested; - break; - } + let serverExePath: string | undefined; + for (const exe of SERVER_PROCESS_NAMES) { + // Check direct path or SPT subdirectory + const direct = path.join(installInfo.path, exe); + if (await pathExists(direct)) { + serverExePath = direct; + break; } - - if (serverExePath) { - console.log(`[spt-launcher] Starting Server: ${serverExePath}`); - const serverBat = stripExe(serverExePath) + ".bat"; // Sometimes users use bat, but exe should work if arguments aren't complex - - // Prefer EXE for now. - const child = require("child_process").spawn(serverExePath, [], { - cwd: path.dirname(serverExePath), - detached: true, - stdio: "ignore" - }); - child.unref(); - - // Wait for server health - let attempts = 0; - const maxAttempts = 30; // 30 seconds approx - while(attempts < maxAttempts) { - const health = await checkServerHealth(); - if (health.ok) { - break; - } - await new Promise(r => setTimeout(r, 1000)); - attempts++; - } + const nested = path.join(installInfo.path, "SPT", exe); + if (await pathExists(nested)) { + serverExePath = nested; + break; } + } + + if (serverExePath) { + console.log(`[spt-launcher] Starting Server: ${serverExePath}`); + const serverBat = stripExe(serverExePath) + ".bat"; // Sometimes users use bat, but exe should work if arguments aren't complex + + // Prefer EXE for now. + const child = require("child_process").spawn(serverExePath, [], { + cwd: path.dirname(serverExePath), + detached: true, + stdio: "ignore" + }); + child.unref(); + + // Wait for server health + let attempts = 0; + const maxAttempts = 30; // 30 seconds approx + while (attempts < maxAttempts) { + const health = await checkServerHealth(); + if (health.ok) { + break; + } + await new Promise(r => setTimeout(r, 1000)); + attempts++; + } + } } // Launch Client @@ -1500,7 +1569,7 @@ app.whenReady().then(() => { "SPT/EscapeFromTarkov.exe", "SPT/EscapeFromTarkov_BE.exe" ]; - + let executablePath: string | undefined; for (const exe of candidates) { const fullPath = path.join(installInfo.path, exe); @@ -1511,7 +1580,7 @@ app.whenReady().then(() => { } if (!executablePath) { - return { ok: false, error: "executable_not_found" }; + return { ok: false, error: "executable_not_found" }; } // Get Session ID for args @@ -1527,13 +1596,13 @@ app.whenReady().then(() => { }); const args = [`-token=${session.sessionId}`, `-config=${configArg}`]; console.log(`[spt-launcher] Launching: ${executablePath} with args`, args); - + // Sanitize Environment // Strip Electron/Node specifics to prevent BepInEx/Plugin conflicts const sanitizedEnv = { ...process.env }; for (const key of Object.keys(sanitizedEnv)) { if (key.startsWith("ELECTRON_") || key.startsWith("NODE_") || key === "VITE_DEV_SERVER_URL") { - delete sanitizedEnv[key]; + delete sanitizedEnv[key]; } } @@ -1554,9 +1623,9 @@ app.whenReady().then(() => { } }); -function stripExe(filename: string) { + function stripExe(filename: string) { return filename.replace(/\.exe$/i, ""); -} + } ipcMain.handle("spt:checkGameRunning", async () => { return await checkGameRunning();