UNPKG

bee-mod-parser

Version:

A Node.js Package To Get Metadata Of Fabric/Forge/Liteloader Mods. Used By Bee Launcher.

452 lines (451 loc) 19.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ForgeModParseFailedError = exports.readForgeMod = exports.readForgeModJson = exports.readForgeModAsm = exports.readForgeModToml = exports.readForgeModManifest = void 0; const system_1 = require("@xmcl/system"); const toml_1 = require("@iarna/toml"); const asm_1 = require("@xmcl/asm"); class ModAnnotationVisitor extends asm_1.AnnotationVisitor { constructor(map) { super(asm_1.Opcodes.ASM5); this.map = map; } visit(s, o) { if (s === "value") { this.map.modid = o; } else { this.map[s] = o; } } } class DummyModConstructorVisitor extends asm_1.MethodVisitor { constructor(parent, api) { super(api); this.parent = parent; this.stack = []; } visitLdcInsn(value) { this.stack.push(value); } visitFieldInsn(opcode, owner, name, desc) { if (opcode === asm_1.Opcodes.PUTFIELD) { const last = this.stack.pop(); if (last) { if (name === "modId") { this.parent.guess.modid = last; } else if (name === "version") { this.parent.guess.version = last; } else if (name === "name") { this.parent.guess.name = last; } else if (name === "url") { this.parent.guess.url = last; } else if (name === "parent") { this.parent.guess.parent = last; } else if (name === "mcversion") { this.parent.guess.mcversion = last; } } } } } class ModClassVisitor extends asm_1.ClassVisitor { constructor(result, guess, corePlugin) { super(asm_1.Opcodes.ASM5); this.result = result; this.guess = guess; this.corePlugin = corePlugin; this.fields = {}; this.className = ""; this.isDummyModContainer = false; this.isPluginClass = false; } validateType(desc) { if (desc.indexOf("net/minecraftforge") !== -1) { this.result.usedForgePackage = true; } if (desc.indexOf("net/minecraft") !== -1) { this.result.usedMinecraftPackage = true; } if (desc.indexOf("cpw/mods/fml") !== -1) { this.result.usedLegacyFMLPackage = true; } if (desc.indexOf("net/minecraft/client") !== -1) { this.result.usedMinecraftClientPackage = true; } } visit(version, access, name, signature, superName, interfaces) { this.className = name; this.isPluginClass = name === this.corePlugin; if (superName === "net/minecraftforge/fml/common/DummyModContainer") { this.isDummyModContainer = true; } this.validateType(superName); for (const intef of interfaces) { this.validateType(intef); } } visitMethod(access, name, desc, signature, exceptions) { if (this.isDummyModContainer && name === "<init>") { return new DummyModConstructorVisitor(this, asm_1.Opcodes.ASM5); } this.validateType(desc); return null; } visitField(access, name, desc, signature, value) { this.fields[name] = value; return null; } visitAnnotation(desc, visible) { if (desc === "Lnet/minecraftforge/fml/common/Mod;" || desc === "Lcpw/mods/fml/common/Mod;") { const annotationData = { modid: "", name: "", version: "", dependencies: "", useMetadata: true, clientSideOnly: false, serverSideOnly: false, acceptedMinecraftVersions: "", acceptableRemoteVersions: "", acceptableSaveVersions: "", modLanguage: "java", modLanguageAdapter: "", value: "", }; this.result.modAnnotations.push(annotationData); return new ModAnnotationVisitor(annotationData); } return null; } visitEnd() { if (this.className === "Config" && this.fields && this.fields.OF_NAME) { this.result.modAnnotations.push({ modid: this.fields.OF_NAME, name: this.fields.OF_NAME, mcversion: this.fields.MC_VERSION, version: `${this.fields.OF_EDITION}_${this.fields.OF_RELEASE}`, description: "OptiFine is a Minecraft optimization mod. It allows Minecraft to run faster and look better with full support for HD textures and many configuration options.", authorList: ["sp614x"], url: "https://optifine.net", clientSideOnly: true, serverSideOnly: false, value: "", dependencies: "", useMetadata: false, acceptableRemoteVersions: "", acceptableSaveVersions: "", acceptedMinecraftVersions: `[${this.fields.MC_VERSION}]`, modLanguage: "java", modLanguageAdapter: "", }); } for (const [k, v] of Object.entries(this.fields)) { switch (k.toUpperCase()) { case "MODID": case "MOD_ID": this.guess.modid = this.guess.modid || v; break; case "MODNAME": case "MOD_NAME": this.guess.name = this.guess.name || v; break; case "VERSION": case "MOD_VERSION": this.guess.version = this.guess.version || v; break; case "MCVERSION": this.guess.mcversion = this.guess.mcversion || v; break; } } } } /** * Read the mod info from `META-INF/MANIFEST.MF` * @returns The manifest directionary */ async function readForgeModManifest(mod, manifestStore = {}) { const fs = await system_1.resolveFileSystem(mod); if (!await fs.existsFile("META-INF/MANIFEST.MF")) { return undefined; } const data = await fs.readFile("META-INF/MANIFEST.MF"); const manifest = data.toString().split("\n") .map((l) => l.trim()) .filter((l) => l.length > 0) .map((l) => l.split(":").map((s) => s.trim())) .reduce((a, b) => ({ ...a, [b[0]]: b[1] }), {}); Object.assign(manifestStore, manifest); const metadata = { modid: "", name: "", authors: new Array(), version: "", description: "", url: "", }; if (typeof manifest.TweakName === "string") { metadata.modid = manifest.TweakName; metadata.name = manifest.TweakName; } if (typeof manifest.TweakAuthor === "string") { metadata.authors = [manifest.TweakAuthor]; } if (typeof manifest.TweakVersion === "string") { metadata.version = manifest.TweakVersion; } if (manifest.TweakMetaFile) { const file = manifest.TweakMetaFile; if (await fs.existsFile(`META-INF/${file}`)) { const metadataContent = await fs.readFile(`META-INF/${file}`, "utf-8").then((s) => s.replace(/^\uFEFF/, "")).then(JSON.parse); if (metadataContent.id) { metadata.modid = metadataContent.id; } if (metadataContent.name) { metadata.name = metadataContent.name; } if (metadataContent.version) { metadata.version = metadataContent.version; } if (metadataContent.authors) { metadata.authors = metadataContent.authors; } if (metadataContent.description) { metadata.description = metadataContent.description; } if (metadataContent.url) { metadata.url = metadataContent.url; } } } return metadata; } exports.readForgeModManifest = readForgeModManifest; /** * Read mod metadata from new toml metadata file. */ async function readForgeModToml(mod, manifest) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; const fs = await system_1.resolveFileSystem(mod); const existed = await fs.existsFile("META-INF/mods.toml"); const all = []; if (existed) { const str = await fs.readFile("META-INF/mods.toml", "utf-8"); const root = toml_1.parse(str); if (root.mods instanceof Array) { for (const mod of root.mods) { const tomlMod = mod; const modObject = { modid: (_a = tomlMod.modId) !== null && _a !== void 0 ? _a : "", authors: (_c = (_b = tomlMod.authors) !== null && _b !== void 0 ? _b : root.authors) !== null && _c !== void 0 ? _c : "", version: tomlMod.version === "${file.jarVersion}" && typeof (manifest === null || manifest === void 0 ? void 0 : manifest["Implementation-Version"]) === "string" ? manifest === null || manifest === void 0 ? void 0 : manifest["Implementation-Version"] : tomlMod.version, displayName: (_d = tomlMod.displayName) !== null && _d !== void 0 ? _d : "", description: (_e = tomlMod.description) !== null && _e !== void 0 ? _e : "", displayURL: (_g = (_f = tomlMod.displayURL) !== null && _f !== void 0 ? _f : root.displayURL) !== null && _g !== void 0 ? _g : "", updateJSONURL: (_j = (_h = tomlMod.updateJSONURL) !== null && _h !== void 0 ? _h : root.updateJSONURL) !== null && _j !== void 0 ? _j : "", dependencies: [], logoFile: (_k = tomlMod.logoFile) !== null && _k !== void 0 ? _k : "", credits: (_l = tomlMod.credits) !== null && _l !== void 0 ? _l : "", loaderVersion: (_m = root.loaderVersion) !== null && _m !== void 0 ? _m : "", modLoader: (_o = root.modLoader) !== null && _o !== void 0 ? _o : "", issueTrackerURL: (_p = root.issueTrackerURL) !== null && _p !== void 0 ? _p : "", }; all.push(modObject); } } if (typeof root.dependencies === "object") { for (const mod of all) { const dep = root.dependencies[mod.modid]; if (dep) { mod.dependencies = dep; } } } } return all; } exports.readForgeModToml = readForgeModToml; /** * Use asm to scan all the class files of the mod. This might take long time to read. */ async function readForgeModAsm(mod, manifest = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o; const fs = await system_1.resolveFileSystem(mod); let corePluginClass; if (manifest) { if (typeof manifest.FMLCorePlugin === "string") { const clazz = manifest.FMLCorePlugin.replace(/\./g, "/"); if (await fs.existsFile(clazz) || await fs.existsFile(`/${clazz}`)) { corePluginClass = clazz; } } } const result = { usedForgePackage: false, usedLegacyFMLPackage: false, usedMinecraftClientPackage: false, usedMinecraftPackage: false, modAnnotations: [] }; const guessing = {}; await fs.walkFiles("/", async (f) => { if (!f.endsWith(".class")) { return; } const data = await fs.readFile(f); const visitor = new ModClassVisitor(result, guessing, corePluginClass); new asm_1.ClassReader(data).accept(visitor); }); if (result.modAnnotations.length === 0 && guessing.modid && (result.usedForgePackage || result.usedLegacyFMLPackage)) { result.modAnnotations.push({ modid: (_a = guessing.modid) !== null && _a !== void 0 ? _a : "", name: (_b = guessing.name) !== null && _b !== void 0 ? _b : "", version: (_c = guessing.version) !== null && _c !== void 0 ? _c : "", dependencies: (_d = guessing.dependencies) !== null && _d !== void 0 ? _d : "", useMetadata: (_e = guessing.useMetadata) !== null && _e !== void 0 ? _e : false, clientSideOnly: (_f = guessing.clientSideOnly) !== null && _f !== void 0 ? _f : false, serverSideOnly: (_g = guessing.serverSideOnly) !== null && _g !== void 0 ? _g : false, acceptedMinecraftVersions: (_h = guessing.acceptedMinecraftVersions) !== null && _h !== void 0 ? _h : "", acceptableRemoteVersions: (_j = guessing.acceptableRemoteVersions) !== null && _j !== void 0 ? _j : "", acceptableSaveVersions: (_k = guessing.acceptableSaveVersions) !== null && _k !== void 0 ? _k : "", modLanguage: (_l = guessing.modLanguage) !== null && _l !== void 0 ? _l : "java", modLanguageAdapter: (_m = guessing.modLanguageAdapter) !== null && _m !== void 0 ? _m : "", value: (_o = guessing.value) !== null && _o !== void 0 ? _o : "", }); } return result; } exports.readForgeModAsm = readForgeModAsm; /** * Read `mcmod.info`, `cccmod.info`, and `neimod.info` json file * @param mod The mod path or buffer or opened file system. */ async function readForgeModJson(mod) { const fs = await system_1.resolveFileSystem(mod); const all = []; function normalize(json) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s; const metadata = { modid: "", name: "", description: "", version: "", mcversion: "", url: "", updateUrl: "", updateJSON: "", authorList: [], credits: "", logoFile: "", screenshots: [], parent: "", useDependencyInformation: false, requiredMods: [], dependencies: [], dependants: [], }; metadata.modid = (_a = json.modid) !== null && _a !== void 0 ? _a : metadata.modid; metadata.name = (_b = json.name) !== null && _b !== void 0 ? _b : metadata.name; metadata.description = (_c = json.description) !== null && _c !== void 0 ? _c : metadata.description; metadata.version = (_d = json.version) !== null && _d !== void 0 ? _d : metadata.version; metadata.mcversion = (_e = json.mcversion) !== null && _e !== void 0 ? _e : metadata.mcversion; metadata.url = (_f = json.url) !== null && _f !== void 0 ? _f : metadata.url; metadata.updateUrl = (_g = json.updateUrl) !== null && _g !== void 0 ? _g : metadata.updateUrl; metadata.updateJSON = (_h = json.updateJSON) !== null && _h !== void 0 ? _h : metadata.updateJSON; metadata.authorList = (_j = json.authorList) !== null && _j !== void 0 ? _j : metadata.authorList; metadata.credits = (_k = json.credits) !== null && _k !== void 0 ? _k : metadata.credits; metadata.logoFile = (_l = json.logoFile) !== null && _l !== void 0 ? _l : metadata.logoFile; metadata.screenshots = (_m = json.screenshots) !== null && _m !== void 0 ? _m : metadata.screenshots; metadata.parent = (_o = json.parent) !== null && _o !== void 0 ? _o : metadata.parent; metadata.useDependencyInformation = (_p = json.useDependencyInformation) !== null && _p !== void 0 ? _p : metadata.useDependencyInformation; metadata.requiredMods = (_q = json.requiredMods) !== null && _q !== void 0 ? _q : metadata.requiredMods; metadata.dependencies = (_r = json.dependencies) !== null && _r !== void 0 ? _r : metadata.dependencies; metadata.dependants = (_s = json.dependants) !== null && _s !== void 0 ? _s : metadata.dependants; return metadata; } function readJsonMetadata(json) { const modList = []; if (json instanceof Array) { modList.push(...json); } else if (json.modList instanceof Array) { modList.push(...json.modList); } else if (json.modid) { modList.push(json); } all.push(...modList.map(normalize)); } if (await fs.existsFile("mcmod.info")) { try { const json = JSON.parse((await fs.readFile("mcmod.info", "utf-8")).replace(/^\uFEFF/, "")); readJsonMetadata(json); } catch (e) { } } else if (await fs.existsFile("cccmod.info")) { try { const text = (await fs.readFile("cccmod.info", "utf-8")).replace(/^\uFEFF/, "").replace(/\n\n/g, "\\n").replace(/\n/g, ""); const json = JSON.parse(text); readJsonMetadata(json); } catch (e) { } } else if (await fs.existsFile("neimod.info")) { try { const text = (await fs.readFile("neimod.info", "utf-8")).replace(/^\uFEFF/, "").replace(/\n\n/g, "\\n").replace(/\n/g, ""); const json = JSON.parse(text); readJsonMetadata(json); } catch (e) { } } return all; } exports.readForgeModJson = readForgeModJson; /** * Read metadata of the input mod. * * This will scan the mcmod.info file, all class file for `@Mod` & coremod `DummyModContainer` class. * This will also scan the manifest file on `META-INF/MANIFEST.MF` for tweak mod. * * If the input is totally not a mod. It will throw {@link NonForgeModFileError}. * * @throws {@link NonForgeModFileError} * @param mod The mod path or data * @returns The mod metadata */ async function readForgeMod(mod) { const fs = await system_1.resolveFileSystem(mod); const jsons = await readForgeModJson(fs); const manifest = {}; const manifestMetadata = await readForgeModManifest(fs, manifest); const tomls = await readForgeModToml(fs, manifest); const base = await readForgeModAsm(fs, manifest); if (jsons.length === 0 && (!manifestMetadata || !manifestMetadata.modid) && tomls.length === 0 && base.modAnnotations.length === 0) { throw new ForgeModParseFailedError(mod, base, manifest); } const result = { mcmodInfo: jsons, manifest: manifest, manifestMetadata: (manifestMetadata === null || manifestMetadata === void 0 ? void 0 : manifestMetadata.modid) ? manifestMetadata : undefined, modsToml: tomls, ...base, }; return result; } exports.readForgeMod = readForgeMod; class ForgeModParseFailedError extends Error { constructor(mod, asm, manifest) { super("Cannot find the mod metadata in the mod!"); this.mod = mod; this.asm = asm; this.manifest = manifest; } } exports.ForgeModParseFailedError = ForgeModParseFailedError;