From 5ddcaf9b451f6df70074cd5b7fc5f9b7b7a30254 Mon Sep 17 00:00:00 2001 From: Tianpao Date: Thu, 24 Jul 2025 02:40:20 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E4=BF=AE=E5=A4=8DTOML=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84DeEarth=EF=BC=88=E6=9C=AA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 20 ++-- package.json | 2 +- src/main copy.ts | 167 +++++++++++++++++++++++++++++ src/main.ts | 10 +- src/utils/DeEarth.ts | 242 +++++++++++++++++++++++++++++-------------- src/utils/logger.ts | 4 +- 6 files changed, 351 insertions(+), 94 deletions(-) create mode 100644 src/main copy.ts diff --git a/package-lock.json b/package-lock.json index 8537c89..91125d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "inquirer": "^12.6.3", "p-map": "^7.0.3", "p-retry": "^6.2.1", - "toml": "^3.0.0", + "smol-toml": "^1.4.1", "yauzl": "^3.2.0" }, "devDependencies": { @@ -5482,6 +5482,18 @@ "node": ">=8" } }, + "node_modules/smol-toml": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/smol-toml/-/smol-toml-1.4.1.tgz", + "integrity": "sha512-CxdwHXyYTONGHThDbq5XdwbFsuY4wlClRGejfE2NtwUtiHYsP1QtNsHb/hnj31jKYSchztJsaA8pSQoVzkfCFg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/sort-keys/-/sort-keys-1.1.2.tgz", @@ -5838,12 +5850,6 @@ "node": ">=8.0" } }, - "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", - "license": "MIT" - }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/trim-repeated/-/trim-repeated-1.0.0.tgz", diff --git a/package.json b/package.json index aa4acde..2fc11b5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "inquirer": "^12.6.3", "p-map": "^7.0.3", "p-retry": "^6.2.1", - "toml": "^3.0.0", + "smol-toml": "^1.4.1", "yauzl": "^3.2.0" }, "devDependencies": { diff --git a/src/main copy.ts b/src/main copy.ts new file mode 100644 index 0000000..68c8148 --- /dev/null +++ b/src/main copy.ts @@ -0,0 +1,167 @@ +import inquirer from "inquirer"; +import yauzl, { Entry, ZipFile } from "yauzl"; +import process, { emit } from "node:process"; +import fs from "node:fs"; +import fse from "fs-extra"; +import { exec } from "node:child_process"; +import { join, basename, dirname } from "node:path"; +import { platform, what_platform } from "./platform/index.js"; +import { isDevelopment, readzipentry } from "./utils/utils.js"; +import fabric from "./ml_install/fabric.js"; +import forge from "./ml_install/forge.js"; +import neoforge from "./ml_install/neoforge.js"; +import { DeEarthMain } from "./utils/DeEarth.js"; +import { LOGGER } from "./utils/logger.js"; +import { fileURLToPath } from "node:url"; +interface Answers { + modpack_path: string | undefined; +} +let unzip_path: string = ""; +if (isDevelopment) { + unzip_path = join("./", "instance/"); +} else { + unzip_path = join(getCurrnetDir().replace("dist", ""), "instance"); +} +let zipnamew: string = ""; +const argv = process.argv.slice(2)[0]; + +if (!argv) { + const answer: Answers = await inquirer.prompt([ + { type: "input", name: "modpack_path", message: "请输入整合包路径" }, + ]); + if (answer.modpack_path) { + initdir(); + await main(answer.modpack_path); + } +} else { + initdir(); + await main(argv); +} + +function initdir() { + if (!fs.existsSync(unzip_path)) { + fse.ensureDirSync(join(unzip_path, "rubbish")); + } +} + +async function main(modpack_path: string) { + const zipname = basename(modpack_path) + .replace(".zip", "") + .replace(".mrpack", ""); + zipnamew = zipname; + let dud_files: Array = []; + let pack_info: object | undefined = undefined; + let entry_arr: Entry[] = []; + //unzip + const zipfile: ZipFile = await new Promise((resolve, reject) => { + yauzl.open(modpack_path, { lazyEntries: true }, (err, zipfile) => { + if (err) { + reject(err); + } + resolve(zipfile); + }); + }); + zipfile.readEntry(); //首次读取 + zipfile.on("entry", (entry) => { + entry_arr.push(entry); + zipfile.readEntry(); + }); + await new Promise((resolve) => zipfile.on("end", async () => resolve)); + for (let i = 0; i < entry_arr.length; i++) { + const entry = entry_arr[i]; + const name: string = entry.fileName; + if (/\/$/.test(name)) { + continue; + } else if (name.includes("overrides/")) { + const zipfilex = join( + unzip_path, + zipname, + name.replace("overrides/", "") + ); + if (!fs.existsSync(zipfilex)) { + zipfile.openReadStream(entry, (err, stream) => { + //读取overrides文件夹下的所有文件和文件夹 + const dir = dirname(zipfilex); + fse.ensureDirSync(dir); + console.log(zipfilex); + stream.pipe(fse.createWriteStream(zipfilex)); + }); + } + } else if (name.endsWith(".json") || name.includes("mcbbs.packmeta")) { + dud_files.push(name); + if ( + name.includes("manifest.json") || + name.includes("modrinth.index.json") + ) { + pack_info = JSON.parse((await readzipentry(zipfile, entry)).toString()); + } + } + } + //zipfile.on("end", async () => { + //zip + //try { + const dirx = join(unzip_path, zipname); + const plat = platform(what_platform(dud_files)); + if (typeof pack_info !== "object") throw new Error("未找到manifest.json"); + const info = await plat.getinfo(pack_info); + await plat.downloadfile(pack_info, dirx); + await DeEarthMain(join(dirx, "mods"), join(unzip_path, "rubbish")); + await install(info.loader, info.minecraft, info.loader_version, dirx); + fs.writeFileSync( + join(dirx, "eula.txt"), + "#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).\n#This serverpack created by DeEarthX(QQ_Group:559349662)\neula=true" + ); + LOGGER.info("DeEarthX已将服务端制作完成!"); + zipfile.close(); + //} catch (e) { + //LOGGER.error(e); + //} + //}); + //}); + //}) +} + +async function install( + type: string, + minecraft: string, + loaderver: string, + path: string +) { + if (await checkJava()) { + LOGGER.error( + "未安装Java或系统环境变量不存在Java,已跳过自动安装模组加载器服务端!" + ); + return; + } + switch (type) { + case "fabric": + await fabric(minecraft, loaderver, path); + break; + case "fabric-loader": + await fabric(minecraft, loaderver, path); + break; + case "forge": + await forge(minecraft, loaderver, path); + break; + case "neoforge": + await neoforge(minecraft, loaderver, path); + break; + } +} + +function checkJava() { + return new Promise((resolve) => { + exec("java -version", (err) => { + if (err) { + resolve(false); + } else { + resolve(true); + } + }); + }); +} + +function getCurrnetDir() { + const url = new URL(".", import.meta.url); + return fileURLToPath(url); +} diff --git a/src/main.ts b/src/main.ts index 771a791..48cead7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -71,12 +71,14 @@ async function main(modpack_path: string) { stream.pipe(fse.createWriteStream(zipfilex)); }); } - } else if (name.endsWith(".json")||name.includes("mcbbs.packmeta")) { + } else if (name.endsWith(".json")||name.endsWith("mcbbs.packmeta")) { dud_files.push(name); //console.log((await readzipentry(zipfile, entry))) - pack_info = JSON.parse( - (await readzipentry(zipfile, entry)).toString() - ); + if(name.endsWith("modrinth.index.json")|| name.endsWith("manifest.json")){ + pack_info = JSON.parse( + (await readzipentry(zipfile, entry)).toString() + ); + } } zipfile.readEntry(); }); diff --git a/src/utils/DeEarth.ts b/src/utils/DeEarth.ts index 8a5f876..945ec17 100644 --- a/src/utils/DeEarth.ts +++ b/src/utils/DeEarth.ts @@ -7,15 +7,15 @@ import AdmZip from "adm-zip"; import got from "got"; import fs from "fs"; -import toml from 'toml'; +import toml from 'smol-toml' import path from 'path'; import pMap from "p-map"; import { LOGGER } from "./logger.js"; import { MultiBar } from "cli-progress"; export async function DeEarthMain(modspath: string, movepath: any) { - if(!fs.existsSync(movepath)){ -fs.mkdirSync(movepath) + if (!fs.existsSync(movepath)) { + fs.mkdirSync(movepath) } LOGGER.info(`DeEarth V1.0.0`) LOGGER.info(`如有无法筛选的mods,请前往 https://dearth.0771010.xyz/ 提交未成功筛选的模组的modid`) @@ -33,103 +33,185 @@ fs.mkdirSync(movepath) } multibar.stop() } - +/* export async function DeEarth(modpath: string, movepath: string) { - try{ - const zip = new AdmZip(modpath).getEntries(); - //for (let i = 0; i < zip.length; i++) { - //const e = zip[i] - try { //Modrinth - for (let i = 0; i < zip.length; i++) { - const e = zip[i] - if (isForge(e.entryName)) { //Forge,NeoForge - const modid = toml.parse(e.getData().toString('utf-8')).mods[0].modId - //const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json() - const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`)) - if (body.client_side == "required" && body.server_side !== "required") { - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - } else if (e.entryName == "fabric.mod.json") { //Fabric - const modid = JSON.parse(e.getData().toString('utf-8')).id - //const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json() - const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`)) - if (body.client_side == "required" && body.server_side !== "required") { - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - } - } - } catch (error) { //mods.toml或fabric.mod.json判断 - try{//DeEarthPublic + try { + const zip = new AdmZip(modpath).getEntries(); + //for (let i = 0; i < zip.length; i++) { + //const e = zip[i] + try { //Modrinth for (let i = 0; i < zip.length; i++) { const e = zip[i] - if (isForge(e.entryName)) { //Forge,Neoforge + if (isForge(e.entryName)) { //Forge,NeoForge const modid = toml.parse(e.getData().toString('utf-8')).mods[0].modId - const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)) - if (body.isClient) { + //const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json() + const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`)) + if (body.client_side == "required" && body.server_side !== "required") { fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) } } else if (e.entryName == "fabric.mod.json") { //Fabric const modid = JSON.parse(e.getData().toString('utf-8')).id - const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)) - if (body.isClient) { + //const body = await got.get(`https://api.modrinth.com/v2/project/${modid}`, { headers: { "User-Agent": "DeEarth" } }).json() + const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`)) + if (body.client_side == "required" && body.server_side !== "required") { fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) } } + console.log("Modrinth") } - }catch(errorr){ - for (let i = 0; i < zip.length; i++) { - const e = zip[i] - try { - if (isForge(e.entryName)) { //Forge,Neoforge - const tr = toml.parse(e.getData().toString('utf-8')) - const mcside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "minecraft").side - if (mcside == "CLIENT"){ //从Minecraft判断 - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - const forgeside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "forge").side - if (forgeside == "CLIENT") { //从Forge判断 - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - const neoside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "neoforge").side - if (neoside == "CLIENT") { //从NeoForge判断 - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - } else if (e.entryName == "fabric.mod.json") { //Fabric - const fmj = JSON.parse(e.getData().toString('utf-8')).environment - if (fmj == "client") { - fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) - } - } - } catch (erro) {//从Mixin判断 但是可能为不准确 - for (let i = 0; i < zip.length; i++) { + } catch (error) { //mods.toml或fabric.mod.json判断 + for (let i = 0; i < zip.length; i++) { + try { const e = zip[i] - try { - if (isMixinFile(e.entryName)) { - LOGGER.info(e.entryName) - const resx = JSON.parse(e.getData().toString('utf-8')) - if (e.entryName.includes("common.mixins.json")) { //第一步从common mixins文件判断,判断失败后再使用modid.mixins.json进行判断 - if (isMixin(resx)) { + if (isForge(e.entryName)) { //Forge,Neoforge + const modid = toml.parse(e.getData().toString('utf-8')).mods[0].modId + const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)) + if (body.isClient) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } else if (e.entryName == "fabric.mod.json") { //Fabric + const modid = JSON.parse(e.getData().toString('utf-8')).id + const body = JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)) + if (body.isClient) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } + console.log("DeEarth") + } catch (errorr) { + for (let i = 0; i < zip.length; i++) { + const e = zip[i] + try { + if (isForge(e.entryName)) { //Forge,Neoforge + const tr = toml.parse(e.getData().toString('utf-8')) + const mcside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "minecraft").side + if (mcside == "CLIENT") { //从Minecraft判断 fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) } - } else { - if (isMixin(resx)) { + const forgeside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "forge").side + if (forgeside == "CLIENT") { //从Forge判断 + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + const neoside = tr.dependencies[tr.mods[0].modId].find((mod: { modId: string; }) => mod.modId === "neoforge").side + if (neoside == "CLIENT") { //从NeoForge判断 + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } else if (e.entryName == "fabric.mod.json") { //Fabric + const fmj = JSON.parse(e.getData().toString('utf-8')).environment + if (fmj == "client") { fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) } } - } - } catch (err:any) {//避免有傻逼JSON写注释(虽然GSON可以这样 但是这样一点也不人道) - if (err.errno !== -4058) { - LOGGER.error(`大天才JSON写注释了估计,模组路径:${modpath},过滤失败`) + } catch (erro) {//从Mixin判断 但是可能为不准确 + for (let i = 0; i < zip.length; i++) { + const e = zip[i] + try { + if (isMixinFile(e.entryName)) { + LOGGER.info(e.entryName) + const resx = JSON.parse(e.getData().toString('utf-8')) + if (e.entryName.includes("common.mixins.json")) { //第一步从common mixins文件判断,判断失败后再使用modid.mixins.json进行判断 + if (isMixin(resx)) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } else { + if (isMixin(resx)) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } + } + } catch (err: any) {//避免有傻逼JSON写注释(虽然GSON可以这样 但是这样一点也不人道) + if (err.errno !== -4058) { + LOGGER.error(`大天才JSON写注释了估计,模组路径:${modpath},过滤失败`) + } + } + } } } } } } + } catch (error) { + LOGGER.error("DeEarth: " + error) + } +} +*/ + +export async function DeEarth(modpath: string, movepath: string) { + const zipinfo = ZipInfo(modpath) + let modid:string = "" + if(zipinfo.modinfo.type === "forge"){ + modid = zipinfo.modinfo.data.mods[0].modId + }else if(zipinfo.modinfo.type === "fabric"){ + modid = zipinfo.modinfo.data.id + } + + try { //Modrinth + const body = JSON.parse(await FastGot(`https://api.modrinth.com/v2/project/${modid}`)) + if(body.client_side == "required" && body.server_side !== "required"){ + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } catch (error) { //DeEarthPublic + try { + if (JSON.parse(await FastGot(`https://dearth.0771010.xyz/api/modid?modid=${modid}`)).isClient) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } catch (error) { //mods.toml或fabric.mod.json判断 + try{ + if(zipinfo.modinfo.type === "forge"){ + const mcside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "minecraft").side //Minecraft + if (mcside == "CLIENT") { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + const forgeside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "forge").side //Forge + if (forgeside == "CLIENT") { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + const neoside = zipinfo.modinfo.data.dependencies[modid].find((mod: { modId: string; }) => mod.modId === "neoforge").side //NeoForge + if (neoside == "CLIENT") { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + }else if(zipinfo.modinfo.type === "fabric"){ //Fabric + const fmj = zipinfo.modinfo.data.environment + if (fmj == "client") { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } + }catch(error){ + try{ + for (let i = 0; i < zipinfo.mixins.length; i++) { + const e = zipinfo.mixins[i] + const info = JSON.parse(e.info) + if (isMixin(info)) { + fs.renameSync(modpath, `${movepath}/${path.basename(modpath)}`) + } + } + }catch(error){ + LOGGER.error(`大天才JSON写注释了估计,模组路径:${modpath},过滤失败`) + } } } -}catch(error){ - LOGGER.error("DeEarth: "+error) + } } + +function ZipInfo(modpath: string) { + interface ZipInfo { + mixins: { filename: string, info: string }[] + modinfo: {type:string,data:any}; + } + let zipinfo: ZipInfo = { mixins: [], modinfo: {type: "",data: {}} } + const zip = new AdmZip(modpath).getEntries(); + for (let i = 0; i < zip.length; i++) { + const e = zip[i] + if (isMixinFile(e.entryName)) { + zipinfo.mixins.push({ filename: e.entryName, info: e.getData().toString('utf-8') }) + } else if (isForge(e.entryName)) { + zipinfo.modinfo.type = "forge" + zipinfo.modinfo.data = toml.parse(e.getData().toString('utf-8')) + } else if (e.entryName.endsWith("fabric.mod.json")) { + zipinfo.modinfo.type = "fabric" + zipinfo.modinfo.data = JSON.parse(e.getData().toString('utf-8')) + } + } + return zipinfo; } async function FastGot(url: string) { @@ -156,7 +238,7 @@ async function FastGot(url: string) { }) if (fastgot[0] !== undefined) { return fastgot[0] - }else{ + } else { return "null" } } @@ -164,18 +246,18 @@ async function FastGot(url: string) { const multibar = new MultiBar({ format: ' {bar} | {filename} | {value}/{total}', noTTYOutput: true, - notTTYSchedule: 10*1000, + notTTYSchedule: 10 * 1000, }) -function isMixin(resx: { mixins: {} | null; client: {}; }){ +function isMixin(resx: { mixins: {} | null; client: {}; }) { return resx.mixins == null || Object.keys(resx.mixins).length == 0 && Object.keys(resx.client).length !== 0 } -function isForge(name:string):boolean{ - return name.includes("mods.toml")||name.includes("META-INF") +function isForge(name: string): boolean { + return name.endsWith("mods.toml") || name.endsWith("neoforge.mod.toml") } -function isMixinFile(name:string):boolean{ +function isMixinFile(name: string): boolean { return !name.includes("/") && name.endsWith(".json") && !name.endsWith("refmap.json") && !name.endsWith("mod.json") } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index c6afeac..96486bd 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -6,7 +6,7 @@ export const LOGGER = { warn(text: string) { warn(text); }, - error(text: string | object) { + error(text: string | object | unknown) { error(text); }, }; @@ -27,7 +27,7 @@ function warn(text: string) { )}` ); } -function error(error: object | string) { +function error(error: object | string | unknown) { if (typeof error === "object") { console.log( `[${chalk.blue(