import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; /** 源数据非法时抛出。第二准则:缺失/非法一律显式暴露,不兜底。 */ export class SourceError extends Error { constructor(message) { super(message); this.name = 'SourceError'; } } function readJson(dir, name) { const p = join(dir, name); if (!existsSync(p)) throw new SourceError(`缺少源数据文件: ${p}`); try { return JSON.parse(readFileSync(p, 'utf8')); } catch (e) { throw new SourceError(`源数据 JSON 解析失败: ${p} — ${e.message}`); } } function readArray(dir, fileName, key) { const json = readJson(dir, fileName); const arr = json[key]; if (!Array.isArray(arr)) { throw new SourceError( `源数据 ${fileName} 缺少顶层数组 "${key}"(实际为 ${typeof arr})`, ); } return arr; } /** * 读取并校验 gameabc 导出数据。 * 源数组用 `{}` 占位空槽(下标即 ID),须跳过。 */ export function loadSource(outputDir) { const objects = new Map(); for (const entry of readArray(outputDir, 'gameabc_Object.json', 'ObjectList')) { if (!entry || !entry.Property) continue; const p = entry.Property; objects.set(p.ObjectID, { ...p, events: Object.keys(entry.Event ?? {}) }); } const images = new Map(); for (const i of readArray(outputDir, 'gameabc_Image.json', 'ImageFileList')) { if (!i || i.id === undefined) continue; if (i.w * i.h !== i.frame_all) { throw new SourceError( `图 ${i.bmp} 的网格与帧数不符: ${i.w}x${i.h}=${i.w * i.h} != frame_all=${i.frame_all}`, ); } images.set(i.id, i); } const layers = readArray(outputDir, 'gameabc_Layer.json', 'LayerList') .filter((l) => l && l.Property) .map((l) => ({ layerId: l.Property.LayerID, name: l.Property.LayerName, objectIds: l.ObjectList ?? [], })); const groups = readArray(outputDir, 'gameabc_GroupList.json', 'GroupList') .filter((g) => g && g.GroupID !== undefined) .map((g) => ({ groupId: g.GroupID, objectIds: g.ObjectList ?? [] })); return { objects, images, layers, groups }; }