fix(installer): preserve JVM args + link runtime dirs from .minecraft
1) launcher_profiles.json 의 javaArgs 를 통째로 덮어쓰던 코드를 수정. mergeRamArgs() 로 -Xmx/-Xms 토큰만 새 값으로 교체하고 그 외 사용자 추가 JVM 인수(-Xss, -XX:..., -Dfoo=bar 등)는 보존. 2) .mc_custom 을 gameDir 로 쓰면 마인크래프트 런처가 assets/libraries/ versions 를 못 찾아 "Unable to prepare assets for download" 로 실패. linkMinecraftRuntimeDirs() 가 .minecraft 의 해당 세 폴더를 .mc_custom 으로 junction(Windows) / symlink(POSIX) 연결. 이미 같은 자리에 무언가 있으면 손대지 않음. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -557,6 +557,10 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) =
|
||||
|
||||
await downloadMapZip(pack.pack, customRoot)
|
||||
|
||||
// 런처가 .mc_custom 을 gameDir 로 잡아도 assets/libraries/versions 를
|
||||
// 찾을 수 있도록 .minecraft 의 해당 폴더로 junction 링크.
|
||||
await linkMinecraftRuntimeDirs(customRoot)
|
||||
|
||||
await updateLauncherProfile(pack.pack, customRoot)
|
||||
})
|
||||
|
||||
@@ -575,6 +579,26 @@ function getAppDataDir(): string {
|
||||
return app.getPath('appData')
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 javaArgs 에서 -Xmx/-Xms 토큰만 새 값으로 교체하고 나머지 args 는 보존한다.
|
||||
* 기존에 없으면 새 RAM 인자를 앞에 붙인다.
|
||||
*/
|
||||
function mergeRamArgs(existing: string, maxMb: number, minMb: number): string {
|
||||
const newXmx = `-Xmx${maxMb}M`
|
||||
const newXms = `-Xms${minMb}M`
|
||||
const tokens = (existing || '').split(/\s+/).filter(Boolean)
|
||||
let foundXmx = false
|
||||
let foundXms = false
|
||||
const merged = tokens.map((t) => {
|
||||
if (t.startsWith('-Xmx')) { foundXmx = true; return newXmx }
|
||||
if (t.startsWith('-Xms')) { foundXms = true; return newXms }
|
||||
return t
|
||||
})
|
||||
if (!foundXmx) merged.unshift(newXmx)
|
||||
if (!foundXms) merged.splice(foundXmx ? 0 : 1, 0, newXms)
|
||||
return merged.join(' ').trim()
|
||||
}
|
||||
|
||||
async function updateLauncherProfile(pack: PackDefinition, gameDir: string): Promise<void> {
|
||||
const launcherPath = path.join(getAppDataDir(), '.minecraft', 'launcher_profiles.json')
|
||||
if (!fs.existsSync(launcherPath)) {
|
||||
@@ -585,12 +609,17 @@ async function updateLauncherProfile(pack: PackDefinition, gameDir: string): Pro
|
||||
const json = JSON.parse(raw) as { profiles?: Record<string, Record<string, unknown>> }
|
||||
json.profiles = json.profiles ?? {}
|
||||
const profileKey = pack.name
|
||||
const javaArgs = `-Xmx${pack.serverMaxRam}M -Xms${pack.serverMinRam}M`
|
||||
const existingProfile = json.profiles[profileKey] ?? {}
|
||||
const existingJavaArgs = typeof existingProfile.javaArgs === 'string' ? (existingProfile.javaArgs as string) : ''
|
||||
const javaArgs = mergeRamArgs(existingJavaArgs, pack.serverMaxRam, pack.serverMinRam)
|
||||
if (existingJavaArgs && existingJavaArgs !== javaArgs) {
|
||||
sendLog(`기존 JVM 인수 유지, RAM 만 갱신: "${existingJavaArgs}" → "${javaArgs}"`)
|
||||
}
|
||||
const lastVersionId = pack.platform.type === 'vanilla'
|
||||
? pack.mcVersion
|
||||
: `${pack.mcVersion}-${pack.platform.type}`
|
||||
json.profiles[profileKey] = {
|
||||
...(json.profiles[profileKey] ?? {}),
|
||||
...existingProfile,
|
||||
name: profileKey,
|
||||
type: 'custom',
|
||||
gameDir,
|
||||
@@ -601,6 +630,41 @@ async function updateLauncherProfile(pack: PackDefinition, gameDir: string): Pro
|
||||
sendLog(`launcher_profiles.json 갱신: 프로필 "${profileKey}", gameDir=${gameDir}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* .mc_custom 에서 마인크래프트 런처가 찾는 assets/libraries/versions 를
|
||||
* .minecraft 의 같은 폴더로 junction(Windows) / symlink(POSIX) 한다.
|
||||
* 이미 같은 자리에 무언가 있으면 손대지 않는다.
|
||||
*
|
||||
* 이걸 안 하면 런처가 .mc_custom/assets 가 없다며 "Unable to prepare assets
|
||||
* for download" 에러로 실행에 실패한다.
|
||||
*/
|
||||
async function linkMinecraftRuntimeDirs(customRoot: string): Promise<void> {
|
||||
const mcRoot = path.join(getAppDataDir(), '.minecraft')
|
||||
for (const dir of ['assets', 'libraries', 'versions']) {
|
||||
const src = path.join(mcRoot, dir)
|
||||
const dst = path.join(customRoot, dir)
|
||||
if (!fs.existsSync(src)) {
|
||||
sendLog(`.minecraft/${dir} 가 없습니다. 마인크래프트 런처를 한 번 실행한 뒤 다시 시도해주세요.`)
|
||||
continue
|
||||
}
|
||||
let existing: import('node:fs').Stats | null = null
|
||||
try { existing = await fsp.lstat(dst) } catch { existing = null }
|
||||
if (existing) {
|
||||
if (existing.isSymbolicLink()) continue // 이미 링크됨
|
||||
sendLog(`.mc_custom/${dir} 가 실제 폴더로 이미 존재 — 건너뜀.`)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
// 'junction' 은 Windows 에서 권한 없이 만들 수 있는 디렉터리 링크.
|
||||
// 다른 OS 에서는 Node 가 알아서 일반 symlink 로 처리.
|
||||
await fsp.symlink(src, dst, 'junction')
|
||||
sendLog(`링크 생성: .mc_custom/${dir} → .minecraft/${dir}`)
|
||||
} catch (err) {
|
||||
sendLog(`링크 생성 실패 (${dir}): ${(err as Error).message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle('finish:openServerFolder', async () => {
|
||||
if (!state.installPath) return
|
||||
await shell.openPath(state.installPath)
|
||||
|
||||
Reference in New Issue
Block a user