UNPKG

@tts-tools/savefile

Version:

Module to extract a savefile from Tabletop Simulator into multiple files.

132 lines (107 loc) 3.69 kB
import { readFileSync } from "fs"; import { bundleSave } from "./bundle"; import { ChildObjectsFile, ContentsFile, StatesFile } from "./model/tool"; import { SaveFile, TTSObject } from "./model/tts"; /** * Available options for [[embedSave]]. */ export interface Options { /** The path where the scripts and XML files will be included from. */ includePath: string | string[]; metadataField?: string; } export const readExtractedSave = (path: string, options: Options) => { const saveFile = readData(path, options) as SaveFile; saveFile.LuaScript = readScript(path); saveFile.LuaScriptState = readScriptState(path); saveFile.XmlUI = readUi(path); saveFile.ObjectStates = readContents(path, options) ?? []; return saveFile; }; export const readExtractedObject = (path: string, options: Options) => { return readObject(path, options); }; /** * Embeds the content of an previously extracted save file and returns a new save file. * * @param path The path to an extracted save file. * @param options The [[Options]] to use. * @returns The embedded save file. */ export const embedSave = (path: string, options: Options): SaveFile => { const saveFile = readExtractedSave(path, options); return bundleSave(saveFile, options); }; const readData = (path: string, options: Options) => { return readJson(path, "Data.json", true); }; const readObject = (path: string, options: Options): TTSObject => { const data = readData(path, options) as TTSObject; data.LuaScript = readScript(path); data.LuaScriptState = readScriptState(path); data.XmlUI = readUi(path); if (options.metadataField) { const metadata = readMetadata(path); if (metadata !== "") { data[options.metadataField] = metadata; } } data.ContainedObjects = readContents(path, options); data.States = readStates(path, options); data.ChildObjects = readChildObjects(path, options); return data; }; const readContents = (path: string, options: Options): TTSObject[] | undefined => { const contents = readJson<ContentsFile>(path, "Contents.json"); if (!contents) { return undefined; } return contents.map((e) => readObject(`${path}/${e.path}`, options)); }; const readStates = (path: string, options: Options): Record<string, TTSObject> | undefined => { const states = readJson<StatesFile>(path, "States.json"); if (!states) { return undefined; } return Object.entries(states).reduce((obj, [id, item]) => { return { ...obj, [id]: readObject(`${path}/${item.path}`, options), }; }, {}); }; const readChildObjects = (path: string, options: Options): TTSObject[] | undefined => { const children = readJson<ChildObjectsFile>(path, "Children.json"); if (!children) { return undefined; } return children.map((e) => readObject(`${path}/${e.path}`, options)); }; const readScript = (path: string): string => { return readFile(path, "Script.ttslua"); }; const readScriptState = (path: string): string => { return readFile(path, "State.txt"); }; const readUi = (path: string): string => { return readFile(path, "UI.xml"); }; const readMetadata = (path: string): string => { return readFile(path, "Metadata.toml"); }; const readFile = (path: string, fileName: string, required: boolean = false) => { try { return readFileSync(`${path}/${fileName}`, { encoding: "utf-8" }); } catch (e) { if (!required && (e as any).code === "ENOENT") { return ""; } throw e; } }; const readJson = <T>(path: string, fileName: string, required: boolean = false) => { const content = readFile(path, fileName, required); if (content) { return JSON.parse(content) as T; } };