Compare commits
5 Commits
v3.0.5
...
ee28b380f6
| Author | SHA1 | Date | |
|---|---|---|---|
| ee28b380f6 | |||
| 7988cafc0f | |||
| 0e053a7809 | |||
| 41c80af92d | |||
| 5c278e1925 |
@@ -3,115 +3,210 @@
|
||||
## 项目分析
|
||||
|
||||
DeEarthX-CE 是一个与 Minecraft 相关的工具,包含以下组件:
|
||||
- **后端**:TypeScript + Express 构建,提供模组检测、过滤、平台集成等功能
|
||||
- **前端**:Vue 3 + Ant Design Vue + Tauri 构建的桌面应用
|
||||
- **文档**:VitePress 构建的文档网站
|
||||
|
||||
* **后端**:TypeScript + Express 构建,提供模组检测、过滤、平台集成等功能
|
||||
|
||||
* **前端**:Vue 3 + Ant Design Vue + Tauri 构建的桌面应用
|
||||
|
||||
* **文档**:VitePress 构建的文档网站
|
||||
|
||||
## 改进任务列表
|
||||
|
||||
### [ ] 任务 1:代码质量检查与优化
|
||||
- **Priority**:P1
|
||||
- **Depends On**:None
|
||||
- **Description**:
|
||||
- 检查并清理未使用的依赖
|
||||
- 统一代码风格和命名规范
|
||||
- 优化错误处理机制
|
||||
- 提高代码可读性和可维护性
|
||||
- **Success Criteria**:
|
||||
- 所有依赖都是必要的
|
||||
- 代码风格统一
|
||||
- 错误处理完善
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-1.1:运行 `npm run build` 无错误
|
||||
- `programmatic` TR-1.2:运行代码检查工具无严重警告
|
||||
- `human-judgement` TR-1.3:代码结构清晰,注释完善
|
||||
### \[ ] 任务 1:代码质量检查与优化
|
||||
|
||||
### [ ] 任务 2:性能优化
|
||||
- **Priority**:P2
|
||||
- **Depends On**:任务 1
|
||||
- **Description**:
|
||||
- 优化文件操作性能
|
||||
- 优化网络请求和响应
|
||||
- 减少不必要的计算和重复操作
|
||||
- 提高模组处理速度
|
||||
- **Success Criteria**:
|
||||
- 文件操作速度提升
|
||||
- 网络请求响应时间减少
|
||||
- 模组处理效率提高
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-2.1:模组处理时间减少 20%
|
||||
- `programmatic` TR-2.2:内存使用降低 15%
|
||||
- `human-judgement` TR-2.3:用户操作响应更流畅
|
||||
* **Priority**:P1
|
||||
|
||||
### [ ] 任务 3:安全性增强
|
||||
- **Priority**:P1
|
||||
- **Depends On**:任务 1
|
||||
- **Description**:
|
||||
- 检查并修复安全漏洞
|
||||
- 加强输入验证
|
||||
- 优化文件操作安全性
|
||||
- 检查依赖的安全状态
|
||||
- **Success Criteria**:
|
||||
- 无安全漏洞
|
||||
- 输入验证完善
|
||||
- 依赖无安全问题
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-3.1:运行安全扫描工具无严重漏洞
|
||||
- `programmatic` TR-3.2:所有输入都经过验证
|
||||
- `human-judgement` TR-3.3:安全措施到位
|
||||
* **Depends On**:None
|
||||
|
||||
### [ ] 任务 4:功能增强
|
||||
- **Priority**:P2
|
||||
- **Depends On**:任务 1, 任务 3
|
||||
- **Description**:
|
||||
- 完善用户界面交互
|
||||
- 增加更多模组平台支持
|
||||
- 优化模板管理功能
|
||||
- 增强多语言支持
|
||||
- **Success Criteria**:
|
||||
- 用户界面更友好
|
||||
- 支持更多模组平台
|
||||
- 模板管理更便捷
|
||||
- 多语言支持更完善
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-4.1:所有新增功能正常工作
|
||||
- `human-judgement` TR-4.2:用户界面美观易用
|
||||
- `human-judgement` TR-4.3:多语言支持准确
|
||||
* **Description**:
|
||||
|
||||
### [ ] 任务 5:构建和部署优化
|
||||
- **Priority**:P2
|
||||
- **Depends On**:任务 1, 任务 2
|
||||
- **Description**:
|
||||
- 优化构建流程
|
||||
- 减少构建时间
|
||||
- 优化打包大小
|
||||
- 完善部署文档
|
||||
- **Success Criteria**:
|
||||
- 构建流程更高效
|
||||
- 构建时间减少
|
||||
- 打包大小优化
|
||||
- 部署文档完善
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-5.1:构建时间减少 25%
|
||||
- `programmatic` TR-5.2:打包大小减少 20%
|
||||
- `human-judgement` TR-5.3:部署文档清晰完整
|
||||
* 检查并清理未使用的依赖
|
||||
|
||||
### [ ] 任务 6:测试覆盖度提升
|
||||
- **Priority**:P3
|
||||
- **Depends On**:任务 1
|
||||
- **Description**:
|
||||
- 增加单元测试
|
||||
- 增加集成测试
|
||||
- 提高测试覆盖度
|
||||
- 建立测试自动化流程
|
||||
- **Success Criteria**:
|
||||
- 测试覆盖度达到 80% 以上
|
||||
- 关键功能有测试用例
|
||||
- 测试自动化流程建立
|
||||
- **Test Requirements**:
|
||||
- `programmatic` TR-6.1:测试覆盖度达到 80% 以上
|
||||
- `programmatic` TR-6.2:所有测试用例通过
|
||||
- `human-judgement` TR-6.3:测试用例设计合理
|
||||
* 统一代码风格和命名规范
|
||||
|
||||
* 优化错误处理机制
|
||||
|
||||
* 提高代码可读性和可维护性
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 所有依赖都是必要的
|
||||
|
||||
* 代码风格统一
|
||||
|
||||
* 错误处理完善
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-1.1:运行 `npm run build` 无错误
|
||||
|
||||
* `programmatic` TR-1.2:运行代码检查工具无严重警告
|
||||
|
||||
* `human-judgement` TR-1.3:代码结构清晰,注释完善
|
||||
|
||||
### \[ ] 任务 2:性能优化
|
||||
|
||||
* **Priority**:P2
|
||||
|
||||
* **Depends On**:任务 1
|
||||
|
||||
* **Description**:
|
||||
|
||||
* 优化文件操作性能
|
||||
|
||||
* 优化网络请求和响应
|
||||
|
||||
* 减少不必要的计算和重复操作
|
||||
|
||||
* 提高模组处理速度
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 文件操作速度提升
|
||||
|
||||
* 网络请求响应时间减少
|
||||
|
||||
* 模组处理效率提高
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-2.1:模组处理时间减少 20%
|
||||
|
||||
* `programmatic` TR-2.2:内存使用降低 15%
|
||||
|
||||
* `human-judgement` TR-2.3:用户操作响应更流畅
|
||||
|
||||
### \[ ] 任务 3:安全性增强
|
||||
|
||||
* **Priority**:P1
|
||||
|
||||
* **Depends On**:任务 1
|
||||
|
||||
* **Description**:
|
||||
|
||||
* 检查并修复安全漏洞
|
||||
|
||||
* 加强输入验证
|
||||
|
||||
* 优化文件操作安全性
|
||||
|
||||
* 检查依赖的安全状态
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 无安全漏洞
|
||||
|
||||
* 输入验证完善
|
||||
|
||||
* 依赖无安全问题
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-3.1:运行安全扫描工具无严重漏洞
|
||||
|
||||
* `programmatic` TR-3.2:所有输入都经过验证
|
||||
|
||||
* `human-judgement` TR-3.3:安全措施到位
|
||||
|
||||
### \[ ] 任务 4:功能增强
|
||||
|
||||
* **Priority**:P2
|
||||
|
||||
* **Depends On**:任务 1, 任务 3
|
||||
|
||||
* **Description**:
|
||||
|
||||
* 完善用户界面交互
|
||||
|
||||
* 增加更多模组平台支持
|
||||
|
||||
* 优化模板管理功能
|
||||
|
||||
* 增强多语言支持
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 用户界面更友好
|
||||
|
||||
* 支持更多模组平台
|
||||
|
||||
* 模板管理更便捷
|
||||
|
||||
* 多语言支持更完善
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-4.1:所有新增功能正常工作
|
||||
|
||||
* `human-judgement` TR-4.2:用户界面美观易用
|
||||
|
||||
* `human-judgement` TR-4.3:多语言支持准确
|
||||
|
||||
### \[ ] 任务 5:构建和部署优化
|
||||
|
||||
* **Priority**:P2
|
||||
|
||||
* **Depends On**:任务 1, 任务 2
|
||||
|
||||
* **Description**:
|
||||
|
||||
* 优化构建流程
|
||||
|
||||
* 减少构建时间
|
||||
|
||||
* 优化打包大小
|
||||
|
||||
* 完善部署文档
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 构建流程更高效
|
||||
|
||||
* 构建时间减少
|
||||
|
||||
* 打包大小优化
|
||||
|
||||
* 部署文档完善
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-5.1:构建时间减少 25%
|
||||
|
||||
* `programmatic` TR-5.2:打包大小减少 20%
|
||||
|
||||
* `human-judgement` TR-5.3:部署文档清晰完整
|
||||
|
||||
### \[ ] 任务 6:测试覆盖度提升
|
||||
|
||||
* **Priority**:P3
|
||||
|
||||
* **Depends On**:任务 1
|
||||
|
||||
* **Description**:
|
||||
|
||||
* 增加单元测试
|
||||
|
||||
* 增加集成测试
|
||||
|
||||
* 提高测试覆盖度
|
||||
|
||||
* 建立测试自动化流程
|
||||
|
||||
* **Success Criteria**:
|
||||
|
||||
* 测试覆盖度达到 80% 以上
|
||||
|
||||
* 关键功能有测试用例
|
||||
|
||||
* 测试自动化流程建立
|
||||
|
||||
* **Test Requirements**:
|
||||
|
||||
* `programmatic` TR-6.1:测试覆盖度达到 80% 以上
|
||||
|
||||
* `programmatic` TR-6.2:所有测试用例通过
|
||||
|
||||
* `human-judgement` TR-6.3:测试用例设计合理
|
||||
|
||||
## 实施步骤
|
||||
|
||||
@@ -125,9 +220,16 @@ DeEarthX-CE 是一个与 Minecraft 相关的工具,包含以下组件:
|
||||
## 预期成果
|
||||
|
||||
通过以上改进,DeEarthX-CE 项目将:
|
||||
- 代码质量更高,更易维护
|
||||
- 性能更优,响应更快
|
||||
- 安全性更强,更可靠
|
||||
- 功能更完善,用户体验更好
|
||||
- 构建和部署更高效
|
||||
- 测试覆盖更全面,质量更有保障
|
||||
|
||||
* 代码质量更高,更易维护
|
||||
|
||||
* 性能更优,响应更快
|
||||
|
||||
* 安全性更强,更可靠
|
||||
|
||||
* 功能更完善,用户体验更好
|
||||
|
||||
* 构建和部署更高效
|
||||
|
||||
* 测试覆盖更全面,质量更有保障
|
||||
|
||||
|
||||
91
README-EN.md
Normal file
91
README-EN.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# DeEarthX-CE
|
||||
|
||||
[简体中文](README.md) | English
|
||||
|
||||
## Project Overview
|
||||
|
||||
DeEarthX V3 is a Minecraft server-side modpack creation tool that helps you quickly convert client-side modpacks into runnable servers, while also providing template management capabilities.
|
||||
|
||||
QQ Group: 1090666196
|
||||
|
||||
Documentation: [https://dex.xcclyc.cn/](https://dex.xcclyc.cn/)
|
||||
|
||||
## Core Features
|
||||
|
||||
### Modpack Support
|
||||
- CurseForge
|
||||
- Modrinth
|
||||
- MCBBS
|
||||
|
||||
### Mod Processing
|
||||
Automatically distinguish between client-side and server-side mods, keeping what the server needs and removing client-exclusive ones (shaders, resource packs, etc.).
|
||||
|
||||
### Working Modes
|
||||
- **Server Mode**: Downloads server and mod loaders, fully generates the server
|
||||
- **Upload Mode**: Only performs mod filtering, without downloading server files
|
||||
|
||||
### Mod Loaders
|
||||
- Forge
|
||||
- NeoForge
|
||||
- Fabric
|
||||
|
||||
### Version Support
|
||||
Supports versions from 1.16.5 to the latest.
|
||||
|
||||
### Template Management
|
||||
- Create, edit, and delete local templates
|
||||
- Import/export templates
|
||||
- Template store, support downloading templates from remote servers
|
||||
- Smart download speed test, select the fastest download link
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Backend
|
||||
TypeScript + Node.js, Express provides web services, WebSocket real-time communication, packaged as standalone exe using Node.js SEA.
|
||||
|
||||
### Frontend
|
||||
Vue 3 + TypeScript, Tauri 2 desktop framework, Ant Design Vue UI components, Tailwind CSS styling.
|
||||
|
||||
## Usage Process
|
||||
|
||||
1. Prepare the modpack file
|
||||
2. Select the mode (Server/Upload)
|
||||
3. Upload the file
|
||||
4. Wait for processing to complete
|
||||
5. Download the server
|
||||
|
||||
## Template Management Process
|
||||
|
||||
1. Enter the template management page
|
||||
2. Select local templates or template store
|
||||
3. Local templates: Create, edit, delete, export templates
|
||||
4. Template store: Browse and download templates
|
||||
|
||||
## Project Features
|
||||
|
||||
- Upload and use immediately, no configuration required
|
||||
- Real-time progress display
|
||||
- Built-in BMCLAPI and MCIM mirror sources for accelerated downloads
|
||||
- Multi-language support
|
||||
- Smart template management system
|
||||
- Template store provides rich preset templates
|
||||
|
||||
> [!WARNING]
|
||||
> Mods may not be filtered completely, and the generated server is prohibited from being used for sale!
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
Simply download and install the installer to use.
|
||||
|
||||
**Note**: It is recommended not to install on drive C to avoid permission issues.
|
||||
|
||||
## System Requirements
|
||||
|
||||
- Operating System: Windows
|
||||
- Server mode requires Java environment
|
||||
- Upload mode does not require Java
|
||||
|
||||
## Development Team
|
||||
|
||||
- **Tianpao**: Core development, original author
|
||||
- **XCC**: Feature optimization, CE version author
|
||||
12
README.md
12
README.md
@@ -1,4 +1,6 @@
|
||||
# DeEarthX V3
|
||||
# DeEarthX-CE
|
||||
|
||||
[English](README-EN.md) | 简体中文
|
||||
|
||||
## 项目概述
|
||||
|
||||
@@ -6,6 +8,10 @@ DeEarthX V3 是一个 Minecraft 整合包服务端制作工具,帮你快速把
|
||||
|
||||
QQ群:1090666196
|
||||
|
||||
## 文档地址
|
||||
|
||||
文档地址:[https://dex.xcclyc.cn/](https://dex.xcclyc.cn/)
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 整合包支持
|
||||
@@ -83,5 +89,5 @@ Vue 3 + TypeScript,Tauri 2 桌面框架,Ant Design Vue UI 组件,Tailwind
|
||||
|
||||
## 开发团队
|
||||
|
||||
- **Tianpao**:核心开发
|
||||
- **XCC**:功能优化
|
||||
- **Tianpao**:核心开发,原作者
|
||||
- **XCC**:功能优化,CE版作者
|
||||
|
||||
@@ -550,12 +550,12 @@ export class ModCheckService {
|
||||
jarData = fs.readFileSync(file.filename);
|
||||
}
|
||||
|
||||
const { Azip } = await import("../utils/ziplib.js");
|
||||
const zipEntries = Azip(jarData);
|
||||
const { yauzl_promise } = await import("../utils/ziplib.js");
|
||||
const zipEntries = await yauzl_promise(jarData);
|
||||
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.entryName === iconPath || entry.entryName.endsWith(iconPath)) {
|
||||
const data = await entry.getData();
|
||||
if (entry.fileName === iconPath || entry.fileName.endsWith(iconPath)) {
|
||||
const data = await entry.ReadEntry;
|
||||
const ext = iconPath.split('.').pop()?.toLowerCase();
|
||||
const mimeType = ext === 'png' ? 'png' : 'jpeg';
|
||||
|
||||
|
||||
@@ -22,23 +22,18 @@ export class FileExtractor {
|
||||
const fullPath = path.join(this.modsPath, jarFilename);
|
||||
|
||||
try {
|
||||
let fileData: Buffer | null = null;
|
||||
try {
|
||||
fileData = fs.readFileSync(fullPath);
|
||||
const mixins = await JarParser.extractMixins(fileData);
|
||||
const infos = await JarParser.extractModInfo(fileData);
|
||||
const hash = await this.calculateFileHash(fullPath);
|
||||
const mixins = await JarParser.extractMixinsFromFile(fullPath);
|
||||
const infos = await JarParser.extractModInfoFromFile(fullPath);
|
||||
|
||||
files.push({
|
||||
filename: fullPath,
|
||||
hash: crypto.createHash('sha1').update(fileData).digest('hex'),
|
||||
mixins,
|
||||
infos,
|
||||
});
|
||||
files.push({
|
||||
filename: fullPath,
|
||||
hash,
|
||||
mixins,
|
||||
infos,
|
||||
});
|
||||
|
||||
logger.debug("文件已处理", { 文件名: fullPath, 绝对路径: path.resolve(fullPath), Mixin数量: mixins.length });
|
||||
} finally {
|
||||
fileData = null;
|
||||
}
|
||||
logger.debug("文件已处理", { 文件名: fullPath, 绝对路径: path.resolve(fullPath), Mixin数量: mixins.length });
|
||||
} catch (error: any) {
|
||||
logger.error("处理文件时出错", { 文件名: fullPath, 错误: error.message });
|
||||
}
|
||||
@@ -48,6 +43,25 @@ export class FileExtractor {
|
||||
return files;
|
||||
}
|
||||
|
||||
private async calculateFileHash(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('sha1');
|
||||
const stream = fs.createReadStream(filePath);
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
hash.update(chunk);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
resolve(hash.digest('hex'));
|
||||
});
|
||||
|
||||
stream.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getJarFiles(): string[] {
|
||||
if (!fs.existsSync(this.modsPath)) {
|
||||
fs.mkdirSync(this.modsPath, { recursive: true });
|
||||
|
||||
@@ -2,7 +2,7 @@ import got, { Got } from "got";
|
||||
import fs from "node:fs";
|
||||
import fse from "fs-extra";
|
||||
import { execPromise, fastdownload, version_compare, verifySHA1 } from "../utils/utils.js";
|
||||
import { Azip } from "../utils/ziplib.js";
|
||||
import { yauzl_promise } from "../utils/ziplib.js";
|
||||
import { Config } from "../utils/config.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
|
||||
@@ -84,18 +84,20 @@ 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 = Azip(data);
|
||||
const zip = await yauzl_promise(data);
|
||||
|
||||
for await (const entry of zip) {
|
||||
if (entry.entryName === "version.json" || entry.entryName === "install_profile.json") {
|
||||
JSON.parse((entry.getData()).toString()).libraries.forEach(async (e: any) => {
|
||||
for (const entry of zip) {
|
||||
if (entry.fileName === "version.json" || entry.fileName === "install_profile.json") {
|
||||
const entryData = await entry.ReadEntry;
|
||||
JSON.parse(entryData.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.entryName === "install_profile.json") {
|
||||
const json = JSON.parse((entry.getData()).toString()) as IForge;
|
||||
if (entry.fileName === "install_profile.json") {
|
||||
const entryData = await entry.ReadEntry;
|
||||
const json = JSON.parse(entryData.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);
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import { fastdownload, version_compare } from "../utils/utils.js";
|
||||
import got from "got";
|
||||
import p from "path";
|
||||
import { Azip } from "../utils/ziplib.js";
|
||||
import { yauzl_promise } from "../utils/ziplib.js";
|
||||
import { Config } from "../utils/config.js";
|
||||
|
||||
interface ILInfo {
|
||||
@@ -54,12 +54,12 @@ export class Minecraft {
|
||||
if (version_compare(this.minecraft, "1.18") === 1) {
|
||||
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 Azip(await fs.promises.readFile(mcpath));
|
||||
for await (const entry of zip) {
|
||||
if (entry.entryName.startsWith("META-INF/libraries/") && !entry.entryName.endsWith("/")) {
|
||||
console.log(entry.entryName);
|
||||
const data = entry.getData();
|
||||
const filepath = `${this.path}/libraries/${entry.entryName.replace("META-INF/libraries/", "")}`;
|
||||
const zip = await yauzl_promise(await fs.promises.readFile(mcpath));
|
||||
for (const entry of zip) {
|
||||
if (entry.fileName.startsWith("META-INF/libraries/") && !entry.fileName.endsWith("/")) {
|
||||
console.log(entry.fileName);
|
||||
const data = await entry.ReadEntry;
|
||||
const filepath = `${this.path}/libraries/${entry.fileName.replace("META-INF/libraries/", "")}`;
|
||||
const dir = p.dirname(filepath);
|
||||
await fs.promises.mkdir(dir, { recursive: true });
|
||||
await fs.promises.writeFile(filepath, data);
|
||||
|
||||
@@ -1,20 +1,124 @@
|
||||
import { IInfoFile, IMixinFile } from "../dearth/types.js";
|
||||
import { Azip } from "./ziplib.js";
|
||||
import { yauzl_promise } from "./ziplib.js";
|
||||
import toml from "smol-toml";
|
||||
import fs from "node:fs";
|
||||
import yauzl from "yauzl";
|
||||
|
||||
export class JarParser {
|
||||
static async extractModInfo(jarData: Buffer): Promise<IInfoFile[]> {
|
||||
return this.extractModInfoFromBuffer(jarData);
|
||||
}
|
||||
|
||||
static async extractMixins(jarData: Buffer): Promise<IMixinFile[]> {
|
||||
return this.extractMixinsFromBuffer(jarData);
|
||||
}
|
||||
|
||||
static async extractModInfoFromFile(filePath: string): Promise<IInfoFile[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const infos: IInfoFile[] = [];
|
||||
|
||||
yauzl.open(filePath, (err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
zipfile.on("entry", (entry) => {
|
||||
if (entry.fileName.endsWith("neoforge.mods.toml") || entry.fileName.endsWith("mods.toml") || entry.fileName.endsWith("fabric.mod.json")) {
|
||||
zipfile.openReadStream(entry, (err, stream) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on("data", (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
stream.on("end", () => {
|
||||
try {
|
||||
const data = Buffer.concat(chunks);
|
||||
if (entry.fileName.endsWith(".toml")) {
|
||||
infos.push({ name: entry.fileName, data: JSON.stringify(toml.parse(data.toString())) });
|
||||
} else if (entry.fileName.endsWith(".json")) {
|
||||
infos.push({ name: entry.fileName, data: data.toString() });
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
zipfile.on("end", () => {
|
||||
resolve(infos);
|
||||
});
|
||||
|
||||
zipfile.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async extractMixinsFromFile(filePath: string): Promise<IMixinFile[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mixins: IMixinFile[] = [];
|
||||
|
||||
yauzl.open(filePath, (err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
zipfile.on("entry", (entry) => {
|
||||
if (entry.fileName.endsWith(".mixins.json") && !entry.fileName.includes("/")) {
|
||||
zipfile.openReadStream(entry, (err, stream) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on("data", (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
stream.on("end", () => {
|
||||
try {
|
||||
const data = Buffer.concat(chunks);
|
||||
mixins.push({ name: entry.fileName, data: data.toString() });
|
||||
} catch (error) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
zipfile.on("end", () => {
|
||||
resolve(mixins);
|
||||
});
|
||||
|
||||
zipfile.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async extractModInfoFromBuffer(jarData: Buffer): Promise<IInfoFile[]> {
|
||||
const infos: IInfoFile[] = [];
|
||||
const zipEntries = Azip(jarData);
|
||||
const zipEntries = await yauzl_promise(jarData);
|
||||
|
||||
for (const entry of zipEntries) {
|
||||
try {
|
||||
if (entry.entryName.endsWith("neoforge.mods.toml") || entry.entryName.endsWith("mods.toml")) {
|
||||
const data = await entry.getData();
|
||||
infos.push({ name: entry.entryName, data: JSON.stringify(toml.parse(data.toString())) });
|
||||
} else if (entry.entryName.endsWith("fabric.mod.json")) {
|
||||
const data = await entry.getData();
|
||||
infos.push({ name: entry.entryName, data: data.toString() });
|
||||
if (entry.fileName.endsWith("neoforge.mods.toml") || entry.fileName.endsWith("mods.toml")) {
|
||||
const data = await entry.ReadEntry;
|
||||
infos.push({ name: entry.fileName, data: JSON.stringify(toml.parse(data.toString())) });
|
||||
} else if (entry.fileName.endsWith("fabric.mod.json")) {
|
||||
const data = await entry.ReadEntry;
|
||||
infos.push({ name: entry.fileName, data: data.toString() });
|
||||
}
|
||||
} catch (error: any) {
|
||||
continue;
|
||||
@@ -24,15 +128,15 @@ export class JarParser {
|
||||
return infos;
|
||||
}
|
||||
|
||||
static async extractMixins(jarData: Buffer): Promise<IMixinFile[]> {
|
||||
private static async extractMixinsFromBuffer(jarData: Buffer): Promise<IMixinFile[]> {
|
||||
const mixins: IMixinFile[] = [];
|
||||
const zipEntries = Azip(jarData);
|
||||
const zipEntries = await yauzl_promise(jarData);
|
||||
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.entryName.endsWith(".mixins.json") && !entry.entryName.includes("/")) {
|
||||
if (entry.fileName.endsWith(".mixins.json") && !entry.fileName.includes("/")) {
|
||||
try {
|
||||
const data = await entry.getData();
|
||||
mixins.push({ name: entry.entryName, data: data.toString() });
|
||||
const data = await entry.ReadEntry;
|
||||
mixins.push({ name: entry.fileName, data: data.toString() });
|
||||
} catch (error: any) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import admZip from "adm-zip";
|
||||
import yauzl from "yauzl";
|
||||
import Stream from "node:stream";
|
||||
|
||||
@@ -11,7 +10,7 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]> {
|
||||
const zip = await (new Promise((resolve, reject) => {
|
||||
yauzl.fromBuffer(
|
||||
buffer,
|
||||
/*{lazyEntries:true},*/ (err, zipfile) => {
|
||||
(err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
@@ -43,6 +42,9 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]> {
|
||||
stream.on("end", () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
stream.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -64,16 +66,15 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]> {
|
||||
|
||||
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("entry", (entry: yauzl.Entry) => {
|
||||
const entryP = entry as IentryP;
|
||||
entryP.openReadStream = _openReadStream(zip, entry);
|
||||
entryP.ReadEntry = _ReadEntry(zip, entry);
|
||||
entries.push(entryP);
|
||||
});
|
||||
zip.on("end", () => {
|
||||
zip.close();
|
||||
resolve(entries);
|
||||
});
|
||||
zip.on("error", (err) => {
|
||||
reject(err);
|
||||
@@ -81,8 +82,15 @@ export async function yauzl_promise(buffer: Buffer): Promise<IentryP[]> {
|
||||
});
|
||||
}
|
||||
|
||||
export function Azip(buffer: Buffer) {
|
||||
const zip = new admZip(buffer);
|
||||
const entries = zip.getEntries();
|
||||
return entries;
|
||||
// 从文件路径打开 zip 文件的函数
|
||||
export async function yauzl_from_file(filePath: string): Promise<yauzl.ZipFile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(filePath, { lazyEntries: true }, (err, zipfile) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(zipfile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^6.4.1",
|
||||
"vue-tsc": "^2.2.12"
|
||||
"vue-tsc": "^2.2.12",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "3.0.35",
|
||||
"buildTime": "2026-03-10",
|
||||
"author": "DeEarthX Team"
|
||||
"version": "3.0.36",
|
||||
"buildTime": "2026-03-15",
|
||||
"author": "xcclyc"
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ async function getVersionFromJson(): Promise<VersionInfo> {
|
||||
return {
|
||||
version: '1.0.0',
|
||||
buildTime: 'Unknown',
|
||||
author: 'Tianpao'
|
||||
author: 'DeEarthX Team'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.xcclyc.cn/15736060610/DeEarthX-CE.git"
|
||||
"url": "https://git.xcclyc.cn/xcclyc/DeEarthX-CE.git"
|
||||
},
|
||||
"license": "ISC",
|
||||
"author": "DexV3 Team",
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -166,6 +166,9 @@ importers:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.4
|
||||
version: 5.2.4(vite@6.4.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0))(vue@3.5.29(typescript@5.6.3))
|
||||
tslib:
|
||||
specifier: ^2.8.1
|
||||
version: 2.8.1
|
||||
typescript:
|
||||
specifier: ~5.6.3
|
||||
version: 5.6.3
|
||||
@@ -6593,8 +6596,7 @@ snapshots:
|
||||
babel-jest: 30.2.0(@babel/core@7.29.0)
|
||||
jest-util: 30.2.0
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
tslib@2.8.1: {}
|
||||
|
||||
type-detect@4.0.8: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user