feat(installer-rp): use registered base resourcepack as overlay base

/manifest/<key>.json 의 resourcepackPath 가 비어있지 않으면
/file/resourcepacks/<path> 의 zip 을 받아 임시 폴더에 풀고, 그 위에
음악·사진·sounds.json·pack.mcmeta 를 얹어 최종 zip 을 만든다.

- types.ts: RpFetchedPack 에 resourcepackPath 필드 추가
- main.ts: 로드 시 normalizePackDefinition 으로 resourcepackPath 캡처,
  설치 시 베이스 zip 다운로드 → tempRoot/base.zip → buildResourcepackZip 에 전달
- pack.ts: baseZipPath 옵션 추가. extract-zip 으로 베이스 압축 해제 후 위에 얹기.
  sounds.json 은 기존 항목과 병합, pack.mcmeta 는 mcVersion 에 맞춰 항상 덮어씀.

베이스가 없으면(빈 문자열) 기존처럼 새 리소스팩을 처음부터 생성.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 00:37:00 +09:00
parent df3d0a5cda
commit 45540f3db7
3 changed files with 70 additions and 17 deletions

View File

@@ -188,10 +188,16 @@ ipcMain.handle('rp:packs:load', async (_event, manifestUrlInput?: string): Promi
music: Array.isArray(listRaw.music) ? listRaw.music : [],
images: Array.isArray(listRaw.images) ? listRaw.images : []
}
const mcVersion = packRaw
? normalizePackDefinition(packRaw as Partial<PackDefinition>).mcVersion
: ''
results.push({ key: entry.file, name: entry.name || entry.file, mcVersion, list })
const normalized = packRaw ? normalizePackDefinition(packRaw as Partial<PackDefinition>) : null
const mcVersion = normalized?.mcVersion ?? ''
const resourcepackPath = normalized?.resourcepackPath ?? ''
results.push({
key: entry.file,
name: entry.name || entry.file,
mcVersion,
resourcepackPath,
list
})
} catch (error) {
sendLog(`목록 로드 실패 (${entry.file}): ${(error as Error).message}`)
}
@@ -334,13 +340,32 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 100, status: 'done' })
}
// 2-4. 리소스팩 zip 빌드 (pack.mcmeta + sounds.json + 음악·이미지)
// 2-4. 베이스 리소스팩 다운로드 (있을 때만)
throwIfCancelled()
let baseZipPath: string | undefined
if (pack.resourcepackPath) {
const baseUrl = `${state.baseUrl}/file/resourcepacks/${pack.resourcepackPath.replace(/^\/+/, '')}`
baseZipPath = path.join(tempRoot, 'base.zip')
sendLog(`베이스 리소스팩 다운로드: ${pack.resourcepackPath}`)
sendProgress({ phase: 'package', message: '베이스 리소스팩 다운로드 중' })
try {
const buf = await fetchBuffer(baseUrl)
await fsp.writeFile(baseZipPath, buf)
sendLog(`베이스 리소스팩 받음 (${(buf.length / 1024).toFixed(1)} KB)`)
} catch (err) {
throw new Error(`베이스 리소스팩 다운로드 실패: ${(err as Error).message}`)
}
} else {
sendLog('베이스 리소스팩 없음 — 새 리소스팩으로 생성')
}
// 2-5. 리소스팩 zip 빌드 (pack.mcmeta + sounds.json + 음악·이미지, 베이스 위에 얹기)
throwIfCancelled()
const resourcepackName = `${state.selectedKey}_musicquiz.zip`
const resourcepackDir = path.join(getAppDataDir(), '.minecraft', 'resourcepacks')
const resourcepackPath = path.join(resourcepackDir, resourcepackName)
sendLog(`리소스팩 zip 빌드 중… (${resourcepackName})`)
sendProgress({ phase: 'package', message: 'zip 빌드 중' })
sendProgress({ phase: 'package', message: baseZipPath ? '베이스에 음악·사진 추가 중' : 'zip 빌드 중' })
await buildResourcepackZip({
musicDir,
paintingDir,
@@ -348,10 +373,11 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
mcVersion: pack.mcVersion,
workDir: tempRoot,
outZipPath: resourcepackPath,
baseZipPath,
log: sendLog
})
// 2-5. %appdata%/.minecraft/resourcepacks/ 에 배치 (위 빌드가 직접 outZipPath 에 저장)
// 2-6. %appdata%/.minecraft/resourcepacks/ 에 배치 (위 빌드가 직접 outZipPath 에 저장)
sendLog(`설치 완료: ${resourcepackPath}`)
sendProgress({ phase: 'package', message: '설치 완료', done: true })
return { resourcepackPath }