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
JavaScript
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();
}
}
});
}
}