UNPKG

minecraft-java-core-azbetter

Version:

A library starting minecraft game NW.js and Electron.js

296 lines 14.8 kB
"use strict"; /** * 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")); /** * 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; } /** * 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. * * @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; // Fetch versions from the legacy API const legacyMetaData = await fetch(Loader.legacyMetaData).then(res => res.json()); const metaData = await fetch(Loader.metaData).then(res => res.json()); // Filter versions for the specified Minecraft version let versions = legacyMetaData.versions.filter((v) => v.includes(`${this.options.loader.version}-`)); // If none found, fallback to the new API approach if (!versions.length) { const splitted = this.options.loader.version.split('.'); const shortVersion = `${splitted[1]}.${splitted[2] || 0}`; versions = metaData.versions.filter((v) => v.startsWith(shortVersion)); oldAPI = false; } // If still no versions found, return an error if (!versions.length) { return { error: `NeoForge doesn't support Minecraft ${this.options.loader.version}` }; } // 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, 'neoForge'); 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 { // If no direct reference, do not skip the library filtering skipNeoForgeFilter = false; } // If processors exist, we likely need to store client.lzma 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