Switch mods to per-folder auto-discovery and resourcepack to single zip
- PackDefinition: replace mods[]/resourcepacks[] with modsFolder (string) + resourcepackPath (string); drop PackAsset - Editor: replace dynamic add/remove lists with two single inputs; remove the now-dead JS for adding/removing rows - Server: expose GET /file/mods/<folder>/index.json that returns the list of .jar names; folder name restricted to [a-zA-Z0-9_-]+ - Installer: fetch the listing JSON and download each jar from /file/mods/<folder>/<file>.jar; download the single resourcepack from /file/resourcepacks/<file>.zip directly into resourcepacks/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -246,6 +246,43 @@ async function downloadMapZip(pack: PackDefinition, customRoot: string): Promise
|
||||
await downloadAndExtractZip(url, '맵', savesDir)
|
||||
}
|
||||
|
||||
async function downloadModsFolder(pack: PackDefinition, customRoot: string): Promise<void> {
|
||||
if (!pack.modsFolder) {
|
||||
sendLog('modsFolder가 비어 있어 모드 다운로드를 건너뜁니다.')
|
||||
return
|
||||
}
|
||||
const indexUrl = `${state.baseUrl}/file/mods/${encodeURIComponent(pack.modsFolder)}/index.json`
|
||||
sendLog(`모드 목록 조회: ${indexUrl}`)
|
||||
const listing = await fetchJson<{ files?: unknown }>(indexUrl)
|
||||
const files = Array.isArray(listing.files)
|
||||
? listing.files.filter((name): name is string => typeof name === 'string' && /\.jar$/i.test(name))
|
||||
: []
|
||||
if (files.length === 0) {
|
||||
sendLog(`/file/mods/${pack.modsFolder}/ 안에 .jar 파일이 없습니다.`)
|
||||
return
|
||||
}
|
||||
const modsDir = path.join(customRoot, 'mods')
|
||||
await fsp.mkdir(modsDir, { recursive: true })
|
||||
for (const fileName of files) {
|
||||
const url = `${state.baseUrl}/file/mods/${encodeURIComponent(pack.modsFolder)}/${encodeURIComponent(fileName)}`
|
||||
const target = path.join(modsDir, fileName)
|
||||
sendLog(`모드 다운로드: ${fileName}`)
|
||||
await downloadFile(url, target)
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadResourcepackZip(pack: PackDefinition, customRoot: string): Promise<void> {
|
||||
if (!pack.resourcepackPath) {
|
||||
sendLog('resourcepackPath가 비어 있어 리소스팩 다운로드를 건너뜁니다.')
|
||||
return
|
||||
}
|
||||
const url = `${state.baseUrl}/file/resourcepacks/${pack.resourcepackPath.replace(/^\/+/, '')}`
|
||||
const target = path.join(customRoot, 'resourcepacks', pack.resourcepackPath.replace(/^\/+/, ''))
|
||||
await fsp.mkdir(path.dirname(target), { recursive: true })
|
||||
sendLog(`리소스팩 다운로드: ${url}`)
|
||||
await downloadFile(url, target)
|
||||
}
|
||||
|
||||
ipcMain.handle('server:install', async (_event, payload: ServerInstallPayload) => {
|
||||
const pack = state.packs.get(payload.packKey)
|
||||
if (!pack) throw new Error('음악퀴즈를 찾을 수 없습니다.')
|
||||
@@ -496,18 +533,8 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) =
|
||||
sendLog('플랫폼 설치 건너뜀. 바닐라로 진행합니다.')
|
||||
}
|
||||
|
||||
for (const mod of pack.pack.mods) {
|
||||
if (!mod.downloadUrl) continue
|
||||
const target = path.join(customRoot, 'mods', deriveFileName(mod.downloadUrl) || `${mod.name}.jar`)
|
||||
sendLog(`모드 다운로드: ${mod.name}`)
|
||||
await downloadFile(mod.downloadUrl, target)
|
||||
}
|
||||
for (const resourcePack of pack.pack.resourcepacks) {
|
||||
if (!resourcePack.downloadUrl) continue
|
||||
const target = path.join(customRoot, 'resourcepacks', deriveFileName(resourcePack.downloadUrl) || `${resourcePack.name}.zip`)
|
||||
sendLog(`리소스팩 다운로드: ${resourcePack.name}`)
|
||||
await downloadFile(resourcePack.downloadUrl, target)
|
||||
}
|
||||
await downloadModsFolder(pack.pack, customRoot)
|
||||
await downloadResourcepackZip(pack.pack, customRoot)
|
||||
|
||||
await downloadMapZip(pack.pack, customRoot)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user