From 4b83d95cbf1281951a99fc31260b559a6e166bb7 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 12 May 2026 15:44:46 +0900 Subject: [PATCH] Resolve pack_format from the pack's mcVersion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous hardcoded pack_format 34 + supported_formats 34..75 covered 1.21 through 1.21.11 only, so a pack generated for the current latest (26.1.2 → format 84) was rejected as outdated. Add src/installer-rp/packFormat.ts with a 1.21 → 26.2 lookup table from the Minecraft wiki and resolveResourcePackFormat() that returns {matched, format}. Unknown mcVersion falls back to the table's most recent entry, with a log line warning the user. Plumb mcVersion through the load → install flow: - rp:packs:load now also fetches /manifest/.json alongside /file/list/.json and runs it through the existing normalizePackDefinition so the editor and the installer agree on the mcVersion shape. Pack manifest load failures fall back to an empty mcVersion (which then triggers the latest-format fallback). - RpFetchedPack carries mcVersion; the install handler hands it to buildResourcepackZip. - buildResourcepackZip drops the constant pack_format / supported_ formats and uses the resolved format both as pack_format and as the {min,max} of supported_formats. Each pack is thus pinned to exactly the MC version it was authored for. - The renderer's pack card now shows "마인크래프트 " in the small line so the user can confirm before installing. Verified locally: pack.mcmeta generated for mcVersion "1.21", "1.21.6", "26.1.2", and the bogus "99.9.9" produce pack_format 34 / 63 / 84 / 86 (last falls back to the table tail) respectively. Co-Authored-By: Claude Opus 4.7 --- installer-rp/renderer.js | 4 +++- src/installer-rp/main.ts | 30 ++++++++++++++++------- src/installer-rp/pack.ts | 24 ++++++++++++------- src/installer-rp/packFormat.ts | 44 ++++++++++++++++++++++++++++++++++ src/installer-rp/types.ts | 2 ++ 5 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 src/installer-rp/packFormat.ts diff --git a/installer-rp/renderer.js b/installer-rp/renderer.js index fa6a790..7e1973d 100644 --- a/installer-rp/renderer.js +++ b/installer-rp/renderer.js @@ -69,9 +69,11 @@ function renderStep1() { card.type = 'button' card.className = 'choiceCard' if (state.selectedKey === pack.key) card.classList.add('active') + var verLabel = pack.mcVersion ? '마인크래프트 ' + escapeHtml(pack.mcVersion) + ' · ' : '' card.innerHTML = '' + escapeHtml(pack.name) + '' + - '음악 ' + pack.list.music.length + '곡 · 사진 ' + pack.list.images.length + '장' + '' + verLabel + + '음악 ' + pack.list.music.length + '곡 · 사진 ' + pack.list.images.length + '장' card.addEventListener('click', function () { state.selectedKey = pack.key nextBtn.disabled = false diff --git a/src/installer-rp/main.ts b/src/installer-rp/main.ts index c49c74b..65f4feb 100644 --- a/src/installer-rp/main.ts +++ b/src/installer-rp/main.ts @@ -6,7 +6,8 @@ import fs from 'node:fs' import fsp from 'node:fs/promises' import { URL } from 'node:url' import type { ChildProcess } from 'node:child_process' -import type { Manifest, PackList } from '../shared/types.js' +import type { Manifest, PackDefinition, PackList } from '../shared/types.js' +import { normalizePackDefinition } from '../shared/store.js' import { getAppDataDir, getMcCustomDir } from '../shared/paths.js' import type { RpFetchedPack } from './types.js' import { ensureYtDlpExe } from './ytdlp.js' @@ -112,15 +113,26 @@ ipcMain.handle('rp:packs:load', async (_event, manifestUrlInput?: string): Promi for (const entry of manifest.packs ?? []) { if (typeof entry?.file !== 'string') continue const listUrl = `${state.baseUrl}/file/list/${encodeURIComponent(entry.file)}.json` + const packUrl = `${state.baseUrl}/manifest/${encodeURIComponent(entry.file)}.json` try { - const raw = await fetchJson>(listUrl) + // 목록(필수) + 팩 정의(mcVersion 용, 실패해도 폴백) 동시 로드. + const [listRaw, packRaw] = await Promise.all([ + fetchJson>(listUrl), + fetchJson>(packUrl).catch((err) => { + sendLog(`팩 정의 로드 실패 (${entry.file}): ${(err as Error).message} — mcVersion 폴백`) + return null + }) + ]) const list: PackList = { - musicPlaylistUrl: typeof raw.musicPlaylistUrl === 'string' ? raw.musicPlaylistUrl : '', - imagePlaylistUrl: typeof raw.imagePlaylistUrl === 'string' ? raw.imagePlaylistUrl : '', - music: Array.isArray(raw.music) ? raw.music : [], - images: Array.isArray(raw.images) ? raw.images : [] + musicPlaylistUrl: typeof listRaw.musicPlaylistUrl === 'string' ? listRaw.musicPlaylistUrl : '', + imagePlaylistUrl: typeof listRaw.imagePlaylistUrl === 'string' ? listRaw.imagePlaylistUrl : '', + music: Array.isArray(listRaw.music) ? listRaw.music : [], + images: Array.isArray(listRaw.images) ? listRaw.images : [] } - results.push({ key: entry.file, name: entry.name || entry.file, list }) + const mcVersion = packRaw + ? normalizePackDefinition(packRaw as Partial).mcVersion + : '' + results.push({ key: entry.file, name: entry.name || entry.file, mcVersion, list }) } catch (error) { sendLog(`목록 로드 실패 (${entry.file}): ${(error as Error).message}`) } @@ -222,8 +234,10 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string musicDir, paintingDir, packName: pack.name, + mcVersion: pack.mcVersion, workDir: tempRoot, - outZipPath: resourcepackPath + outZipPath: resourcepackPath, + log: sendLog }) // 2-5. %appdata%/.minecraft/resourcepacks/ 에 배치 (위 빌드가 직접 outZipPath 에 저장) diff --git a/src/installer-rp/pack.ts b/src/installer-rp/pack.ts index 5c8b054..47aa8b4 100644 --- a/src/installer-rp/pack.ts +++ b/src/installer-rp/pack.ts @@ -1,16 +1,10 @@ import { promises as fs, createWriteStream } from 'node:fs' import path from 'node:path' import archiver from 'archiver' +import { resolveResourcePackFormat } from './packFormat.js' const NAMESPACE = 'musicquiz' -/** - * 1.21 (pack_format 34) 를 기준으로 하되 supported_formats 로 1.21 ~ 1.21.11 - * 까지 받아들이도록 선언. 더 신 버전이 나오면 max_inclusive 만 올리면 됨. - */ -const RESOURCE_PACK_FORMAT = 34 -const SUPPORTED_FORMATS = { min_inclusive: 34, max_inclusive: 75 } - export interface BuildResourcepackOptions { /** ogg 음악 파일들이 들어 있는 폴더 (01.ogg, 02.ogg, …). */ musicDir: string @@ -18,10 +12,14 @@ export interface BuildResourcepackOptions { paintingDir: string /** pack.mcmeta 의 description 에 들어갈 표시 이름. */ packName: string + /** /manifest/.json 의 mcVersion. pack_format 결정용. */ + mcVersion: string /** 작업 폴더(임시). 이 안에 트리를 펼친 뒤 zip 생성. */ workDir: string /** 최종 zip 출력 경로. */ outZipPath: string + /** 진단용 로그 콜백 (선택). */ + log?: (line: string) => void } /** @@ -42,11 +40,19 @@ export async function buildResourcepackZip(opts: BuildResourcepackOptions): Prom await fs.mkdir(paintingOutDir, { recursive: true }) // 1) pack.mcmeta + // mcVersion 으로 resource pack format 을 결정. 알 수 없는 버전이면 가장 최근 + // 알려진 포맷으로 폴백 (resolveResourcePackFormat 가 알아서 처리). + const resolved = resolveResourcePackFormat(opts.mcVersion) + if (resolved.matched) { + opts.log?.(`pack_format = ${resolved.format} (mcVersion ${resolved.matched})`) + } else { + opts.log?.(`pack_format = ${resolved.format} (mcVersion "${opts.mcVersion}" 매칭 실패, 최신 폴백)`) + } const mcmeta = { pack: { description: `음악퀴즈 리소스팩 - ${opts.packName}`, - pack_format: RESOURCE_PACK_FORMAT, - supported_formats: SUPPORTED_FORMATS + pack_format: resolved.format, + supported_formats: { min_inclusive: resolved.format, max_inclusive: resolved.format } } } await fs.writeFile(path.join(root, 'pack.mcmeta'), JSON.stringify(mcmeta, null, 2) + '\n') diff --git a/src/installer-rp/packFormat.ts b/src/installer-rp/packFormat.ts new file mode 100644 index 0000000..537761f --- /dev/null +++ b/src/installer-rp/packFormat.ts @@ -0,0 +1,44 @@ +// Minecraft Java Edition 버전 → resource pack format 번호. +// 출처: https://minecraft.wiki/w/Pack_format (수동 동기화). +// 1.21.9 부터는 minor 버전(예: 69.0)이 도입됐지만 JSON Number 로 0 차이는 +// 표현되지 않으므로 정수만 사용한다. +const TABLE: Array = [ + ['1.21', 34], + ['1.21.1', 34], + ['1.21.2', 42], + ['1.21.3', 42], + ['1.21.4', 46], + ['1.21.5', 55], + ['1.21.6', 63], + ['1.21.7', 64], + ['1.21.8', 64], + ['1.21.9', 69], + ['1.21.10', 69], + ['1.21.11', 75], + ['26.1', 84], + ['26.1.1', 84], + ['26.1.2', 84], + ['26.2', 86] +] + +/** 테이블에서 마지막(=최신) 항목의 포맷. 알 수 없는 mcVersion 에 대한 폴백. */ +export const LATEST_KNOWN_FORMAT: number = TABLE[TABLE.length - 1][1] + +export interface ResolvedFormat { + /** 매칭된 mcVersion 키 (없으면 null). */ + matched: string | null + /** pack.mcmeta 에 들어갈 pack_format 값. */ + format: number +} + +/** + * mcVersion 문자열 ("1.21.6", "26.1.2", …) 에서 pack_format 을 찾는다. + * 정확히 일치하는 게 있으면 그 값, 없으면 가장 최근 알려진 포맷을 폴백. + */ +export function resolveResourcePackFormat(mcVersion: string): ResolvedFormat { + const key = (mcVersion || '').trim() + for (const [v, f] of TABLE) { + if (v === key) return { matched: v, format: f } + } + return { matched: null, format: LATEST_KNOWN_FORMAT } +} diff --git a/src/installer-rp/types.ts b/src/installer-rp/types.ts index ba5e659..cbf92b2 100644 --- a/src/installer-rp/types.ts +++ b/src/installer-rp/types.ts @@ -3,6 +3,8 @@ import type { PackList } from '../shared/types.js' export interface RpFetchedPack { key: string name: string + /** /manifest/.json 의 mcVersion (예: "1.21.6", "26.1.2"). */ + mcVersion: string /** /file/list/.json 의 음악·사진 목록. */ list: PackList }