minecraft-java-core
Version:
A library starting minecraft game NW.js and Electron.js
343 lines • 17 kB
JavaScript
;
/**
* @author Luuxis
* Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN)
*/
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"));
/**
* This class handles downloading and installing NeoForge (formerly Forge) for Minecraft,
* including picking the correct build, extracting libraries, and running patchers if needed.
*/
class NeoForgeMC extends events_1.EventEmitter {
constructor(options) {
super();
this.options = options;
}
/**
* Converts a NeoForge version string to its corresponding Minecraft version.
* Handles all NeoForge versioning formats:
*
* - Snapshot special: "0.25w14craftmine.3-beta" → "25w14craftmine"
* - 4-parts + suffix: "26.1.0.0-alpha.1+snapshot-1" → "26.1-snapshot-1"
* - 4-parts + suffix: "26.1.0.0-alpha.15+pre-3" → "26.1-pre-3"
* - 4-parts patch 0: "26.1.0.1-beta" → "26.1"
* - 4-parts patch >0: "26.1.1.0-beta" → "26.1.1"
* - 3-parts classic: "21.4.121" → "1.21.4"
* - 3-parts minor=0: "21.0.143" → "1.21"
*
* @param version The NeoForge version string
* @returns The corresponding Minecraft version, or null if unrecognized
*/
neoforgeVersionToMinecraft(version) {
// 1. Snapshot special: starts with "0." → e.g., "0.25w14craftmine.3-beta"
const snapshotMatch = version.match(/^0\.([^.]+)\./);
if (snapshotMatch) {
return snapshotMatch[1];
}
// 2. Strip suffix (everything after first "-" or "+") and split numeric part
const numericPart = version.split(/[-+]/)[0];
const parts = numericPart.split('.');
// 3. 4+ parts → 2026+ format (e.g., "26.1.0.1" → MC "26.1", or "26.1.1.0" → MC "26.1.1")
if (parts.length >= 4) {
// parts[0] = major, parts[1] = minor, parts[2] = patch, parts[3] = build
let mc = `${parts[0]}.${parts[1]}`;
// Include patch version if it's non-zero (like Minecraft versions)
if (parts[2] !== '0')
mc += `.${parts[2]}`;
// Check for "+snapshot-N" or "+pre-N" suffix
const suffixMatch = version.match(/\+(snapshot-\d+|pre-\d+)/);
if (suffixMatch)
mc += `-${suffixMatch[1]}`;
return mc;
}
// 4. 3 parts → classic format (e.g., "21.4.121" → MC "1.21.4")
if (parts.length === 3) {
if (parts[1] === '0')
return `1.${parts[0]}`;
return `1.${parts[0]}.${parts[1]}`;
}
return null;
}
/**
* Downloads the NeoForge installer jar for the specified version and build,
* either using a legacy API or the newer metaData approach. If "latest" or "recommended"
* is specified, it picks the newest build from the filtered list.
*
* Uses neoforgeVersionToMinecraft() to map each NeoForge version to its
* Minecraft version, supporting all formats (releases, snapshots, pre-releases,
* 4-part 2026+ versions, etc.).
*
* @param Loader An object containing URLs and patterns for legacy and new metadata/installers.
* @returns An object with filePath and oldAPI fields, or an error.
*/
async downloadInstaller(Loader) {
let build;
let neoForgeURL;
let oldAPI = true;
const minecraftVersion = this.options.loader.version;
// Fetch versions from both APIs
const legacyMetaData = await fetch(Loader.legacyMetaData).then(res => res.json());
const metaData = await fetch(Loader.metaData).then(res => res.json());
// Try legacy API first (old Forge-era versions like "1.20.1-47.1.0")
let versions = legacyMetaData.versions.filter((v) => v.includes(`${minecraftVersion}-`));
// If none found in legacy, use modern API with neoforgeVersionToMinecraft
if (!versions.length) {
versions = metaData.versions.filter((v) => {
const mc = this.neoforgeVersionToMinecraft(v);
return mc === minecraftVersion;
});
oldAPI = false;
}
// If still no versions found, return an error
if (!versions.length) {
return { error: `NeoForge doesn't support Minecraft ${minecraftVersion}` };
}
// Determine which build to use
if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') {
build = versions[versions.length - 1]; // The most recent build
}
else {
build = versions.find(v => v === this.options.loader.build);
}
if (!build) {
return {
error: `NeoForge Loader ${this.options.loader.build} not found, Available builds: ${versions.join(', ')}`
};
}
// Build the installer URL, depending on whether we use the legacy or new API
if (oldAPI) {
neoForgeURL = Loader.legacyInstall.replaceAll(/\${version}/g, build);
}
else {
neoForgeURL = Loader.install.replaceAll(/\${version}/g, build);
}
// Create a local folder for "neoForge" if it doesn't exist
const neoForgeFolder = path_1.default.resolve(this.options.path, 'libraries/net/neoforged/installer');
const installerFilePath = path_1.default.resolve(neoForgeFolder, `neoForge-${build}-installer.jar`);
if (!fs_1.default.existsSync(installerFilePath)) {
if (!fs_1.default.existsSync(neoForgeFolder)) {
fs_1.default.mkdirSync(neoForgeFolder, { recursive: true });
}
const downloader = new Downloader_js_1.default();
downloader.on('progress', (downloaded, size) => {
this.emit('progress', downloaded, size, `neoForge-${build}-installer.jar`);
});
await downloader.downloadFile(neoForgeURL, neoForgeFolder, `neoForge-${build}-installer.jar`);
}
return { filePath: installerFilePath, oldAPI };
}
/**
* Extracts the main JSON profile (install_profile.json) from the NeoForge installer.
* If the JSON references an additional file, it also extracts and parses that, returning
* a unified object with `install` and `version` keys.
*
* @param pathInstaller Full path to the downloaded NeoForge installer jar.
* @returns A NeoForgeProfile object, or an error if invalid.
*/
async extractProfile(pathInstaller) {
const fileContent = await (0, Index_js_1.getFileFromArchive)(pathInstaller, 'install_profile.json');
if (!fileContent) {
return { error: { message: 'Invalid neoForge installer' } };
}
const neoForgeJsonOrigin = JSON.parse(fileContent.toString());
if (!neoForgeJsonOrigin) {
return { error: { message: 'Invalid neoForge installer' } };
}
const result = { data: {} };
if (neoForgeJsonOrigin.install) {
result.install = neoForgeJsonOrigin.install;
result.version = neoForgeJsonOrigin.versionInfo;
}
else {
result.install = neoForgeJsonOrigin;
const extraFile = await (0, Index_js_1.getFileFromArchive)(pathInstaller, path_1.default.basename(result.install.json));
if (extraFile) {
result.version = JSON.parse(extraFile.toString());
}
else {
return { error: { message: 'Unable to read additional JSON from neoForge installer' } };
}
}
return result;
}
/**
* Extracts the universal jar or associated files for NeoForge into the local "libraries" directory.
* Also handles client.lzma if processors are present. Returns a boolean indicating whether we skip
* certain neoforge libraries in subsequent steps.
*
* @param profile The extracted NeoForge profile with file path references
* @param pathInstaller Path to the NeoForge installer
* @param oldAPI Whether we are dealing with the old or new NeoForge API (affects library naming)
* @returns A boolean indicating if we should filter out certain libraries afterwards
*/
async extractUniversalJar(profile, pathInstaller, oldAPI) {
let skipNeoForgeFilter = true;
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 });
}
}
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}`);
if (filesInArchive && Array.isArray(filesInArchive)) {
for (const file of filesInArchive) {
const fileName = path_1.default.basename(file);
this.emit('extract', `Extracting ${fileName}...`);
const content = await (0, Index_js_1.getFileFromArchive)(pathInstaller, file);
if (!content)
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), content, { mode: 0o777 });
}
}
}
else {
skipNeoForgeFilter = false;
}
if (profile.processors?.length) {
const universalPath = profile.libraries?.find(lib => (lib.name || '').startsWith(oldAPI ? 'net.neoforged:forge' : 'net.neoforged:neoforge'));
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 skipNeoForgeFilter;
}
/**
* Downloads all libraries referenced in the NeoForge profile. If skipNeoForgeFilter is true,
* certain core libraries are excluded. Checks for duplicates and local existence before downloading.
*
* @param profile The NeoForge profile containing version/install libraries
* @param skipNeoForgeFilter Whether we skip specific "net.minecraftforge:neoforged" libs
* @returns An array of library objects after download, or an error object if something fails
*/
async downloadLibraries(profile, skipNeoForgeFilter) {
let libraries = profile.version?.libraries || [];
const dl = new Downloader_js_1.default();
let checkCount = 0;
const pendingFiles = [];
let totalSize = 0;
// Combine install.libraries with version.libraries
if (profile.install?.libraries) {
libraries = libraries.concat(profile.install.libraries);
}
// Remove duplicates by 'name'
libraries = libraries.filter((lib, index, self) => index === self.findIndex(item => item.name === lib.name));
// If skipping certain neoforge libs
const skipNeoForge = ['net.minecraftforge:neoforged:', 'net.minecraftforge:minecraftforge:'];
// Evaluate each library
for (const lib of libraries) {
if (skipNeoForgeFilter && skipNeoForge.some(str => lib.name.includes(str))) {
// If there's no valid artifact URL, skip it
if (!lib.downloads?.artifact?.url) {
this.emit('check', checkCount++, libraries.length, 'libraries');
continue;
}
}
// If the library has rules, skip automatically
if (lib.rules) {
this.emit('check', checkCount++, libraries.length, 'libraries');
continue;
}
// Construct the local path to the library
const libInfo = (0, Index_js_1.getPathLibraries)(lib.name);
const libFolder = path_1.default.resolve(this.options.path, 'libraries', libInfo.path);
const libFilePath = path_1.default.resolve(libFolder, libInfo.name);
// If it doesn't exist locally, schedule for download
if (!fs_1.default.existsSync(libFilePath)) {
let finalURL = null;
let fileSize = 0;
// Attempt to resolve via mirror first
const baseURL = `${libInfo.path}/${libInfo.name}`;
const mirrorCheck = await dl.checkMirror(baseURL, Index_js_1.mirrors);
if (mirrorCheck && typeof mirrorCheck === 'object' && 'status' in mirrorCheck && mirrorCheck.status === 200) {
finalURL = mirrorCheck.url;
fileSize = mirrorCheck.size;
totalSize += fileSize;
}
else if (lib.downloads?.artifact) {
finalURL = lib.downloads.artifact.url;
fileSize = lib.downloads.artifact.size;
totalSize += fileSize;
}
if (!finalURL) {
return { error: `Impossible to download ${libInfo.name}` };
}
pendingFiles.push({
url: finalURL,
folder: libFolder,
path: libFilePath,
name: libInfo.name,
size: fileSize
});
}
this.emit('check', checkCount++, libraries.length, 'libraries');
}
// Download all pending files
if (pendingFiles.length > 0) {
dl.on('progress', (downloaded, totDL) => {
this.emit('progress', downloaded, totDL, 'libraries');
});
await dl.downloadFileMultiple(pendingFiles, totalSize, this.options.downloadFileMultiple);
}
return libraries;
}
/**
* Runs the NeoForge patch process, if any processors exist. Checks if patching is needed,
* then uses the `NeoForgePatcher` class. If the patch is already applied, it skips.
*
* @param profile The NeoForge profile, which may include processors.
* @param oldAPI Whether we are dealing with the old or new API (passed to the patcher).
* @returns True on success or if no patch was needed.
*/
async patchneoForge(profile, oldAPI) {
if (profile?.processors?.length) {
const patcher = new patcher_js_1.default(this.options);
// Relay events
patcher.on('patch', (data) => {
this.emit('patch', data);
});
patcher.on('error', (error) => {
this.emit('error', error);
});
// If not already patched, run the patcher
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, oldAPI);
}
}
return true;
}
}
exports.default = NeoForgeMC;
//# sourceMappingURL=neoForge.js.map