node-asset-studio-mod 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +152 -0
- package/package.json +31 -0
- package/readme.md +114 -0
- package/scripts/download-cli.js +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 StarFreedomX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const AssetTypes: readonly ["all", "tex2d", "tex2dArray", "sprite", "textasset", "monobehaviour", "font", "shader", "movietexture", "audio", "video", "mesh", "animator"];
|
|
2
|
+
export type ExportMode = "extract" | "export" | "exportRaw" | "dump" | "info" | "live2d" | "splitObjects" | "animator";
|
|
3
|
+
export type AssetType = typeof AssetTypes[number];
|
|
4
|
+
export interface ExportAssetsDefaultConfig {
|
|
5
|
+
mode?: ExportMode;
|
|
6
|
+
cliPath?: string;
|
|
7
|
+
log?: boolean;
|
|
8
|
+
assetType?: AssetType | AssetType[];
|
|
9
|
+
group?: "none" | "type" | "container" | "containerFull" | "fileName" | "sceneHierarchy";
|
|
10
|
+
filenameFormat?: "assetName" | "assetName_pathID" | "pathID";
|
|
11
|
+
overwrite?: boolean;
|
|
12
|
+
logLevel?: "verbose" | "debug" | "info" | "warning" | "error";
|
|
13
|
+
logOutput?: "console" | "file" | "both";
|
|
14
|
+
imageFormat?: "none" | "jpg" | "png" | "bmp" | "tga" | "webp";
|
|
15
|
+
audioFormat?: "none" | "wav";
|
|
16
|
+
l2dGroupOption?: "container" | "fileName" | "modelName";
|
|
17
|
+
l2dMotionMode?: "monoBehaviour" | "animationClip";
|
|
18
|
+
l2dSearchByFilename?: boolean;
|
|
19
|
+
l2dForceBezier?: boolean;
|
|
20
|
+
fbxScaleFactor?: number;
|
|
21
|
+
fbxBoneSize?: number;
|
|
22
|
+
fbxAnimation?: "auto" | "skip" | "all";
|
|
23
|
+
fbxUVsAsDiffuse?: boolean;
|
|
24
|
+
filterByName?: string;
|
|
25
|
+
filterByContainer?: string;
|
|
26
|
+
filterByPathID?: string;
|
|
27
|
+
filterByText?: string;
|
|
28
|
+
filterWithRegex?: boolean;
|
|
29
|
+
blockinfoComp?: "auto" | "zstd" | "oodle" | "lz4" | "lzma";
|
|
30
|
+
blockComp?: "auto" | "zstd" | "oodle" | "lz4" | "lzma";
|
|
31
|
+
maxExportTasks?: number;
|
|
32
|
+
exportAssetList?: "none" | "xml";
|
|
33
|
+
assemblyFolder?: string;
|
|
34
|
+
unityVersion?: string;
|
|
35
|
+
decompressToDisk?: boolean;
|
|
36
|
+
notRestoreExtension?: boolean;
|
|
37
|
+
ignoreTypetree?: boolean;
|
|
38
|
+
loadAll?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare class AssetExporter {
|
|
41
|
+
private defaultConfig;
|
|
42
|
+
constructor(defaultConfig?: ExportAssetsDefaultConfig);
|
|
43
|
+
private getDefaultCliPath;
|
|
44
|
+
exportAssets(input: string, output: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
export declare const exportAssets: any;
|
|
47
|
+
export declare const createExporter: (config?: ExportAssetsDefaultConfig) => AssetExporter;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
export const AssetTypes = [
|
|
9
|
+
"all",
|
|
10
|
+
"tex2d",
|
|
11
|
+
"tex2dArray",
|
|
12
|
+
"sprite",
|
|
13
|
+
"textasset",
|
|
14
|
+
"monobehaviour",
|
|
15
|
+
"font",
|
|
16
|
+
"shader",
|
|
17
|
+
"movietexture",
|
|
18
|
+
"audio",
|
|
19
|
+
"video",
|
|
20
|
+
"mesh",
|
|
21
|
+
"animator",
|
|
22
|
+
];
|
|
23
|
+
const cliArgMap = {
|
|
24
|
+
overwrite: "-r",
|
|
25
|
+
logLevel: "--log-level",
|
|
26
|
+
logOutput: "--log-output",
|
|
27
|
+
imageFormat: "--image-format",
|
|
28
|
+
audioFormat: "--audio-format",
|
|
29
|
+
l2dGroupOption: "--l2d-group-option",
|
|
30
|
+
l2dMotionMode: "--l2d-motion-mode",
|
|
31
|
+
l2dSearchByFilename: "--l2d-search-by-filename",
|
|
32
|
+
l2dForceBezier: "--l2d-force-bezier",
|
|
33
|
+
fbxScaleFactor: "--fbx-scale-factor",
|
|
34
|
+
fbxBoneSize: "--fbx-bone-size",
|
|
35
|
+
fbxAnimation: "--fbx-animation",
|
|
36
|
+
fbxUVsAsDiffuse: "--fbx-uvs-as-diffuse",
|
|
37
|
+
filterByName: "--filter-by-name",
|
|
38
|
+
filterByContainer: "--filter-by-container",
|
|
39
|
+
filterByPathID: "--filter-by-pathid",
|
|
40
|
+
filterByText: "--filter-by-text",
|
|
41
|
+
filterWithRegex: "--filter-with-regex",
|
|
42
|
+
blockinfoComp: "--blockinfo-comp",
|
|
43
|
+
blockComp: "--block-comp",
|
|
44
|
+
maxExportTasks: "--max-export-tasks",
|
|
45
|
+
exportAssetList: "--export-asset-list",
|
|
46
|
+
assemblyFolder: "--assembly-folder",
|
|
47
|
+
unityVersion: "--unity-version",
|
|
48
|
+
decompressToDisk: "--decompress-to-disk",
|
|
49
|
+
notRestoreExtension: "--not-restore-extension",
|
|
50
|
+
ignoreTypetree: "--ignore-typetree",
|
|
51
|
+
loadAll: "--load-all",
|
|
52
|
+
};
|
|
53
|
+
export class AssetExporter {
|
|
54
|
+
defaultConfig;
|
|
55
|
+
constructor(defaultConfig = {}) {
|
|
56
|
+
this.defaultConfig = {
|
|
57
|
+
mode: "export",
|
|
58
|
+
log: true,
|
|
59
|
+
group: "container",
|
|
60
|
+
assetType: "all",
|
|
61
|
+
...defaultConfig,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
getDefaultCliPath() {
|
|
65
|
+
const base = path.resolve(__dirname, "../bin");
|
|
66
|
+
// 遍历 bin 下的所有子目录
|
|
67
|
+
const subdirs = fs.readdirSync(base, { withFileTypes: true })
|
|
68
|
+
.filter(d => d.isDirectory())
|
|
69
|
+
.map(d => path.join(base, d.name));
|
|
70
|
+
for (const dir of subdirs) {
|
|
71
|
+
const exe = os.platform() === "win32"
|
|
72
|
+
? path.join(dir, "AssetStudioModCLI.exe")
|
|
73
|
+
: path.join(dir, "AssetStudioModCLI");
|
|
74
|
+
if (fs.existsSync(exe))
|
|
75
|
+
return exe;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
async exportAssets(input, output) {
|
|
80
|
+
const cfg = this.defaultConfig;
|
|
81
|
+
const useLog = cfg.log ?? true;
|
|
82
|
+
if (!input)
|
|
83
|
+
throw new Error("missing input path");
|
|
84
|
+
if (!output)
|
|
85
|
+
throw new Error("missing output path");
|
|
86
|
+
if (!fs.existsSync(input))
|
|
87
|
+
throw new Error(`input not exist: ${input}`);
|
|
88
|
+
if (!fs.existsSync(output))
|
|
89
|
+
fs.mkdirSync(output, { recursive: true });
|
|
90
|
+
let cliPath = cfg.cliPath || this.getDefaultCliPath();
|
|
91
|
+
if (!cliPath)
|
|
92
|
+
throw new Error("Cannot find AssetStudioModCLI,please run `pnpm install` or check the folder /bin");
|
|
93
|
+
let types = "all";
|
|
94
|
+
if (cfg.assetType) {
|
|
95
|
+
types = Array.isArray(cfg.assetType) ? cfg.assetType.join(",") : cfg.assetType;
|
|
96
|
+
types.split(",").forEach((t) => {
|
|
97
|
+
if (!AssetTypes.includes(t))
|
|
98
|
+
throw new Error(`unsupported type: ${t}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const args = [
|
|
102
|
+
wrapPath(input),
|
|
103
|
+
"-m", cfg.mode,
|
|
104
|
+
"-t", types,
|
|
105
|
+
"-g", cfg.group,
|
|
106
|
+
"-o", wrapPath(output),
|
|
107
|
+
];
|
|
108
|
+
// 自动生成 args
|
|
109
|
+
for (const [key, cliName] of Object.entries(cliArgMap)) {
|
|
110
|
+
const val = cfg[key];
|
|
111
|
+
if (val === undefined || val === false)
|
|
112
|
+
continue;
|
|
113
|
+
if (typeof cliName === "string") {
|
|
114
|
+
// 布尔参数或有值参数
|
|
115
|
+
if (typeof val === "boolean")
|
|
116
|
+
args.push(cliName);
|
|
117
|
+
else
|
|
118
|
+
args.push(cliName, val.toString());
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// tuple
|
|
122
|
+
args.push(cliName[0], val.toString());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (useLog)
|
|
126
|
+
console.log("exec:", cliPath, args.join(" "));
|
|
127
|
+
await new Promise((resolve, reject) => {
|
|
128
|
+
const proc = spawn(cliPath, args, { shell: true, windowsHide: true });
|
|
129
|
+
if (useLog) {
|
|
130
|
+
proc.stdout.on("data", (d) => process.stdout.write(d.toString()));
|
|
131
|
+
proc.stderr.on("data", (d) => process.stderr.write(d.toString()));
|
|
132
|
+
}
|
|
133
|
+
proc.on("error", reject);
|
|
134
|
+
proc.on("close", (code) => {
|
|
135
|
+
if (code === 0) {
|
|
136
|
+
resolve();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
reject(new Error(`AssetStudio CLI exit code: ${code}`));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function wrapPath(p) {
|
|
146
|
+
const resolved = path.resolve(p);
|
|
147
|
+
return `"${resolved}"`;
|
|
148
|
+
}
|
|
149
|
+
// 默认快捷导出
|
|
150
|
+
const defaultExporter = new AssetExporter();
|
|
151
|
+
export const exportAssets = defaultExporter.exportAssets.bind(defaultExporter);
|
|
152
|
+
export const createExporter = (config) => new AssetExporter(config);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-asset-studio-mod",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js adapter for AssetStudioMod CLI",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"scripts"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "StarFreedomX",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@types/node": "^24.10.1",
|
|
23
|
+
"dotenv": "^17.2.3",
|
|
24
|
+
"unzipper": "^0.12.3"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"postinstall": "node scripts/download-cli.js",
|
|
29
|
+
"pub": "pnpm publish"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# node-asset-studio-mod
|
|
2
|
+
|
|
3
|
+
Node.js wrapper for [AssetStudioMod](https://github.com/aelurum/AssetStudio) CLI.
|
|
4
|
+
|
|
5
|
+
other language:
|
|
6
|
+
* [简体中文](readme_CN.md)
|
|
7
|
+
|
|
8
|
+
## Environment
|
|
9
|
+
|
|
10
|
+
**.NET 9 Runtime required**
|
|
11
|
+
|
|
12
|
+
- **Windows**: [.NET Desktop Runtime 9.0](https://dotnet.microsoft.com/download/dotnet/9.0)
|
|
13
|
+
- **Linux / Mac**: [.NET Runtime 9.0](https://dotnet.microsoft.com/download/dotnet/9.0)
|
|
14
|
+
|
|
15
|
+
Ensure the runtime is installed before using this library.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
# using pnpm
|
|
23
|
+
pnpm install node-asset-studio-mod
|
|
24
|
+
|
|
25
|
+
# or npm
|
|
26
|
+
npm install node-asset-studio-mod
|
|
27
|
+
|
|
28
|
+
# or yarn
|
|
29
|
+
yarn add node-asset-studio-mod
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> During installation, the CLI binary will be downloaded automatically into `bin/` according to your platform.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### 1. Using the default exporter function
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
import { exportAssets } from "node-asset-studio-mod";
|
|
42
|
+
|
|
43
|
+
const input = "path/to/assets/9.3.0.180";
|
|
44
|
+
const output = "path/to/analysing/9.3.0.180";
|
|
45
|
+
|
|
46
|
+
await exportAssets(input, output);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Default options:**
|
|
50
|
+
|
|
51
|
+
- `mode`: `"export"`
|
|
52
|
+
- `log`: `true`
|
|
53
|
+
- `group`: `"container"`
|
|
54
|
+
- `assetType`: `"all"`
|
|
55
|
+
- `cliPath`: automatically detected from `bin/`
|
|
56
|
+
|
|
57
|
+
You can override options by passing a config object:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
import { exportAssets } from "node-asset-studio-mod";
|
|
61
|
+
|
|
62
|
+
await exportAssets(input, output, {
|
|
63
|
+
assetType: ["tex2d", "sprite", "textasset"],
|
|
64
|
+
overwrite: true,
|
|
65
|
+
imageFormat: "png",
|
|
66
|
+
logLevel: "info",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 2. Using class instance for more control
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
import { AssetExporter } from "node-asset-studio-mod";
|
|
76
|
+
|
|
77
|
+
const exporter = new AssetExporter({
|
|
78
|
+
cliPath: "E:/myproject/bin/AssetStudioModCLI.exe", // optional
|
|
79
|
+
mode: "export",
|
|
80
|
+
assetType: ["tex2d", "sprite"],
|
|
81
|
+
overwrite: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await exporter.exportAssets(input, output);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- You can create multiple instances with different configurations.
|
|
88
|
+
- `cliPath` is optional; the library will automatically detect the binary in `bin/`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 3. Asset Types
|
|
93
|
+
|
|
94
|
+
The supported asset types (matching TS `AssetTypes`) are:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
"all", "tex2d", "tex2dArray", "sprite", "textasset", "monobehaviour",
|
|
98
|
+
"font", "shader", "movietexture", "audio", "video", "mesh", "animator"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Notes:**
|
|
102
|
+
|
|
103
|
+
- `"all"` exports all types listed above.
|
|
104
|
+
- You can specify multiple types using an array, e.g., `["tex2d", "sprite"]`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### 4. Export Modes
|
|
109
|
+
|
|
110
|
+
Supported modes (matching TS `ExportMode`):
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
"extract", "export", "exportRaw", "dump", "info", "live2d", "splitObjects", "animator"
|
|
114
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// scripts/download-cli.js
|
|
2
|
+
import https from 'https';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { execSync, exec } from 'child_process';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import unzipper from 'unzipper';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const binDir = path.join(__dirname, '../bin');
|
|
13
|
+
const zipPath = path.join(binDir, 'cli.zip');
|
|
14
|
+
|
|
15
|
+
async function checkDotnet() {
|
|
16
|
+
try {
|
|
17
|
+
const out = execSync('dotnet --list-runtimes').toString();
|
|
18
|
+
if (out.includes('Microsoft.NETCore.App 9.')) {
|
|
19
|
+
console.log('✔ 已检测到 .NET 9 Runtime');
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
console.log(`
|
|
24
|
+
⚠ 检测到未安装 .NET 9 Runtime
|
|
25
|
+
Windows (.NET Desktop): https://dotnet.microsoft.com/download/dotnet/9.0
|
|
26
|
+
Linux/macOS (.NET Runtime): https://dotnet.microsoft.com/download/dotnet/9.0
|
|
27
|
+
`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getDownloadURL() {
|
|
32
|
+
const base = 'https://github.com/aelurum/AssetStudio/releases/download/v0.19.0';
|
|
33
|
+
|
|
34
|
+
switch (os.platform()) {
|
|
35
|
+
case 'win32':
|
|
36
|
+
return `${base}/AssetStudioModCLI_net9_win64.zip`;
|
|
37
|
+
case 'darwin':
|
|
38
|
+
return `${base}/AssetStudioModCLI_net9_osx.zip`;
|
|
39
|
+
case 'linux':
|
|
40
|
+
return `${base}/AssetStudioModCLI_net9_linux64.zip`;
|
|
41
|
+
}
|
|
42
|
+
throw new Error('Unsupported platform');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function mirrorURLs(url) {
|
|
46
|
+
const u = new URL(url);
|
|
47
|
+
return [
|
|
48
|
+
`https://ghproxy.net/${url}`,
|
|
49
|
+
`https://download.fastgit.org${u.pathname}`,
|
|
50
|
+
`https://github.com.cnpmjs.org${u.pathname}`
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function download(url) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
console.log('Downloading:', url);
|
|
57
|
+
|
|
58
|
+
https.get(url, res => {
|
|
59
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
60
|
+
return resolve(download(res.headers.location)); // 处理302跳转
|
|
61
|
+
}
|
|
62
|
+
if (res.statusCode !== 200) {
|
|
63
|
+
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
67
|
+
const file = fs.createWriteStream(zipPath);
|
|
68
|
+
|
|
69
|
+
res.pipe(file);
|
|
70
|
+
file.on('finish', () => file.close(resolve));
|
|
71
|
+
file.on('error', reject);
|
|
72
|
+
}).on('error', reject);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function extract() {
|
|
77
|
+
console.log('Extracting...');
|
|
78
|
+
await fs.createReadStream(zipPath)
|
|
79
|
+
.pipe(unzipper.Extract({ path: binDir }))
|
|
80
|
+
.promise();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
await checkDotnet();
|
|
85
|
+
|
|
86
|
+
const url = getDownloadURL();
|
|
87
|
+
const urls = [...mirrorURLs(url), url];
|
|
88
|
+
|
|
89
|
+
for (const u of urls) {
|
|
90
|
+
try {
|
|
91
|
+
console.log('尝试下载:', u);
|
|
92
|
+
await download(u);
|
|
93
|
+
console.log('✔ 下载成功:', u);
|
|
94
|
+
await extract();
|
|
95
|
+
console.log('AssetStudioModCLI 下载完成');
|
|
96
|
+
fs.unlinkSync(zipPath);
|
|
97
|
+
console.log('已清理临时文件 cli.zip');
|
|
98
|
+
return;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.log(`✘ 失败: ${u}`);
|
|
101
|
+
console.log(err.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.error('所有下载方式均失败,请手动下载');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main();
|