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.

138 lines (137 loc) 5.43 kB
import https from "node:https"; import { mkdir, writeFile } from "node:fs/promises"; import { join, dirname } from "node:path"; import { EventEmitter } from "node:events"; import { TaskLimiter } from "../Utils/Index.js"; export class RuntimeDownloader extends EventEmitter { options; versionManifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; javaAllUrl = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; taskLimiter; downloadedBytes = 0; constructor(options) { super(); this.options = options; this.taskLimiter = new TaskLimiter(options.concurrency || 5); } async getTotalBytes() { const javaManifest = await this.getJavaManifest(); const manifestJson = await this.fetchJson(javaManifest.manifest.url); let total = 0; for (const file of Object.values(manifestJson.files)) { if (file.type === "file" && file.downloads?.raw?.size) { total += file.downloads.raw.size; } } return total; } async start() { this.emit("Start"); this.downloadedBytes = 0; try { const javaManifest = await this.getJavaManifest(); const javaRoot = join(this.options.root, "runtime", `java-${javaManifest.version.name}`); const manifestJson = await this.fetchJson(javaManifest.manifest.url); const downloadTasks = []; for (const [relativePath, file] of Object.entries(manifestJson.files)) { if (file.type === "directory") continue; const destPath = join(javaRoot, relativePath); await mkdir(dirname(destPath), { recursive: true }); const task = this.taskLimiter.limit(() => this.downloadFileWithRetry(file.downloads.raw.url, destPath)); downloadTasks.push(task); } await Promise.all(downloadTasks); this.emit("Done", javaRoot); } catch (err) { this.emit("Stopped", err); } } async downloadFileWithRetry(url, dest) { const maxRetries = this.options.maxRetries || 3; let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { await this.downloadFile(url, dest); return; } catch (err) { lastError = err; if (attempt < maxRetries) { const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); continue; } } } throw lastError; } async getJavaManifest() { const versionManifest = await this.fetchJson(this.versionManifestUrl); const versionData = versionManifest.versions.find((v) => v.id === this.options.version); if (!versionData) throw new Error("Versión de Minecraft no encontrada"); const versionJson = await this.fetchJson(versionData.url); const javaType = versionJson.javaVersion.component; const platform = this.getPlatform(); const javaAll = await this.fetchJson(this.javaAllUrl); const javaList = javaAll[platform]?.[javaType]; if (!javaList || javaList.length === 0) { throw new Error(`No hay Java disponible para ${platform} (${javaType})`); } const javaManifest = javaList[javaList.length - 1]; return javaManifest; } getPlatform() { const plat = process.platform; const arch = process.arch; if (plat === "win32" && arch === "x64") return "windows-x64"; if (plat === "win32" && arch === "ia32") return "windows-x86"; if (plat === "darwin" && arch === "arm64") return "mac-os-arm64"; if (plat === "darwin") return "mac-os"; if (plat === "linux" && arch === "x64") return "linux"; if (plat === "linux" && arch === "ia32") return "linux-i386"; throw new Error("Plataforma no soportada"); } fetchJson(url) { return new Promise((resolve, reject) => { https.get(url, res => { let data = ""; res.on("data", chunk => (data += chunk)); res.on("end", () => resolve(JSON.parse(data))); res.on("error", reject); }); }); } downloadFile(url, dest) { return new Promise((resolve, reject) => { https.get(url, res => { const chunks = []; let downloaded = 0; res.on("data", chunk => { chunks.push(chunk); downloaded += chunk.length; this.downloadedBytes += chunk.length; this.emit("Bytes", this.downloadedBytes); }); res.on("end", async () => { try { await writeFile(dest, Buffer.concat(chunks)); resolve(); } catch (err) { reject(err); } }); res.on("error", reject); }); }); } }