Switch login to password-only and split pack zip paths
- Login form/route accepts password only; matched account row provides session userId
- PackDefinition: replace packPath with mapPath (.mc_custom/saves) and serverPath (server install dir); editor exposes two .zip fields
- Installer resolves relative platform/map/server URLs against manifest origin under /file/{platforms,maps,servers}/<name>; downloads and extracts the zips
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ import fsp from 'node:fs/promises'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { URL } from 'node:url'
|
||||
import natUpnp from 'nat-upnp'
|
||||
// extract-zip은 CommonJS 기본 export.
|
||||
const extractZip: (source: string, options: { dir: string }) => Promise<void> = require('extract-zip')
|
||||
import type {
|
||||
ClientInstallPayload,
|
||||
FetchedPack,
|
||||
@@ -201,30 +203,47 @@ ipcMain.handle('jdk:detect', async () => {
|
||||
return { found: false, path: '' }
|
||||
})
|
||||
|
||||
async function downloadServerFiles(pack: PackDefinition, targetDir: string): Promise<void> {
|
||||
const indexUrl = `${state.baseUrl}/file/${pack.packPath.replace(/^\/+|\/+$/g, '')}`
|
||||
sendLog(`서버 파일 인덱스: ${indexUrl}`)
|
||||
let listing: string[] = []
|
||||
try {
|
||||
const directoryHtml = (await fetchBuffer(indexUrl)).toString('utf8')
|
||||
listing = Array.from(directoryHtml.matchAll(/href=\"([^\"]+)\"/g))
|
||||
.map((match) => match[1])
|
||||
.filter((href) => !href.startsWith('?') && !href.endsWith('/'))
|
||||
} catch (error) {
|
||||
sendLog(`서버 파일 인덱스 로드 실패: ${(error as Error).message}`)
|
||||
}
|
||||
/**
|
||||
* 입력값이 절대 URL이면 그대로, 상대값이면 manifest 도메인의 /file/<subDir>/<file> 로 해석.
|
||||
*/
|
||||
function resolveManifestRelative(input: string, subDir: string): string {
|
||||
if (!input) return ''
|
||||
if (/^https?:\/\//i.test(input)) return input
|
||||
const fileName = input.replace(/^\/+/, '')
|
||||
return `${state.baseUrl}/file/${subDir}/${fileName}`
|
||||
}
|
||||
|
||||
if (listing.length === 0) {
|
||||
sendLog('서버 파일 인덱스를 가져올 수 없습니다. packPath 또는 사이트 디렉토리 인덱스 설정을 확인해 주세요.')
|
||||
async function downloadAndExtractZip(url: string, label: string, extractDir: string): Promise<void> {
|
||||
await fsp.mkdir(extractDir, { recursive: true })
|
||||
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'mq-zip-'))
|
||||
const tempZip = path.join(tempDir, 'package.zip')
|
||||
try {
|
||||
sendLog(`${label} 다운로드: ${url}`)
|
||||
await downloadFile(url, tempZip)
|
||||
sendLog(`${label} 압축 해제: ${extractDir}`)
|
||||
await extractZip(tempZip, { dir: extractDir })
|
||||
} finally {
|
||||
await fsp.rm(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadServerZip(pack: PackDefinition, targetDir: string): Promise<void> {
|
||||
if (!pack.serverPath) {
|
||||
sendLog('서버 파일(serverPath)이 비어 있어 서버 zip 다운로드를 건너뜁니다.')
|
||||
return
|
||||
}
|
||||
const url = resolveManifestRelative(pack.serverPath, 'servers')
|
||||
await downloadAndExtractZip(url, '서버 파일', targetDir)
|
||||
}
|
||||
|
||||
for (const fileName of listing) {
|
||||
const targetUrl = `${indexUrl.replace(/\/$/, '')}/${fileName}`
|
||||
const target = path.join(targetDir, decodeURIComponent(fileName))
|
||||
sendLog(`다운로드: ${fileName}`)
|
||||
await downloadFile(targetUrl, target)
|
||||
async function downloadMapZip(pack: PackDefinition, customRoot: string): Promise<void> {
|
||||
if (!pack.mapPath) {
|
||||
sendLog('맵 파일(mapPath)이 비어 있어 맵 다운로드를 건너뜁니다.')
|
||||
return
|
||||
}
|
||||
const url = resolveManifestRelative(pack.mapPath, 'maps')
|
||||
const savesDir = path.join(customRoot, 'saves')
|
||||
await downloadAndExtractZip(url, '맵', savesDir)
|
||||
}
|
||||
|
||||
ipcMain.handle('server:install', async (_event, payload: ServerInstallPayload) => {
|
||||
@@ -238,7 +257,7 @@ ipcMain.handle('server:install', async (_event, payload: ServerInstallPayload) =
|
||||
await fsp.mkdir(installPath, { recursive: true })
|
||||
sendLog(`서버 설치 경로: ${installPath}`)
|
||||
|
||||
await downloadServerFiles(pack.pack, installPath)
|
||||
await downloadServerZip(pack.pack, installPath)
|
||||
|
||||
const eulaPath = path.join(installPath, 'eula.txt')
|
||||
if (fs.existsSync(eulaPath)) {
|
||||
@@ -466,11 +485,12 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) =
|
||||
await fsp.mkdir(path.join(customRoot, 'resourcepacks'), { recursive: true })
|
||||
|
||||
if (payload.installPlatform && pack.pack.platform.type !== 'vanilla' && pack.pack.platform.downloadUrl) {
|
||||
const platformUrl = resolveManifestRelative(pack.pack.platform.downloadUrl, 'platforms')
|
||||
const cacheDir = path.join(customRoot, 'platform-cache')
|
||||
await fsp.mkdir(cacheDir, { recursive: true })
|
||||
const installerPath = path.join(cacheDir, deriveFileName(pack.pack.platform.downloadUrl) || 'platform-installer.jar')
|
||||
sendLog(`플랫폼(${pack.pack.platform.type}) 다운로드: ${pack.pack.platform.downloadUrl}`)
|
||||
await downloadFile(pack.pack.platform.downloadUrl, installerPath)
|
||||
const installerPath = path.join(cacheDir, deriveFileName(platformUrl) || 'platform-installer.jar')
|
||||
sendLog(`플랫폼(${pack.pack.platform.type}) 다운로드: ${platformUrl}`)
|
||||
await downloadFile(platformUrl, installerPath)
|
||||
sendLog(`플랫폼 설치파일 저장: ${installerPath} (사용자가 직접 실행하거나 마인크래프트 런처에서 인식할 수 있습니다.)`)
|
||||
} else if (!payload.installPlatform) {
|
||||
sendLog('플랫폼 설치 건너뜀. 바닐라로 진행합니다.')
|
||||
@@ -489,6 +509,8 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) =
|
||||
await downloadFile(resourcePack.downloadUrl, target)
|
||||
}
|
||||
|
||||
await downloadMapZip(pack.pack, customRoot)
|
||||
|
||||
await updateLauncherProfile(pack.pack, customRoot)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user