1 | import { _exists, _mkdir, MinecraftFolder, _writeFile, _readFile, checksum, _pipeline, Version, LibraryInfo, getPlatform, diagnoseFile } from '@xmcl/core';
|
2 | import 'crypto';
|
3 | import { request, Agent } from 'http';
|
4 | import { unlink as unlink$1, stat as stat$1, link as link$1, open as open$1, close as close$1, copyFile as copyFile$1, ftruncate, createReadStream, fstat, fdatasync, createWriteStream } from 'fs';
|
5 | import { request as request$1, Agent as Agent$1 } from 'https';
|
6 | import { URL, fileURLToPath } from 'url';
|
7 | import { spawn, exec } from 'child_process';
|
8 | import { dirname, join, delimiter, basename, resolve } from 'path';
|
9 | import { promisify } from 'util';
|
10 | import { task, AbortableTask, CancelledError, BaseTask, TaskState } from '@xmcl/task';
|
11 | import { parse } from '@xmcl/forge-site-parser';
|
12 | import { open as open$2, walkEntriesGenerator, readEntry, filterEntries, openEntryReadStream, readAllEntries, getEntriesRecord } from '@xmcl/unzip';
|
13 | import { O_RDWR, O_CREAT } from 'constants';
|
14 | import { cpus, tmpdir, platform, EOL } from 'os';
|
15 | import { ClassReader, Opcodes, ClassVisitor } from '@xmcl/asm';
|
16 |
|
17 | const unlink = promisify(unlink$1);
|
18 | const stat = promisify(stat$1);
|
19 | const link = promisify(link$1);
|
20 | const open = promisify(open$1);
|
21 | const close = promisify(close$1);
|
22 | const copyFile = promisify(copyFile$1);
|
23 | const truncate = promisify(ftruncate);
|
24 | function missing(target) {
|
25 | return _exists(target).then((v) => !v);
|
26 | }
|
27 | async function ensureDir(target) {
|
28 | try {
|
29 | await _mkdir(target);
|
30 | }
|
31 | catch (err) {
|
32 | const e = err;
|
33 | if (await stat(target).then((s) => s.isDirectory()).catch(() => false)) {
|
34 | return;
|
35 | }
|
36 | if (e.code === "EEXIST") {
|
37 | return;
|
38 | }
|
39 | if (e.code === "ENOENT") {
|
40 | if (dirname(target) === target) {
|
41 | throw e;
|
42 | }
|
43 | try {
|
44 | await ensureDir(dirname(target));
|
45 | await _mkdir(target);
|
46 | }
|
47 | catch (_a) {
|
48 | if (await stat(target).then((s) => s.isDirectory()).catch((e) => false)) {
|
49 | return;
|
50 | }
|
51 | throw e;
|
52 | }
|
53 | return;
|
54 | }
|
55 | throw e;
|
56 | }
|
57 | }
|
58 | function ensureFile(target) {
|
59 | return ensureDir(dirname(target));
|
60 | }
|
61 | function normalizeArray(arr = []) {
|
62 | return arr instanceof Array ? arr : [arr];
|
63 | }
|
64 | function spawnProcess(spawnJavaOptions, args, options) {
|
65 | var _a, _b;
|
66 | const process = ((_a = spawnJavaOptions === null || spawnJavaOptions === void 0 ? void 0 : spawnJavaOptions.spawn) !== null && _a !== void 0 ? _a : spawn)((_b = spawnJavaOptions.java) !== null && _b !== void 0 ? _b : "java", args, options);
|
67 | return waitProcess(process);
|
68 | }
|
69 | function waitProcess(process) {
|
70 | return new Promise((resolve, reject) => {
|
71 | var _a, _b, _c, _d;
|
72 | let errorMsg = [];
|
73 | process.on("error", reject);
|
74 | process.on("close", (code) => {
|
75 | if (code !== 0) {
|
76 | reject(errorMsg.join(""));
|
77 | }
|
78 | else {
|
79 | resolve();
|
80 | }
|
81 | });
|
82 | process.on("exit", (code) => {
|
83 | if (code !== 0) {
|
84 | reject(errorMsg.join(""));
|
85 | }
|
86 | else {
|
87 | resolve();
|
88 | }
|
89 | });
|
90 | (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.setEncoding("utf-8");
|
91 | (_b = process.stdout) === null || _b === void 0 ? void 0 : _b.on("data", (buf) => { });
|
92 | (_c = process.stderr) === null || _c === void 0 ? void 0 : _c.setEncoding("utf-8");
|
93 | (_d = process.stderr) === null || _d === void 0 ? void 0 : _d.on("data", (buf) => { errorMsg.push(buf.toString()); });
|
94 | });
|
95 | }
|
96 | function errorToString(e) {
|
97 | if (e instanceof Error) {
|
98 | return e.stack ? e.stack : e.message;
|
99 | }
|
100 | return e.toString();
|
101 | }
|
102 |
|
103 | function isValidProtocol(protocol) {
|
104 | return protocol === "http:" || protocol === "https:";
|
105 | }
|
106 | function urlToRequestOptions(url) {
|
107 | return {
|
108 | host: url.host,
|
109 | hostname: url.hostname,
|
110 | protocol: url.protocol,
|
111 | port: url.port,
|
112 | path: url.pathname + url.search,
|
113 | };
|
114 | }
|
115 | function format(url) {
|
116 | return `${url.protocol}//${url.host}${url.path}`;
|
117 | }
|
118 | function mergeRequestOptions(original, newOptions) {
|
119 | let options = { ...original };
|
120 | for (let [key, value] of Object.entries(newOptions)) {
|
121 | if (value !== null) {
|
122 | options[key] = value;
|
123 | }
|
124 | }
|
125 | return options;
|
126 | }
|
127 | function fetch(options, agents = {}) {
|
128 | return new Promise((resolve, reject) => {
|
129 | function follow(options) {
|
130 | if (!isValidProtocol(options.protocol)) {
|
131 | reject(new Error(`Invalid URL: ${format(options)}`));
|
132 | }
|
133 | else {
|
134 | let [req, agent] = options.protocol === "http:" ? [request, agents.http] : [request$1, agents.https];
|
135 | let clientReq = req({ ...options, agent }, (m) => {
|
136 | if ((m.statusCode === 302 || m.statusCode === 301 || m.statusCode === 303 || m.statusCode === 308) && typeof m.headers.location === "string") {
|
137 | m.resume();
|
138 | follow(mergeRequestOptions(options, urlToRequestOptions(new URL(m.headers.location, `${options.protocol}//${options.host}`))));
|
139 | }
|
140 | else {
|
141 | m.url = m.url || format(options);
|
142 | clientReq.removeListener("error", reject);
|
143 | resolve({ request: clientReq, message: m });
|
144 | }
|
145 | });
|
146 | clientReq.addListener("error", reject);
|
147 | clientReq.end();
|
148 | }
|
149 | }
|
150 | follow(options);
|
151 | });
|
152 | }
|
153 |
|
154 |
|
155 |
|
156 | function joinUrl(a, b) {
|
157 | if (a.endsWith("/") && b.startsWith("/")) {
|
158 | return a + b.substring(1);
|
159 | }
|
160 | if (!a.endsWith("/") && !b.startsWith("/")) {
|
161 | return a + "/" + b;
|
162 | }
|
163 | return a + b;
|
164 | }
|
165 |
|
166 | async function fetchText(url, agent) {
|
167 | let parsed = new URL(url);
|
168 | if (!isValidProtocol(parsed.protocol)) {
|
169 | throw new Error(`Invalid protocol ${parsed.protocol}`);
|
170 | }
|
171 | let { message: msg } = await fetch({
|
172 | method: "GET",
|
173 | ...urlToRequestOptions(parsed),
|
174 | headers: {
|
175 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 Edg/80.0.361.48",
|
176 | },
|
177 | }, agent);
|
178 | let buf = await new Promise((resolve, reject) => {
|
179 | let contents = [];
|
180 | msg.on("data", (chunk) => { contents.push(chunk); });
|
181 | msg.on("end", () => { resolve(Buffer.concat(contents)); });
|
182 | msg.on("error", reject);
|
183 | });
|
184 | return buf.toString();
|
185 | }
|
186 | async function fetchJson(url, agent) {
|
187 | return JSON.parse(await fetchText(url, agent));
|
188 | }
|
189 | async function getIfUpdate(url, timestamp, agent = {}) {
|
190 | let parsed = new URL(url);
|
191 | if (!isValidProtocol(parsed.protocol)) {
|
192 | throw new Error(`Invalid protocol ${parsed.protocol}`);
|
193 | }
|
194 | let { message: msg } = await fetch({
|
195 | method: "GET",
|
196 | ...urlToRequestOptions(parsed),
|
197 | headers: {
|
198 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 Edg/80.0.361.48",
|
199 | "If-Modified-Since": timestamp !== null && timestamp !== void 0 ? timestamp : "",
|
200 | },
|
201 | }, agent);
|
202 | let buf = await new Promise((resolve, reject) => {
|
203 | let contents = [];
|
204 | msg.on("data", (chunk) => { contents.push(chunk); });
|
205 | msg.on("end", () => { resolve(Buffer.concat(contents)); });
|
206 | msg.on("error", reject);
|
207 | });
|
208 | let { statusCode, headers } = msg;
|
209 | if (statusCode === 304) {
|
210 | return {
|
211 | timestamp: headers["last-modified"],
|
212 | content: undefined,
|
213 | };
|
214 | }
|
215 | else if (statusCode === 200 || statusCode === 204) {
|
216 | return {
|
217 | timestamp: headers["last-modified"],
|
218 | content: buf.toString(),
|
219 | };
|
220 | }
|
221 | throw new Error(`Failure on response status code: ${statusCode}.`);
|
222 | }
|
223 | async function getAndParseIfUpdate(url, parser, lastObject) {
|
224 | let { content, timestamp } = await getIfUpdate(url, lastObject === null || lastObject === void 0 ? void 0 : lastObject.timestamp);
|
225 | if (content) {
|
226 | return { ...parser(content), timestamp, };
|
227 | }
|
228 | return lastObject;
|
229 | }
|
230 | async function getLastModified(url, timestamp, agent = {}) {
|
231 | let parsed = new URL(url);
|
232 | if (!isValidProtocol(parsed.protocol)) {
|
233 | throw new Error(`Invalid protocol ${parsed.protocol}`);
|
234 | }
|
235 | let { message: msg } = await fetch({
|
236 | method: "HEAD",
|
237 | ...urlToRequestOptions(parsed),
|
238 | headers: {
|
239 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 Edg/80.0.361.48",
|
240 | "If-Modified-Since": timestamp !== null && timestamp !== void 0 ? timestamp : "",
|
241 | },
|
242 | }, agent);
|
243 | msg.resume();
|
244 | let { headers, statusCode } = msg;
|
245 | if (statusCode === 304) {
|
246 | return [true, headers["last-modified"]];
|
247 | }
|
248 | else if (statusCode === 200 || statusCode === 204) {
|
249 | return [false, headers["last-modified"]];
|
250 | }
|
251 | throw new Error(`Failure on response status code: ${statusCode}.`);
|
252 | }
|
253 |
|
254 | const YARN_MAVEN_URL = "https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml";
|
255 | const LOADER_MAVEN_URL = "https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml";
|
256 | const DEFAULT_FABRIC_API = "https://meta.fabricmc.net/v2";
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | function getFabricArtifacts(remote = DEFAULT_FABRIC_API) {
|
263 | return fetchJson(remote + "/versions");
|
264 | }
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | function getYarnArtifactList(remote = DEFAULT_FABRIC_API) {
|
271 | return fetchJson(remote + "/versions/yarn");
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | function getYarnArtifactListFor(minecraft, remote = DEFAULT_FABRIC_API) {
|
280 | return fetchJson(remote + "/versions/yarn/" + minecraft);
|
281 | }
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 | function getLoaderArtifactList(remote = DEFAULT_FABRIC_API) {
|
288 | return fetchJson(remote + "/versions/loader");
|
289 | }
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | function getLoaderArtifactListFor(minecraft, remote = DEFAULT_FABRIC_API) {
|
297 | return fetchJson(remote + "/versions/loader/" + minecraft);
|
298 | }
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 | function getFabricLoaderArtifact(minecraft, loader, remote = DEFAULT_FABRIC_API) {
|
307 | return fetchJson(remote + "/versions/loader/" + minecraft + "/" + loader);
|
308 | }
|
309 |
|
310 |
|
311 |
|
312 |
|
313 | async function getYarnVersionListFromXML(option = {}) {
|
314 | var _a;
|
315 | let [modified, timestamp] = await getLastModified(YARN_MAVEN_URL, (_a = option.original) === null || _a === void 0 ? void 0 : _a.timestamp);
|
316 | if (modified || !option.original) {
|
317 | let versions = await getYarnArtifactList(option.remote);
|
318 | return {
|
319 | versions: versions,
|
320 | timestamp: timestamp !== null && timestamp !== void 0 ? timestamp : "",
|
321 | };
|
322 | }
|
323 | return option.original;
|
324 | }
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | async function getLoaderVersionListFromXML(option) {
|
330 | var _a;
|
331 | let [modified, timestamp] = await getLastModified(LOADER_MAVEN_URL, (_a = option.original) === null || _a === void 0 ? void 0 : _a.timestamp);
|
332 | if (modified || !option.original) {
|
333 | let versions = await getLoaderArtifactList(option.remote);
|
334 | return {
|
335 | versions: versions,
|
336 | timestamp: timestamp !== null && timestamp !== void 0 ? timestamp : "",
|
337 | };
|
338 | }
|
339 | return option.original;
|
340 | }
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 | async function installFabricYarnAndLoader(yarnVersion, loaderVersion, minecraft, options = {}) {
|
350 | const folder = MinecraftFolder.from(minecraft);
|
351 | const mcversion = yarnVersion.split("+")[0];
|
352 | const id = options.versionId || `${mcversion}-fabric${yarnVersion}-${loaderVersion}`;
|
353 | const jsonFile = folder.getVersionJson(id);
|
354 | const body = await fetchJson(`https://fabricmc.net/download/technic/?yarn=${encodeURIComponent(yarnVersion)}&loader=${encodeURIComponent(loaderVersion)}`);
|
355 | body.id = id;
|
356 | if (typeof options.inheritsFrom === "string") {
|
357 | body.inheritsFrom = options.inheritsFrom;
|
358 | }
|
359 | await ensureFile(jsonFile);
|
360 | await _writeFile(jsonFile, JSON.stringify(body));
|
361 | return id;
|
362 | }
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | async function installFabric(loader, minecraft, options = {}) {
|
373 | var _a;
|
374 | const folder = MinecraftFolder.from(minecraft);
|
375 | let yarn;
|
376 | let side = (_a = options.side) !== null && _a !== void 0 ? _a : "client";
|
377 | let id = options.versionId;
|
378 | let mcversion;
|
379 | if (options.yarnVersion) {
|
380 | let yarnVersion = options.yarnVersion;
|
381 | if (typeof yarnVersion === "string") {
|
382 | yarn = yarnVersion;
|
383 | mcversion = yarn.split("+")[0];
|
384 | }
|
385 | else {
|
386 | yarn = yarnVersion.version;
|
387 | mcversion = yarnVersion.gameVersion || yarn.split("+")[0];
|
388 | }
|
389 | }
|
390 | else {
|
391 | mcversion = loader.intermediary.version;
|
392 | }
|
393 | if (!id) {
|
394 | id = mcversion;
|
395 | if (yarn) {
|
396 | id += `-fabric${yarn}-loader${loader.loader.version}`;
|
397 | }
|
398 | else {
|
399 | id += `-fabric${loader.loader.version}`;
|
400 | }
|
401 | }
|
402 | let libraries = [
|
403 | { name: loader.loader.maven, url: "https://maven.fabricmc.net/" },
|
404 | { name: loader.intermediary.maven, url: "https://maven.fabricmc.net/" },
|
405 | ...(options.yarnVersion
|
406 | ? [{ name: `net.fabricmc:yarn:${yarn}`, url: "https://maven.fabricmc.net/" }] : []),
|
407 | ...loader.launcherMeta.libraries.common,
|
408 | ...loader.launcherMeta.libraries[side],
|
409 | ];
|
410 | let mainClass = loader.launcherMeta.mainClass[side];
|
411 | let inheritsFrom = options.inheritsFrom || mcversion;
|
412 | let jsonFile = folder.getVersionJson(id);
|
413 | await ensureFile(jsonFile);
|
414 | await _writeFile(jsonFile, JSON.stringify({
|
415 | id,
|
416 | inheritsFrom,
|
417 | mainClass,
|
418 | libraries,
|
419 | arguments: {
|
420 | game: [],
|
421 | jvm: [],
|
422 | },
|
423 | releaseTime: new Date().toJSON(),
|
424 | time: new Date().toJSON(),
|
425 | }));
|
426 | return id;
|
427 | }
|
428 |
|
429 | const DEFAULT_VERSION_MANIFEST = "http://dl.liteloader.com/versions/versions.json";
|
430 | function processLibraries(lib) {
|
431 | if (Object.keys(lib).length === 1 && lib.name) {
|
432 | if (lib.name.startsWith("org.ow2.asm")) {
|
433 | lib.url = "https://files.minecraftforge.net/maven/";
|
434 | }
|
435 | }
|
436 | return lib;
|
437 | }
|
438 | var LiteloaderVersionList;
|
439 | (function (LiteloaderVersionList) {
|
440 | function parse(content) {
|
441 | const result = JSON.parse(content);
|
442 | const metalist = { meta: result.meta, versions: {} };
|
443 | for (const mcversion in result.versions) {
|
444 | const versions = metalist.versions[mcversion] = {};
|
445 | const snapshots = result.versions[mcversion].snapshots;
|
446 | const artifacts = result.versions[mcversion].artefacts;
|
447 | const url = result.versions[mcversion].repo.url;
|
448 | if (snapshots) {
|
449 | const { stream, file, version, md5, timestamp, tweakClass, libraries } = snapshots["com.mumfrey:liteloader"].latest;
|
450 | const type = (stream === "RELEASE" ? "RELEASE" : "SNAPSHOT");
|
451 | versions.snapshot = {
|
452 | url,
|
453 | type,
|
454 | file,
|
455 | version,
|
456 | md5,
|
457 | timestamp,
|
458 | mcversion,
|
459 | tweakClass,
|
460 | libraries: libraries.map(processLibraries),
|
461 | };
|
462 | }
|
463 | if (artifacts) {
|
464 | const { stream, file, version, md5, timestamp, tweakClass, libraries } = artifacts["com.mumfrey:liteloader"].latest;
|
465 | const type = (stream === "RELEASE" ? "RELEASE" : "SNAPSHOT");
|
466 | versions.release = {
|
467 | url,
|
468 | type,
|
469 | file,
|
470 | version,
|
471 | md5,
|
472 | timestamp,
|
473 | mcversion,
|
474 | tweakClass,
|
475 | libraries: libraries.map(processLibraries),
|
476 | };
|
477 | }
|
478 | }
|
479 | return metalist;
|
480 | }
|
481 | LiteloaderVersionList.parse = parse;
|
482 | })(LiteloaderVersionList || (LiteloaderVersionList = {}));
|
483 | const snapshotRoot = "http://dl.liteloader.com/versions/";
|
484 | const releaseRoot = "http://repo.mumfrey.com/content/repositories/liteloader/";
|
485 |
|
486 |
|
487 |
|
488 | class MissingVersionJsonError extends Error {
|
489 | constructor(version,
|
490 | /**
|
491 | * The path of version json
|
492 | */
|
493 | path) {
|
494 | super();
|
495 | this.version = version;
|
496 | this.path = path;
|
497 | this.error = "MissingVersionJson";
|
498 | }
|
499 | }
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 | function getLiteloaderVersionList(option = {}) {
|
506 | return getAndParseIfUpdate(option.remote || DEFAULT_VERSION_MANIFEST, LiteloaderVersionList.parse, option.original);
|
507 | }
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 |
|
520 | function installLiteloader(versionMeta, location, options) {
|
521 | return installLiteloaderTask(versionMeta, location, options).startAndWait();
|
522 | }
|
523 | function buildVersionInfo(versionMeta, mountedJSON) {
|
524 | const id = `${mountedJSON.id}-Liteloader${versionMeta.mcversion}-${versionMeta.version}`;
|
525 | const time = new Date(Number.parseInt(versionMeta.timestamp, 10) * 1000).toISOString();
|
526 | const releaseTime = time;
|
527 | const type = versionMeta.type;
|
528 | const libraries = [
|
529 | {
|
530 | name: `com.mumfrey:liteloader:${versionMeta.version}`,
|
531 | url: type === "SNAPSHOT" ? snapshotRoot : releaseRoot,
|
532 | },
|
533 | ...versionMeta.libraries.map(processLibraries),
|
534 | ];
|
535 | const mainClass = "net.minecraft.launchwrapper.Launch";
|
536 | const inheritsFrom = mountedJSON.id;
|
537 | const jar = mountedJSON.jar || mountedJSON.id;
|
538 | const info = {
|
539 | id, time, releaseTime, type, libraries, mainClass, inheritsFrom, jar,
|
540 | };
|
541 | if (mountedJSON.arguments) {
|
542 |
|
543 |
|
544 | info.arguments = {
|
545 | game: ["--tweakClass", versionMeta.tweakClass],
|
546 | jvm: [],
|
547 | };
|
548 | }
|
549 | else {
|
550 | info.minecraftArguments = `--tweakClass ${versionMeta.tweakClass} ` + mountedJSON.minecraftArguments;
|
551 | }
|
552 | return info;
|
553 | }
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 |
|
567 | function installLiteloaderTask(versionMeta, location, options = {}) {
|
568 | return task("installLiteloader", async function installLiteloader() {
|
569 | const mc = MinecraftFolder.from(location);
|
570 | const mountVersion = options.inheritsFrom || versionMeta.mcversion;
|
571 | const mountedJSON = await this.yield(task("resolveVersionJson", async function resolveVersionJson() {
|
572 | if (await missing(mc.getVersionJson(mountVersion))) {
|
573 | throw new MissingVersionJsonError(mountVersion, mc.getVersionJson(mountVersion));
|
574 | }
|
575 | return _readFile(mc.getVersionJson(mountVersion)).then((b) => b.toString()).then(JSON.parse);
|
576 | }));
|
577 | const versionInf = await this.yield(task("generateLiteloaderJson", async function generateLiteloaderJson() {
|
578 | const inf = buildVersionInfo(versionMeta, mountedJSON);
|
579 | inf.id = options.versionId || inf.id;
|
580 | inf.inheritsFrom = options.inheritsFrom || inf.inheritsFrom;
|
581 | const versionPath = mc.getVersionRoot(inf.id);
|
582 | await ensureDir(versionPath);
|
583 | await _writeFile(join(versionPath, inf.id + ".json"), JSON.stringify(inf, undefined, 4));
|
584 | return inf;
|
585 | }));
|
586 | return versionInf.id;
|
587 | });
|
588 | }
|
589 |
|
590 | function resolveAbortSignal(signal) {
|
591 | if (signal) {
|
592 | return signal;
|
593 | }
|
594 | return {
|
595 | aborted: false,
|
596 | addEventListener() { return this; },
|
597 | removeEventListener() { return this; }
|
598 | };
|
599 | }
|
600 |
|
601 | function isAgents(agents) {
|
602 | if (!agents) {
|
603 | return false;
|
604 | }
|
605 | return "http" in agents || "https" in agents;
|
606 | }
|
607 | function resolveAgents(agents) {
|
608 | if (isAgents(agents)) {
|
609 | return agents;
|
610 | }
|
611 | return createAgents(agents);
|
612 | }
|
613 |
|
614 |
|
615 |
|
616 | function createAgents(options = {}) {
|
617 | var _a, _b, _c, _d;
|
618 | return {
|
619 | http: new Agent({
|
620 | maxSockets: (_a = options.maxSocket) !== null && _a !== void 0 ? _a : cpus().length * 4,
|
621 | maxFreeSockets: (_b = options.maxFreeSocket) !== null && _b !== void 0 ? _b : 64,
|
622 | keepAlive: true,
|
623 | }),
|
624 | https: new Agent$1({
|
625 | maxSockets: (_c = options.maxSocket) !== null && _c !== void 0 ? _c : cpus().length * 4,
|
626 | maxFreeSockets: (_d = options.maxFreeSocket) !== null && _d !== void 0 ? _d : 64,
|
627 | keepAlive: true,
|
628 | })
|
629 | };
|
630 | }
|
631 | async function withAgents(options, scope) {
|
632 | var _a, _b;
|
633 | if (!isAgents(options.agents)) {
|
634 | const agents = resolveAgents(options.agents);
|
635 | try {
|
636 | const r = await scope({ ...options, agents });
|
637 | return r;
|
638 | }
|
639 | finally {
|
640 | (_a = agents.http) === null || _a === void 0 ? void 0 : _a.destroy();
|
641 | (_b = agents.https) === null || _b === void 0 ? void 0 : _b.destroy();
|
642 | }
|
643 | }
|
644 | else {
|
645 | return scope(options);
|
646 | }
|
647 | }
|
648 |
|
649 |
|
650 |
|
651 |
|
652 | class DownloadError extends Error {
|
653 | constructor(error, metadata, headers, destination, segments, segmentErrors) {
|
654 | super(`The download failed! ${error}`);
|
655 | this.error = error;
|
656 | this.metadata = metadata;
|
657 | this.headers = headers;
|
658 | this.destination = destination;
|
659 | this.segments = segments;
|
660 | this.segmentErrors = segmentErrors;
|
661 | }
|
662 | }
|
663 | function resolveNetworkErrorType(e) {
|
664 | if (e.code === "ECONNRESET") {
|
665 | return "ConnectionReset";
|
666 | }
|
667 | if (e.code === "ETIMEDOUT") {
|
668 | return "ConnectionTimeout";
|
669 | }
|
670 | if (e.code === "EPROTO") {
|
671 | return "ProtocolError";
|
672 | }
|
673 | if (e.code === "ECANCELED") {
|
674 | return "OperationCancelled";
|
675 | }
|
676 | }
|
677 |
|
678 |
|
679 |
|
680 |
|
681 | function isCommonNetworkError(e) {
|
682 | if (typeof e.code === "string") {
|
683 | return e.code === "ECONNRESET"
|
684 | || e.code === "ETIMEDOUT"
|
685 | || e.code === "EPROTO"
|
686 | || e.code === "ECANCELED";
|
687 | }
|
688 | return false;
|
689 | }
|
690 |
|
691 | class FetchMetadataError extends Error {
|
692 | constructor(error, statusCode, url, message) {
|
693 | super(message);
|
694 | this.error = error;
|
695 | this.statusCode = statusCode;
|
696 | this.url = url;
|
697 | }
|
698 | }
|
699 | async function getMetadata(srcUrl, _headers, agents, useGet = false) {
|
700 | var _a;
|
701 | const { message, request } = await fetch({
|
702 | ...urlToRequestOptions(srcUrl),
|
703 | method: useGet ? "GET" : "HEAD",
|
704 | ..._headers
|
705 | }, agents);
|
706 | message.resume();
|
707 | request.destroy();
|
708 | let { headers, url: resultUrl, statusCode } = message;
|
709 | if (statusCode === 405 && !useGet) {
|
710 | return getMetadata(srcUrl, _headers, agents, useGet);
|
711 | }
|
712 | statusCode = statusCode !== null && statusCode !== void 0 ? statusCode : 500;
|
713 | if (statusCode !== 200 && statusCode !== 201) {
|
714 | throw new FetchMetadataError(statusCode === 404 ? "FetchResourceNotFound"
|
715 | : statusCode >= 500 ? "FetchResourceServerUnavaiable"
|
716 | : "BadResourceRequest", statusCode, resultUrl !== null && resultUrl !== void 0 ? resultUrl : srcUrl.toString(), `Fetch download metadata failed due to http error. Status code: ${statusCode} on ${resultUrl}`);
|
717 | }
|
718 | const url = resultUrl ? new URL(resultUrl) : srcUrl;
|
719 | const isAcceptRanges = headers["accept-ranges"] === "bytes";
|
720 | const contentLength = headers["content-length"] ? Number.parseInt(headers["content-length"]) : -1;
|
721 | const lastModified = (_a = headers["last-modified"]) !== null && _a !== void 0 ? _a : undefined;
|
722 | const eTag = headers.etag;
|
723 | return {
|
724 | url,
|
725 | isAcceptRanges,
|
726 | contentLength,
|
727 | lastModified,
|
728 | eTag,
|
729 | };
|
730 | }
|
731 |
|
732 | function isRetryHandler(options) {
|
733 | if (!options) {
|
734 | return false;
|
735 | }
|
736 | return "retry" in options && typeof options.retry === "function";
|
737 | }
|
738 | function resolveRetryHandler(options) {
|
739 | var _a, _b;
|
740 | if (isRetryHandler(options)) {
|
741 | return options;
|
742 | }
|
743 | return createRetryHandler((_a = options === null || options === void 0 ? void 0 : options.maxRetryCount) !== null && _a !== void 0 ? _a : 3, (_b = options === null || options === void 0 ? void 0 : options.shouldRetry) !== null && _b !== void 0 ? _b : isCommonNetworkError);
|
744 | }
|
745 |
|
746 |
|
747 |
|
748 |
|
749 |
|
750 | function createRetryHandler(maxRetryCount, shouldRetry) {
|
751 | const handler = {
|
752 | retry(url, attempt, error) {
|
753 | return shouldRetry(error) && attempt < maxRetryCount;
|
754 | }
|
755 | };
|
756 | return handler;
|
757 | }
|
758 |
|
759 | function isSegmentPolicy(segmentOptions) {
|
760 | if (!segmentOptions) {
|
761 | return false;
|
762 | }
|
763 | return "computeSegments" in segmentOptions && typeof segmentOptions.computeSegments === "function";
|
764 | }
|
765 | function resolveSegmentPolicy(segmentOptions) {
|
766 | var _a;
|
767 | if (isSegmentPolicy(segmentOptions)) {
|
768 | return segmentOptions;
|
769 | }
|
770 | return new DefaultSegmentPolicy((_a = segmentOptions === null || segmentOptions === void 0 ? void 0 : segmentOptions.segmentThreshold) !== null && _a !== void 0 ? _a : 2 * 1000000, 4);
|
771 | }
|
772 | class DefaultSegmentPolicy {
|
773 | constructor(segmentThreshold, concurrency) {
|
774 | this.segmentThreshold = segmentThreshold;
|
775 | this.concurrency = concurrency;
|
776 | }
|
777 | computeSegments(total) {
|
778 | const { segmentThreshold: chunkSize, concurrency } = this;
|
779 | const partSize = Math.max(chunkSize, Math.floor(total / concurrency));
|
780 | const segments = [];
|
781 | for (let cur = 0, chunkSize = 0; cur < total; cur += chunkSize) {
|
782 | const remain = total - cur;
|
783 | if (remain >= partSize) {
|
784 | chunkSize = partSize;
|
785 | segments.push({ start: cur, end: cur + chunkSize - 1 });
|
786 | }
|
787 | else {
|
788 | const last = segments[segments.length - 1];
|
789 | if (!last) {
|
790 | segments.push({ start: 0, end: remain - 1 });
|
791 | }
|
792 | else {
|
793 | last.end = last.end + remain;
|
794 | }
|
795 | cur = total;
|
796 | }
|
797 | }
|
798 | return segments;
|
799 | }
|
800 | }
|
801 |
|
802 | function createStatusController() {
|
803 | let total = 0;
|
804 | let progress = 0;
|
805 | const controller = {
|
806 | get total() { return total; },
|
807 | get progress() { return progress; },
|
808 | reset(_progress, _total) { progress = _progress; total = _total; },
|
809 | onProgress(_, _progress) { progress = _progress; }
|
810 | };
|
811 | return controller;
|
812 | }
|
813 | function resolveStatusController(controller) {
|
814 | if (!controller) {
|
815 | return createStatusController();
|
816 | }
|
817 | return controller;
|
818 | }
|
819 |
|
820 | class ChecksumValidator {
|
821 | constructor(checksum) {
|
822 | this.checksum = checksum;
|
823 | }
|
824 | async validate(fd, destination, url) {
|
825 | if (this.checksum) {
|
826 | const actual = await checksum(destination, this.checksum.algorithm);
|
827 | const expect = this.checksum.hash;
|
828 | if (actual !== expect) {
|
829 | throw new ChecksumNotMatchError(this.checksum.algorithm, this.checksum.hash, actual, destination, url);
|
830 | }
|
831 | }
|
832 | }
|
833 | }
|
834 | function isValidator(options) {
|
835 | if (!options) {
|
836 | return false;
|
837 | }
|
838 | return "validate" in options && typeof options.validate === "function";
|
839 | }
|
840 | function resolveValidator(options) {
|
841 | if (isValidator(options)) {
|
842 | return options;
|
843 | }
|
844 | if (options) {
|
845 | return new ChecksumValidator({ hash: options.hash, algorithm: options.algorithm });
|
846 | }
|
847 | return { validate() { return Promise.resolve(); } };
|
848 | }
|
849 | class ZipValidator {
|
850 | async validate(fd, destination, url) {
|
851 | try {
|
852 | const file = await open$2(fd);
|
853 | file.close();
|
854 | }
|
855 | catch (e) {
|
856 | throw new ValidationError("InvalidZipError", e.message);
|
857 | }
|
858 | }
|
859 | }
|
860 | class JsonValidator {
|
861 | validate(fd, destination, url) {
|
862 | return new Promise((resolve, reject) => {
|
863 | const read = createReadStream(destination, {
|
864 | fd,
|
865 | autoClose: false,
|
866 | emitClose: true,
|
867 | });
|
868 | let content = "";
|
869 | read.on("data", (buf) => {
|
870 | content += buf.toString();
|
871 | });
|
872 | read.on("end", () => {
|
873 | try {
|
874 | JSON.parse(content);
|
875 | resolve();
|
876 | }
|
877 | catch (e) {
|
878 | reject(e);
|
879 | }
|
880 | });
|
881 | });
|
882 | }
|
883 | }
|
884 | class ValidationError extends Error {
|
885 | constructor(error, message) {
|
886 | super(message);
|
887 | this.error = error;
|
888 | }
|
889 | }
|
890 | class ChecksumNotMatchError extends ValidationError {
|
891 | constructor(algorithm, expect, actual, file, source) {
|
892 | super("ChecksumNotMatchError", source ? `File ${file} (${source}) ${algorithm} checksum not match. Expect: ${expect}. Actual: ${actual}.` : `File ${file} ${algorithm} checksum not match. Expect: ${expect}. Actual: ${actual}.`);
|
893 | this.algorithm = algorithm;
|
894 | this.expect = expect;
|
895 | this.actual = actual;
|
896 | this.file = file;
|
897 | this.source = source;
|
898 | }
|
899 | }
|
900 |
|
901 |
|
902 | const pfstat = promisify(fstat);
|
903 | const pfdatasync = promisify(fdatasync);
|
904 | class AbortError extends Error {
|
905 | }
|
906 |
|
907 |
|
908 |
|
909 | function download(options) {
|
910 | const worker = createDownload(options);
|
911 | return worker.start();
|
912 | }
|
913 | function createDownload(options) {
|
914 | var _a;
|
915 | return new Download(typeof options.url === "string" ? [options.url] : options.url, (_a = options.headers) !== null && _a !== void 0 ? _a : {}, resolveAgents(options.agents), options.destination, options.segments, options.metadata, resolveSegmentPolicy(options.segmentPolicy), resolveStatusController(options.statusController), resolveRetryHandler(options.retryHandler), resolveValidator(options.validator));
|
916 | }
|
917 | class Download {
|
918 | constructor(
|
919 | /**
|
920 | * The original request url with fallback
|
921 | */
|
922 | urls,
|
923 | /**
|
924 | * The headers of the request
|
925 | */
|
926 | headers,
|
927 | /**
|
928 | * The agent of the request
|
929 | */
|
930 | agents,
|
931 | /**
|
932 | * Where the file download to
|
933 | */
|
934 | destination,
|
935 | /**
|
936 | * The current download status
|
937 | */
|
938 | segments = [],
|
939 | /**
|
940 | * The cached resource metadata
|
941 | */
|
942 | metadata, segmentPolicy, statusController, retryHandler, validator) {
|
943 | this.urls = urls;
|
944 | this.headers = headers;
|
945 | this.agents = agents;
|
946 | this.destination = destination;
|
947 | this.segments = segments;
|
948 | this.metadata = metadata;
|
949 | this.segmentPolicy = segmentPolicy;
|
950 | this.statusController = statusController;
|
951 | this.retryHandler = retryHandler;
|
952 | this.validator = validator;
|
953 | |
954 |
|
955 |
|
956 | this.fd = -1;
|
957 | }
|
958 | async updateMetadata(url) {
|
959 | var _a, _b;
|
960 | const metadata = await getMetadata(url, this.headers, this.agents);
|
961 | if (!metadata || metadata.eTag != ((_a = this.metadata) === null || _a === void 0 ? void 0 : _a.eTag) || metadata.eTag === undefined || metadata.contentLength !== ((_b = this.metadata) === null || _b === void 0 ? void 0 : _b.contentLength)) {
|
962 | this.metadata = metadata;
|
963 | const contentLength = metadata.contentLength;
|
964 | this.segments = contentLength && metadata.isAcceptRanges
|
965 | ? this.segmentPolicy.computeSegments(contentLength)
|
966 | : [{ start: 0, end: contentLength }];
|
967 | this.statusController.reset(0, metadata.contentLength);
|
968 | await truncate(this.fd, metadata.contentLength);
|
969 | }
|
970 | else {
|
971 | this.statusController.reset(this.segments.reduce((a, b) => a + (b.end - b.start), 0), metadata.contentLength);
|
972 | }
|
973 | return this.metadata;
|
974 | }
|
975 | async processDownload(metadata, abortSignal) {
|
976 | var _a;
|
977 | let flag = 0;
|
978 | const abortHandlers = [];
|
979 | const errors = [];
|
980 | await Promise.all(this.segments.map(async (segment, index) => {
|
981 | var _a;
|
982 | if (segment.start > segment.end) {
|
983 |
|
984 | return;
|
985 | }
|
986 | const options = {
|
987 | ...urlToRequestOptions(metadata.url),
|
988 | method: "GET",
|
989 | headers: {
|
990 | ...this.headers,
|
991 | Range: `bytes=${segment.start}-${(_a = (segment.end)) !== null && _a !== void 0 ? _a : ""}`,
|
992 | },
|
993 | };
|
994 | try {
|
995 | if (abortSignal.aborted || flag) {
|
996 | throw new AbortError();
|
997 | }
|
998 | const { message: response, request } = await fetch(options, this.agents);
|
999 | if (abortSignal.aborted || flag) {
|
1000 |
|
1001 | response.resume();
|
1002 | throw new AbortError();
|
1003 | }
|
1004 | const fileStream = createWriteStream(this.destination, {
|
1005 | fd: this.fd,
|
1006 | start: segment.start,
|
1007 |
|
1008 | autoClose: false,
|
1009 | });
|
1010 |
|
1011 | response.on("data", (chunk) => {
|
1012 | segment.start += chunk.length;
|
1013 | this.statusController.onProgress(chunk.length, this.statusController.progress + chunk.length);
|
1014 | });
|
1015 |
|
1016 | const abortHandler = () => {
|
1017 | request.destroy(new AbortError());
|
1018 | response.unpipe();
|
1019 | };
|
1020 | abortHandlers.push(abortHandler);
|
1021 |
|
1022 | abortSignal.addEventListener("abort", abortHandler);
|
1023 | await _pipeline(response, fileStream);
|
1024 | abortSignal.removeEventListener("abort", abortHandler);
|
1025 | }
|
1026 | catch (e) {
|
1027 | if (e instanceof AbortError || e.message === "aborted") {
|
1028 |
|
1029 | if (flag === 0) {
|
1030 | flag = 1;
|
1031 | }
|
1032 | }
|
1033 | else {
|
1034 |
|
1035 | flag = 2;
|
1036 |
|
1037 | abortHandlers.forEach((f) => f());
|
1038 | errors.push(e);
|
1039 | }
|
1040 | }
|
1041 | }));
|
1042 |
|
1043 |
|
1044 | if (flag) {
|
1045 | throw new DownloadError(flag === 1 ? "DownloadAborted" : (_a = resolveNetworkErrorType(errors[0])) !== null && _a !== void 0 ? _a : "GeneralDownloadException", this.metadata, this.headers, this.destination, this.segments, errors);
|
1046 | }
|
1047 | }
|
1048 | async downloadUrl(url, abortSignal) {
|
1049 | let attempt = 0;
|
1050 | const parsedUrl = new URL(url);
|
1051 | if (parsedUrl.protocol === "file:") {
|
1052 | const filePath = fileURLToPath(url);
|
1053 | if (await _exists(filePath)) {
|
1054 |
|
1055 |
|
1056 | await copyFile(fileURLToPath(url), this.destination);
|
1057 | return;
|
1058 | }
|
1059 | }
|
1060 | while (true) {
|
1061 | try {
|
1062 | attempt += 1;
|
1063 | const metadata = await this.updateMetadata(parsedUrl);
|
1064 | await this.processDownload(metadata, abortSignal);
|
1065 | return;
|
1066 | }
|
1067 | catch (e) {
|
1068 |
|
1069 | if (e instanceof DownloadError && e.error === "DownloadAborted") {
|
1070 | throw e;
|
1071 | }
|
1072 |
|
1073 | if (await this.retryHandler.retry(url, attempt, e)) {
|
1074 | continue;
|
1075 | }
|
1076 | const networkError = resolveNetworkErrorType(e);
|
1077 | if (networkError) {
|
1078 | throw new DownloadError(networkError, this.metadata, this.headers, this.destination, this.segments, [e]);
|
1079 | }
|
1080 | throw e;
|
1081 | }
|
1082 | }
|
1083 | }
|
1084 | |
1085 |
|
1086 |
|
1087 | async start(abortSignal = resolveAbortSignal()) {
|
1088 | try {
|
1089 | if (this.fd === -1) {
|
1090 | await ensureFile(this.destination);
|
1091 |
|
1092 | this.fd = await open(this.destination, O_RDWR | O_CREAT);
|
1093 | }
|
1094 |
|
1095 | const size = (await pfstat(this.fd)).size;
|
1096 | if (size !== 0) {
|
1097 | const error = await this.validator.validate(this.fd, this.destination, this.urls[0]).catch((e) => e);
|
1098 |
|
1099 | if (!error) {
|
1100 | return;
|
1101 | }
|
1102 | }
|
1103 | let succeed = false;
|
1104 | const aggregatedErrors = [];
|
1105 | for (const url of this.urls) {
|
1106 | try {
|
1107 | await this.downloadUrl(url, abortSignal);
|
1108 | await pfdatasync(this.fd);
|
1109 | await this.validator.validate(this.fd, this.destination, url);
|
1110 | succeed = true;
|
1111 | break;
|
1112 | }
|
1113 | catch (e) {
|
1114 | if (e instanceof DownloadError && e.error === "DownloadAborted") {
|
1115 | throw e;
|
1116 | }
|
1117 | aggregatedErrors.push(e);
|
1118 | }
|
1119 | }
|
1120 | if (!succeed && aggregatedErrors.length > 0) {
|
1121 | throw aggregatedErrors;
|
1122 | }
|
1123 | }
|
1124 | catch (e) {
|
1125 | const errs = e instanceof Array ? e : [e];
|
1126 | const lastError = errs[0];
|
1127 | if (!(lastError instanceof DownloadError) && !(lastError instanceof ValidationError)) {
|
1128 | await unlink(this.destination).catch(() => { });
|
1129 | }
|
1130 | throw e;
|
1131 | }
|
1132 | finally {
|
1133 | if (this.fd !== -1) {
|
1134 | await close(this.fd).catch(() => { });
|
1135 | }
|
1136 | this.fd = -1;
|
1137 | }
|
1138 | }
|
1139 | }
|
1140 |
|
1141 | class DownloadTask extends AbortableTask {
|
1142 | constructor(options) {
|
1143 | super();
|
1144 | this.abort = () => { };
|
1145 | options.statusController = this;
|
1146 | this.download = createDownload(options);
|
1147 | }
|
1148 | reset(progress, total) {
|
1149 | this._progress = progress;
|
1150 | this._total = total;
|
1151 | }
|
1152 | onProgress(chunkSize, progress) {
|
1153 | this._progress = progress;
|
1154 | this.update(chunkSize);
|
1155 | }
|
1156 | process() {
|
1157 | const listeners = [];
|
1158 | const aborted = () => this.isCancelled || this.isPaused;
|
1159 | const signal = {
|
1160 | get aborted() { return aborted(); },
|
1161 | addEventListener(event, listener) {
|
1162 | if (event !== "abort") {
|
1163 | return this;
|
1164 | }
|
1165 | listeners.push(listener);
|
1166 | return this;
|
1167 | },
|
1168 | removeEventListener(event, listener) {
|
1169 |
|
1170 | return this;
|
1171 | }
|
1172 | };
|
1173 | this.abort = () => {
|
1174 | listeners.forEach((l) => l());
|
1175 | };
|
1176 | return this.download.start(signal);
|
1177 | }
|
1178 | isAbortedError(e) {
|
1179 | if (e instanceof Array) {
|
1180 | e = e[0];
|
1181 | }
|
1182 | if (e instanceof DownloadError && e.error === "DownloadAborted") {
|
1183 | return true;
|
1184 | }
|
1185 | return false;
|
1186 | }
|
1187 | }
|
1188 |
|
1189 |
|
1190 |
|
1191 |
|
1192 | const DEFAULT_VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
1193 |
|
1194 |
|
1195 |
|
1196 | const DEFAULT_RESOURCE_ROOT_URL = "https://resources.download.minecraft.net";
|
1197 |
|
1198 |
|
1199 |
|
1200 |
|
1201 |
|
1202 |
|
1203 |
|
1204 | function getVersionList(option = {}) {
|
1205 | return getAndParseIfUpdate(option.remote || DEFAULT_VERSION_MANIFEST_URL, JSON.parse, option.original);
|
1206 | }
|
1207 | function resolveDownloadUrls(original, version, option) {
|
1208 | let result = [original];
|
1209 | if (typeof option === "function") {
|
1210 | result.unshift(...normalizeArray(option(version)));
|
1211 | }
|
1212 | else {
|
1213 | result.unshift(...normalizeArray(option));
|
1214 | }
|
1215 | return result;
|
1216 | }
|
1217 |
|
1218 |
|
1219 |
|
1220 |
|
1221 |
|
1222 |
|
1223 |
|
1224 |
|
1225 |
|
1226 | async function install(versionMeta, minecraft, option = {}) {
|
1227 | return installTask(versionMeta, minecraft, option).startAndWait();
|
1228 | }
|
1229 |
|
1230 |
|
1231 |
|
1232 |
|
1233 |
|
1234 |
|
1235 | function installVersion(versionMeta, minecraft, options = {}) {
|
1236 | return installVersionTask(versionMeta, minecraft, options).startAndWait();
|
1237 | }
|
1238 |
|
1239 |
|
1240 |
|
1241 |
|
1242 |
|
1243 |
|
1244 | function installDependencies(version, options) {
|
1245 | return installDependenciesTask(version, options).startAndWait();
|
1246 | }
|
1247 |
|
1248 |
|
1249 |
|
1250 |
|
1251 |
|
1252 |
|
1253 | function installAssets(version, options = {}) {
|
1254 | return installAssetsTask(version, options).startAndWait();
|
1255 | }
|
1256 |
|
1257 |
|
1258 |
|
1259 |
|
1260 |
|
1261 | function installLibraries(version, options = {}) {
|
1262 | return installLibrariesTask(version, options).startAndWait();
|
1263 | }
|
1264 |
|
1265 |
|
1266 |
|
1267 |
|
1268 |
|
1269 |
|
1270 | async function installResolvedLibraries(libraries, minecraft, option) {
|
1271 | await installLibrariesTask({ libraries, minecraftDirectory: typeof minecraft === "string" ? minecraft : minecraft.root }, option).startAndWait();
|
1272 | }
|
1273 |
|
1274 |
|
1275 |
|
1276 |
|
1277 |
|
1278 |
|
1279 |
|
1280 |
|
1281 |
|
1282 |
|
1283 | function installTask(versionMeta, minecraft, options = {}) {
|
1284 | return task("install", async function () {
|
1285 | return withAgents(options, async (options) => {
|
1286 | const version = await this.yield(installVersionTask(versionMeta, minecraft, options));
|
1287 | if (options.side !== "server") {
|
1288 | await this.yield(installDependenciesTask(version, options));
|
1289 | }
|
1290 | return version;
|
1291 | });
|
1292 | });
|
1293 | }
|
1294 |
|
1295 |
|
1296 |
|
1297 |
|
1298 |
|
1299 |
|
1300 |
|
1301 | function installVersionTask(versionMeta, minecraft, options = {}) {
|
1302 | return task("version", async function () {
|
1303 | return withAgents(options, async (options) => {
|
1304 | await this.yield(new InstallJsonTask(versionMeta, minecraft, options));
|
1305 | const version = await Version.parse(minecraft, versionMeta.id);
|
1306 | await this.yield(new InstallJarTask(version, minecraft, options));
|
1307 | return version;
|
1308 | });
|
1309 | }, versionMeta);
|
1310 | }
|
1311 |
|
1312 |
|
1313 |
|
1314 |
|
1315 |
|
1316 |
|
1317 | function installDependenciesTask(version, options = {}) {
|
1318 | return task("dependencies", async function () {
|
1319 | await withAgents(options, (options) => Promise.all([
|
1320 | this.yield(installAssetsTask(version, options)),
|
1321 | this.yield(installLibrariesTask(version, options)),
|
1322 | ]));
|
1323 | return version;
|
1324 | });
|
1325 | }
|
1326 |
|
1327 |
|
1328 |
|
1329 |
|
1330 |
|
1331 |
|
1332 | function installAssetsTask(version, options = {}) {
|
1333 | return task("assets", async function () {
|
1334 | const folder = MinecraftFolder.from(version.minecraftDirectory);
|
1335 | const jsonPath = folder.getPath("assets", "indexes", version.assets + ".json");
|
1336 | await this.yield(new InstallAssetIndexTask(version, options));
|
1337 | await ensureDir(folder.getPath("assets", "objects"));
|
1338 | const { objects } = JSON.parse(await _readFile(jsonPath).then((b) => b.toString()));
|
1339 | const objectArray = Object.keys(objects).map((k) => ({ name: k, ...objects[k] }));
|
1340 |
|
1341 | await withAgents(options, (options) => {
|
1342 | var _a;
|
1343 | return this.all(objectArray.map((o) => new InstallAssetTask(o, folder, options)), {
|
1344 | throwErrorImmediately: (_a = options.throwErrorImmediately) !== null && _a !== void 0 ? _a : false,
|
1345 | getErrorMessage: (errs) => `Errors during install Minecraft ${version.id}'s assets at ${version.minecraftDirectory}: ${errs.map(errorToString).join("\n")}`
|
1346 | });
|
1347 | });
|
1348 | return version;
|
1349 | });
|
1350 | }
|
1351 |
|
1352 |
|
1353 |
|
1354 |
|
1355 |
|
1356 | function installLibrariesTask(version, options = {}) {
|
1357 | return task("libraries", async function () {
|
1358 | const folder = MinecraftFolder.from(version.minecraftDirectory);
|
1359 | await withAgents(options, (options) => {
|
1360 | var _a;
|
1361 | return this.all(version.libraries.map((lib) => new InstallLibraryTask(lib, folder, options)), {
|
1362 | throwErrorImmediately: (_a = options.throwErrorImmediately) !== null && _a !== void 0 ? _a : false,
|
1363 | getErrorMessage: (errs) => `Errors during install libraries at ${version.minecraftDirectory}: ${errs.map(errorToString).join("\n")}`
|
1364 | });
|
1365 | });
|
1366 | });
|
1367 | }
|
1368 |
|
1369 |
|
1370 |
|
1371 |
|
1372 |
|
1373 |
|
1374 | function installResolvedLibrariesTask(libraries, minecraft, option) {
|
1375 | return installLibrariesTask({ libraries, minecraftDirectory: typeof minecraft === "string" ? minecraft : minecraft.root }, option);
|
1376 | }
|
1377 |
|
1378 |
|
1379 |
|
1380 |
|
1381 |
|
1382 |
|
1383 | function installResolvedAssetsTask(assets, folder, options = {}) {
|
1384 | return task("assets", async function () {
|
1385 | await ensureDir(folder.getPath("assets", "objects"));
|
1386 |
|
1387 | await withAgents(options, (options) => this.all(assets.map((o) => new InstallAssetTask(o, folder, options)), {
|
1388 | throwErrorImmediately: false,
|
1389 | getErrorMessage: (errs) => `Errors during install assets at ${folder.root}:\n${errs.map(errorToString).join("\n")}`,
|
1390 | }));
|
1391 | });
|
1392 | }
|
1393 | class InstallJsonTask extends DownloadTask {
|
1394 | constructor(version, minecraft, options) {
|
1395 | const folder = MinecraftFolder.from(minecraft);
|
1396 | const destination = folder.getVersionJson(version.id);
|
1397 | const expectSha1 = version.url.split("/")[5];
|
1398 | const urls = resolveDownloadUrls(version.url, version, options.json);
|
1399 | super({
|
1400 | url: urls,
|
1401 | agents: options.agents,
|
1402 | segmentPolicy: options.segmentPolicy,
|
1403 | retryHandler: options.retryHandler,
|
1404 | validator: expectSha1 ? { algorithm: "sha1", hash: expectSha1 } : new JsonValidator(),
|
1405 | destination,
|
1406 | });
|
1407 | this.name = "json";
|
1408 | this.param = version;
|
1409 | }
|
1410 | }
|
1411 | class InstallJarTask extends DownloadTask {
|
1412 | constructor(version, minecraft, options) {
|
1413 | var _a;
|
1414 | const folder = MinecraftFolder.from(minecraft);
|
1415 | const type = (_a = options.side) !== null && _a !== void 0 ? _a : "client";
|
1416 | const destination = join(folder.getVersionRoot(version.id), type === "client" ? version.id + ".jar" : version.id + "-" + type + ".jar");
|
1417 | const urls = resolveDownloadUrls(version.downloads[type].url, version, options[type]);
|
1418 | const expectSha1 = version.downloads[type].sha1;
|
1419 | super({
|
1420 | url: urls,
|
1421 | validator: { algorithm: "sha1", hash: expectSha1 },
|
1422 | destination,
|
1423 | agents: options.agents,
|
1424 | segmentPolicy: options.segmentPolicy,
|
1425 | retryHandler: options.retryHandler,
|
1426 | });
|
1427 | this.name = "jar";
|
1428 | this.param = version;
|
1429 | }
|
1430 | }
|
1431 | class InstallAssetIndexTask extends DownloadTask {
|
1432 | constructor(version, options = {}) {
|
1433 | const folder = MinecraftFolder.from(version.minecraftDirectory);
|
1434 | const jsonPath = folder.getPath("assets", "indexes", version.assets + ".json");
|
1435 | super({
|
1436 | url: resolveDownloadUrls(version.assetIndex.url, version, options.assetsIndexUrl),
|
1437 | destination: jsonPath,
|
1438 | validator: {
|
1439 | algorithm: "sha1",
|
1440 | hash: version.assetIndex.sha1,
|
1441 | },
|
1442 | agents: options.agents,
|
1443 | segmentPolicy: options.segmentPolicy,
|
1444 | retryHandler: options.retryHandler,
|
1445 | });
|
1446 | this.name = "assetIndex";
|
1447 | this.param = version;
|
1448 | }
|
1449 | }
|
1450 | class InstallLibraryTask extends DownloadTask {
|
1451 | constructor(lib, folder, options) {
|
1452 | const libraryPath = lib.download.path;
|
1453 | const destination = join(folder.libraries, libraryPath);
|
1454 | const urls = resolveLibraryDownloadUrls(lib, options);
|
1455 | super({
|
1456 | url: urls,
|
1457 | validator: lib.download.sha1 === "" ? new ZipValidator() : {
|
1458 | algorithm: "sha1",
|
1459 | hash: lib.download.sha1,
|
1460 | },
|
1461 | destination,
|
1462 | agents: options.agents,
|
1463 | segmentPolicy: options.segmentPolicy,
|
1464 | retryHandler: options.retryHandler,
|
1465 | });
|
1466 | this.name = "library";
|
1467 | this.param = lib;
|
1468 | }
|
1469 | }
|
1470 | class InstallAssetTask extends DownloadTask {
|
1471 | constructor(asset, folder, options) {
|
1472 | const assetsHosts = [
|
1473 | ...normalizeArray(options.assetsHost),
|
1474 | DEFAULT_RESOURCE_ROOT_URL,
|
1475 | ];
|
1476 | const { hash, size, name } = asset;
|
1477 | const head = hash.substring(0, 2);
|
1478 | const dir = folder.getPath("assets", "objects", head);
|
1479 | const file = join(dir, hash);
|
1480 | const urls = assetsHosts.map((h) => `${h}/${head}/${hash}`);
|
1481 | super({
|
1482 | url: urls,
|
1483 | destination: file,
|
1484 | validator: { hash, algorithm: "sha1", },
|
1485 | agents: options.agents,
|
1486 | segmentPolicy: options.segmentPolicy,
|
1487 | retryHandler: options.retryHandler,
|
1488 | });
|
1489 | this._total = size;
|
1490 | this.name = "asset";
|
1491 | this.param = asset;
|
1492 | }
|
1493 | }
|
1494 | const DEFAULT_MAVENS = ["https://repo1.maven.org/maven2/"];
|
1495 |
|
1496 |
|
1497 |
|
1498 |
|
1499 |
|
1500 |
|
1501 | function resolveLibraryDownloadUrls(library, libraryOptions) {
|
1502 | var _a, _b;
|
1503 | const libraryHosts = (_b = (_a = libraryOptions.libraryHost) === null || _a === void 0 ? void 0 : _a.call(libraryOptions, library)) !== null && _b !== void 0 ? _b : [];
|
1504 | return [
|
1505 |
|
1506 | ...normalizeArray(libraryHosts),
|
1507 | ...normalizeArray(libraryOptions.mavenHost).map((m) => joinUrl(m, library.download.path)),
|
1508 | library.download.url,
|
1509 | ...DEFAULT_MAVENS.map((m) => joinUrl(m, library.download.path)),
|
1510 | ];
|
1511 | }
|
1512 |
|
1513 |
|
1514 |
|
1515 |
|
1516 | function resolveProcessors(side, installProfile, minecraft) {
|
1517 | function normalizePath(val) {
|
1518 | if (val && val.match(/^\[.+\]$/g)) {
|
1519 | const name = val.substring(1, val.length - 1);
|
1520 | return minecraft.getLibraryByPath(LibraryInfo.resolve(name).path);
|
1521 | }
|
1522 | return val;
|
1523 | }
|
1524 | function normalizeVariable(val) {
|
1525 | if (val && val.match(/^{.+}$/g)) {
|
1526 | const key = val.substring(1, val.length - 1);
|
1527 | return variables[key][side];
|
1528 | }
|
1529 | return val;
|
1530 | }
|
1531 |
|
1532 | const variables = {
|
1533 | SIDE: {
|
1534 | client: "client",
|
1535 | server: "server",
|
1536 | },
|
1537 | MINECRAFT_JAR: {
|
1538 | client: minecraft.getVersionJar(installProfile.minecraft),
|
1539 | server: minecraft.getVersionJar(installProfile.minecraft, "server"),
|
1540 | },
|
1541 | };
|
1542 | if (installProfile.data) {
|
1543 | for (const key in installProfile.data) {
|
1544 | const { client, server } = installProfile.data[key];
|
1545 | variables[key] = {
|
1546 | client: normalizePath(client),
|
1547 | server: normalizePath(server),
|
1548 | };
|
1549 | }
|
1550 | }
|
1551 | if (variables.INSTALLER) {
|
1552 | variables.ROOT = {
|
1553 | client: dirname(variables.INSTALLER.client),
|
1554 | server: dirname(variables.INSTALLER.server),
|
1555 | };
|
1556 | }
|
1557 | const processors = (installProfile.processors || []).map((proc) => ({
|
1558 | ...proc,
|
1559 | args: proc.args.map(normalizePath).map(normalizeVariable),
|
1560 | outputs: proc.outputs
|
1561 | ? Object.entries(proc.outputs).map(([k, v]) => ({ [normalizeVariable(k)]: normalizeVariable(v) })).reduce((a, b) => Object.assign(a, b), {})
|
1562 | : undefined,
|
1563 | })).filter((proc) => proc.sides ? proc.sides.indexOf(side) !== -1 : true);
|
1564 | return processors;
|
1565 | }
|
1566 |
|
1567 |
|
1568 |
|
1569 |
|
1570 |
|
1571 |
|
1572 |
|
1573 |
|
1574 | function postProcess(processors, minecraft, javaOptions) {
|
1575 | return new PostProcessingTask(processors, minecraft, javaOptions).startAndWait();
|
1576 | }
|
1577 |
|
1578 |
|
1579 |
|
1580 |
|
1581 |
|
1582 |
|
1583 |
|
1584 |
|
1585 | function installByProfile(installProfile, minecraft, options = {}) {
|
1586 | return installByProfileTask(installProfile, minecraft, options).startAndWait();
|
1587 | }
|
1588 |
|
1589 |
|
1590 |
|
1591 |
|
1592 |
|
1593 |
|
1594 |
|
1595 | function installByProfileTask(installProfile, minecraft, options = {}) {
|
1596 | return task("installByProfile", async function () {
|
1597 | const minecraftFolder = MinecraftFolder.from(minecraft);
|
1598 | const processor = resolveProcessors(options.side || "client", installProfile, minecraftFolder);
|
1599 | const versionJson = await _readFile(minecraftFolder.getVersionJson(installProfile.version)).then((b) => b.toString()).then(JSON.parse);
|
1600 | const libraries = Version.resolveLibraries([...installProfile.libraries, ...versionJson.libraries]);
|
1601 | await this.yield(installResolvedLibrariesTask(libraries, minecraft, options));
|
1602 | await this.yield(new PostProcessingTask(processor, minecraftFolder, options));
|
1603 | });
|
1604 | }
|
1605 | class PostProcessBadJarError extends Error {
|
1606 | constructor(jarPath, causeBy) {
|
1607 | super(`Fail to post process bad jar: ${jarPath}`);
|
1608 | this.jarPath = jarPath;
|
1609 | this.causeBy = causeBy;
|
1610 | this.error = "PostProcessBadJar";
|
1611 | }
|
1612 | }
|
1613 | class PostProcessNoMainClassError extends Error {
|
1614 | constructor(jarPath) {
|
1615 | super(`Fail to post process bad jar without main class: ${jarPath}`);
|
1616 | this.jarPath = jarPath;
|
1617 | this.error = "PostProcessNoMainClass";
|
1618 | }
|
1619 | }
|
1620 | class PostProcessFailedError extends Error {
|
1621 | constructor(jarPath, commands, message) {
|
1622 | super(message);
|
1623 | this.jarPath = jarPath;
|
1624 | this.commands = commands;
|
1625 | this.error = "PostProcessFailed";
|
1626 | }
|
1627 | }
|
1628 |
|
1629 |
|
1630 |
|
1631 |
|
1632 |
|
1633 |
|
1634 |
|
1635 |
|
1636 | class PostProcessingTask extends AbortableTask {
|
1637 | constructor(processors, minecraft, java) {
|
1638 | super();
|
1639 | this.processors = processors;
|
1640 | this.minecraft = minecraft;
|
1641 | this.java = java;
|
1642 | this.name = "postProcessing";
|
1643 | this.pointer = 0;
|
1644 | this.param = processors;
|
1645 | this._total = processors.length;
|
1646 | }
|
1647 | async findMainClass(lib) {
|
1648 | var _a;
|
1649 | let zip;
|
1650 | let mainClass;
|
1651 | try {
|
1652 | zip = await open$2(lib, { lazyEntries: true });
|
1653 | for await (const entry of walkEntriesGenerator(zip)) {
|
1654 | if (entry.fileName === "META-INF/MANIFEST.MF") {
|
1655 | const content = await readEntry(zip, entry).then((b) => b.toString());
|
1656 | mainClass = (_a = content.split("\n")
|
1657 | .map((l) => l.split(": "))
|
1658 | .find((arr) => arr[0] === "Main-Class")) === null || _a === void 0 ? void 0 : _a[1].trim();
|
1659 | break;
|
1660 | }
|
1661 | }
|
1662 | }
|
1663 | catch (e) {
|
1664 | throw new PostProcessBadJarError(lib, e);
|
1665 | }
|
1666 | finally {
|
1667 | zip === null || zip === void 0 ? void 0 : zip.close();
|
1668 | }
|
1669 | if (!mainClass) {
|
1670 | throw new PostProcessNoMainClassError(lib);
|
1671 | }
|
1672 | return mainClass;
|
1673 | }
|
1674 | async isInvalid(outputs) {
|
1675 | for (const [file, expect] of Object.entries(outputs)) {
|
1676 | let sha1 = await checksum(file, "sha1").catch((e) => "");
|
1677 | let expected = expect.replace(/'/g, "");
|
1678 | if (expected !== sha1) {
|
1679 | return true;
|
1680 | }
|
1681 | }
|
1682 | return false;
|
1683 | }
|
1684 | async postProcess(mc, proc, javaOptions) {
|
1685 | var _a, _b;
|
1686 | let jarRealPath = mc.getLibraryByPath(LibraryInfo.resolve(proc.jar).path);
|
1687 | let mainClass = await this.findMainClass(jarRealPath);
|
1688 | let cp = [...proc.classpath, proc.jar].map(LibraryInfo.resolve).map((p) => mc.getLibraryByPath(p.path)).join(delimiter);
|
1689 | let cmd = ["-cp", cp, mainClass, ...proc.args];
|
1690 | try {
|
1691 | await spawnProcess(javaOptions, cmd);
|
1692 | }
|
1693 | catch (e) {
|
1694 | if (typeof e === "string") {
|
1695 | throw new PostProcessFailedError(proc.jar, [(_a = javaOptions.java) !== null && _a !== void 0 ? _a : "java", ...cmd], e);
|
1696 | }
|
1697 | throw e;
|
1698 | }
|
1699 | if (proc.outputs && await this.isInvalid(proc.outputs)) {
|
1700 | throw new PostProcessFailedError(proc.jar, [(_b = javaOptions.java) !== null && _b !== void 0 ? _b : "java", ...cmd], "Validate the output of process failed!");
|
1701 | }
|
1702 | }
|
1703 | async process() {
|
1704 | for (; this.pointer < this.processors.length; this.pointer++) {
|
1705 | const proc = this.processors[this.pointer];
|
1706 | if (this.isCancelled) {
|
1707 | throw new CancelledError();
|
1708 | }
|
1709 | if (this.isPaused) {
|
1710 | throw "PAUSED";
|
1711 | }
|
1712 | if (!proc.outputs || await this.isInvalid(proc.outputs)) {
|
1713 | await this.postProcess(this.minecraft, proc, this.java);
|
1714 | }
|
1715 | if (this.isCancelled) {
|
1716 | throw new CancelledError();
|
1717 | }
|
1718 | if (this.isPaused) {
|
1719 | throw "PAUSED";
|
1720 | }
|
1721 | this._progress = this.pointer;
|
1722 | this.update(1);
|
1723 | }
|
1724 | }
|
1725 | async abort(isCancelled) {
|
1726 |
|
1727 | }
|
1728 | isAbortedError(e) {
|
1729 | return e === "PAUSED";
|
1730 | }
|
1731 | }
|
1732 |
|
1733 | const DEFAULT_FORGE_MAVEN = "http://files.minecraftforge.net/maven";
|
1734 | class DownloadForgeInstallerTask extends DownloadTask {
|
1735 | constructor(forgeVersion, installer, minecraft, options) {
|
1736 | const path = installer ? installer.path : `net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-installer.jar`;
|
1737 | const forgeMavenPath = path.replace("/maven", "").replace("maven", "");
|
1738 | const library = Version.resolveLibrary({
|
1739 | name: `net.minecraftforge:forge:${forgeVersion}:installer`,
|
1740 | downloads: {
|
1741 | artifact: {
|
1742 | url: joinUrl(DEFAULT_FORGE_MAVEN, forgeMavenPath),
|
1743 | path: `net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-installer.jar`,
|
1744 | size: -1,
|
1745 | sha1: (installer === null || installer === void 0 ? void 0 : installer.sha1) || "",
|
1746 | }
|
1747 | }
|
1748 | });
|
1749 | const mavenHost = options.mavenHost ? [...normalizeArray(options.mavenHost), DEFAULT_FORGE_MAVEN] : [DEFAULT_FORGE_MAVEN];
|
1750 | const urls = resolveLibraryDownloadUrls(library, { ...options, mavenHost });
|
1751 | const installJarPath = minecraft.getLibraryByPath(library.path);
|
1752 | super({
|
1753 | url: urls,
|
1754 | destination: installJarPath,
|
1755 | validator: (installer === null || installer === void 0 ? void 0 : installer.sha1) ? {
|
1756 | hash: installer.sha1,
|
1757 | algorithm: "sha1",
|
1758 | } : new ZipValidator(),
|
1759 | agents: options.agents,
|
1760 | segmentPolicy: options.segmentPolicy,
|
1761 | retryHandler: options.retryHandler,
|
1762 | });
|
1763 | this.installJarPath = installJarPath;
|
1764 | this.name = "downloadInstaller";
|
1765 | this.param = { version: forgeVersion };
|
1766 | }
|
1767 | }
|
1768 | function getLibraryPathWithoutMaven(mc, name) {
|
1769 |
|
1770 | return mc.getLibraryByPath(name.substring(name.indexOf("/") + 1));
|
1771 | }
|
1772 | function extractEntryTo(zip, e, dest) {
|
1773 | return openEntryReadStream(zip, e).then((stream) => _pipeline(stream, createWriteStream(dest)));
|
1774 | }
|
1775 | async function installLegacyForgeFromZip(zip, entries, profile, mc, options) {
|
1776 | const versionJson = profile.versionInfo;
|
1777 |
|
1778 | versionJson.id = options.versionId || versionJson.id;
|
1779 | versionJson.inheritsFrom = options.inheritsFrom || versionJson.inheritsFrom;
|
1780 | const rootPath = mc.getVersionRoot(versionJson.id);
|
1781 | const versionJsonPath = join(rootPath, `${versionJson.id}.json`);
|
1782 | await ensureFile(versionJsonPath);
|
1783 | const library = LibraryInfo.resolve(versionJson.libraries.find((l) => l.name.startsWith("net.minecraftforge:forge")));
|
1784 | await Promise.all([
|
1785 | _writeFile(versionJsonPath, JSON.stringify(versionJson, undefined, 4)),
|
1786 | extractEntryTo(zip, entries.legacyUniversalJar, mc.getLibraryByPath(library.path)),
|
1787 | ]);
|
1788 | return versionJson.id;
|
1789 | }
|
1790 |
|
1791 |
|
1792 |
|
1793 |
|
1794 |
|
1795 |
|
1796 |
|
1797 |
|
1798 |
|
1799 | async function unpackForgeInstaller(zip, entries, forgeVersion, profile, mc, jarPath, options) {
|
1800 | const versionJson = await readEntry(zip, entries.versionJson).then((b) => b.toString()).then(JSON.parse);
|
1801 |
|
1802 | versionJson.id = options.versionId || versionJson.id;
|
1803 | versionJson.inheritsFrom = options.inheritsFrom || versionJson.inheritsFrom;
|
1804 |
|
1805 | const rootPath = mc.getVersionRoot(versionJson.id);
|
1806 | const versionJsonPath = join(rootPath, `${versionJson.id}.json`);
|
1807 | const installJsonPath = join(rootPath, "install_profile.json");
|
1808 | const dataRoot = dirname(jarPath);
|
1809 | const unpackData = (entry) => {
|
1810 | promises.push(extractEntryTo(zip, entry, join(dataRoot, entry.fileName.substring("data/".length))));
|
1811 | };
|
1812 | await ensureFile(versionJsonPath);
|
1813 | const promises = [];
|
1814 | if (entries.forgeUniversalJar) {
|
1815 | promises.push(extractEntryTo(zip, entries.forgeUniversalJar, getLibraryPathWithoutMaven(mc, entries.forgeUniversalJar.fileName)));
|
1816 | }
|
1817 | if (!profile.data) {
|
1818 | profile.data = {};
|
1819 | }
|
1820 | const installerMaven = `net.minecraftforge:forge:${forgeVersion}:installer`;
|
1821 | profile.data.INSTALLER = {
|
1822 | client: `[${installerMaven}]`,
|
1823 | server: `[${installerMaven}]`,
|
1824 | };
|
1825 | if (entries.serverLzma) {
|
1826 |
|
1827 | const serverMaven = `net.minecraftforge:forge:${forgeVersion}:serverdata@lzma`;
|
1828 |
|
1829 | profile.data.BINPATCH.server = `[${serverMaven}]`;
|
1830 | const serverBinPath = mc.getLibraryByPath(LibraryInfo.resolve(serverMaven).path);
|
1831 | await ensureFile(serverBinPath);
|
1832 | promises.push(extractEntryTo(zip, entries.serverLzma, serverBinPath));
|
1833 | }
|
1834 | if (entries.clientLzma) {
|
1835 |
|
1836 | const clientMaven = `net.minecraftforge:forge:${forgeVersion}:clientdata@lzma`;
|
1837 |
|
1838 | profile.data.BINPATCH.client = `[${clientMaven}]`;
|
1839 | const clientBinPath = mc.getLibraryByPath(LibraryInfo.resolve(clientMaven).path);
|
1840 | await ensureFile(clientBinPath);
|
1841 | promises.push(extractEntryTo(zip, entries.clientLzma, clientBinPath));
|
1842 | }
|
1843 | if (entries.forgeJar) {
|
1844 | promises.push(extractEntryTo(zip, entries.forgeJar, getLibraryPathWithoutMaven(mc, entries.forgeJar.fileName)));
|
1845 | }
|
1846 | if (entries.runBat) {
|
1847 | unpackData(entries.runBat);
|
1848 | }
|
1849 | if (entries.runSh) {
|
1850 | unpackData(entries.runSh);
|
1851 | }
|
1852 | if (entries.winArgs) {
|
1853 | unpackData(entries.winArgs);
|
1854 | }
|
1855 | if (entries.unixArgs) {
|
1856 | unpackData(entries.unixArgs);
|
1857 | }
|
1858 | if (entries.userJvmArgs) {
|
1859 | unpackData(entries.userJvmArgs);
|
1860 | }
|
1861 | promises.push(_writeFile(installJsonPath, JSON.stringify(profile)), _writeFile(versionJsonPath, JSON.stringify(versionJson)));
|
1862 | await Promise.all(promises);
|
1863 | return versionJson.id;
|
1864 | }
|
1865 | function isLegacyForgeInstallerEntries(entries) {
|
1866 | return !!entries.legacyUniversalJar && !!entries.installProfileJson;
|
1867 | }
|
1868 | function isForgeInstallerEntries(entries) {
|
1869 | return !!entries.installProfileJson && !!entries.versionJson;
|
1870 | }
|
1871 |
|
1872 |
|
1873 |
|
1874 |
|
1875 |
|
1876 | async function walkForgeInstallerEntries(zip, forgeVersion) {
|
1877 | const [forgeJar, forgeUniversalJar, clientLzma, serverLzma, installProfileJson, versionJson, legacyUniversalJar, runSh, runBat, unixArgs, userJvmArgs, winArgs] = await filterEntries(zip, [
|
1878 | `maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}.jar`,
|
1879 | `maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-universal.jar`,
|
1880 | "data/client.lzma",
|
1881 | "data/server.lzma",
|
1882 | "install_profile.json",
|
1883 | "version.json",
|
1884 | `forge-${forgeVersion}-universal.jar`,
|
1885 | "data/run.sh",
|
1886 | "data/run.bat",
|
1887 | "data/unix_args.txt",
|
1888 | "data/user_jvm_args.txt",
|
1889 | "data/win_args.txt",
|
1890 | ]);
|
1891 | return {
|
1892 | forgeJar,
|
1893 | forgeUniversalJar,
|
1894 | clientLzma,
|
1895 | serverLzma,
|
1896 | installProfileJson,
|
1897 | versionJson,
|
1898 | legacyUniversalJar,
|
1899 | runSh, runBat, unixArgs, userJvmArgs, winArgs,
|
1900 | };
|
1901 | }
|
1902 | class BadForgeInstallerJarError extends Error {
|
1903 | constructor(jarPath,
|
1904 | /**
|
1905 | * What entry in jar is missing
|
1906 | */
|
1907 | entry) {
|
1908 | super(entry ? `Missing entry ${entry} in forge installer jar: ${jarPath}` : `Bad forge installer: ${jarPath}`);
|
1909 | this.jarPath = jarPath;
|
1910 | this.entry = entry;
|
1911 | this.error = "BadForgeInstallerJar";
|
1912 | }
|
1913 | }
|
1914 | function installByInstallerTask(version, minecraft, options) {
|
1915 | return task("installForge", async function () {
|
1916 | function getForgeArtifactVersion() {
|
1917 | let [_, minor] = version.mcversion.split(".");
|
1918 | let minorVersion = Number.parseInt(minor);
|
1919 | if (minorVersion >= 7 && minorVersion <= 8) {
|
1920 | return `${version.mcversion}-${version.version}-${version.mcversion}`;
|
1921 | }
|
1922 | return `${version.mcversion}-${version.version}`;
|
1923 | }
|
1924 | const forgeVersion = getForgeArtifactVersion();
|
1925 | const mc = MinecraftFolder.from(minecraft);
|
1926 | return withAgents(options, async (options) => {
|
1927 | const jarPath = await this.yield(new DownloadForgeInstallerTask(forgeVersion, version.installer, mc, options)
|
1928 | .map(function () { return this.installJarPath; }));
|
1929 | const zip = await open$2(jarPath, { lazyEntries: true, autoClose: false });
|
1930 | const entries = await walkForgeInstallerEntries(zip, forgeVersion);
|
1931 | if (!entries.installProfileJson) {
|
1932 | throw new BadForgeInstallerJarError(jarPath, "install_profile.json");
|
1933 | }
|
1934 | const profile = await readEntry(zip, entries.installProfileJson).then((b) => b.toString()).then(JSON.parse);
|
1935 | if (isForgeInstallerEntries(entries)) {
|
1936 |
|
1937 | const versionId = await unpackForgeInstaller(zip, entries, forgeVersion, profile, mc, jarPath, options);
|
1938 | await this.concat(installByProfileTask(profile, minecraft, options));
|
1939 | return versionId;
|
1940 | }
|
1941 | else if (isLegacyForgeInstallerEntries(entries)) {
|
1942 |
|
1943 | return installLegacyForgeFromZip(zip, entries, profile, mc, options);
|
1944 | }
|
1945 | else {
|
1946 |
|
1947 | throw new BadForgeInstallerJarError(jarPath);
|
1948 | }
|
1949 | });
|
1950 | });
|
1951 | }
|
1952 |
|
1953 |
|
1954 |
|
1955 |
|
1956 |
|
1957 |
|
1958 |
|
1959 | function installForge(version, minecraft, options) {
|
1960 | return installForgeTask(version, minecraft, options).startAndWait();
|
1961 | }
|
1962 |
|
1963 |
|
1964 |
|
1965 |
|
1966 |
|
1967 |
|
1968 |
|
1969 | function installForgeTask(version, minecraft, options = {}) {
|
1970 | return installByInstallerTask(version, minecraft, options);
|
1971 | }
|
1972 |
|
1973 |
|
1974 |
|
1975 |
|
1976 |
|
1977 |
|
1978 |
|
1979 |
|
1980 | async function getForgeVersionList(option = {}) {
|
1981 | const mcversion = option.mcversion || "";
|
1982 | const url = mcversion === "" ? "http://files.minecraftforge.net/maven/net/minecraftforge/forge/index.html" : `http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_${mcversion}.html`;
|
1983 | return getAndParseIfUpdate(url, parse, option.original);
|
1984 | }
|
1985 |
|
1986 | function getDefaultEntryResolver() {
|
1987 | return (e) => e.fileName;
|
1988 | }
|
1989 | class UnzipTask extends BaseTask {
|
1990 | constructor(zipFile, entries, destination, resolver = getDefaultEntryResolver()) {
|
1991 | super();
|
1992 | this.zipFile = zipFile;
|
1993 | this.entries = entries;
|
1994 | this.resolver = resolver;
|
1995 | this.streams = [];
|
1996 | this._onCancelled = () => { };
|
1997 | this._to = destination;
|
1998 | }
|
1999 | async handleEntry(entry, relativePath) {
|
2000 | const file = join(this.to, relativePath);
|
2001 | if (this._state === TaskState.Cancelled) {
|
2002 | throw new CancelledError();
|
2003 | }
|
2004 | const readStream = await openEntryReadStream(this.zipFile, entry);
|
2005 | if (this.isCancelled) {
|
2006 | throw new CancelledError();
|
2007 | }
|
2008 | if (this._state === TaskState.Paused) {
|
2009 | readStream.pause();
|
2010 | }
|
2011 | await ensureFile(file);
|
2012 | const writeStream = createWriteStream(file);
|
2013 | readStream.on("data", (buf) => {
|
2014 | this._progress += buf.length;
|
2015 | this.update(buf.length);
|
2016 | });
|
2017 | await _pipeline(readStream, writeStream);
|
2018 | }
|
2019 | async runTask() {
|
2020 | const promises = [];
|
2021 | for (const e of this.entries) {
|
2022 | const path = await this.resolver(e);
|
2023 | if (this.isCancelled) {
|
2024 | throw new CancelledError();
|
2025 | }
|
2026 | this._total += e.uncompressedSize;
|
2027 | promises.push(this.handleEntry(e, path));
|
2028 | }
|
2029 | this.update(0);
|
2030 | try {
|
2031 | await Promise.all(promises);
|
2032 | }
|
2033 | catch (e) {
|
2034 | if (e instanceof CancelledError) {
|
2035 | this._onCancelled();
|
2036 | }
|
2037 | throw e;
|
2038 | }
|
2039 | }
|
2040 | cancelTask() {
|
2041 | for (const [read, write] of this.streams) {
|
2042 | read.unpipe();
|
2043 | read.destroy(new CancelledError());
|
2044 | this.zipFile.close();
|
2045 | write.destroy(new CancelledError());
|
2046 | }
|
2047 | return new Promise((resolve) => {
|
2048 | this._onCancelled = resolve;
|
2049 | });
|
2050 | }
|
2051 | async pauseTask() {
|
2052 | const promise = Promise.all(this.streams.map(([read]) => new Promise((resolve) => read.once("pause", resolve))));
|
2053 | for (const [read] of this.streams) {
|
2054 | read.pause();
|
2055 | }
|
2056 | await promise;
|
2057 | }
|
2058 | async resumeTask() {
|
2059 | const promise = Promise.all(this.streams.map(([read]) => new Promise((resolve) => read.once("readable", resolve))));
|
2060 | for (const [read] of this.streams) {
|
2061 | read.resume();
|
2062 | }
|
2063 | await promise;
|
2064 | }
|
2065 | }
|
2066 |
|
2067 | class BadCurseforgeModpackError extends Error {
|
2068 | constructor(modpack,
|
2069 | /**
|
2070 | * What required entry is missing in modpack.
|
2071 | */
|
2072 | entry) {
|
2073 | super(`Missing entry ${entry} in curseforge modpack: ${modpack}`);
|
2074 | this.modpack = modpack;
|
2075 | this.entry = entry;
|
2076 | this.error = "BadCurseforgeModpack";
|
2077 | }
|
2078 | }
|
2079 |
|
2080 |
|
2081 |
|
2082 |
|
2083 | function readManifestTask(input) {
|
2084 | return task("unpack", async () => {
|
2085 | const zip = await normalizeInput(input);
|
2086 | const mainfiestEntry = zip.entries.find((e) => e.fileName === "manifest.json");
|
2087 | if (!mainfiestEntry) {
|
2088 | throw new BadCurseforgeModpackError(input, "manifest.json");
|
2089 | }
|
2090 | const buffer = await readEntry(zip.zip, mainfiestEntry);
|
2091 | const content = JSON.parse(buffer.toString());
|
2092 | return content;
|
2093 | });
|
2094 | }
|
2095 |
|
2096 |
|
2097 |
|
2098 |
|
2099 | function readManifest(zip) {
|
2100 | return readManifestTask(zip).startAndWait();
|
2101 | }
|
2102 | function createDefaultCurseforgeQuery() {
|
2103 | let agent = new Agent$1();
|
2104 | return (projectId, fileId) => fetchText(`https://addons-ecs.forgesvc.net/api/v2/addon/${projectId}/file/${fileId}/download-url`, { https: agent });
|
2105 | }
|
2106 |
|
2107 |
|
2108 |
|
2109 |
|
2110 |
|
2111 |
|
2112 |
|
2113 | function installCurseforgeModpack(zip, minecraft, options) {
|
2114 | return installCurseforgeModpackTask(zip, minecraft, options).startAndWait();
|
2115 | }
|
2116 | async function normalizeInput(input) {
|
2117 | if (typeof input === "string" || input instanceof Buffer) {
|
2118 | const zip = await open$2(input, { lazyEntries: true, autoClose: false });
|
2119 | return { zip, entries: await readAllEntries(zip) };
|
2120 | }
|
2121 | else {
|
2122 | return input;
|
2123 | }
|
2124 | }
|
2125 |
|
2126 |
|
2127 |
|
2128 |
|
2129 |
|
2130 |
|
2131 |
|
2132 |
|
2133 |
|
2134 |
|
2135 |
|
2136 | function installCurseforgeModpackTask(input, minecraft, options = {}) {
|
2137 | return task("installCurseforgeModpack", async function () {
|
2138 | var _a;
|
2139 | const folder = MinecraftFolder.from(minecraft);
|
2140 | const zip = await normalizeInput(input);
|
2141 | const manifest = (_a = options === null || options === void 0 ? void 0 : options.manifest) !== null && _a !== void 0 ? _a : (await this.yield(readManifestTask(zip)));
|
2142 | const requestor = (options === null || options === void 0 ? void 0 : options.queryFileUrl) || createDefaultCurseforgeQuery();
|
2143 | const resolver = (options === null || options === void 0 ? void 0 : options.filePathResolver) || ((p, f, m, u) => m.getMod(basename(u)));
|
2144 | await withAgents(options, async (options) => {
|
2145 | var _a;
|
2146 | const tasks = await Promise.all(manifest.files.map(async (f) => {
|
2147 | const from = await requestor(f.projectID, f.fileID);
|
2148 | const to = await resolver(f.projectID, f.fileID, folder, from);
|
2149 | return new DownloadTask({
|
2150 | url: from,
|
2151 | destination: to,
|
2152 | agents: options.agents,
|
2153 | segmentPolicy: options.segmentPolicy,
|
2154 | retryHandler: options.retryHandler,
|
2155 | }).setName("download");
|
2156 | }));
|
2157 | await this.all(tasks, {
|
2158 | throwErrorImmediately: (_a = options.throwErrorImmediately) !== null && _a !== void 0 ? _a : false,
|
2159 | getErrorMessage: (errs) => `Fail to install curseforge modpack to ${folder.root}: ${errs.map(errorToString).join("\n")}`
|
2160 | });
|
2161 | });
|
2162 | await this.yield(new UnzipTask(zip.zip, zip.entries.filter((e) => !e.fileName.endsWith("/") && e.fileName.startsWith(manifest.overrides)), folder.root, (e) => e.fileName.substring(manifest.overrides.length)).setName("unpack"));
|
2163 | return manifest;
|
2164 | });
|
2165 | }
|
2166 |
|
2167 |
|
2168 |
|
2169 | function installCurseforgeFile(file, destination, options) {
|
2170 | return installCurseforgeFileTask(file, destination, options).startAndWait();
|
2171 | }
|
2172 |
|
2173 |
|
2174 |
|
2175 | function installCurseforgeFileTask(file, destination, options = {}) {
|
2176 | return task("installCurseforgeFile", async function () {
|
2177 | const requestor = options.queryFileUrl || createDefaultCurseforgeQuery();
|
2178 | const url = await requestor(file.projectID, file.fileID);
|
2179 | await new DownloadTask({
|
2180 | url,
|
2181 | destination: join(destination, basename(url)),
|
2182 | agents: options.agents,
|
2183 | segmentPolicy: options.segmentPolicy,
|
2184 | retryHandler: options.retryHandler,
|
2185 | }).startAndWait(this.context, this.parent);
|
2186 | });
|
2187 | }
|
2188 |
|
2189 |
|
2190 |
|
2191 |
|
2192 |
|
2193 |
|
2194 |
|
2195 |
|
2196 |
|
2197 | function generateOptifineVersion(editionRelease, minecraftVersion, launchWrapperVersion, options = {}) {
|
2198 | var _a, _b;
|
2199 | let id = (_a = options.versionId) !== null && _a !== void 0 ? _a : `${minecraftVersion}-Optifine_${editionRelease}`;
|
2200 | let inheritsFrom = (_b = options.inheritsFrom) !== null && _b !== void 0 ? _b : minecraftVersion;
|
2201 | let mainClass = "net.minecraft.launchwrapper.Launch";
|
2202 | let libraries = [{ name: `optifine:Optifine:${minecraftVersion}_${editionRelease}` }];
|
2203 | if (launchWrapperVersion) {
|
2204 | libraries.unshift({ name: `optifine:launchwrapper-of:${launchWrapperVersion}` });
|
2205 | }
|
2206 | else {
|
2207 | libraries.unshift({ name: "net.minecraft:launchwrapper:1.12" });
|
2208 | }
|
2209 | return {
|
2210 | id,
|
2211 | inheritsFrom,
|
2212 | arguments: {
|
2213 | game: ["--tweakClass", options.useForgeTweaker ? "optifine.OptiFineForgeTweaker" : "optifine.OptiFineTweaker"],
|
2214 | jvm: [],
|
2215 | },
|
2216 | releaseTime: new Date().toJSON(),
|
2217 | time: new Date().toJSON(),
|
2218 | type: "release",
|
2219 | libraries,
|
2220 | mainClass,
|
2221 | minimumLauncherVersion: 21,
|
2222 | };
|
2223 | }
|
2224 |
|
2225 |
|
2226 |
|
2227 |
|
2228 |
|
2229 |
|
2230 |
|
2231 |
|
2232 |
|
2233 | function installOptifine(installer, minecraft, options) {
|
2234 | return installOptifineTask(installer, minecraft, options).startAndWait();
|
2235 | }
|
2236 | class BadOptifineJarError extends Error {
|
2237 | constructor(optifine,
|
2238 | /**
|
2239 | * What entry in jar is missing
|
2240 | */
|
2241 | entry) {
|
2242 | super(`Missing entry ${entry} in optifine installer: ${optifine}`);
|
2243 | this.optifine = optifine;
|
2244 | this.entry = entry;
|
2245 | this.error = "BadOptifineJar";
|
2246 | }
|
2247 | }
|
2248 |
|
2249 |
|
2250 |
|
2251 |
|
2252 |
|
2253 |
|
2254 |
|
2255 |
|
2256 |
|
2257 | function installOptifineTask(installer, minecraft, options = {}) {
|
2258 | return task("installOptifine", async function () {
|
2259 | var _a, _b;
|
2260 | let mc = MinecraftFolder.from(minecraft);
|
2261 |
|
2262 | const zip = await open$2(installer);
|
2263 | const entries = await readAllEntries(zip);
|
2264 | const record = getEntriesRecord(entries);
|
2265 |
|
2266 | const entry = (_b = (_a = record["net/optifine/Config.class"]) !== null && _a !== void 0 ? _a : record["Config.class"]) !== null && _b !== void 0 ? _b : record["notch/net/optifine/Config.class"];
|
2267 | if (!entry) {
|
2268 | throw new BadOptifineJarError(installer, "net/optifine/Config.class");
|
2269 | }
|
2270 | const launchWrapperVersionEntry = record["launchwrapper-of.txt"];
|
2271 | const launchWrapperVersion = launchWrapperVersionEntry ? await readEntry(zip, launchWrapperVersionEntry).then((b) => b.toString())
|
2272 | : undefined;
|
2273 |
|
2274 | const buf = await readEntry(zip, entry);
|
2275 | const reader = new ClassReader(buf);
|
2276 | class OptifineVisitor extends ClassVisitor {
|
2277 | constructor() {
|
2278 | super(...arguments);
|
2279 | this.fields = {};
|
2280 | }
|
2281 | visitField(access, name, desc, signature, value) {
|
2282 | this.fields[name] = value;
|
2283 | return null;
|
2284 | }
|
2285 | }
|
2286 | const visitor = new OptifineVisitor(Opcodes.ASM5);
|
2287 | reader.accept(visitor);
|
2288 | const mcversion = visitor.fields.MC_VERSION;
|
2289 | const edition = visitor.fields.OF_EDITION;
|
2290 | const release = visitor.fields.OF_RELEASE;
|
2291 | const editionRelease = edition + "_" + release;
|
2292 | const versionJSON = generateOptifineVersion(editionRelease, mcversion, launchWrapperVersion, options);
|
2293 | const versionJSONPath = mc.getVersionJson(versionJSON.id);
|
2294 |
|
2295 |
|
2296 | await this.yield(task("json", async () => {
|
2297 | await ensureFile(versionJSONPath);
|
2298 | await _writeFile(versionJSONPath, JSON.stringify(versionJSON, null, 4));
|
2299 | }));
|
2300 | const launchWrapperEntry = record[`launchwrapper-of-${launchWrapperVersion}.jar`];
|
2301 |
|
2302 | if (launchWrapperEntry) {
|
2303 | await this.yield(task("library", async () => {
|
2304 | const wrapperDest = mc.getLibraryByPath(`optifine/launchwrapper-of/${launchWrapperVersion}/launchwrapper-of-${launchWrapperVersion}.jar`);
|
2305 | await ensureFile(wrapperDest);
|
2306 | await _writeFile(wrapperDest, await readEntry(zip, launchWrapperEntry));
|
2307 | }));
|
2308 | }
|
2309 |
|
2310 | await this.yield(task("jar", async () => {
|
2311 | const dest = mc.getLibraryByPath(`optifine/Optifine/${mcversion}_${editionRelease}/Optifine-${mcversion}_${editionRelease}.jar`);
|
2312 | const mcJar = mc.getVersionJar(mcversion);
|
2313 | await ensureFile(dest);
|
2314 | await spawnProcess(options, ["-cp", installer, "optifine.Patcher", mcJar, installer, dest]);
|
2315 | }));
|
2316 | return versionJSON.id;
|
2317 | });
|
2318 | }
|
2319 |
|
2320 | class DownloadJRETask extends DownloadTask {
|
2321 | constructor(jre, dir, options) {
|
2322 | const { sha1, url } = jre;
|
2323 | const filename = basename(url);
|
2324 | const downloadDestination = resolve(dir, filename);
|
2325 | super({
|
2326 | url,
|
2327 | destination: downloadDestination,
|
2328 | validator: {
|
2329 | algorithm: "sha1",
|
2330 | hash: sha1,
|
2331 | },
|
2332 | segmentPolicy: options.segmentPolicy,
|
2333 | retryHandler: options.retryHandler,
|
2334 | agents: options.agents,
|
2335 | });
|
2336 | this.name = "downloadJre";
|
2337 | this.param = jre;
|
2338 | }
|
2339 | }
|
2340 |
|
2341 |
|
2342 |
|
2343 |
|
2344 | function installJreFromMojangTask(options) {
|
2345 | const { destination, unpackLZMA, cacheDir = tmpdir(), platform = getPlatform(), } = options;
|
2346 | return task("installJreFromMojang", async function () {
|
2347 | const info = await this.yield(task("fetchInfo", () => fetchJson("https://launchermeta.mojang.com/mc/launcher.json")));
|
2348 | const system = platform.name;
|
2349 | function resolveArch() {
|
2350 | switch (platform.arch) {
|
2351 | case "x86":
|
2352 | case "x32": return "32";
|
2353 | case "x64": return "64";
|
2354 | default: return "32";
|
2355 | }
|
2356 | }
|
2357 | const currentArch = resolveArch();
|
2358 | if (!info[system] || !info[system][currentArch] || !info[system][currentArch].jre) {
|
2359 | throw new Error("No Java package available for your platform");
|
2360 | }
|
2361 | const lzmaPath = await this.yield(new DownloadJRETask(info[system][currentArch].jre, cacheDir, options).map(function () { return this.to; }));
|
2362 | const result = unpackLZMA(lzmaPath, destination);
|
2363 | await ensureDir(destination);
|
2364 | if (result instanceof Promise) {
|
2365 | await this.yield(task("decompress", () => result));
|
2366 | }
|
2367 | else {
|
2368 | await this.yield(result);
|
2369 | }
|
2370 | await this.yield(task("cleanup", () => unlink(lzmaPath)));
|
2371 | });
|
2372 | }
|
2373 |
|
2374 |
|
2375 |
|
2376 |
|
2377 | function installJreFromMojang(options) {
|
2378 | return installJreFromMojangTask(options).startAndWait();
|
2379 | }
|
2380 |
|
2381 |
|
2382 |
|
2383 |
|
2384 | async function resolveJava(path) {
|
2385 | if (await missing(path)) {
|
2386 | return undefined;
|
2387 | }
|
2388 | return new Promise((resolve) => {
|
2389 | exec(`"${path}" -version`, (err, sout, serr) => {
|
2390 | if (serr) {
|
2391 | let ver = parseJavaVersion(serr);
|
2392 | if (ver) {
|
2393 | resolve({ path, ...ver });
|
2394 | }
|
2395 | else {
|
2396 | resolve(undefined);
|
2397 | }
|
2398 | }
|
2399 | else {
|
2400 | resolve(undefined);
|
2401 | }
|
2402 | });
|
2403 | });
|
2404 | }
|
2405 |
|
2406 |
|
2407 |
|
2408 |
|
2409 |
|
2410 | function parseJavaVersion(versionText) {
|
2411 | const getVersion = (str) => {
|
2412 | if (!str) {
|
2413 | return undefined;
|
2414 | }
|
2415 | const match = /(\d+\.\d+\.\d+)(_(\d+))?/.exec(str);
|
2416 | if (match === null) {
|
2417 | return undefined;
|
2418 | }
|
2419 | return match[1];
|
2420 | };
|
2421 | let javaVersion = getVersion(versionText);
|
2422 | if (!javaVersion) {
|
2423 | return undefined;
|
2424 | }
|
2425 | let majorVersion = Number.parseInt(javaVersion.split(".")[0], 10);
|
2426 | if (majorVersion === 1) {
|
2427 | majorVersion = Number.parseInt(javaVersion.split(".")[1], 10);
|
2428 | }
|
2429 | let java = {
|
2430 | version: javaVersion,
|
2431 | majorVersion,
|
2432 | };
|
2433 | return java;
|
2434 | }
|
2435 |
|
2436 |
|
2437 |
|
2438 |
|
2439 |
|
2440 |
|
2441 |
|
2442 | async function getPotentialJavaLocations() {
|
2443 | let unchecked = new Set();
|
2444 | let currentPlatform = platform();
|
2445 | let javaFile = currentPlatform === "win32" ? "javaw.exe" : "java";
|
2446 | if (process.env.JAVA_HOME) {
|
2447 | unchecked.add(join(process.env.JAVA_HOME, "bin", javaFile));
|
2448 | }
|
2449 | const which = () => new Promise((resolve) => {
|
2450 | exec("which java", (error, stdout) => {
|
2451 | resolve(stdout.replace("\n", ""));
|
2452 | });
|
2453 | });
|
2454 | const where = () => new Promise((resolve) => {
|
2455 | exec("where java", (error, stdout) => {
|
2456 | resolve(stdout.split("\r\n"));
|
2457 | });
|
2458 | });
|
2459 | if (currentPlatform === "win32") {
|
2460 | const out = await new Promise((resolve) => {
|
2461 | exec("REG QUERY HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\ /s /v JavaHome", (error, stdout) => {
|
2462 | if (!stdout) {
|
2463 | resolve([]);
|
2464 | }
|
2465 | resolve(stdout.split(EOL).map((item) => item.replace(/[\r\n]/g, ""))
|
2466 | .filter((item) => item != null && item !== undefined)
|
2467 | .filter((item) => item[0] === " ")
|
2468 | .map((item) => `${item.split(" ")[3]}\\bin\\javaw.exe`));
|
2469 | });
|
2470 | });
|
2471 | for (const o of [...out, ...await where()]) {
|
2472 | unchecked.add(o);
|
2473 | }
|
2474 | unchecked.add("C:\\Program Files (x86)\\Minecraft Launcher\\runtime\\jre-x64/X86");
|
2475 | }
|
2476 | else if (currentPlatform === "darwin") {
|
2477 | unchecked.add("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
|
2478 | unchecked.add(await which());
|
2479 | }
|
2480 | else {
|
2481 | unchecked.add(await which());
|
2482 | }
|
2483 | let checkingList = Array.from(unchecked).filter((jPath) => typeof jPath === "string").filter((p) => p !== "");
|
2484 | return checkingList;
|
2485 | }
|
2486 |
|
2487 |
|
2488 |
|
2489 |
|
2490 |
|
2491 |
|
2492 |
|
2493 |
|
2494 |
|
2495 |
|
2496 |
|
2497 | async function scanLocalJava(locations) {
|
2498 | let unchecked = new Set(locations);
|
2499 | let potential = await getPotentialJavaLocations();
|
2500 | potential.forEach((p) => unchecked.add(p));
|
2501 | let checkingList = [...unchecked].filter((jPath) => typeof jPath === "string").filter((p) => p !== "");
|
2502 | const javas = await Promise.all(checkingList.map((jPath) => resolveJava(jPath)));
|
2503 | return javas.filter(((j) => j !== undefined));
|
2504 | }
|
2505 |
|
2506 | var JavaRuntimeTargetType;
|
2507 | (function (JavaRuntimeTargetType) {
|
2508 | |
2509 |
|
2510 |
|
2511 | JavaRuntimeTargetType["Legacy"] = "jre-legacy";
|
2512 | |
2513 |
|
2514 |
|
2515 | JavaRuntimeTargetType["Alpha"] = "java-runtime-alpha";
|
2516 | JavaRuntimeTargetType["Beta"] = "java-runtime-beta";
|
2517 | JavaRuntimeTargetType["JavaExe"] = "minecraft-java-exe";
|
2518 | })(JavaRuntimeTargetType || (JavaRuntimeTargetType = {}));
|
2519 | const DEFAULT_RUNTIME_ALL_URL = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
|
2520 | function normalizeUrls(url, fileHost) {
|
2521 | if (!fileHost) {
|
2522 | return [url];
|
2523 | }
|
2524 | if (typeof fileHost === "string") {
|
2525 | const u = new URL(url);
|
2526 | u.hostname = fileHost;
|
2527 | return [u.toString(), url];
|
2528 | }
|
2529 | return fileHost.map((host) => {
|
2530 | const u = new URL(url);
|
2531 | u.hostname = host;
|
2532 | return u.toString();
|
2533 | }).concat(url);
|
2534 | }
|
2535 |
|
2536 |
|
2537 |
|
2538 |
|
2539 |
|
2540 |
|
2541 | async function fetchJavaRuntimeManifest(options = {}) {
|
2542 | var _a, _b, _c, _d;
|
2543 | const manifestIndex = (_a = options.manifestIndex) !== null && _a !== void 0 ? _a : await fetchJson(normalizeUrls((_b = options.url) !== null && _b !== void 0 ? _b : DEFAULT_RUNTIME_ALL_URL, options.apiHost)[0]);
|
2544 | const platform = (_c = options.platform) !== null && _c !== void 0 ? _c : getPlatform();
|
2545 | const runtimeTarget = (_d = options.target) !== null && _d !== void 0 ? _d : JavaRuntimeTargetType.Beta;
|
2546 | const resolveTarget = () => {
|
2547 | if (platform.name === "windows") {
|
2548 | if (platform.arch === "x64") {
|
2549 | return manifestIndex["windows-x64"];
|
2550 | }
|
2551 | if (platform.arch === "x86" || platform.arch === "x32") {
|
2552 | return manifestIndex["windows-x86"];
|
2553 | }
|
2554 | }
|
2555 | if (platform.name === "osx") {
|
2556 | return manifestIndex["mac-os"];
|
2557 | }
|
2558 | if (platform.name === "linux") {
|
2559 | if (platform.arch === "x86" || platform.arch === "x32") {
|
2560 | return manifestIndex["linux-i386"];
|
2561 | }
|
2562 | if (platform.arch === "x64") {
|
2563 | return manifestIndex.linux;
|
2564 | }
|
2565 | }
|
2566 | throw new Error("Cannot resolve platform");
|
2567 | };
|
2568 | const targets = resolveTarget()[runtimeTarget];
|
2569 | if (targets && targets.length > 0) {
|
2570 | const target = targets[0];
|
2571 | const manifestUrl = normalizeUrls(target.manifest.url, options.apiHost)[0];
|
2572 | const manifest = await fetchJson(manifestUrl);
|
2573 | const result = {
|
2574 | files: manifest.files,
|
2575 | target: runtimeTarget,
|
2576 | version: target.version,
|
2577 | };
|
2578 | return result;
|
2579 | }
|
2580 | else {
|
2581 | throw new Error();
|
2582 | }
|
2583 | }
|
2584 |
|
2585 |
|
2586 |
|
2587 |
|
2588 | function installJavaRuntimesTask(options) {
|
2589 | return task("installJavaRuntime", async function () {
|
2590 | const destination = options.destination;
|
2591 | const manifest = options.manifest;
|
2592 | const decompressFunction = typeof options.lzma === "function" ? options.lzma : undefined;
|
2593 | const downloadLzma = !!options.lzma;
|
2594 | class DownloadAndDecompressTask extends DownloadTask {
|
2595 | constructor(options) {
|
2596 | super(options);
|
2597 | }
|
2598 | async runTask() {
|
2599 | const result = await super.runTask();
|
2600 | if (this._total === this._progress) {
|
2601 | const dest = this.download.destination.substring(0, this.download.destination.length - 5);
|
2602 | await decompressFunction(this.download.destination, dest);
|
2603 | }
|
2604 | return result;
|
2605 | }
|
2606 | }
|
2607 | await withAgents(options, (options) => this.all(Object.entries(manifest.files)
|
2608 | .filter(([file, entry]) => entry.type === "file")
|
2609 | .map(([file, entry]) => {
|
2610 | const fEntry = entry;
|
2611 | const downloadInfo = (downloadLzma && fEntry.downloads.lzma) ? fEntry.downloads.lzma : fEntry.downloads.raw;
|
2612 | const isLzma = downloadInfo == fEntry.downloads.lzma;
|
2613 | const dest = isLzma ? (join(destination, file) + ".lzma") : join(destination, file);
|
2614 | const urls = normalizeUrls(downloadInfo.url, options.apiHost);
|
2615 | const downloadOptions = {
|
2616 | url: urls,
|
2617 | validator: {
|
2618 | algorithm: "sha1",
|
2619 | hash: downloadInfo.sha1,
|
2620 | },
|
2621 | destination: dest,
|
2622 | segmentPolicy: options.segmentPolicy,
|
2623 | retryHandler: options.retryHandler,
|
2624 | agents: options.agents,
|
2625 | };
|
2626 | return isLzma && decompressFunction
|
2627 | ? new DownloadAndDecompressTask(downloadOptions).setName("download")
|
2628 | : new DownloadTask(downloadOptions).setName("download");
|
2629 | }), {
|
2630 | throwErrorImmediately: options.throwErrorImmediately,
|
2631 | getErrorMessage: (e) => `Fail to install java runtime ${manifest.version.name} on ${manifest.target}`,
|
2632 | }));
|
2633 | await Promise.all(Object.entries(manifest.files)
|
2634 | .filter(([file, entry]) => entry.type !== "file")
|
2635 | .map(async ([file, entry]) => {
|
2636 | const dest = join(destination, file);
|
2637 | if (entry.type === "directory") {
|
2638 | await ensureDir(dest);
|
2639 | }
|
2640 | else if (entry.type === "link") {
|
2641 | await link(join(destination, entry.target), destination);
|
2642 | }
|
2643 | }));
|
2644 | });
|
2645 | }
|
2646 |
|
2647 |
|
2648 |
|
2649 |
|
2650 |
|
2651 |
|
2652 |
|
2653 |
|
2654 |
|
2655 |
|
2656 | async function diagnoseInstall(installProfile, minecraftLocation) {
|
2657 | const mc = MinecraftFolder.from(minecraftLocation);
|
2658 | const report = {
|
2659 | minecraftLocation: mc,
|
2660 | installProfile,
|
2661 | issues: [],
|
2662 | };
|
2663 | const issues = report.issues;
|
2664 | const processors = resolveProcessors("client", installProfile, mc);
|
2665 | await Promise.all(Version.resolveLibraries(installProfile.libraries).map(async (lib) => {
|
2666 | const libPath = mc.getLibraryByPath(lib.download.path);
|
2667 | const issue = await diagnoseFile({
|
2668 | role: "library",
|
2669 | file: libPath,
|
2670 | expectedChecksum: lib.download.sha1,
|
2671 | hint: "Problem on install_profile! Please consider to use Installer.installByProfile to fix."
|
2672 | });
|
2673 | if (issue) {
|
2674 | issues.push(Object.assign(issue, { library: lib }));
|
2675 | }
|
2676 | }));
|
2677 | for (const proc of processors) {
|
2678 | if (proc.outputs) {
|
2679 | for (const file in proc.outputs) {
|
2680 | const issue = await diagnoseFile({
|
2681 | role: "processor",
|
2682 | file,
|
2683 | expectedChecksum: proc.outputs[file].replace(/'/g, ""),
|
2684 | hint: "Re-install this installer profile!"
|
2685 | });
|
2686 | if (issue) {
|
2687 | issues.push(Object.assign(issue, { processor: proc }));
|
2688 | }
|
2689 | }
|
2690 | }
|
2691 | }
|
2692 | return report;
|
2693 | }
|
2694 |
|
2695 | export { BadCurseforgeModpackError, BadForgeInstallerJarError, BadOptifineJarError, ChecksumNotMatchError, ChecksumValidator, DEFAULT_FABRIC_API, DEFAULT_FORGE_MAVEN, DEFAULT_RESOURCE_ROOT_URL, DEFAULT_RUNTIME_ALL_URL, DEFAULT_VERSION_MANIFEST, DEFAULT_VERSION_MANIFEST_URL, DefaultSegmentPolicy, Download, DownloadForgeInstallerTask, DownloadJRETask, DownloadTask, FetchMetadataError, InstallAssetIndexTask, InstallAssetTask, InstallJarTask, InstallJsonTask, InstallLibraryTask, JavaRuntimeTargetType, JsonValidator, LOADER_MAVEN_URL, LiteloaderVersionList, MissingVersionJsonError, PostProcessBadJarError, PostProcessFailedError, PostProcessNoMainClassError, PostProcessingTask, UnzipTask, ValidationError, YARN_MAVEN_URL, ZipValidator, createAgents, createDefaultCurseforgeQuery, createDownload, createRetryHandler, createStatusController, diagnoseInstall, download, fetchJavaRuntimeManifest, fetchJson, fetchText, generateOptifineVersion, getAndParseIfUpdate, getDefaultEntryResolver, getFabricArtifacts, getFabricLoaderArtifact, getForgeVersionList, getIfUpdate, getLastModified, getLiteloaderVersionList, getLoaderArtifactList, getLoaderArtifactListFor, getLoaderVersionListFromXML, getMetadata, getPotentialJavaLocations, getVersionList, getYarnArtifactList, getYarnArtifactListFor, getYarnVersionListFromXML, install, installAssets, installAssetsTask, installByProfile, installByProfileTask, installCurseforgeFile, installCurseforgeFileTask, installCurseforgeModpack, installCurseforgeModpackTask, installDependencies, installDependenciesTask, installFabric, installFabricYarnAndLoader, installForge, installForgeTask, installJavaRuntimesTask, installJreFromMojang, installJreFromMojangTask, installLibraries, installLibrariesTask, installLiteloader, installLiteloaderTask, installOptifine, installOptifineTask, installResolvedAssetsTask, installResolvedLibraries, installResolvedLibrariesTask, installTask, installVersion, installVersionTask, isAgents, isForgeInstallerEntries, isLegacyForgeInstallerEntries, isRetryHandler, isSegmentPolicy, isValidator, parseJavaVersion, postProcess, readManifest, readManifestTask, resolveAbortSignal, resolveAgents, resolveJava, resolveLibraryDownloadUrls, resolveProcessors, resolveRetryHandler, resolveSegmentPolicy, resolveStatusController, resolveValidator, scanLocalJava, walkForgeInstallerEntries, withAgents };
|
2696 |
|