chore(HM):使解压功能可用

This commit is contained in:
Tianpao
2026-01-01 00:26:14 +08:00
parent 81ae7bec50
commit c9a7b6f303
9 changed files with 278 additions and 142 deletions

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import p from "node:path";
import websocket, { WebSocketServer } from "ws";
import { yauzl_promise } from "./utils/yauzl.promise.js";
//import { yauzl_promise } from "./utils/yauzl.promise.js";
import { pipeline } from "node:stream/promises";
import { platform, what_platform } from "./platform/index.js";
import { DeEarth } from "./utils/DeEarth.js";
@@ -10,6 +10,7 @@ import config from "./utils/config.js";
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";
export class Dex {
wsx!: WebSocketServer;
@@ -24,7 +25,8 @@ export class Dex {
public async Main(buffer: Buffer, dser: boolean) {
try {
const first = Date.now();
const { contain, info } = await this._getinfo(buffer);
const zps = await this._zps(buffer);
const { contain, info } = await zps._getinfo();
const plat = what_platform(contain);
logger.debug("Platform detected", plat);
logger.debug("Modpack info", info);
@@ -32,9 +34,11 @@ export class Dex {
const unpath = `./instance/${mpname}`;
// 解压和下载(并行处理)
await Promise.all([
this._unzip(buffer, mpname),
zps._unzip(mpname),
platform(plat).downloadfile(info, unpath, this.message)
]);
]).catch(e=>{
console.log(e);
});
this.message.statusChange(); //改变状态
await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`).Main();
this.message.statusChange(); //改变状态(DeEarth筛选模组完毕)
@@ -68,58 +72,55 @@ export class Dex {
}
}
private async _getinfo(buffer: Buffer) {
const importantFiles = ["manifest.json", "modrinth.index.json"];
private async _zps(buffer: Buffer) {
const zip = await yauzl_promise(buffer);
for await (const entry of zip) {
if (importantFiles.includes(entry.fileName)) {
const content = await entry.ReadEntry;
const info = JSON.parse(content.toString());
logger.debug("Found important file", { fileName: entry.fileName, info });
return { contain: entry.fileName, info };
}
}
throw new Error("No manifest file found in modpack");
}
private async _unzip(buffer: Buffer, instancename: string) {
logger.info("Starting unzip process", { instancename });
const zip = await yauzl_promise(buffer);
const instancePath = `./instance/${instancename}`;
let index = 1;
for await (const entry of zip) {
const isDir = entry.fileName.endsWith("/");
if (isDir) {
await fs.promises.mkdir(`${instancePath}/${entry.fileName}`, {
recursive: true,
});
} else if (entry.fileName.startsWith("overrides/")) {
// 跳过黑名单文件
if (this._ublack(entry.fileName)) {
index++;
continue;
const _getinfo = async () => {
const importantFiles = ["manifest.json", "modrinth.index.json"];
for await (const entry of zip) {
if (importantFiles.includes(entry.fileName)) {
const content = await entry.ReadEntry;
const info = JSON.parse(content.toString());
logger.debug("Found important file", { fileName: entry.fileName, info });
return { contain: entry.fileName, info };
}
// 创建目标目录
const targetPath = entry.fileName.replace("overrides/", "");
const dirPath = `${instancePath}/${targetPath.substring(0, targetPath.lastIndexOf("/"))}`;
await fs.promises.mkdir(dirPath, { recursive: true });
// 解压文件
const stream = await entry.openReadStream;
const write = fs.createWriteStream(`${instancePath}/${targetPath}`);
await pipeline(stream, write);
}
this.message.unzip(entry.fileName, zip.length, index);
index++;
throw new Error("No manifest file found in modpack");
}
logger.info("Unzip process completed", { instancename, totalFiles: zip.length });
const _unzip = async (instancename: string) => {
logger.info("Starting unzip process", { instancename });
const instancePath = `./instance/${instancename}`;
let index = 1;
for await (const entry of zip) {
const isDir = entry.fileName.endsWith("/");
console.log(index, entry.fileName);
if (isDir) {
await fs.promises.mkdir(`${instancePath}/${entry.fileName}`, {
recursive: true,
});
} else if (entry.fileName.startsWith("overrides/")) {
// 跳过黑名单文件
if (this._ublack(entry.fileName)) {
console.log("Skip blacklist file", entry.fileName);
index++;
continue;
}
// 创建目标目录
const targetPath = entry.fileName.replace("overrides/", "");
const dirPath = `${instancePath}/${targetPath.substring(0, targetPath.lastIndexOf("/"))}`;
await fs.promises.mkdir(dirPath, { recursive: true });
// 解压文件
const stream = await entry.openReadStream;
console.log(entry.fileName);
const write = fs.createWriteStream(`${instancePath}/${targetPath}`);
await pipeline(stream, write);
}
this.message.unzip(entry.fileName, zip.length, index);
index++;
}
logger.info("Unzip process completed", { instancename, totalFiles: zip.length });
}
return { _getinfo, _unzip };
}
/**
@@ -128,8 +129,8 @@ export class Dex {
* @returns 是否在黑名单中
*/
private _ublack(filename: string): boolean {
if (filename === "overrides/") return true;
const blacklist = [
"overrides/",
"overrides/options.txt",
"shaderpacks",
"essential",

View File

@@ -2,7 +2,7 @@ import got, { Got } from "got";
import fs from "node:fs"
import fse from "fs-extra"
import { execPromise, fastdownload, version_compare } from "../utils/utils.js";
import { yauzl_promise } from "../utils/yauzl.promise.js";
import { Azip } from "../utils/ziplib.js";
import { execSync } from "node:child_process";
interface Iforge{
@@ -51,17 +51,17 @@ export class Forge {
async library(){
const _downlist: [string,string][]= []
const data = await fs.promises.readFile(`${this.path}/forge-${this.minecraft}-${this.loaderVersion}-installer.jar`)
const zip = await yauzl_promise(data)
const zip = Azip(data)
for await(const entry of zip){
if(entry.fileName === "version.json" || entry.fileName === "install_profile.json"){ //Libraries
JSON.parse((await entry.ReadEntry).toString()).libraries.forEach(async (e:any)=>{
if(entry.entryName === "version.json" || entry.entryName === "install_profile.json"){ //Libraries
JSON.parse((entry.getData()).toString()).libraries.forEach(async (e:any)=>{
const t = e.downloads.artifact.path
_downlist.push([`https://bmclapi2.bangbang93.com/maven/${t}`,`${this.path}/libraries/${t}`])
})
}
if(entry.fileName === "install_profile.json"){ //MOJMAPS
if(entry.entryName === "install_profile.json"){ //MOJMAPS
/* 获取MOJMAPS */
const json = JSON.parse((await entry.ReadEntry).toString()) as Iforge
const json = JSON.parse((entry.getData()).toString()) as Iforge
const vjson = await this.got.get(`version/${this.minecraft}/json`).json<Iversion>()
console.log(`${new URL(vjson.downloads.server_mappings.url).pathname}`)
const mojpath = this.MTP(json.data.MOJMAPS.server)

View File

@@ -1,8 +1,8 @@
import fs from "node:fs"
import { fastdownload, version_compare } from "../utils/utils.js";
import { yauzl_promise } from "../utils/yauzl.promise.js";
import { pipeline } from "node:stream/promises";
import got from "got";
import { Azip } from "../utils/ziplib.js";
interface ILInfo {
libraries: {
@@ -49,12 +49,12 @@ export class Minecraft {
// 1.18.x + MC依赖解压
const mcpath = `${this.path}/libraries/net/minecraft/server/${this.minecraft}/server-${this.minecraft}.jar`
await fastdownload([`https://bmclapi2.bangbang93.com/version/${this.minecraft}/server`, mcpath])
const zip = await yauzl_promise(await fs.promises.readFile(mcpath))
const zip = await Azip(await fs.promises.readFile(mcpath))
for await (const entry of zip) {
if (entry.fileName.startsWith("META-INF/libraries/") && !entry.fileName.endsWith("/")) {
console.log(entry.fileName)
const stream = await entry.openReadStream;
const write = fs.createWriteStream(`${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`);
if (entry.entryName.startsWith("META-INF/libraries/") && !entry.entryName.endsWith("/")) {
console.log(entry.entryName)
const stream = entry.getData();
const write = fs.createWriteStream(`${this.path}/libraries/${entry.entryName.replace("META-INF/libraries/", "")}`);
await pipeline(stream, write);
}
}

View File

@@ -1,6 +1,7 @@
import fs from "node:fs"
import crypto from "node:crypto"
import { yauzl_promise } from "./yauzl.promise.js"
//import { yauzl_promise } from "./yauzl.promise.js"
import { Azip } from "./ziplib.js"
import got from "got"
import { Utils } from "./utils.js"
import config from "./config.js"
@@ -56,6 +57,7 @@ export class DeEarth{
const result = [...new Set(hash.concat(mixins))]
//console.log(result)
result.forEach(async e=>{
console.log(e)
await fs.promises.rename(`${e}`,`${this.movepath}/${e}`.replace(this.modspath,""))
//await fs.promises.rename(`${this.modspath}/${e}`,`${this.movepath}/${e}`)
})
@@ -136,9 +138,9 @@ export class DeEarth{
const data = fs.readFileSync(file)
const sha1 = crypto.createHash('sha1').update(data).digest('hex') //Get Hash
const mxarr:{name:string,data:string}[] = []
const mixins = (await yauzl_promise(data)).forEach(async e=>{ //Get Mixins Info to check
if(e.fileName.endsWith(".mixins.json")&&!e.fileName.includes("/")){
mxarr.push({name:e.fileName,data:(await e.ReadEntry).toString()})
const mixins = (Azip(data)).forEach(async e=>{ //Get Mixins Info to check
if(e.entryName.endsWith(".mixins.json")&&!e.entryName.includes("/")){
mxarr.push({name:e.entryName,data:(await e.getData()).toString()})
}
})
arr.push({filename:file,hash:sha1,mixins:mxarr})

View File

@@ -1,72 +0,0 @@
import yauzl from "yauzl";
import Stream from "node:stream"
export interface IentryP extends yauzl.Entry {
openReadStream: Promise<Stream.Readable>;
ReadEntry: Promise<Buffer>;
}
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
const zip = await (new Promise((resolve,reject)=>{
yauzl.fromBuffer(buffer,/*{lazyEntries:true},*/ (err, zipfile) => {
if (err){
reject(err);
return;
}
resolve(zipfile);
});
return;
}) as Promise<yauzl.ZipFile>);
return new Promise((resolve, reject) => {
const entries: IentryP[]= []
zip.on("entry", async (entry: yauzl.Entry) => {
const _entry = {
...entry,
getLastModDate: entry.getLastModDate,
isEncrypted: entry.isEncrypted,
isCompressed: entry.isCompressed,
openReadStream: _openReadStream(zip,entry),
ReadEntry: _ReadEntry(zip,entry),
}
entries.push(_entry)
if (zip.entryCount === entries.length){
zip.close();
resolve(entries);
}
});
zip.on("error",err=>{
reject(err);
})
});
}
async function _ReadEntry(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Buffer>{
return new Promise((resolve,reject)=>{
zip.openReadStream(entry,(err,stream)=>{
if (err){
reject(err);
return;
}
const chunks: Buffer[] = [];
stream.on("data",(chunk)=>{
chunks.push(chunk);
})
stream.on("end",()=>{
resolve(Buffer.concat(chunks));
})
})
})
}
async function _openReadStream(zip:yauzl.ZipFile,entry:yauzl.Entry): Promise<Stream.Readable>{
return new Promise((resolve,reject)=>{
zip.openReadStream(entry,(err,stream)=>{
if (err){
reject(err);
return;
}
resolve(stream);
})
})
}

View File

@@ -0,0 +1,89 @@
import yauzl from "yauzl";
import Stream from "node:stream"
export interface IentryP extends yauzl.Entry {
openReadStream: Promise<Stream.Readable>;
ReadEntry: Promise<Buffer>;
}
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]>{
const zip = await (new Promise((resolve,reject)=>{
yauzl.fromBuffer(buffer, { lazyEntries: true }, (err, zipfile) => {
if (err){
reject(err);
return;
}
resolve(zipfile);
});
}) as Promise<yauzl.ZipFile>);
return await new Promise((resolve, reject) => {
const entries: IentryP[] = [];
let entryCount = 0;
zip.on("entry", (entry: yauzl.Entry) => {
// 创建新对象并复制所有entry属性避免yauzl重用对象导致的引用问题
const _entry = Object.assign({}, entry) as IentryP;
_entry.openReadStream = _openReadStream(zip, entry);
_entry.ReadEntry = _ReadEntry(zip, entry);
entries.push(_entry);
entryCount++;
//console.log(entryCount, entry.fileName);
// 继续读取下一个条目
zip.readEntry();
});
zip.on("end", () => {
zip.close();
console.log(entryCount, "entries read");
if(entryCount === zip.entryCount){
console.log("All entries read");
resolve(entries);
}
});
zip.on("error", (err) => {
reject(err);
});
// 开始读取第一个条目
zip.readEntry();
});
}
async function _openReadStream(zip: yauzl.ZipFile, entry: yauzl.Entry): Promise<Stream.Readable>{
return new Promise((resolve, reject) => {
zip.openReadStream(entry, (err, stream) => {
if (err) {
reject(err);
return;
}
resolve(stream);
});
});
}
async function _ReadEntry(zip: yauzl.ZipFile, entry: yauzl.Entry): Promise<Buffer>{
return new Promise((resolve, reject) => {
zip.openReadStream(entry, (err, stream) => {
if (err) {
reject(err);
return;
}
const chunks: Buffer[] = [];
stream.on("data", (chunk) => {
chunks.push(chunk);
});
stream.on("end", () => {
resolve(Buffer.concat(chunks));
});
stream.on("error", (err) => {
reject(err);
});
});
});
}

View File

@@ -0,0 +1,84 @@
import admZip from "adm-zip";
import yauzl from "yauzl";
import Stream from "node:stream";
export interface IentryP extends yauzl.Entry {
openReadStream: Promise<Stream.Readable>;
ReadEntry: Promise<Buffer>;
}
export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]> {
const zip = await (new Promise((resolve, reject) => {
yauzl.fromBuffer(
buffer,
/*{lazyEntries:true},*/ (err, zipfile) => {
if (err) {
reject(err);
return;
}
resolve(zipfile);
}
);
return;
}) as Promise<yauzl.ZipFile>);
const _ReadEntry = async (
zip: yauzl.ZipFile,
entry: yauzl.Entry
): Promise<Buffer> => {
return new Promise((resolve, reject) => {
zip.openReadStream(entry, (err, stream) => {
if (err) {
reject(err);
return;
}
const chunks: Buffer[] = [];
stream.on("data", (chunk) => {
chunks.push(chunk);
});
stream.on("end", () => {
resolve(Buffer.concat(chunks));
});
});
});
};
const _openReadStream = async (
zip: yauzl.ZipFile,
entry: yauzl.Entry
): Promise<Stream.Readable> => {
return new Promise((resolve, reject) => {
zip.openReadStream(entry, (err, stream) => {
if (err) {
reject(err);
return;
}
resolve(stream);
});
});
};
return new Promise((resolve, reject) => {
const entries: IentryP[] = [];
zip.on("entry", async (entry: yauzl.Entry) => {
const entryP = entry as IentryP;
//console.log(entry.fileName);
entryP.openReadStream = _openReadStream(zip, entry);
entryP.ReadEntry = _ReadEntry(zip, entry);
entries.push(entryP);
if (zip.entryCount === entries.length) {
zip.close();
resolve(entries);
}
});
zip.on("error", (err) => {
reject(err);
});
});
}
export function Azip(buffer: Buffer) {
const zip = new admZip(buffer);
const entries = zip.getEntries();
return entries;
}