UNPKG

108 kBJavaScriptView Raw
1import { _exists, _mkdir, MinecraftFolder, _writeFile, _readFile, checksum, _pipeline, Version, LibraryInfo, getPlatform, diagnoseFile } from '@xmcl/core';
2import 'crypto';
3import { request, Agent } from 'http';
4import { 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';
5import { request as request$1, Agent as Agent$1 } from 'https';
6import { URL, fileURLToPath } from 'url';
7import { spawn, exec } from 'child_process';
8import { dirname, join, delimiter, basename, resolve } from 'path';
9import { promisify } from 'util';
10import { task, AbortableTask, CancelledError, BaseTask, TaskState } from '@xmcl/task';
11import { parse } from '@xmcl/forge-site-parser';
12import { open as open$2, walkEntriesGenerator, readEntry, filterEntries, openEntryReadStream, readAllEntries, getEntriesRecord } from '@xmcl/unzip';
13import { O_RDWR, O_CREAT } from 'constants';
14import { cpus, tmpdir, platform, EOL } from 'os';
15import { ClassReader, Opcodes, ClassVisitor } from '@xmcl/asm';
16
17const unlink = promisify(unlink$1);
18const stat = promisify(stat$1);
19const link = promisify(link$1);
20const open = promisify(open$1);
21const close = promisify(close$1);
22const copyFile = promisify(copyFile$1);
23const truncate = promisify(ftruncate);
24function missing(target) {
25 return _exists(target).then((v) => !v);
26}
27async 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}
58function ensureFile(target) {
59 return ensureDir(dirname(target));
60}
61function normalizeArray(arr = []) {
62 return arr instanceof Array ? arr : [arr];
63}
64function 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}
69function 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}
96function errorToString(e) {
97 if (e instanceof Error) {
98 return e.stack ? e.stack : e.message;
99 }
100 return e.toString();
101}
102
103function isValidProtocol(protocol) {
104 return protocol === "http:" || protocol === "https:";
105}
106function 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}
115function format(url) {
116 return `${url.protocol}//${url.host}${url.path}`;
117}
118function 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}
127function 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 * Join two urls
155 */
156function 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
166async 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}
186async function fetchJson(url, agent) {
187 return JSON.parse(await fetchText(url, agent));
188}
189async 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}
223async 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; // this cannot be undefined as the content be null only and only if the lastObject is presented.
229}
230async 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
254const YARN_MAVEN_URL = "https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml";
255const LOADER_MAVEN_URL = "https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml";
256const DEFAULT_FABRIC_API = "https://meta.fabricmc.net/v2";
257/**
258 * Get all the artifacts provided by fabric
259 * @param remote The fabric API host
260 * @beta
261 */
262function getFabricArtifacts(remote = DEFAULT_FABRIC_API) {
263 return fetchJson(remote + "/versions");
264}
265/**
266 * Get fabric-yarn artifact list
267 * @param remote The fabric API host
268 * @beta
269 */
270function getYarnArtifactList(remote = DEFAULT_FABRIC_API) {
271 return fetchJson(remote + "/versions/yarn");
272}
273/**
274 * Get fabric-yarn artifact list by Minecraft version
275 * @param minecraft The Minecraft version
276 * @param remote The fabric API host
277 * @beta
278 */
279function getYarnArtifactListFor(minecraft, remote = DEFAULT_FABRIC_API) {
280 return fetchJson(remote + "/versions/yarn/" + minecraft);
281}
282/**
283 * Get fabric-loader artifact list
284 * @param remote The fabric API host
285 * @beta
286 */
287function getLoaderArtifactList(remote = DEFAULT_FABRIC_API) {
288 return fetchJson(remote + "/versions/loader");
289}
290/**
291 * Get fabric-loader artifact list by Minecraft version
292 * @param minecraft The minecraft version
293 * @param remote The fabric API host
294 * @beta
295 */
296function getLoaderArtifactListFor(minecraft, remote = DEFAULT_FABRIC_API) {
297 return fetchJson(remote + "/versions/loader/" + minecraft);
298}
299/**
300 * Get fabric-loader artifact list by Minecraft version
301 * @param minecraft The minecraft version
302 * @param loader The yarn-loader version
303 * @param remote The fabric API host
304 * @beta
305 */
306function getFabricLoaderArtifact(minecraft, loader, remote = DEFAULT_FABRIC_API) {
307 return fetchJson(remote + "/versions/loader/" + minecraft + "/" + loader);
308}
309/**
310 * Get or refresh the yarn version list.
311 * @beta
312 */
313async 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 * Get or refresh the fabric mod loader version list.
327 * @beta
328 */
329async 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 * Install the fabric to the client. Notice that this will only install the json.
343 * You need to call `Installer.installDependencies` to get a full client.
344 * @param yarnVersion The yarn version
345 * @param loaderVersion The fabric loader version
346 * @param minecraft The minecraft location
347 * @returns The installed version id
348 */
349async 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 * Generate fabric version json to the disk according to yarn and loader
365 * @param side Client or server
366 * @param yarnVersion The yarn version string or artifact
367 * @param loader The loader artifact
368 * @param minecraft The Minecraft Location
369 * @param options The options
370 * @beta
371 */
372async 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
429const DEFAULT_VERSION_MANIFEST = "http://dl.liteloader.com/versions/versions.json";
430function 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}
438var 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; // that's right, artefact
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 = {}));
483const snapshotRoot = "http://dl.liteloader.com/versions/";
484const releaseRoot = "http://repo.mumfrey.com/content/repositories/liteloader/";
485/**
486 * This error is only thrown from liteloader install currently.
487 */
488class 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 * Get or update the LiteLoader version list.
502 *
503 * This will request liteloader offical json by default. You can replace the request by assigning the remote option.
504 */
505function getLiteloaderVersionList(option = {}) {
506 return getAndParseIfUpdate(option.remote || DEFAULT_VERSION_MANIFEST, LiteloaderVersionList.parse, option.original);
507}
508/**
509 * Install the liteloader to specific minecraft location.
510 *
511 * This will install the liteloader amount on the corresponded Minecraft version by default.
512 * If you want to install over the forge. You should first install forge and pass the installed forge version id to the third param,
513 * like `1.12-forge-xxxx`
514 *
515 * @param versionMeta The liteloader version metadata.
516 * @param location The minecraft location you want to install
517 * @param version The real existed version id (under the the provided minecraft location) you want to installed liteloader inherit
518 * @throws {@link MissingVersionJsonError}
519 */
520function installLiteloader(versionMeta, location, options) {
521 return installLiteloaderTask(versionMeta, location, options).startAndWait();
522}
523function 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 // liteloader not supported for version > 1.12...
543 // just write this for exception
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 * Install the liteloader to specific minecraft location.
556 *
557 * This will install the liteloader amount on the corresponded Minecraft version by default.
558 * If you want to install over the forge. You should first install forge and pass the installed forge version id to the third param,
559 * like `1.12-forge-xxxx`
560 *
561 * @tasks installLiteloader, installLiteloader.resolveVersionJson installLiteloader.generateLiteloaderJson
562 *
563 * @param versionMeta The liteloader version metadata.
564 * @param location The minecraft location you want to install
565 * @param version The real existed version id (under the the provided minecraft location) you want to installed liteloader inherit
566 */
567function 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
590function 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
601function isAgents(agents) {
602 if (!agents) {
603 return false;
604 }
605 return "http" in agents || "https" in agents;
606}
607function resolveAgents(agents) {
608 if (isAgents(agents)) {
609 return agents;
610 }
611 return createAgents(agents);
612}
613/**
614 * Default create agents object
615 */
616function 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}
631async 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 * Download
651 */
652class 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}
663function 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 * A simple util function to determine if this is a common network condition error.
679 * @param e Error object
680 */
681function 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
691class 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}
699async 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
732function isRetryHandler(options) {
733 if (!options) {
734 return false;
735 }
736 return "retry" in options && typeof options.retry === "function";
737}
738function 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 * Create a simple count based retry handler
747 * @param maxRetryCount The max count we should try
748 * @param shouldRetry Should the error be retry
749 */
750function createRetryHandler(maxRetryCount, shouldRetry) {
751 const handler = {
752 retry(url, attempt, error) {
753 return shouldRetry(error) && attempt < maxRetryCount;
754 }
755 };
756 return handler;
757}
758
759function isSegmentPolicy(segmentOptions) {
760 if (!segmentOptions) {
761 return false;
762 }
763 return "computeSegments" in segmentOptions && typeof segmentOptions.computeSegments === "function";
764}
765function 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}
772class 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
802function 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}
813function resolveStatusController(controller) {
814 if (!controller) {
815 return createStatusController();
816 }
817 return controller;
818}
819
820class 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}
834function isValidator(options) {
835 if (!options) {
836 return false;
837 }
838 return "validate" in options && typeof options.validate === "function";
839}
840function 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}
849class 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}
860class 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}
884class ValidationError extends Error {
885 constructor(error, message) {
886 super(message);
887 this.error = error;
888 }
889}
890class 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// @ts-ignore
902const pfstat = promisify(fstat);
903const pfdatasync = promisify(fdatasync);
904class AbortError extends Error {
905}
906/**
907 * Download url or urls to a file path. This process is abortable, it's compatible with the dom like `AbortSignal`.
908 */
909function download(options) {
910 const worker = createDownload(options);
911 return worker.start();
912}
913function 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}
917class 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 * current fd
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 // the segment is finished, just ignore it
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 // ensure we correctly release the message
1001 response.resume();
1002 throw new AbortError();
1003 }
1004 const fileStream = createWriteStream(this.destination, {
1005 fd: this.fd,
1006 start: segment.start,
1007 // we should not close the file stream, as it will close the fd as the same time!
1008 autoClose: false,
1009 });
1010 // track the progress
1011 response.on("data", (chunk) => {
1012 segment.start += chunk.length;
1013 this.statusController.onProgress(chunk.length, this.statusController.progress + chunk.length);
1014 });
1015 // create abort handler
1016 const abortHandler = () => {
1017 request.destroy(new AbortError());
1018 response.unpipe();
1019 };
1020 abortHandlers.push(abortHandler);
1021 // add abort handler to abort signal
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 // user abort the operation, or abort by other sibling error
1029 if (flag === 0) {
1030 flag = 1;
1031 }
1032 }
1033 else {
1034 // true error thrown.
1035 flag = 2;
1036 // all other sibling requests should be aborted
1037 abortHandlers.forEach((f) => f());
1038 errors.push(e);
1039 }
1040 }
1041 }));
1042 // use local aborted flag instead of signal.aborted
1043 // as local aborted flag means the request is TRUELY aborted
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 // properly handle the file protocol. we just copy the file
1055 // luckly, opening file won't affect file copy 😀
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 // user abort should throw anyway
1069 if (e instanceof DownloadError && e.error === "DownloadAborted") {
1070 throw e;
1071 }
1072 // some common error we want to retry
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 * Start to download
1086 */
1087 async start(abortSignal = resolveAbortSignal()) {
1088 try {
1089 if (this.fd === -1) {
1090 await ensureFile(this.destination);
1091 // use O_RDWR for read write which won't be truncated
1092 this.fd = await open(this.destination, O_RDWR | O_CREAT);
1093 }
1094 // prevalidate the file
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 // if the file size is not 0 and checksum matched, we just don't process the file
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
1141class 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 // noop as this will be auto gc
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 * Default minecraft version manifest url.
1191 */
1192const DEFAULT_VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
1193/**
1194 * Default resource/assets url root
1195 */
1196const DEFAULT_RESOURCE_ROOT_URL = "https://resources.download.minecraft.net";
1197/**
1198 * Get and update the version list.
1199 * This try to send http GET request to offical Minecraft metadata endpoint by default.
1200 * You can swap the endpoint by passing url on `remote` in option.
1201 *
1202 * @returns The new list if there is
1203 */
1204function getVersionList(option = {}) {
1205 return getAndParseIfUpdate(option.remote || DEFAULT_VERSION_MANIFEST_URL, JSON.parse, option.original);
1206}
1207function 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 * Install the Minecraft game to a location by version metadata.
1219 *
1220 * This will install version json, version jar, and all dependencies (assets, libraries)
1221 *
1222 * @param versionMeta The version metadata
1223 * @param minecraft The Minecraft location
1224 * @param option
1225 */
1226async function install(versionMeta, minecraft, option = {}) {
1227 return installTask(versionMeta, minecraft, option).startAndWait();
1228}
1229/**
1230 * Only install the json/jar. Do not install dependencies.
1231 *
1232 * @param versionMeta the version metadata; get from updateVersionMeta
1233 * @param minecraft minecraft location
1234 */
1235function installVersion(versionMeta, minecraft, options = {}) {
1236 return installVersionTask(versionMeta, minecraft, options).startAndWait();
1237}
1238/**
1239 * Install the completeness of the Minecraft game assets and libraries on a existed version.
1240 *
1241 * @param version The resolved version produced by Version.parse
1242 * @param minecraft The minecraft location
1243 */
1244function installDependencies(version, options) {
1245 return installDependenciesTask(version, options).startAndWait();
1246}
1247/**
1248 * Install or check the assets to resolved version
1249 *
1250 * @param version The target version
1251 * @param options The option to replace assets host url
1252 */
1253function installAssets(version, options = {}) {
1254 return installAssetsTask(version, options).startAndWait();
1255}
1256/**
1257 * Install all the libraries of providing version
1258 * @param version The target version
1259 * @param options The library host swap option
1260 */
1261function installLibraries(version, options = {}) {
1262 return installLibrariesTask(version, options).startAndWait();
1263}
1264/**
1265 * Only install several resolved libraries
1266 * @param libraries The resolved libraries
1267 * @param minecraft The minecraft location
1268 * @param option The install option
1269 */
1270async function installResolvedLibraries(libraries, minecraft, option) {
1271 await installLibrariesTask({ libraries, minecraftDirectory: typeof minecraft === "string" ? minecraft : minecraft.root }, option).startAndWait();
1272}
1273/**
1274 * Install the Minecraft game to a location by version metadata.
1275 *
1276 * This will install version json, version jar, and all dependencies (assets, libraries)
1277 *
1278 * @param type The type of game, client or server
1279 * @param versionMeta The version metadata
1280 * @param minecraft The Minecraft location
1281 * @param options
1282 */
1283function 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 * Only install the json/jar. Do not install dependencies.
1296 *
1297 * @param type client or server
1298 * @param versionMeta the version metadata; get from updateVersionMeta
1299 * @param minecraft minecraft location
1300 */
1301function 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 * Install the completeness of the Minecraft game assets and libraries on a existed version.
1313 *
1314 * @param version The resolved version produced by Version.parse
1315 * @param minecraft The minecraft location
1316 */
1317function 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 * Install or check the assets to resolved version
1328 *
1329 * @param version The target version
1330 * @param options The option to replace assets host url
1331 */
1332function 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 // let sizes = objectArray.map((a) => a.size).map((a, b) => a + b, 0);
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 * Install all the libraries of providing version
1353 * @param version The target version
1354 * @param options The library host swap option
1355 */
1356function 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 * Only install several resolved libraries
1370 * @param libraries The resolved libraries
1371 * @param minecraft The minecraft location
1372 * @param option The install option
1373 */
1374function installResolvedLibrariesTask(libraries, minecraft, option) {
1375 return installLibrariesTask({ libraries, minecraftDirectory: typeof minecraft === "string" ? minecraft : minecraft.root }, option);
1376}
1377/**
1378 * Only install several resolved assets.
1379 * @param assets The assets to install
1380 * @param folder The minecraft folder
1381 * @param options The asset option
1382 */
1383function installResolvedAssetsTask(assets, folder, options = {}) {
1384 return task("assets", async function () {
1385 await ensureDir(folder.getPath("assets", "objects"));
1386 // const sizes = assets.map((a) => a.size).map((a, b) => a + b, 0);
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}
1393class 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}
1411class 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}
1431class 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}
1450class 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}
1470class 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}
1494const DEFAULT_MAVENS = ["https://repo1.maven.org/maven2/"];
1495/**
1496 * Resolve a library download urls with fallback.
1497 *
1498 * @param library The resolved library
1499 * @param libraryOptions The library install options
1500 */
1501function 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 // user defined alternative host to download
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 * Resolve processors in install profile
1515 */
1516function resolveProcessors(side, installProfile, minecraft) {
1517 function normalizePath(val) {
1518 if (val && val.match(/^\[.+\]$/g)) { // match sth like [net.minecraft:client:1.15.2:slim]
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)) { // match sth like {MAPPINGS}
1526 const key = val.substring(1, val.length - 1);
1527 return variables[key][side];
1528 }
1529 return val;
1530 }
1531 // store the mapping of {VARIABLE_NAME} -> real path in disk
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 * Post process the post processors from `InstallProfile`.
1568 *
1569 * @param processors The processor info
1570 * @param minecraft The minecraft location
1571 * @param java The java executable path
1572 * @throws {@link PostProcessError}
1573 */
1574function postProcess(processors, minecraft, javaOptions) {
1575 return new PostProcessingTask(processors, minecraft, javaOptions).startAndWait();
1576}
1577/**
1578 * Install by install profile. The install profile usually contains some preprocess should run before installing dependencies.
1579 *
1580 * @param installProfile The install profile
1581 * @param minecraft The minecraft location
1582 * @param options The options to install
1583 * @throws {@link PostProcessError}
1584 */
1585function installByProfile(installProfile, minecraft, options = {}) {
1586 return installByProfileTask(installProfile, minecraft, options).startAndWait();
1587}
1588/**
1589 * Install by install profile. The install profile usually contains some preprocess should run before installing dependencies.
1590 *
1591 * @param installProfile The install profile
1592 * @param minecraft The minecraft location
1593 * @param options The options to install
1594 */
1595function 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}
1605class 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}
1613class 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}
1620class 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 * Post process the post processors from `InstallProfile`.
1630 *
1631 * @param processors The processor info
1632 * @param minecraft The minecraft location
1633 * @param java The java executable path
1634 * @throws {@link PostProcessError}
1635 */
1636class 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 // this.activeProcess?.kill()
1727 }
1728 isAbortedError(e) {
1729 return e === "PAUSED";
1730 }
1731}
1732
1733const DEFAULT_FORGE_MAVEN = "http://files.minecraftforge.net/maven";
1734class 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}
1768function getLibraryPathWithoutMaven(mc, name) {
1769 // remove the maven/ prefix
1770 return mc.getLibraryByPath(name.substring(name.indexOf("/") + 1));
1771}
1772function extractEntryTo(zip, e, dest) {
1773 return openEntryReadStream(zip, e).then((stream) => _pipeline(stream, createWriteStream(dest)));
1774}
1775async function installLegacyForgeFromZip(zip, entries, profile, mc, options) {
1776 const versionJson = profile.versionInfo;
1777 // apply override for inheritsFrom
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 * Unpack forge installer jar file content to the version library artifact directory.
1792 * @param zip The forge jar file
1793 * @param entries The entries
1794 * @param forgeVersion The expected version of forge
1795 * @param profile The forge install profile
1796 * @param mc The minecraft location
1797 * @returns The installed version id
1798 */
1799async 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 // apply override for inheritsFrom
1802 versionJson.id = options.versionId || versionJson.id;
1803 versionJson.inheritsFrom = options.inheritsFrom || versionJson.inheritsFrom;
1804 // resolve all the required paths
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 // forge version and mavens, compatible with twitch api
1827 const serverMaven = `net.minecraftforge:forge:${forgeVersion}:serverdata@lzma`;
1828 // override forge bin patch location
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 // forge version and mavens, compatible with twitch api
1836 const clientMaven = `net.minecraftforge:forge:${forgeVersion}:clientdata@lzma`;
1837 // override forge bin patch location
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}
1865function isLegacyForgeInstallerEntries(entries) {
1866 return !!entries.legacyUniversalJar && !!entries.installProfileJson;
1867}
1868function isForgeInstallerEntries(entries) {
1869 return !!entries.installProfileJson && !!entries.versionJson;
1870}
1871/**
1872 * Walk the forge installer file to find key entries
1873 * @param zip THe forge instal
1874 * @param forgeVersion Forge version to install
1875 */
1876async 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}
1902class 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}
1914function 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 // new forge
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 // legacy forge
1943 return installLegacyForgeFromZip(zip, entries, profile, mc, options);
1944 }
1945 else {
1946 // bad forge
1947 throw new BadForgeInstallerJarError(jarPath);
1948 }
1949 });
1950 });
1951}
1952/**
1953 * Install forge to target location.
1954 * Installation task for forge with mcversion >= 1.13 requires java installed on your pc.
1955 * @param version The forge version meta
1956 * @returns The installed version name.
1957 * @throws {@link BadForgeInstallerJarError}
1958 */
1959function installForge(version, minecraft, options) {
1960 return installForgeTask(version, minecraft, options).startAndWait();
1961}
1962/**
1963 * Install forge to target location.
1964 * Installation task for forge with mcversion >= 1.13 requires java installed on your pc.
1965 * @param version The forge version meta
1966 * @returns The task to install the forge
1967 * @throws {@link BadForgeInstallerJarError}
1968 */
1969function installForgeTask(version, minecraft, options = {}) {
1970 return installByInstallerTask(version, minecraft, options);
1971}
1972/**
1973 * Query the webpage content from files.minecraftforge.net.
1974 *
1975 * You can put the last query result to the fallback option. It will check if your old result is up-to-date.
1976 * It will request a new page only when the fallback option is outdated.
1977 *
1978 * @param option The option can control querying minecraft version, and page caching.
1979 */
1980async 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
1986function getDefaultEntryResolver() {
1987 return (e) => e.fileName;
1988}
1989class 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
2067class 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 * Read the mainifest data from modpack
2081 * @throws {@link BadCurseforgeModpackError}
2082 */
2083function 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 * Read the mainifest data from modpack
2097 * @throws {@link BadCurseforgeModpackError}
2098 */
2099function readManifest(zip) {
2100 return readManifestTask(zip).startAndWait();
2101}
2102function 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 * Install curseforge modpack to a specific Minecraft location.
2108 *
2109 * @param zip The curseforge modpack zip buffer or file path
2110 * @param minecraft The minecraft location
2111 * @param options The options for query curseforge
2112 */
2113function installCurseforgeModpack(zip, minecraft, options) {
2114 return installCurseforgeModpackTask(zip, minecraft, options).startAndWait();
2115}
2116async 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 * Install curseforge modpack to a specific Minecraft location.
2127 *
2128 * This will NOT install the Minecraft version in the modpack, and will NOT install the forge or other modload listed in modpack!
2129 * Please resolve them by yourself.
2130 *
2131 * @param input The curseforge modpack zip buffer or file path
2132 * @param minecraft The minecraft location
2133 * @param options The options for query curseforge
2134 * @throws {@link BadCurseforgeModpackError}
2135 */
2136function 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 * Install a cureseforge xml file to a specific locations
2168 */
2169function installCurseforgeFile(file, destination, options) {
2170 return installCurseforgeFileTask(file, destination, options).startAndWait();
2171}
2172/**
2173 * Install a cureseforge xml file to a specific locations
2174 */
2175function 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 * Generate the optifine version json from provided info.
2191 * @param editionRelease The edition + release with _
2192 * @param minecraftVersion The minecraft version
2193 * @param launchWrapperVersion The launch wrapper version
2194 * @param options The install options
2195 * @beta Might be changed and don't break the major version
2196 */
2197function 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 * Install optifine by optifine installer
2226 *
2227 * @param installer The installer jar file path
2228 * @param minecraft The minecraft location
2229 * @param options The option to install
2230 * @beta Might be changed and don't break the major version
2231 * @throws {@link BadOptifineJarError}
2232 */
2233function installOptifine(installer, minecraft, options) {
2234 return installOptifineTask(installer, minecraft, options).startAndWait();
2235}
2236class 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 * Install optifine by optifine installer task
2250 *
2251 * @param installer The installer jar file path
2252 * @param minecraft The minecraft location
2253 * @param options The option to install
2254 * @beta Might be changed and don't break the major version
2255 * @throws {@link BadOptifineJarError}
2256 */
2257function installOptifineTask(installer, minecraft, options = {}) {
2258 return task("installOptifine", async function () {
2259 var _a, _b;
2260 let mc = MinecraftFolder.from(minecraft);
2261 // context.update(0, 100);
2262 const zip = await open$2(installer);
2263 const entries = await readAllEntries(zip);
2264 const record = getEntriesRecord(entries);
2265 // context.update(10, 100);
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 // context.update(15, 100);
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; // 1.14.4
2289 const edition = visitor.fields.OF_EDITION; // HD_U
2290 const release = visitor.fields.OF_RELEASE; // F5
2291 const editionRelease = edition + "_" + release;
2292 const versionJSON = generateOptifineVersion(editionRelease, mcversion, launchWrapperVersion, options);
2293 const versionJSONPath = mc.getVersionJson(versionJSON.id);
2294 // context.update(20, 100);
2295 // write version json
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 // write launch wrapper
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 // write the optifine
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
2320class 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 * Install JRE from Mojang offical resource. It should install jdk 8.
2342 * @param options The install options
2343 */
2344function 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 * Install JRE from Mojang offical resource. It should install jdk 8.
2375 * @param options The install options
2376 */
2377function installJreFromMojang(options) {
2378 return installJreFromMojangTask(options).startAndWait();
2379}
2380/**
2381 * Try to resolve a java info at this path. This will call `java -version`
2382 * @param path The java exectuable path.
2383 */
2384async 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 * Parse version string and major version number from stderr of java process.
2407 *
2408 * @param versionText The stderr for `java -version`
2409 */
2410function 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 * Get all potential java locations for Minecraft.
2437 *
2438 * On mac/linux, it will perform `which java`. On win32, it will perform `where java`
2439 *
2440 * @returns The absolute java locations path
2441 */
2442async 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 * Scan local java version on the disk.
2488 *
2489 * It will check if the passed `locations` are the home of java.
2490 * Notice that the locations should not be the executable, but the path of java installation, like JAVA_HOME.
2491 *
2492 * This will call `getPotentialJavaLocations` and then `resolveJava`
2493 *
2494 * @param locations The location (like java_home) want to check.
2495 * @returns All validate java info
2496 */
2497async 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
2506var JavaRuntimeTargetType;
2507(function (JavaRuntimeTargetType) {
2508 /**
2509 * The legacy java version
2510 */
2511 JavaRuntimeTargetType["Legacy"] = "jre-legacy";
2512 /**
2513 * The new java environment, which is the java 16
2514 */
2515 JavaRuntimeTargetType["Alpha"] = "java-runtime-alpha";
2516 JavaRuntimeTargetType["Beta"] = "java-runtime-beta";
2517 JavaRuntimeTargetType["JavaExe"] = "minecraft-java-exe";
2518})(JavaRuntimeTargetType || (JavaRuntimeTargetType = {}));
2519const DEFAULT_RUNTIME_ALL_URL = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
2520function 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 * Fetch java runtime manifest. It should be able to resolve to your platform, or you can assign the platform.
2537 *
2538 * Also, you should assign the target to download, or it will use the latest java 16.
2539 * @param options The options of fetch runtime manifest
2540 */
2541async 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 * Install java runtime from java runtime manifest
2586 * @param options The options to install java runtime
2587 */
2588function 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 * Diagnose a install profile status. Check if it processor output correctly processed.
2649 *
2650 * This can be used for check if forge correctly installed when minecraft >= 1.13
2651 * @beta
2652 *
2653 * @param installProfile The install profile.
2654 * @param minecraftLocation The minecraft location
2655 */
2656async 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
2695export { 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//# sourceMappingURL=index.esm.js.map