UNPKG

minecraft-core-master

Version:

Núcleo avanzado para launchers de Minecraft. Descarga, instala y ejecuta versiones de Minecraft, assets, librerías, Java y loaders de forma automática y eficiente.

153 lines (152 loc) 5.19 kB
import { mkdir } from "node:fs/promises"; import { createWriteStream } from "node:fs"; import { join, dirname } from "node:path"; import { EventEmitter } from "node:events"; import https from "node:https"; import { createTaskLimiter } from "../Utils/Index.js"; const agent = new https.Agent({ keepAlive: true, maxSockets: 200, maxFreeSockets: 100, }); export class LibrariesDownloader extends EventEmitter { version; root; concurry; maxRetries; limiter; paused = false; stopped = false; pendingQueue = []; runningTasks = 0; doneEmitted = false; constructor(opts) { super(); this.version = opts.version; this.root = opts.root; this.concurry = opts.concurry ?? 16; this.maxRetries = opts.maxRetries ?? 5; this.limiter = createTaskLimiter(this.concurry); } async start() { this.emit("Start"); this.stopped = false; this.doneEmitted = false; const manifest = await this.fetchVersionManifest(); const versionMeta = manifest.versions.find((v) => v.id === this.version); if (!versionMeta) throw new Error("Versión no encontrada"); const versionJson = await this.downloadJSON(versionMeta.url); const libs = versionJson.libraries.filter((lib) => lib.downloads?.artifact); this.pendingQueue = []; for (const lib of libs) { const item = lib.downloads.artifact; this.pendingQueue.push({ url: item.url, sha1: item.sha1, path: join(this.root, "libraries", item.path), size: item.size, retries: 0, }); } this.processQueue(); } pause() { this.paused = true; this.emit("Paused"); } resume() { if (!this.paused) return; this.paused = false; this.emit("Resumed"); this.processQueue(); } stop() { this.stopped = true; this.pendingQueue = []; this.emit("Stopped"); } async getTotalBytes() { const manifest = await this.fetchVersionManifest(); const versionMeta = manifest.versions.find((v) => v.id === this.version); if (!versionMeta) throw new Error("Versión no encontrada"); const versionJson = await this.downloadJSON(versionMeta.url); const libs = versionJson.libraries.filter((lib) => lib.downloads?.artifact); let total = 0; for (const lib of libs) { const item = lib.downloads.artifact; if (typeof item.size === "number") { total += item.size; } } return total; } processQueue() { if (this.paused || this.stopped) return; if (this.pendingQueue.length === 0) return this.checkDone(); while (!this.paused && !this.stopped && this.pendingQueue.length > 0) { const task = this.pendingQueue.shift(); this.runningTasks++; this.limiter(() => this.downloadLibrary(task)) .then(() => { }) .catch(() => { }) .finally(() => { this.runningTasks--; this.checkDone(); if (!this.paused && !this.stopped) this.processQueue(); }); } } checkDone() { if (this.doneEmitted) return; if (this.pendingQueue.length === 0 && this.runningTasks === 0 && !this.paused && !this.stopped) { this.doneEmitted = true; this.emit("Done"); } } async downloadLibrary(task) { await mkdir(dirname(task.path), { recursive: true }).catch(() => { }); return new Promise((resolve, reject) => { const req = https.get(task.url, { agent }, (res) => { if (res.statusCode !== 200) { return this.retryOrFail(task, reject); } const file = createWriteStream(task.path); res.on("data", (chunk) => this.emit("Bytes", chunk.length)); res.pipe(file); file.on("finish", () => file.close(() => resolve())); file.on("error", reject); }); req.on("error", () => this.retryOrFail(task, reject)); }); } retryOrFail(task, reject) { if (task.retries++ < this.maxRetries) { this.pendingQueue.push(task); } else { reject(new Error(`No se pudo descargar: ${task.url}`)); } } async fetchVersionManifest() { const url = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; return await this.downloadJSON(url); } async downloadJSON(url) { return new Promise((resolve, reject) => { https .get(url, { agent }, (res) => { let data = ""; res.on("data", (c) => (data += c)); res.on("end", () => resolve(JSON.parse(data))); }) .on("error", reject); }); } }