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.

190 lines (189 loc) 6.79 kB
import { EventEmitter } from "node:events"; import { createWriteStream, existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs"; import { get as httpGet } from "node:http"; import { get as httpsGet } from "node:https"; import * as path from "node:path"; export default class FabricDownloader extends EventEmitter { minecraftPath; gameVersion; concurry; maxRetries; paused = false; stopped = false; loaderApi = "https://meta.fabricmc.net/v1/versions/loader"; constructor(opts) { super(); this.minecraftPath = path.resolve(opts.root); this.gameVersion = opts.version; this.concurry = opts.concurry; this.maxRetries = opts.maxRetries; } // -------------------------- // FETCH JSON (PROMISE) // -------------------------- fetchJson(url) { return new Promise((resolve, reject) => { httpsGet(url, (res) => { if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} GET ${url}`)); let data = ""; res.on("data", (c) => (data += c)); res.on("end", () => { try { resolve(JSON.parse(data)); } catch (e) { reject(e); } }); }).on("error", reject); }); } // -------------------------- // DOWNLOAD FILE // -------------------------- downloadFile(url, dest) { return new Promise((resolve, reject) => { mkdirSync(path.dirname(dest), { recursive: true }); const file = createWriteStream(dest); const client = url.startsWith("https") ? httpsGet : httpGet; const req = client(url, (res) => { if (res.statusCode !== 200) { file.close(); unlinkSync(dest); return reject(new Error(`HTTP ${res.statusCode} ${url}`)); } res.on("data", (chunk) => { this.emit("Bytes", chunk.length); }); res.pipe(file); file.on("finish", () => { file.close(); resolve(); }); res.on("error", (err) => { file.close(); unlinkSync(dest); reject(err); }); }); req.on("error", (err) => { file.close(); unlinkSync(dest); reject(err); }); }); } // -------------------------- // LIB PARSER // -------------------------- getLibraryPath(name) { const parts = name.split(":"); if (parts.length !== 3) throw new Error(`Invalid lib: ${name}`); const [group, artifact, version] = parts; const g = group.replace(/\./g, "/"); return path.join(g, artifact, version, `${artifact}-${version}.jar`); } getLibraryUrl(name) { const parts = name.split(":"); if (parts.length !== 3) throw new Error(`Invalid lib: ${name}`); const [group, artifact, version] = parts; const g = group.replace(/\./g, "/"); return `${g}/${artifact}/${version}/${artifact}-${version}.jar`; } // -------------------------- // FETCH LATEST FABRIC LOADER // -------------------------- async getLatestLoader() { const loaders = await this.fetchJson(this.loaderApi); const stable = loaders.find((x) => x.stable) || loaders[0]; return stable.version; } // -------------------------- // INSTALL // -------------------------- async start() { this.emit("Start"); this.stopped = false; this.paused = false; const loaderVersion = await this.getLatestLoader(); const versionId = `fabric-${this.gameVersion}-${loaderVersion}`; const profileUrl = `https://meta.fabricmc.net/v2/versions/loader/${this.gameVersion}/${loaderVersion}/profile/json`; const profile = await this.fetchJson(profileUrl); // Save version JSON const dir = path.join(this.minecraftPath, "versions", versionId); mkdirSync(dir, { recursive: true }); writeFileSync(path.join(dir, `${versionId}.json`), JSON.stringify(profile, null, 2)); const libs = profile.libraries ?? []; await this.downloadLibraries(libs); if (!this.stopped) this.emit("Done"); } // -------------------------- // PAUSE/RESUME/STOP // -------------------------- pause() { this.paused = true; this.emit("Paused"); } resume() { if (!this.paused) return; this.paused = false; this.emit("Resumed"); } stop() { this.stopped = true; this.emit("Stopped"); } // -------------------------- // LIBRARY QUEUE (CONCURRENCY) // -------------------------- async downloadLibraries(libs) { const queue = [...libs]; const active = []; while (queue.length > 0 && !this.stopped) { if (this.paused) { await new Promise((r) => setTimeout(r, 300)); continue; } if (active.length < this.concurry) { const lib = queue.shift(); active.push(this.handleLibrary(lib)); } else { await Promise.race(active); for (let i = active.length - 1; i >= 0; i--) { if (active[i]?.catch(() => { }) && active[i]?.finally) active.splice(i, 1); } } } await Promise.all(active); } handleLibrary(lib) { return new Promise(async (resolve) => { const libPath = this.getLibraryPath(lib.name); const libUrl = this.getLibraryUrl(lib.name); const dest = path.join(this.minecraftPath, "libraries", libPath); const base = lib.url ?? "https://repo1.maven.org/maven2/"; const fullUrl = base.endsWith("/") ? base + libUrl : base + "/" + libUrl; if (existsSync(dest)) return resolve(); let retries = 0; while (retries <= this.maxRetries && !this.stopped) { try { await this.downloadFile(fullUrl, dest); return resolve(); } catch { retries++; if (retries > this.maxRetries) return resolve(); } } }); } }