gmll
Version:
A generic launcher core for building custom launchers
653 lines (652 loc) • 26.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeMRF = exports.getAgentFile = exports.manifests = exports.getForgiac = exports.getRuntimeIndexes = exports.libraries = exports.assets = exports.mojangRFDownloader = exports.runtime = exports.download = void 0;
const tslib_1 = require("tslib");
const fs_1 = require("fs");
const gfsl_1 = require("gfsl");
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
const os_1 = require("os");
const path_1 = require("path");
const config_js_1 = require("./config.js");
const downloadable_js_1 = require("./internal/downloadable.js");
const util_js_1 = require("./internal/util.js");
const assetURL = "https://resources.download.minecraft.net/";
const processCMD = "download.progress";
const failCMD = "download.fail";
const getCMD = "download.get";
const postCMD = "download.post";
/**
* Download function. Can be used for downloading modpacks and launcher updates.
* Checks sha1 hashes and can use multiple cores to download files rapidly.
* Untested on Intel's new CPUs, use at own risk and report to me if it breaks.
*
* -Hanro50
*
* @param obj The objects that will be downloaded
*/
function download(obj) {
(0, config_js_1.emit)("download.started");
obj.sort((a, b) => {
return (b.chk.size || Number.MAX_VALUE) - (a.chk.size || Number.MAX_VALUE);
});
const temp = {};
const unzip = {};
obj.forEach((e) => {
switch ((0, downloadable_js_1.check)(e)) {
case 0:
temp[e.key] = e;
break;
case 1:
unzip[e.key] = e;
break;
}
});
const totalItems = Object.values(temp).length;
const _zips = [];
Object.values(unzip).forEach((json) => {
_zips.push((0, downloadable_js_1.expand)(new gfsl_1.File(...json.path, json.name), json, (0, config_js_1.getMeta)().bin));
});
if (totalItems <= 0) {
console.log(totalItems);
(0, config_js_1.emit)("download.done");
return new Promise((e) => Promise.all(_zips).then(() => e()));
}
return new Promise(async (res) => {
await Promise.all(_zips);
const numCPUs = Math.max((0, os_1.cpus)().length, 2);
const multiCoreMode = (0, config_js_1.getMultiCoreMode)();
let done = 0;
if (multiCoreMode) {
let todo = 0;
const workers = [];
const fire = () => workers.forEach((w) => w.terminate());
try {
(0, config_js_1.emit)("download.setup", numCPUs);
const data = Object.values(temp);
for (let i3 = 0; i3 < numCPUs; i3++) {
const w = await (0, config_js_1.spawnDownloadWorker)({
workerData: {
processCMD,
failCMD,
getCMD,
postCMD,
zipDir: (0, config_js_1.getMeta)().bin.path,
},
});
workers.push(w);
w.on("message", (msg) => {
switch (msg.cmd) {
case processCMD: {
done++;
delete temp[msg.key];
const left = Object.values(temp).length;
(0, config_js_1.emit)("download.progress", msg.key, done, totalItems, left);
if (left < 1) {
// active = false;
// clearTimeout(to);
(0, config_js_1.emit)("download.done");
fire();
return res();
}
}
case getCMD:
w.postMessage({
cmd: postCMD,
data: data[todo],
});
todo++;
break;
case failCMD:
(0, config_js_1.emit)(msg.cmd, msg.key, msg.type, msg.err);
break;
default:
return;
}
});
}
return;
}
catch (e) {
(0, config_js_1.emit)("debug.error", e);
fire();
}
}
if (multiCoreMode)
(0, config_js_1.emit)("debug.warn", "The main downloader encountered an error, using single threaded fallback!");
(0, config_js_1.emit)("download.setup", 1);
const data = Object.values(temp);
const fallback = async (o, retry = 0) => {
try {
await (0, downloadable_js_1.processFile)(o, (0, config_js_1.getMeta)().bin);
return o;
}
catch (e) {
console.trace(e);
if (retry <= 3) {
retry++;
(0, config_js_1.emit)(failCMD, o.key, "retry", e.err);
await fallback(o, retry);
return o;
}
(0, config_js_1.emit)("debug.error", "procedural failure : " + new gfsl_1.Dir(...o.path));
(0, config_js_1.emit)(failCMD, o.key, "system", e.err);
}
return o;
};
const lf = async (o) => {
if (!o)
return;
done++;
delete temp[o.key];
const left = Object.values(temp).length;
(0, config_js_1.emit)("download.progress", o.key, done, totalItems, left);
};
for (let i3 = 0; i3 < data.length; i3 += 100) {
const lst = [];
for (let i2 = 0; i2 < 100 && i3 + i2 < data.length; i2++)
lst.push(fallback(data[i3 + i2])
.then(lf)
.catch(console.trace));
await Promise.all(lst);
}
(0, config_js_1.emit)("download.done");
return res();
});
}
exports.download = download;
/**
* Installs a set version of Java locally.
* @param runtime the name of the Java runtime. Based on the names Mojang gave them.
* @returns This is an async function!
*/
function runtime(runtime) {
const meta = (0, config_js_1.getMeta)();
const cFile = meta.runtimes.getFile(runtime + ".json");
if (!cFile.exists()) {
(0, util_js_1.throwErr)("Cannot find runtime");
}
return mojangRFDownloader(cFile.toJSON(), (0, config_js_1.getRuntimes)().getDir(runtime));
}
exports.runtime = runtime;
/**
* Did you know you can use this file to download dungeons?
* (We prefer not to be sued...so no more details then that)
*
* This can decode Mojang's resource file format and download it.
* @see {@link encodeMRF} if you want to encode a file into this crazy format.
*/
function mojangRFDownloader(file, baseFile, lzma) {
if (!lzma)
lzma = (0, config_js_1.getMeta)().lzma;
lzma.mkdir();
const json = file.files;
const arr = [];
Object.keys(json).forEach((key) => {
const obj = json[key];
let _file = baseFile.getFile(...key.split("/"));
const _dir = baseFile.getDir(...key.split("/"));
const name = key.split("/").pop();
switch (obj.type) {
case "directory":
_dir.mkdir();
break;
case "file":
let chk, url;
const opt = { executable: obj.executable };
if (obj.downloads.lzma) {
opt.unzip = { file: _file.dir() };
opt.executable = _file.javaPath();
const downLoc = (0, util_js_1.assetTag)(lzma, obj.downloads.lzma.sha1);
_file = downLoc.getFile(obj.downloads.lzma.sha1, name + ".xz");
_file.mkdir();
url = obj.downloads.lzma.url;
chk = {
size: obj.downloads.lzma.size,
sha1: obj.downloads.lzma.sha1,
};
}
else {
url = obj.downloads.raw.url;
chk = {
size: obj.downloads.raw.size,
sha1: obj.downloads.raw.sha1,
};
}
arr.push((0, downloadable_js_1.toDownloadable)(_file, url, key, chk, opt));
break;
case "link":
_file.mkdir();
if ((0, util_js_1.getOS)() != "windows") {
_file.rm();
_file.linkTo((0, path_1.resolve)(..._file.path, obj.target));
}
}
});
return download(arr);
}
exports.mojangRFDownloader = mojangRFDownloader;
/**Install a set version's assets based on a provided asset index from that version.
* It is best to let GMLL handle this for you. See the version object instead.
*/
async function assets(index) {
const root = (0, config_js_1.getAssets)();
const indexes = root.getDir("indexes").mkdir();
const file = indexes.getFile(index.id + ".json");
const assetIndex = (await file.download(index.url, { sha1: index.sha1, size: index.size })).toJSON();
const downloader = [];
const getURL = (obj) => assetURL + obj.hash.substring(0, 2) + "/" + obj.hash;
if (assetIndex.map_to_resources) {
const addIn = (path, sck) => {
if (!assetIndex[path]) {
assetIndex.objects[path] = sck;
}
};
addIn("icons/icon_16x16.png", {
hash: "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
size: 3665,
});
addIn("icons/icon_32x32.png", {
hash: "92750c5f93c312ba9ab413d546f32190c56d6f1f",
size: 5362,
});
addIn("icons/minecraft.icns", {
hash: "991b421dfd401f115241601b2b373140a8d78572",
size: 114786,
});
addIn("minecraft/icons/icon_16x16.png", {
hash: "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
size: 3665,
});
addIn("minecraft/icons/icon_32x32.png", {
hash: "92750c5f93c312ba9ab413d546f32190c56d6f1f",
size: 5362,
});
addIn("minecraft/icons/minecraft.icns", {
hash: "991b421dfd401f115241601b2b373140a8d78572",
size: 114786,
});
}
Object.entries(assetIndex.objects).forEach((o) => {
const key = o[0];
const obj = o[1];
if (!obj.ignore)
downloader.push((0, downloadable_js_1.toDownloadable)((0, util_js_1.assetTag)(root.getDir("objects"), obj.hash).getFile(obj.hash), getURL(obj), key, { sha1: obj.hash, size: obj.size }));
});
await download(downloader);
(0, util_js_1.processAssets)(assetIndex);
}
exports.assets = assets;
/**Installs the lib files from a set version */
async function libraries(version) {
const arr = [];
const natives = (0, config_js_1.getNatives)();
natives.rm();
natives.mkdir();
const OS = (0, util_js_1.getOS)();
const libraries = version.libraries;
for (let key = 0; key < libraries.length; key++) {
let finalDownload;
const e = libraries[key];
if (e.rules) {
if (!(0, util_js_1.lawyer)(e.rules))
continue;
}
if (e.downloads) {
if (e.downloads.classifiers &&
e.natives &&
e.natives[OS] &&
e.downloads.classifiers[e.natives[OS]]) {
const art = e.downloads.classifiers[e.natives[OS]];
const subDownload = (0, config_js_1.getlibraries)().getFile(art.path);
arr.push((0, downloadable_js_1.toDownloadable)(subDownload, art.url, art.path, { sha1: art.sha1, size: art.size }, {
unzip: {
file: natives,
exclude: e.extract ? e.extract.exclude : undefined,
},
}));
}
if (e.downloads.artifact) {
if (!e.downloads.artifact.path) {
const namespec = e.name.split(":");
const path = namespec[0].replace(/\./g, "/") +
"/" +
namespec[1] +
"/" +
namespec[2] +
"/" +
namespec[1] +
"-" +
namespec[2] +
".jar";
e.downloads.artifact.path = path;
}
finalDownload = (0, config_js_1.getlibraries)().getFile(e.downloads.artifact.path);
finalDownload.mkdir();
arr.push((0, downloadable_js_1.toDownloadable)(finalDownload, e.downloads.artifact.url, e.downloads.artifact.path, {
sha1: e.downloads.artifact.sha1,
size: e.downloads.artifact.size,
}));
}
}
else {
if (!e.url)
e.url = "https://libraries.minecraft.net/";
const path = (0, util_js_1.classPathResolver)(e.name);
const file = (0, config_js_1.getlibraries)().getFile(path);
let sha1;
//Maven repo
for (let i = 0; i < 3; i++) {
try {
if (e.checksums) {
sha1 = e.checksums;
}
else {
const r = await (0, node_fetch_1.default)(e.url + path + ".sha1");
if (r.ok)
sha1 = await r.text();
else
continue;
}
break;
}
catch (e) {
(0, config_js_1.emit)("debug.error", e);
}
}
arr.push((0, downloadable_js_1.toDownloadable)(file, e.url + path, path, { sha1: sha1 }));
}
}
return await download(arr);
}
exports.libraries = libraries;
async function getRuntimeIndexes(manifest) {
const runtimes = (0, config_js_1.getMeta)().runtimes.mkdir();
let platform;
switch ((0, util_js_1.getOS)()) {
case "linux":
if ((0, util_js_1.getCpuArch)() == "arm64") {
platform = "linux-arm64";
if (!manifest[platform])
manifest[platform] = manifest.gamecore;
//Intel fallback for windows-arm
(0, config_js_1.emit)("debug.warn", "Loading intel fallback for ARM64 Linux. Please contact GMLL's developer if this bugs out.\nPlease make sure box64 is installed!");
for (const key of Object.keys(manifest[platform]))
if (manifest[platform][key].length < 1)
manifest[platform][key] = manifest["linux-x64"][key];
}
else {
platform = (0, util_js_1.getCpuArch)() == "x64" ? "linux" : "linux-i386";
}
break;
case "windows":
if ((0, util_js_1.getCpuArch)() == "arm64") {
platform = "windows-arm64";
//Intel fallback for windows-arm
(0, config_js_1.emit)("debug.warn", "Loading intel fallback for ARM64 Windows. Please contact GMLL's developer if this bugs out.");
for (const key of Object.keys(manifest[platform]))
if (manifest[platform][key].length < 1)
manifest[platform][key] = manifest["windows-x64"][key];
}
else {
platform = (0, util_js_1.getCpuArch)() == "x64" ? "windows-x64" : "windows-x86";
}
break;
case "osx":
if ((0, util_js_1.getCpuArch)() == "arm64") {
platform = "mac-os-arm64";
//Intel fallback for m1
(0, config_js_1.emit)("debug.warn", "Loading intel fallback for M1. Please contact GMLL's developer if this bugs out.");
for (const key of Object.keys(manifest[platform]))
if (manifest[platform][key].length < 1)
manifest[platform][key] = manifest["mac-os"][key];
}
else {
platform = "mac-os";
}
break;
default:
throw "Unsupported operating system";
}
for (const key of Object.keys(manifest[platform])) {
if (manifest[platform][key].length < 1)
continue;
const obj = manifest[platform][key][0];
await runtimes
.getFile(key + ".json")
.download(obj.manifest.url, obj.manifest);
}
}
exports.getRuntimeIndexes = getRuntimeIndexes;
async function getForgiac() {
const forgiacURL = (0, config_js_1.getRepositories)().maven +
"za/net/hanro50/forgiac/basic/1.9.1/basic-1.9.1.jar";
const forgiacSHA = forgiacURL + ".sha1";
const libsFolder = (0, config_js_1.getlibraries)()
.getDir("za", "net", "hanro50", "forgiac", "basic", "1.9.1")
.mkdir()
.getFile("basic-1.9.1.jar");
const rURL2 = await fetch(forgiacSHA);
if (rURL2.status == 200) {
await libsFolder.download(forgiacURL, { sha1: await rURL2.text() });
}
return libsFolder;
}
exports.getForgiac = getForgiac;
/**
* Updates GMLL's manifest files. Used internally
*
*
*/
async function manifests() {
const repositories = (0, config_js_1.getRepositories)();
const legacyFabricLoader = "https://meta.legacyfabric.net/v2/versions/loader/";
const legacyFabricVersions = "https://meta.legacyfabric.net/v2/versions/game/";
const fabricLoader = "https://meta.fabricmc.net/v2/versions/loader/";
const fabricVersions = "https://meta.fabricmc.net/v2/versions/game/";
const quiltLoader = "https://meta.quiltmc.org/v3/versions/loader";
const quiltVersions = "https://meta.quiltmc.org/v3/versions/game";
const mcRuntimes = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
const mcVersionManifest = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";
const agentaURL = repositories.maven + "za/net/hanro50/agenta/1.8.5/agenta-1.8.5.jar";
const update = (0, config_js_1.getUpdateConfig)();
const meta = (0, config_js_1.getMeta)();
const r = await (0, node_fetch_1.default)(mcVersionManifest);
if (r.status == 200) {
const json = await r.json();
meta.index.getFile("latest.json").write(json.latest);
meta.manifests.getFile("vanilla.json").write(json.versions);
}
if (update.includes("fabric")) {
try {
const jsGame = (await meta.index.getFile("fabric_game.json").download(fabricVersions)).toJSON();
const jsLoader = (await meta.index.getFile("fabric_loader.json").download(fabricLoader)).toJSON();
const result = [];
jsGame.forEach((game) => {
const version = game.version;
jsLoader.forEach((l) => {
result.push({
id: "fabric-loader-" + l.version + "-" + version,
base: version,
stable: l.stable,
type: "fabric",
url: fabricLoader + version + "/" + l.version + "/profile/json",
});
});
});
meta.manifests.getFile("fabric.json").write(result);
}
catch (e) {
(0, config_js_1.emit)("debug.error", e);
}
}
if (update.includes("legacy-fabric")) {
try {
const jsGame = (await meta.index
.getFile("legacy_fabric_game.json")
.download(legacyFabricVersions)).toJSON();
const jsLoader = (await meta.index
.getFile("legacy_fabric_loader.json")
.download(legacyFabricLoader)).toJSON();
const result = [];
jsGame.forEach((game) => {
const version = game.version;
jsLoader.forEach((l) => {
result.push({
id: "legacy-fabric-loader-" + l.version + "-" + version,
base: version,
stable: l.stable,
type: "legacy-fabric",
url: legacyFabricLoader + version + "/" + l.version + "/profile/json",
});
});
});
meta.manifests.getFile("legacy-fabric.json").write(result);
}
catch (e) {
(0, config_js_1.emit)("debug.error", e);
}
}
if (update.includes("quilt")) {
try {
const jsGame = (await meta.index.getFile("quilt_game.json").download(quiltVersions)).toJSON();
const jsLoader = (await meta.index.getFile("quilt_loader.json").download(quiltLoader)).toJSON();
const result = [];
jsGame.forEach((game) => {
const version = game.version;
jsLoader.forEach((l) => {
result.push({
id: "quilt-loader-" + l.version + "-" + version,
base: version,
stable: l.stable,
type: "quilt",
url: quiltLoader + "/" + version + "/" + l.version + "/profile/json",
});
});
});
meta.manifests.getFile("quilt.json").write(result);
}
catch (e) {
(0, config_js_1.emit)("debug.error", e);
}
}
if (update.includes("runtime")) {
const indexes = (await meta.index.getFile("runtime.json").download(mcRuntimes)).toJSON();
getRuntimeIndexes(indexes);
}
if (update.includes("agent")) {
let sha1;
try {
const r = await (0, node_fetch_1.default)(agentaURL + ".sha1");
sha1 = await r.text();
await getAgentFile().download(agentaURL, { sha1 });
}
catch (e) {
/* empty */
}
}
const arch = (0, util_js_1.getCpuArch)();
if (["arm", "arm64", "x32", "x64"].includes(arch))
await (0, gfsl_1.download7zip)({
dir: meta.bin,
arch: arch,
z7Repo: repositories.z7Repo,
});
else
await (0, gfsl_1.download7zip)({
dir: meta.bin,
arch: (0, util_js_1.getOS)() != "osx" ? "x32" : "x64",
z7Repo: repositories.z7Repo,
});
}
exports.manifests = manifests;
function getAgentFile() {
return (0, config_js_1.getlibraries)()
.getDir("za", "net", "hanro50", "agenta", "1.6.1")
.mkdir()
.getFile("agenta-1.6.1.jar");
}
exports.getAgentFile = getAgentFile;
/**
* Used for runtime management.
* Short for encode Mojang Resource Format
*
*/
async function encodeMRF(url, root, out) {
const res = { files: {} };
const packed = out.getDir("encoded").mkdir();
let tFiles = 0;
let cFiles = 0;
(0, config_js_1.emit)("encode.start");
async function encodeDir(path, root) {
const ls = root
.ls()
.sort((a, b) => a.sysPath().length - b.sysPath().length);
tFiles += ls.length;
for (let index = 0; index < ls.length; index++) {
const e = ls[index];
const directory = [path, e.getName()].join("/");
cFiles++;
(0, config_js_1.emit)("encode.progress", directory, cFiles, tFiles, tFiles - cFiles);
if (e.isLink()) {
res.files[directory] = {
type: "link",
target: (0, fs_1.readlinkSync)(e.sysPath()),
};
continue;
}
else if (e instanceof gfsl_1.File) {
const rHash = e.getHash();
e.copyTo(packed.getFile(rHash, e.name).mkdir());
let zip = out.getFile("tmp", e.name + ".7z").mkdir();
await (0, gfsl_1.packAsync)(e.sysPath(), zip.sysPath());
const zHash = zip.getHash();
const downloadable = {
type: "file",
executable: await e.isExecutable(),
downloads: {
raw: {
sha1: rHash,
size: e.getSize(),
url: [url, rHash, e.name].join("/"),
},
},
};
if (zip.getSize() < e.getSize()) {
zip = zip.moveTo(packed.getFile(zHash, e.name).mkdir());
downloadable.downloads.lzma = {
sha1: zHash,
size: zip.getSize(),
url: [url, zHash, e.name].join("/"),
};
}
else {
zip.rm();
}
res.files[directory] = downloadable;
continue;
}
else if (e instanceof gfsl_1.Dir) {
res.files[directory] = {
type: "directory",
};
await encodeDir(directory, e);
continue;
}
}
}
await encodeDir("", root);
const manifest = out.getFile(root.getName() + "_manifest.json");
manifest.write(res);
const mHash = manifest.getHash();
manifest.copyTo(packed.getFile(mHash, "manifest.json").mkdir());
const index = out.getFile(root.getName() + "_index.json");
index.write({
sha1: mHash,
size: manifest.getSize(),
url: [url, mHash, "manifest.json"].join("/"),
});
(0, config_js_1.emit)("encode.done");
out.getDir("tmp").rm();
return res;
}
exports.encodeMRF = encodeMRF;