const path = require("node:path"); const fs = require("node:fs"); const fsPromises = require("node:fs/promises"); const httpsWrapper = require("./util/https.js"); const utilities = require("./util/functions.js"); let maxConcurrentDownloads = 16; let downloadQueue = []; let totalDownloads = 0; let isDownloading = false; let ongoingDownloads = 0; function waitUntilDownloadsFinish() { return new Promise(async function (resolve, reject) { var downloadCountChecker = setInterval(function () { if (ongoingDownloads < 1 && downloadQueue.length < 1 && !isDownloading) { resolve(clearInterval(downloadCountChecker)); } }, 256); }); } module.exports = { "download_file": function (url, filePath) { return new Promise(async function (resolve, reject) { var directory = path.dirname(filePath); if (!(await fs.existsSync(directory))) await fsPromises.mkdir(directory, {"recursive": true}); var writeStream = fs.createWriteStream(filePath); writeStream.on("finish", resolve); httpsWrapper.getStream(url).then(function (response) { response.pipe(writeStream); }).catch(reject); }); }, "process_queue": function (progressCB) { return new Promise(async function (resolve, reject) { totalDownloads = downloadQueue.length; if (downloadQueue.length > 0) isDownloading = true; if (progressCB) { progressCB(0.0, 0, downloadQueue.length, totalDownloads); } while (downloadQueue.length > 0) { if (ongoingDownloads < maxConcurrentDownloads) { setImmediate(function (downloadData) { module.exports.download_file(downloadData.url, downloadData.path).then(function () { ongoingDownloads--; if (downloadQueue.length < 1) { isDownloading = false; } }).catch(function (error) { ongoingDownloads--; downloadQueue.push(downloadData); }); }, downloadQueue.shift()); ongoingDownloads++; let progress = (totalDownloads - (downloadQueue.length + ongoingDownloads)) / totalDownloads; if (progressCB) { progressCB(progress, ongoingDownloads, downloadQueue.length, totalDownloads); } } else { await utilities.sleep(256); } } waitUntilDownloadsFinish().then(function () { if (progressCB) { progressCB(1.0, 0, 0, totalDownloads); } resolve(); }).catch(reject); }); }, "download_minecraft": function (directory, meta, manifestSource, progressCB) { return new Promise(async function (resolve, reject) { let gameManifest = JSON.parse(await httpsWrapper.get(manifestSource["versions_manifest"])); let versionInManifest = gameManifest.versions.find(ver => ver.id == meta["version"]); if (!versionInManifest) { return reject(new Error("Minecraft version \"" + meta["version"] + "\" could not be found in given source. ")); } if (!fs.existsSync(directory)) await fsPromises.mkdir(directory, {"recursive": true}); let versionDirectory = path.join(directory, "versions", meta["version"]); if (!fs.existsSync(versionDirectory)) await fsPromises.mkdir(versionDirectory, {"recursive": true}); let versionManifest = JSON.parse(await httpsWrapper.get(versionInManifest.url)); fs.writeFileSync(path.join(versionDirectory, meta["version"] + ".json"), JSON.stringify(versionManifest, null, "\t")); let librariesDirectory = path.join(directory, "libraries"); for (let l = 0; l < versionManifest.libraries.length; l++) { let library = versionManifest.libraries[l]; if ("classifiers" in library.downloads) continue; // Attempt at skipping platform-specific code, hoping the game still starts. let libraryFilePath = path.join(librariesDirectory, ...(library.downloads.artifact.path.split("/"))); downloadQueue.push({ "url": library.downloads.artifact.url, "path": libraryFilePath }); } let assetsDirectory = path.join(directory, "assets"); let assetIndexesDirectory = path.join(assetsDirectory, "indexes"); if (!fs.existsSync(assetIndexesDirectory)) await fsPromises.mkdir(assetIndexesDirectory, {"recursive": true}); let assetIndexPath = path.join(assetIndexesDirectory, versionManifest.assetIndex.id + ".json"); let assetIndex = JSON.parse(await httpsWrapper.get(versionManifest.assetIndex.url)); fs.writeFileSync(assetIndexPath, JSON.stringify(assetIndex, null, "\t")); let assetObjectsDirectory = path.join(assetsDirectory, "objects"); for (const assetLocation in assetIndex.objects) { if (assetIndex.objects.hasOwnProperty(assetLocation)) { let asset = assetIndex.objects[assetLocation]; let assetPath = [ asset.hash.substring(0, 2), asset.hash ]; downloadQueue.push({ "url": manifestSource["asset_base"] + assetPath.join("/"), "path": path.join(assetObjectsDirectory, ...assetPath) }); } } downloadQueue.push({ "url": versionManifest.downloads.client.url, "path": path.join(versionDirectory, meta["version"] + ".jar") }); if (!isDownloading) await module.exports.process_queue(progressCB); resolve(true); }); } };