diff --git a/package.json b/package.json index cc01ee2..3867215 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-music-quiz-installer", - "version": "0.2.1", + "version": "0.2.2", "description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트", "main": "dist/installer/main.js", "scripts": { diff --git a/src/installer-rp/ffmpeg.ts b/src/installer-rp/ffmpeg.ts index 04777ad..6274fbd 100644 --- a/src/installer-rp/ffmpeg.ts +++ b/src/installer-rp/ffmpeg.ts @@ -3,7 +3,7 @@ import { promises as fs, createWriteStream, constants as fsConst } from 'node:fs import path from 'node:path' import https from 'node:https' import http from 'node:http' -import { getMcCustomDir } from '../shared/paths.js' +import { getMcCustomDir, getMcCustomInstallerDir } from '../shared/paths.js' import { loadComponentI18n } from '../shared/i18n.js' const { t } = loadComponentI18n('installer-rp') @@ -13,10 +13,30 @@ const extractZip: (source: string, options: { dir: string }) => Promise = /** * 리소스팩 간편설치기는 Windows .exe 로 배포되므로 ffmpeg.exe 한 종류만 사용. - * 경로: %appdata%/.mc_custom/ffmpeg.exe + * 경로: %appdata%/.mc_custom/installer/ffmpeg.exe */ export function getFfmpegExePath(): string { - return path.join(getMcCustomDir(), 'ffmpeg.exe') + return path.join(getMcCustomInstallerDir(), 'ffmpeg.exe') +} + +/** + * 0.2.1 이전 버전이 `.mc_custom/ffmpeg.exe` 에 받아둔 파일이 있으면 새 위치로 + * 옮긴다. + */ +async function migrateLegacyExe(target: string): Promise { + const legacy = path.join(getMcCustomDir(), 'ffmpeg.exe') + if (legacy === target) return + try { + await fs.access(legacy, fsConst.F_OK) + } catch { + return + } + try { + await fs.mkdir(path.dirname(target), { recursive: true }) + await fs.rename(legacy, target) + } catch { + try { await fs.unlink(legacy) } catch { /* noop */ } + } } /** BtbN/FFmpeg-Builds 의 win64-gpl 빌드. zip 내부에 bin/ffmpeg.exe 가 들어 있음. */ @@ -33,6 +53,7 @@ export async function ensureFfmpegExe( log?: (line: string) => void ): Promise { const target = getFfmpegExePath() + await migrateLegacyExe(target) if (await canExecute(target)) { log?.(t('log.ffmpegExists', { path: target })) return target @@ -40,7 +61,7 @@ export async function ensureFfmpegExe( if (installPromise) return installPromise installPromise = (async () => { - const dir = getMcCustomDir() + const dir = getMcCustomInstallerDir() const zipPath = path.join(dir, '.tmp_ffmpeg.zip') const extractDir = path.join(dir, '.tmp_ffmpeg') try { diff --git a/src/installer-rp/ytdlp.ts b/src/installer-rp/ytdlp.ts index 24382e0..fb2e220 100644 --- a/src/installer-rp/ytdlp.ts +++ b/src/installer-rp/ytdlp.ts @@ -3,17 +3,38 @@ import { promises as fs, createWriteStream, constants as fsConst } from 'node:fs import path from 'node:path' import https from 'node:https' import http from 'node:http' -import { getMcCustomDir } from '../shared/paths.js' +import { getMcCustomDir, getMcCustomInstallerDir } from '../shared/paths.js' import { loadComponentI18n } from '../shared/i18n.js' const { t } = loadComponentI18n('installer-rp') /** * 리소스팩 간편설치기는 Windows .exe 로 배포되므로 yt-dlp.exe 한 종류만 사용. - * 경로: %appdata%/.mc_custom/yt-dlp.exe + * 경로: %appdata%/.mc_custom/installer/yt-dlp.exe */ export function getYtDlpExePath(): string { - return path.join(getMcCustomDir(), 'yt-dlp.exe') + return path.join(getMcCustomInstallerDir(), 'yt-dlp.exe') +} + +/** + * 0.2.1 이전 버전이 `.mc_custom/yt-dlp.exe` 에 받아둔 파일이 있으면 새 위치로 + * 옮긴다. 마인크래프트 게임 폴더 루트가 외부 도구 파일로 더럽혀지지 않도록. + */ +async function migrateLegacyExe(target: string): Promise { + const legacy = path.join(getMcCustomDir(), 'yt-dlp.exe') + if (legacy === target) return + try { + await fs.access(legacy, fsConst.F_OK) + } catch { + return + } + try { + await fs.mkdir(path.dirname(target), { recursive: true }) + await fs.rename(legacy, target) + } catch { + // 권한·드라이브 문제 등으로 실패하면 그냥 새로 받으면 되므로 무시. + try { await fs.unlink(legacy) } catch { /* noop */ } + } } const YT_DLP_DOWNLOAD_URL = @@ -29,6 +50,7 @@ export async function ensureYtDlpExe( log?: (line: string) => void ): Promise { const target = getYtDlpExePath() + await migrateLegacyExe(target) if (await canExecute(target)) { log?.(t('log.ytdlpExists', { path: target })) return target diff --git a/src/installer/main.ts b/src/installer/main.ts index 6d71572..6dd8228 100644 --- a/src/installer/main.ts +++ b/src/installer/main.ts @@ -1163,6 +1163,11 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) = await linkMinecraftRuntimeDirs(customRoot) await updateLauncherProfile(pack.pack, customRoot) + + // 설치가 끝나면 더 이상 필요 없는 platform-cache(다운받은 fabric/forge/neoforge + // installer jar 캐시)를 삭제한다. 다음 실행에서 다시 받으면 되고, 남겨두면 + // 사용자 .mc_custom 폴더만 차지한다. + await fsp.rm(path.join(customRoot, 'platform-cache'), { recursive: true, force: true }).catch(() => {}) }) interface FabricInstallerMeta { diff --git a/src/shared/paths.ts b/src/shared/paths.ts index 2dd9cb0..465d009 100644 --- a/src/shared/paths.ts +++ b/src/shared/paths.ts @@ -32,3 +32,13 @@ export function getAppDataDir(): string { export function getMcCustomDir(): string { return path.join(getAppDataDir(), '.mc_custom') } + +/** + * %appdata%/.mc_custom/installer — 설치기가 자체적으로 다운로드해 사용하는 + * 외부 바이너리(yt-dlp.exe, ffmpeg.exe 등) 보관 위치. .mc_custom 루트가 + * 마인크래프트 게임 폴더(`mods/`, `resourcepacks/`, `saves/` 등)와 섞이지 + * 않도록 별도 하위 폴더에 둔다. + */ +export function getMcCustomInstallerDir(): string { + return path.join(getMcCustomDir(), 'installer') +}