minecraft-java-core-azbetter
Version:
A library starting minecraft game NW.js and Electron.js
364 lines • 17.7 kB
JavaScript
/**
* This code is distributed under the CC-BY-NC 4.0 license:
* https://creativecommons.org/licenses/by-nc/4.0/
*
* Original author: Luuxis
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const events_1 = require("events");
const Index_js_1 = require("../../../utils/Index.js");
const Downloader_js_1 = __importDefault(require("../../../utils/Downloader.js"));
const patcher_js_1 = __importDefault(require("../../patcher.js"));
/**
* Maps Node.js process.platform values to Mojang library naming conventions.
* Used for choosing the right native library.
*/
const Lib = {
win32: 'windows',
darwin: 'osx',
linux: 'linux'
};
/**
* The main class for handling Forge installations, including:
* - Downloading the appropriate Forge installer
* - Extracting relevant files from the installer
* - Patching Forge when necessary
* - Creating a merged jar for older Forge versions
*/
class ForgeMC extends events_1.EventEmitter {
constructor(options) {
super();
this.options = options;
}
/**
* Downloads the Forge installer (or client/universal) for the specified version/build.
* Verifies the downloaded file's MD5 hash. Returns file details or an error.
*
* @param Loader An object containing URLs for metadata and Forge files.
*/
async downloadInstaller(Loader) {
// Fetch metadata for the given Forge version
let metaDataList = await fetch(Loader.metaData)
.then(res => res.json())
.then(json => json[this.options.loader.version]);
if (!metaDataList) {
return { error: `Forge ${this.options.loader.version} not supported` };
}
const allBuilds = metaDataList;
let build;
// Handle "latest" or "recommended" builds by checking promotions
if (this.options.loader.build === 'latest') {
let promotions = await fetch(Loader.promotions).then(res => res.json());
const promoKey = `${this.options.loader.version}-latest`;
const promoBuild = promotions.promos[promoKey];
build = metaDataList.find(b => b.includes(promoBuild));
}
else if (this.options.loader.build === 'recommended') {
let promotions = await fetch(Loader.promotions).then(res => res.json());
let promoKey = `${this.options.loader.version}-recommended`;
let promoBuild = promotions.promos[promoKey] || promotions.promos[`${this.options.loader.version}-latest`];
build = metaDataList.find(b => b.includes(promoBuild));
}
else {
// Else, look for a specific numeric build if provided
build = this.options.loader.build;
}
const chosenBuild = metaDataList.find(b => b === build);
if (!chosenBuild) {
return {
error: `Build ${build} not found, Available builds: ${allBuilds.join(', ')}`
};
}
// Fetch info about the chosen build from the meta URL
const meta = await fetch(Loader.meta.replace(/\${build}/g, chosenBuild)).then(res => res.json());
// Determine which classifier to use (installer, client, or universal)
const hasInstaller = meta.classifiers.installer;
const hasClient = meta.classifiers.client;
const hasUniversal = meta.classifiers.universal;
let forgeURL = '';
let ext = '';
let hashFileOrigin = '';
if (hasInstaller) {
forgeURL = Loader.install.replace(/\${version}/g, chosenBuild);
ext = Object.keys(meta.classifiers.installer)[0];
hashFileOrigin = meta.classifiers.installer[ext];
}
else if (hasClient) {
forgeURL = Loader.client.replace(/\${version}/g, chosenBuild);
ext = Object.keys(meta.classifiers.client)[0];
hashFileOrigin = meta.classifiers.client[ext];
}
else if (hasUniversal) {
forgeURL = Loader.universal.replace(/\${version}/g, chosenBuild);
ext = Object.keys(meta.classifiers.universal)[0];
hashFileOrigin = meta.classifiers.universal[ext];
}
else {
return { error: 'Invalid forge installer' };
}
const forgeFolder = path_1.default.resolve(this.options.path, 'forge');
const fileName = `${forgeURL}.${ext}`.split('/').pop();
const installerPath = path_1.default.resolve(forgeFolder, fileName);
// Download if not already present
if (!fs_1.default.existsSync(installerPath)) {
if (!fs_1.default.existsSync(forgeFolder)) {
fs_1.default.mkdirSync(forgeFolder, { recursive: true });
}
const dl = new Downloader_js_1.default();
dl.on('progress', (downloaded, size) => {
this.emit('progress', downloaded, size, fileName);
});
await dl.downloadFile(`${forgeURL}.${ext}`, forgeFolder, fileName);
}
// Verify the MD5 hash
const hashFileDownload = await (0, Index_js_1.getFileHash)(installerPath, 'md5');
if (hashFileDownload !== hashFileOrigin) {
fs_1.default.rmSync(installerPath);
return { error: 'Invalid hash' };
}
return {
filePath: installerPath,
metaData: chosenBuild,
ext,
id: `forge-${build}`
};
}
/**
* Extracts the main Forge profile from the installer's archive (install_profile.json),
* plus an additional JSON if specified in that profile. Returns an object containing
* both "install" and "version" data for further processing.
*
* @param pathInstaller Path to the downloaded Forge installer file.
*/
async extractProfile(pathInstaller) {
const fileContent = await (0, Index_js_1.getFileFromArchive)(pathInstaller, 'install_profile.json');
if (!fileContent) {
return { error: { message: 'Invalid forge installer' } };
}
const forgeJsonOrigin = JSON.parse(fileContent.toString());
if (!forgeJsonOrigin) {
return { error: { message: 'Invalid forge installer' } };
}
const result = {};
// Distinguish between older and newer Forge installers
if (forgeJsonOrigin.install) {
result.install = forgeJsonOrigin.install;
result.version = forgeJsonOrigin.versionInfo;
}
else {
result.install = forgeJsonOrigin;
const extraFile = await (0, Index_js_1.getFileFromArchive)(pathInstaller, path_1.default.basename(result.install.json));
if (!extraFile) {
return { error: { message: 'Invalid additional JSON in forge installer' } };
}
result.version = JSON.parse(extraFile.toString());
}
return result;
}
/**
* Extracts the "universal" Forge jar (or other relevant data) from the installer,
* placing it in your local "libraries" folder. Also extracts client data if required.
*
* @param profile The Forge profile object containing file paths to extract.
* @param pathInstaller The path to the Forge installer file.
* @returns A boolean (skipForgeFilter) that indicates whether to filter out certain Forge libs
*/
async extractUniversalJar(profile, pathInstaller) {
let skipForgeFilter = true;
// If there's a direct file path, extract just that file
if (profile.filePath) {
const fileInfo = (0, Index_js_1.getPathLibraries)(profile.path);
this.emit('extract', `Extracting ${fileInfo.name}...`);
const destFolder = path_1.default.resolve(this.options.path, 'libraries', fileInfo.path);
if (!fs_1.default.existsSync(destFolder)) {
fs_1.default.mkdirSync(destFolder, { recursive: true });
}
const archiveContent = await (0, Index_js_1.getFileFromArchive)(pathInstaller, profile.filePath);
if (archiveContent) {
fs_1.default.writeFileSync(path_1.default.join(destFolder, fileInfo.name), archiveContent, { mode: 0o777 });
}
}
// Otherwise, if there's a path referencing "maven/<something>"
else if (profile.path) {
const fileInfo = (0, Index_js_1.getPathLibraries)(profile.path);
const filesInArchive = await (0, Index_js_1.getFileFromArchive)(pathInstaller, null, `maven/${fileInfo.path}`);
for (const file of filesInArchive) {
const fileName = path_1.default.basename(file);
this.emit('extract', `Extracting ${fileName}...`);
const fileContent = await (0, Index_js_1.getFileFromArchive)(pathInstaller, file);
if (!fileContent) {
continue;
}
const destFolder = path_1.default.resolve(this.options.path, 'libraries', fileInfo.path);
if (!fs_1.default.existsSync(destFolder)) {
fs_1.default.mkdirSync(destFolder, { recursive: true });
}
fs_1.default.writeFileSync(path_1.default.join(destFolder, fileName), fileContent, { mode: 0o777 });
}
}
else {
// If we do not find filePath or path in profile, skip the Forge filter
skipForgeFilter = false;
}
// If there are processors, we likely have a "client.lzma" to store
if (profile.processors?.length) {
const universalPath = profile.libraries?.find((v) => (v.name || '').startsWith('net.minecraftforge:forge'));
const clientData = await (0, Index_js_1.getFileFromArchive)(pathInstaller, 'data/client.lzma');
if (clientData) {
const fileInfo = (0, Index_js_1.getPathLibraries)(profile.path || universalPath.name, '-clientdata', '.lzma');
const destFolder = path_1.default.resolve(this.options.path, 'libraries', fileInfo.path);
if (!fs_1.default.existsSync(destFolder)) {
fs_1.default.mkdirSync(destFolder, { recursive: true });
}
fs_1.default.writeFileSync(path_1.default.join(destFolder, fileInfo.name), clientData, { mode: 0o777 });
this.emit('extract', `Extracting ${fileInfo.name}...`);
}
}
return skipForgeFilter;
}
/**
* Downloads all the libraries needed by the Forge profile, skipping duplicates
* and any library that is already present. Also applies optional skip logic
* for certain Forge libraries if skipForgeFilter is true.
*
* @param profile The parsed Forge profile.
* @param skipForgeFilter Whether to filter out "net.minecraftforge:forge" or "minecraftforge"
* @returns An array of the final libraries (including newly downloaded ones).
*/
async downloadLibraries(profile, skipForgeFilter) {
let libraries = profile.version?.libraries || [];
const dl = new Downloader_js_1.default();
let checkCount = 0;
const downloadList = [];
let totalSize = 0;
// Combine with any "install.libraries"
if (profile.install?.libraries) {
libraries = libraries.concat(profile.install.libraries);
}
// Remove duplicates by name
libraries = libraries.filter((library, index, self) => index === self.findIndex(t => t.name === library.name));
// Certain Forge libs may be skipped if skipForgeFilter is true
const skipForge = ['net.minecraftforge:forge:', 'net.minecraftforge:minecraftforge:'];
for (const lib of libraries) {
// If skipForgeFilter is true, skip the core Forge libs
if (skipForgeFilter && skipForge.some(forgePrefix => lib.name.includes(forgePrefix))) {
// If the artifact URL is empty, we skip it
if (!lib.downloads?.artifact?.url) {
this.emit('check', checkCount++, libraries.length, 'libraries');
continue;
}
}
// Some libraries might need skipping altogether (e.g., OS-specific constraints)
if ((0, Index_js_1.skipLibrary)(lib)) {
this.emit('check', checkCount++, libraries.length, 'libraries');
continue;
}
// Check if the library includes "natives" for the current OS
let nativesSuffix;
if (lib.natives) {
nativesSuffix = lib.natives[Lib[process.platform]];
}
const libInfo = (0, Index_js_1.getPathLibraries)(lib.name, nativesSuffix ? `-${nativesSuffix}` : '');
const libFolder = path_1.default.resolve(this.options.path, 'libraries', libInfo.path);
const libFilePath = path_1.default.resolve(libFolder, libInfo.name);
// If not present locally, schedule it for download
if (!fs_1.default.existsSync(libFilePath)) {
let url = null;
let fileSize = 0;
// First, try checking a mirror
const baseURL = nativesSuffix ? `${libInfo.path}/` : `${libInfo.path}/${libInfo.name}`;
const mirrorResp = await dl.checkMirror(baseURL, Index_js_1.mirrors);
if (mirrorResp?.status === 200) {
fileSize = mirrorResp.size;
totalSize += fileSize;
url = mirrorResp.url;
}
else if (lib.downloads?.artifact) {
url = lib.downloads.artifact.url;
fileSize = lib.downloads.artifact.size;
totalSize += fileSize;
}
if (!url) {
return { error: `Impossible to download ${libInfo.name}` };
}
downloadList.push({
url,
folder: libFolder,
path: libFilePath,
name: libInfo.name,
size: fileSize
});
}
this.emit('check', checkCount++, libraries.length, 'libraries');
}
// Perform the downloads if any are needed
if (downloadList.length > 0) {
dl.on('progress', (DL, totDL) => {
this.emit('progress', DL, totDL, 'libraries');
});
await dl.downloadFileMultiple(downloadList, totalSize, this.options.downloadFileMultiple);
}
return libraries;
}
/**
* Applies any necessary patches to Forge using the `forgePatcher` class.
* If the patcher determines it's already patched, it skips.
*
* @param profile The Forge profile containing processor information
* @returns True if successful or if no patching was required
*/
async patchForge(profile) {
if (profile?.processors?.length) {
const patcher = new patcher_js_1.default(this.options);
// Forward patcher events
patcher.on('patch', (data) => this.emit('patch', data));
patcher.on('error', (data) => this.emit('error', data));
// If the patch is not valid yet, run the patch process
if (!patcher.check(profile)) {
const config = {
java: this.options.loader.config.javaPath,
minecraft: this.options.loader.config.minecraftJar,
minecraftJson: this.options.loader.config.minecraftJson
};
await patcher.patcher(profile, config);
}
}
return true;
}
/**
* For older Forge versions, merges the vanilla Minecraft jar and Forge installer files
* into a single jar. Writes a modified version.json reflecting the new Forge version.
*
* @param id The new version ID (e.g., "forge-1.12.2-14.23.5.2855")
* @param pathInstaller Path to the Forge installer
* @returns A modified version.json with an isOldForge property and a jarPath
*/
async createProfile(id, pathInstaller) {
// Gather all entries from the Forge installer and the vanilla JAR
const forgeFiles = await (0, Index_js_1.getFileFromArchive)(pathInstaller);
const vanillaJar = await (0, Index_js_1.getFileFromArchive)(this.options.loader.config.minecraftJar);
// Combine them, excluding "META-INF" from the final jar
const mergedZip = await (0, Index_js_1.createZIP)([...vanillaJar, ...forgeFiles], 'META-INF');
// Write the new combined jar to versions/<id>/<id>.jar
const destination = path_1.default.resolve(this.options.path, 'versions', id);
if (!fs_1.default.existsSync(destination)) {
fs_1.default.mkdirSync(destination, { recursive: true });
}
fs_1.default.writeFileSync(path_1.default.resolve(destination, `${id}.jar`), mergedZip, { mode: 0o777 });
// Update the version JSON
const profileData = JSON.parse(fs_1.default.readFileSync(this.options.loader.config.minecraftJson).toString());
profileData.libraries = [];
profileData.id = id;
profileData.isOldForge = true;
profileData.jarPath = path_1.default.resolve(destination, `${id}.jar`);
return profileData;
}
}
exports.default = ForgeMC;
//# sourceMappingURL=forge.js.map
;