UNPKG

gmll

Version:

A generic launcher core for building custom launchers

653 lines (652 loc) 26.4 kB
"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;