From 295f1fbeced9c324acf2f5fa2483bbb37f153aaa Mon Sep 17 00:00:00 2001 From: Tianpao Date: Mon, 2 Feb 2026 11:42:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81PCL=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E7=9A=84zip=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/Dex.ts | 94 ++++++++++++++++++++++++++++++++-- backend/src/core.ts | 2 +- backend/src/modloader/index.ts | 15 ++++++ backend/src/utils/utils.ts | 41 +++++++++++---- 4 files changed, 139 insertions(+), 13 deletions(-) diff --git a/backend/src/Dex.ts b/backend/src/Dex.ts index a0413e5..8d0cee1 100644 --- a/backend/src/Dex.ts +++ b/backend/src/Dex.ts @@ -11,6 +11,7 @@ import { execPromise } from "./utils/utils.js"; import { MessageWS } from "./utils/ws.js"; import { logger } from "./utils/logger.js"; import { yauzl_promise } from "./utils/ziplib.js"; +import yauzl from "yauzl"; export class Dex { wsx!: WebSocketServer; @@ -22,11 +23,17 @@ export class Dex { }); } - public async Main(buffer: Buffer, dser: boolean) { + public async Main(buffer: Buffer, dser: boolean, filename?: string) { try { const first = Date.now(); - const zps = await this._zips(buffer); + const processedBuffer = await this._processModpack(buffer, filename); + const zps = await this._zips(processedBuffer); const { contain, info } = await zps._getinfo(); + if (!contain || !info) { + logger.error("Modpack info is empty"); + this.message.handleError(new Error("It seems that the modpack is not a valid modpack.")); + return; + } const plat = what_platform(contain); logger.debug("Platform detected", plat); logger.debug("Modpack info", info); @@ -72,8 +79,86 @@ export class Dex { } } + private async _processModpack(buffer: Buffer, filename?: string): Promise { + if (!filename || (!filename.endsWith('.zip') && !filename.endsWith('.mrpack'))) { + return buffer; + } + + try { + const zip = await (new Promise((resolve, reject) => { + yauzl.fromBuffer(buffer, { lazyEntries: true }, (err, zipfile) => { + if (err) { + reject(err); + return; + } + resolve(zipfile); + }); + }) as Promise); + + return new Promise((resolve, reject) => { + let mrpackBuffer: Buffer | null = null; + let hasProcessed = false; + + zip.on('entry', (entry: yauzl.Entry) => { + if (hasProcessed || !entry.fileName.endsWith('modpack.mrpack')) { + zip.readEntry(); + return; + } + + if (entry.fileName === 'modpack.mrpack') { + hasProcessed = true; + zip.openReadStream(entry, (err, stream) => { + if (err) { + zip.close(); + reject(err); + return; + } + + const chunks: Buffer[] = []; + stream.on('data', (chunk) => { + chunks.push(chunk); + }); + stream.on('end', () => { + mrpackBuffer = Buffer.concat(chunks); + zip.close(); + resolve(mrpackBuffer); + }); + stream.on('error', (err) => { + zip.close(); + reject(err); + }); + }); + } else { + zip.readEntry(); + } + }); + + zip.on('end', () => { + if (!hasProcessed) { + zip.close(); + resolve(buffer); + } + }); + + zip.on('error', (err) => { + zip.close(); + reject(err); + }); + + zip.readEntry(); + }); + } catch (e) { + logger.warn('Failed to check for modpack.mrpack, using original buffer', e); + return buffer; + } + } + private async _zips(buffer: Buffer) { + if (buffer.length === 0) { + throw new Error("zip buffer is empty"); + } const zip = await yauzl_promise(buffer); + let index = 0; const _getinfo = async () => { const importantFiles = ["manifest.json", "modrinth.index.json"]; for await (const entry of zip) { @@ -83,10 +168,13 @@ export class Dex { logger.debug("Found important file", { fileName: entry.fileName, info }); return { contain: entry.fileName, info }; } + index++; } throw new Error("No manifest file found in modpack"); } - + if (index === zip.length) { + throw new Error("No manifest file found in modpack"); + } const _unzip = async (instancename: string) => { logger.info("Starting unzip process", { instancename }); const instancePath = `./instance/${instancename}`; diff --git a/backend/src/core.ts b/backend/src/core.ts index 824bc1c..4fb185e 100644 --- a/backend/src/core.ts +++ b/backend/src/core.ts @@ -93,7 +93,7 @@ export class Core { logger.info("Starting task", { isServerMode }); // 非阻塞执行主要任务 - this.dex.Main(req.file.buffer, isServerMode).catch(err => { + this.dex.Main(req.file.buffer, isServerMode, req.file.originalname).catch(err => { logger.error("Task execution failed", err); }); diff --git a/backend/src/modloader/index.ts b/backend/src/modloader/index.ts index fb01e10..39b4a2c 100644 --- a/backend/src/modloader/index.ts +++ b/backend/src/modloader/index.ts @@ -2,6 +2,7 @@ import { Fabric } from "./fabric.js"; import { Forge } from "./forge.js"; import { Minecraft } from "./minecraft.js"; import { NeoForge } from "./neoforge.js"; +import fs from "node:fs"; /** * 模组加载器接口 @@ -54,4 +55,18 @@ export async function mlsetup(ml: string, mcv: string, mlv: string, path: string */ export async function dinstall(ml: string, mcv: string, mlv: string, path: string): Promise { await modloader(ml, mcv, mlv, path).installer(); + + let cmd = ''; + if (ml === 'forge' || ml === 'neoforge') { + cmd = `java -jar forge-${mcv}-${mlv}-installer.jar --installServer`; + } else if (ml === 'fabric' || ml === 'fabric-loader') { + await fs.promises.writeFile(`${path}/run.bat`,`@echo off\njava -jar fabric-server-launch.jar\n`) + await fs.promises.writeFile(`${path}/run.sh`,`#!/bin/bash\njava -jar fabric-server-launch.jar\n`) + cmd = `java -jar fabric-installer.jar server -dir . -mcversion ${mcv} -loader ${mlv} -downloadMinecraft`; + } + + if (cmd) { + await fs.promises.writeFile(`${path}/install.bat`, `@echo off\n${cmd}\necho Install Successfully,Enter Some Key to Exit!\npause\n`); + await fs.promises.writeFile(`${path}/install.sh`, `#!/bin/bash\n${cmd}\n`); + } } \ No newline at end of file diff --git a/backend/src/utils/utils.ts b/backend/src/utils/utils.ts index 1180025..11b3253 100644 --- a/backend/src/utils/utils.ts +++ b/backend/src/utils/utils.ts @@ -5,7 +5,7 @@ import pRetry from "p-retry"; import fs from "node:fs"; import fse from "fs-extra"; import { WebSocket } from "ws"; -import { ExecOptions, exec} from "node:child_process"; +import { ExecOptions, exec, spawn} from "node:child_process"; /** * Java版本信息接口 @@ -135,20 +135,43 @@ export async function checkJava(): Promise { export function execPromise(cmd:string,options?:ExecOptions){ logger.debug(`Executing command: ${cmd}`); - return new Promise((resolve,reject)=>{ - exec(cmd,options,(err,stdout,stderr)=>{ - if(err){ - logger.error(`Command execution failed: ${cmd}`, err); + return new Promise((resolve,reject)=>{ + const args = cmd.split(' '); + const command = args.shift() || ''; + + const child = spawn(command, args, { + ...options, + shell: true + }); + + let stdout = ''; + let stderr = ''; + + child.stdout?.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr?.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('close', (code) => { + if (code !== 0) { + logger.error(`Command execution failed: ${cmd}`); logger.debug(`Stderr: ${stderr}`); - reject(err) + reject(new Error(`Command failed with exit code ${code}`)); return; } if (stdout) logger.debug(`Command stdout: ${stdout}`); if (stderr) logger.debug(`Command stderr: ${stderr}`); - }).on('exit',(code)=>{ logger.debug(`Command completed with exit code: ${code}`); - resolve(code) - }) + resolve(code || 0); + }); + + child.on('error', (err) => { + logger.error(`Command execution error: ${cmd}`, err); + reject(err); + }); }) }