From 3d5cdf0bff015d9df8af0d66959d4527e9784eb0 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:47:47 +0200 Subject: [PATCH 001/185] fix error permes --- package-lock.json | 14 +- package.json | 3 +- src/Launch.ts | 10 +- src/Minecraft-Loader/index.ts | 144 +++++++++++ src/Minecraft-Loader/loader/fabric/fabric.ts | 94 +++++++ src/Minecraft-Loader/loader/forge/forge.ts | 244 +++++++++++++++++++ src/Minecraft-Loader/loader/forge/patcher.ts | 141 +++++++++++ src/Minecraft-Loader/loader/quilt/quilt.ts | 97 ++++++++ src/Minecraft/Minecraft-Bundle.ts | 82 ++++++- src/Minecraft/Minecraft-Libraries.ts | 63 ----- src/Minecraft/Minecraft-Loader.ts | 4 +- src/utils/Downloader.ts | 56 +++++ src/utils/Index.ts | 77 +++++- test/index.js | 2 +- 14 files changed, 935 insertions(+), 96 deletions(-) create mode 100644 src/Minecraft-Loader/index.ts create mode 100644 src/Minecraft-Loader/loader/fabric/fabric.ts create mode 100644 src/Minecraft-Loader/loader/forge/forge.ts create mode 100644 src/Minecraft-Loader/loader/forge/patcher.ts create mode 100644 src/Minecraft-Loader/loader/quilt/quilt.ts diff --git a/package-lock.json b/package-lock.json index a7062999..c710d178 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "minecraft-java-core", - "version": "3.5.7", + "version": "3.6.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.5.7", + "version": "3.6.0-beta.1", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", - "minecraft-loader": "^1.3.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", "tslib": "^2.4.1" @@ -219,15 +218,6 @@ "node": ">= 0.6" } }, - "node_modules/minecraft-loader": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/minecraft-loader/-/minecraft-loader-1.3.0.tgz", - "integrity": "sha512-r+tXeGBb8CyIVfpLTL1fdG7GQv0yfzygktYk3BVWMYNFLhd3nGBxN/HCR5Lo53iir8m9P/2X9S2jGJ74BDqbHQ==", - "dependencies": { - "adm-zip": "^0.5.10", - "node-fetch": "^2.6.9" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index a68e30da..fea4beb0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.5.7", + "version": "3.6.0-beta.1", "types": "./build/Index.d.ts", "exports": { ".": { @@ -32,7 +32,6 @@ "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", - "minecraft-loader": "^1.3.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", "tslib": "^2.4.1" diff --git a/src/Launch.ts b/src/Launch.ts index 5c4d8848..ac667776 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -114,18 +114,18 @@ export default class Launch { let { json, version } = InfoVersion; let libraries = new librariesMinecraft(this.options) + let bundle = new bundleMinecraft(this.options) let gameLibraries: any = await libraries.Getlibraries(json); let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); let gameJava: any = this.options.javaPath ? { files: [] } : await new javaMinecraft(this.options).GetJsonJava(json); - let bundle: any = [...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files] - let filesList: any = await new bundleMinecraft(this.options).checkBundle(bundle); + let filesList: any = await bundle.checkBundle([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); if (filesList.length > 0) { let downloader = new Downloader(); - let totsize = await new bundleMinecraft(this.options).getTotalSize(filesList); + let totsize = await bundle.getTotalSize(filesList); downloader.on("progress", (DL: any, totDL: any, element: any) => { this.emit("progress", DL, totDL, element); @@ -172,9 +172,9 @@ export default class Launch { loaderJson = jsonLoader; } - if (this.options.verify) await libraries.checkFiles(bundle); + if (this.options.verify) await bundle.checkFiles([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); - let natives = await libraries.natives(bundle); + let natives = await libraries.natives(gameLibraries); if (natives.length === 0) json.nativesList = false; else json.nativesList = true; diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts new file mode 100644 index 00000000..565b76c3 --- /dev/null +++ b/src/Minecraft-Loader/index.ts @@ -0,0 +1,144 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { loader } from '../utils/Index.js'; +import Forge from './loader/forge/forge.js'; +import Fabric from './loader/fabric/fabric.js'; +import Quilt from './loader/quilt/quilt.js'; + + +import { EventEmitter } from 'events'; +import fs from 'fs' +import path from 'path' + +export default class Loader { + options: any; + on: any; + emit: any; + + constructor(options) { + this.options = options + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async install() { + let Loader = loader(this.options.loader.type); + if (!Loader) return this.emit('error', { error: `Loader ${this.options.loader.type} not found` }); + + if (this.options.loader.type === 'forge') { + let forge = await this.forge(Loader); + if (forge.error) return this.emit('error', forge); + this.emit('json', forge); + } else if (this.options.loader.type === 'fabric') { + let fabric = await this.fabric(Loader); + if (fabric.error) return this.emit('error', fabric); + this.emit('json', fabric); + } else if (this.options.loader.type === 'quilt') { + let quilt = await this.quilt(Loader); + if (quilt.error) return this.emit('error', quilt); + this.emit('json', quilt); + } else { + return this.emit('error', { error: `Loader ${this.options.loader.type} not found` }); + } + } + + async forge(Loader: any) { + let forge = new Forge(this.options); + + // set event + forge.on('check', (progress, size, element) => { + this.emit('check', progress, size, element); + }); + + forge.on('progress', (progress, size, element) => { + this.emit('progress', progress, size, element); + }); + + forge.on('extract', (element) => { + this.emit('extract', element); + }); + + forge.on('patch', patch => { + this.emit('patch', patch); + }); + + // download installer + let installer = await forge.donwloadInstaller(Loader); + if (installer.error) return installer; + + // extract install profile + let profile: any = await forge.extractProfile(installer.filePath); + if (profile.error) return profile + let destination = path.resolve(this.options.path, 'versions', profile.version.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + + // extract universal jar + let universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); + if (universal.error) return universal; + + // download libraries + let libraries: any = await forge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; + + // patch forge if nessary + let patch: any = await forge.patchForge(profile.install); + if (patch.error) return patch; + + return profile.version; + } + + async fabric(Loader: any) { + let fabric = new Fabric(this.options); + + // set event + fabric.on('check', (progress, size, element) => { + this.emit('check', progress, size, element); + }); + + fabric.on('progress', (progress, size, element) => { + this.emit('progress', progress, size, element); + }); + + // download Json + let json = await fabric.downloadJson(Loader); + if (json.error) return json; + let destination = path.resolve(this.options.path, 'versions', json.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + + // download libraries + await fabric.downloadLibraries(json); + + return json; + } + + async quilt(Loader: any) { + let quilt = new Quilt(this.options); + + // set event + quilt.on('check', (progress, size, element) => { + this.emit('check', progress, size, element); + }); + + quilt.on('progress', (progress, size, element) => { + this.emit('progress', progress, size, element); + }); + + // download Json + let json = await quilt.downloadJson(Loader); + + if (json.error) return json; + let destination = path.resolve(this.options.path, 'versions', json.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + + // // download libraries + await quilt.downloadLibraries(json); + + return json; + } +} \ No newline at end of file diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts new file mode 100644 index 00000000..a7e9eb14 --- /dev/null +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -0,0 +1,94 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { getPathLibraries } from '../../../utils/Index.js'; +import download from '../../../utils/Downloader.js'; + +import nodeFetch from 'node-fetch' +import fs from 'fs' +import path from'path' +import { EventEmitter } from 'events'; + +export default class FabricMC { + options: any; + on: any; + emit: any; + + constructor(options = {}) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async downloadJson(Loader) { + let build + let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + + let version = metaData.game.find(version => version.version === this.options.loader.version); + let AvailableBuilds = metaData.loader.map(build => build.version); + if (!version) return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; + + if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { + build = metaData.loader[0]; + } else { + build = metaData.loader.find(loader => loader.version === this.options.loader.build); + } + + if (!build) return { error: `Fabric Loader ${this.options.loader.build} not fond, Available builds: ${AvailableBuilds.join(', ')}` }; + + let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); + let json = await nodeFetch(url).then(res => res.json()).catch(err => err); + return json + } + + async downloadLibraries(json) { + let { libraries } = json; + let downloader = new download(); + let files:any = []; + let check = 0; + let size = 0; + + for (let lib of libraries) { + if (lib.rules) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + let file = {} + let libInfo = getPathLibraries(lib.name); + let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); + let pathLibFile = path.resolve(pathLib, libInfo.name); + + if (!fs.existsSync(pathLibFile)) { + let url = `${lib.url}${libInfo.path}/${libInfo.name}` + let sizeFile = 0 + + let res:any = await downloader.checkURL(url); + if (res.status === 200) { + sizeFile = res.size; + size += res.size; + } + + file = { + url: url, + folder: pathLib, + path: `${pathLib}/${libInfo.name}`, + name: libInfo.name, + size: sizeFile + } + files.push(file); + } + this.emit('check', check++, libraries.length, 'libraries'); + } + + if (files.length > 0) { + downloader.on("progress", (DL, totDL) => { + this.emit("progress", DL, totDL, 'libraries'); + }); + + await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); + } + return libraries + } +} \ No newline at end of file diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts new file mode 100644 index 00000000..ea332b28 --- /dev/null +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -0,0 +1,244 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { getPathLibraries, getFileHash, mirrors, getFileFromJar } from '../../../utils/Index.js'; +import download from '../../../utils/Downloader.js'; +import forgePatcher from './patcher.js' + +import nodeFetch from 'node-fetch' +import fs from 'fs' +import path from 'path' +import { EventEmitter } from 'events'; + +export default class ForgeMC { + options: any; + on: any; + emit: any; + + constructor(options = {}) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async donwloadInstaller(Loader: any) { + let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; + let AvailableBuilds = metaData; + let forgeURL = Loader.install + if (!metaData) return { error: `Forge ${this.options.loader.version} not supported` }; + + let build + if (this.options.loader.build === 'latest') { + let promotions = await nodeFetch(Loader.promotions).then(res => res.json()); + promotions = promotions.promos[`${this.options.loader.version}-latest`]; + build = metaData.find(build => build.includes(promotions)) + } else if (this.options.loader.build === 'recommended') { + let promotion = await nodeFetch(Loader.promotions).then(res => res.json()); + let promotions = promotion.promos[`${this.options.loader.version}-recommended`]; + if (!promotions) promotions = promotion.promos[`${this.options.loader.version}-latest`]; + build = metaData.find(build => build.includes(promotions)) + } else { + build = this.options.loader.build; + } + + metaData = metaData.filter(b => b === build)[0]; + if (!metaData) return { error: `Build ${build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; + + forgeURL = forgeURL.replace(/\${version}/g, metaData); + let urlMeta = Loader.meta.replace(/\${build}/g, metaData); + + let pathFolder = path.resolve(this.options.path, 'forge'); + let filePath = path.resolve(pathFolder, `forge-${metaData}-installer.jar`); + let meta = await nodeFetch(urlMeta).then(res => res.json()); + + if (!fs.existsSync(filePath)) { + if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); + let downloadForge = new download(); + + downloadForge.on('progress', (downloaded, size) => { + this.emit('progress', downloaded, size, `forge-${metaData}-installer.jar`); + }); + + await downloadForge.downloadFile(forgeURL, pathFolder, `forge-${metaData}-installer.jar`); + } + + let hashFileDownload = await getFileHash(filePath, 'md5'); + let hashFileOrigin = meta?.classifiers?.installer?.jar; + + if (hashFileDownload !== hashFileOrigin) { + fs.rmSync(filePath); + return { error: 'Invalid hash' }; + } + return { filePath, metaData } + } + + async extractProfile(pathInstaller: any) { + let forgeJSON: any = {} + + let file: any = await getFileFromJar(pathInstaller, 'install_profile.json') + let forgeJsonOrigin = JSON.parse(file); + + if (!forgeJsonOrigin) return { error: { message: 'Invalid forge installer' } }; + if (forgeJsonOrigin.install) { + forgeJSON.install = forgeJsonOrigin.install; + forgeJSON.version = forgeJsonOrigin.versionInfo; + } else { + forgeJSON.install = forgeJsonOrigin; + let file: any = await getFileFromJar(pathInstaller, path.basename(forgeJSON.install.json)) + forgeJSON.version = JSON.parse(file); + } + + return forgeJSON; + } + + async extractUniversalJar(profile: any, pathInstaller: any) { + let skipForgeFilter = true + + if (profile.filePath) { + let fileInfo = getPathLibraries(profile.path) + this.emit('extract', `Extracting ${fileInfo.name}...`); + + let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) + if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); + + let file: any = await getFileFromJar(pathInstaller, profile.filePath) + fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) + } else if (profile.path) { + let fileInfo = getPathLibraries(profile.path) + let listFile: any = await getFileFromJar(pathInstaller, null, `maven/${fileInfo.path}`) + + await Promise.all( + listFile.map(async (files: any) => { + let fileName = files.split('/') + this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); + let file: any = await getFileFromJar(pathInstaller, files) + let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) + if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); + fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) + }) + ); + } else { + skipForgeFilter = false + } + + if (profile.processors?.length) { + let universalPath = profile.libraries.find(v => { + return (v.name || '').startsWith('net.minecraftforge:forge') + }) + + let client: any = await getFileFromJar(pathInstaller, 'data/client.lzma'); + let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') + let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) + + if (!fs.existsSync(pathFile)) fs.mkdirSync(pathFile, { recursive: true }); + fs.writeFileSync(`${pathFile}/${fileInfo.name}`, client, { mode: 0o777 }) + this.emit('extract', `Extracting ${fileInfo.name}...`); + } + + return skipForgeFilter + } + + async downloadLibraries(profile: any, skipForgeFilter: any) { + let { libraries } = profile.version; + let downloader = new download(); + let check = 0; + let files: any = []; + let size = 0; + + if (profile.install.libraries) libraries = libraries.concat(profile.install.libraries); + + libraries = libraries.filter((library, index, self) => index === self.findIndex(t => t.name === library.name)) + + let skipForge = [ + 'net.minecraftforge:forge:', + 'net.minecraftforge:minecraftforge:' + ] + + for (let lib of libraries) { + if (skipForgeFilter && skipForge.find(libs => lib.name.includes(libs))) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + if (lib.rules) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + let file = {} + let libInfo = getPathLibraries(lib.name); + let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); + let pathLibFile = path.resolve(pathLib, libInfo.name); + + if (!fs.existsSync(pathLibFile)) { + let url + let sizeFile = 0 + + let baseURL = `${libInfo.path}/${libInfo.name}`; + let response: any = await downloader.checkMirror(baseURL, mirrors) + + if (response?.status === 200) { + size += response.size; + sizeFile = response.size; + url = response.url; + } else if (lib.downloads?.artifact) { + url = lib.downloads.artifact.url + size += lib.downloads.artifact.size; + sizeFile = lib.downloads.artifact.size; + } else { + url = null + } + + if (url == null || !url) { + return { error: `Impossible to download ${libInfo.name}` }; + } + + file = { + url: url, + folder: pathLib, + path: `${pathLib}/${libInfo.name}`, + name: libInfo.name, + size: sizeFile + } + files.push(file); + } + this.emit('check', check++, libraries.length, 'libraries'); + } + + if (files.length > 0) { + downloader.on("progress", (DL, totDL) => { + this.emit("progress", DL, totDL, 'libraries'); + }); + + await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); + } + return libraries + } + + async patchForge(profile: any) { + if (profile?.processors?.length) { + let patcher: any = new forgePatcher(this.options); + let config: any = {} + + patcher.on('patch', data => { + this.emit('patch', data); + }); + + patcher.on('error', data => { + this.emit('error', data); + }); + + if (!patcher.check(profile)) { + 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 + } +} \ No newline at end of file diff --git a/src/Minecraft-Loader/loader/forge/patcher.ts b/src/Minecraft-Loader/loader/forge/patcher.ts new file mode 100644 index 00000000..8c46214c --- /dev/null +++ b/src/Minecraft-Loader/loader/forge/patcher.ts @@ -0,0 +1,141 @@ +import { spawn } from 'child_process'; +import fs from 'fs' +import path from 'path' +import { EventEmitter } from 'events'; + +import { getPathLibraries, getFileFromJar } from '../../../utils/Index.js'; + + + +export default class forgePatcher { + options: any; + on: any; + emit: any; + + constructor(options: any) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async patcher(profile: any, config: any) { + let { processors } = profile; + + for (let key in processors) { + if (Object.prototype.hasOwnProperty.call(processors, key)) { + let processor = processors[key]; + if (processor?.sides && !(processor?.sides || []).includes('client')) { + continue; + } + + let jar = getPathLibraries(processor.jar) + let filePath = path.resolve(this.options.path, 'libraries', jar.path, jar.name) + + let args = processor.args.map(arg => this.setArgument(arg, profile, config)).map(arg => this.computePath(arg)); + let classPaths = processor.classpath.map(cp => { + let classPath = getPathLibraries(cp) + return `"${path.join(this.options.path, 'libraries', `${classPath.path}/${classPath.name}`)}"` + }); + let mainClass = await this.readJarManifest(filePath); + + await new Promise((resolve: any) => { + const ps = spawn( + `"${path.resolve(config.java)}"`, + [ + '-classpath', + [`"${filePath}"`, ...classPaths].join(path.delimiter), + mainClass, + ...args + ], { shell: true } + ); + + ps.stdout.on('data', data => { + this.emit('patch', data.toString('utf-8')) + }); + + ps.stderr.on('data', data => { + this.emit('patch', data.toString('utf-8')) + }); + + ps.on('close', code => { + if (code !== 0) { + this.emit('error', `Forge patcher exited with code ${code}`); + resolve(); + } + resolve(); + }); + }); + } + } + + } + + check(profile: any) { + let files = []; + let { processors } = profile; + + for (let key in processors) { + if (Object.prototype.hasOwnProperty.call(processors, key)) { + let processor = processors[key]; + if (processor?.sides && !(processor?.sides || []).includes('client')) continue; + + processor.args.map(arg => { + let finalArg = arg.replace('{', '').replace('}', ''); + if (profile.data[finalArg]) { + if (finalArg === 'BINPATCH') return + files.push(profile.data[finalArg].client) + } + }) + } + } + + files = files.filter((item, index) => files.indexOf(item) === index); + + for (let file of files) { + let libMCP = getPathLibraries(file.replace('[', '').replace(']', '')) + file = `${path.resolve(this.options.path, 'libraries', `${libMCP.path}/${libMCP.name}`)}`; + if (!fs.existsSync(file)) return false + } + return true; + } + + setArgument(arg: any, profile: any, config: any) { + let finalArg = arg.replace('{', '').replace('}', ''); + let universalPath = profile.libraries.find(v => + (v.name || '').startsWith('net.minecraftforge:forge') + ) + + if (profile.data[finalArg]) { + if (finalArg === 'BINPATCH') { + let clientdata = getPathLibraries(profile.path || universalPath.name) + return `"${path + .join(this.options.path, 'libraries', `${clientdata.path}/${clientdata.name}`) + .replace('.jar', '-clientdata.lzma')}"`; + } + return profile.data[finalArg].client; + } + + return arg + .replace('{SIDE}', `client`) + .replace('{ROOT}', `"${path.dirname(path.resolve(this.options.path, 'forge'))}"`) + .replace('{MINECRAFT_JAR}', `"${config.minecraft}"`) + .replace('{MINECRAFT_VERSION}', `"${config.minecraftJson}"`) + .replace('{INSTALLER}', `"${this.options.path}/libraries"`) + .replace('{LIBRARY_DIR}', `"${this.options.path}/libraries"`); + } + + computePath(arg: any) { + if (arg[0] === '[') { + let libMCP = getPathLibraries(arg.replace('[', '').replace(']', '')) + return `"${path.join(this.options.path, 'libraries', `${libMCP.path}/${libMCP.name}`)}"`; + } + return arg; + } + + async readJarManifest(jarPath: string) { + let extraction: any = await getFileFromJar(jarPath, 'META-INF/MANIFEST.MF'); + + if (extraction) return (extraction.toString("utf8")).split('Main-Class: ')[1].split('\r\n')[0]; + return null; + } +} \ No newline at end of file diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts new file mode 100644 index 00000000..1e6f550a --- /dev/null +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -0,0 +1,97 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { getPathLibraries } from '../../../utils/Index.js'; +import download from '../../../utils/Downloader.js'; + +import nodeFetch from 'node-fetch' +import fs from 'fs' +import path from 'path' +import { EventEmitter } from 'events'; + +export default class Quilt { + options: any; + versionMinecraft: any; + on: any; + emit: any; + + constructor(options = {}) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async downloadJson(Loader: any) { + let build: any + let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + + let version = metaData.game.find(version => version.version === this.options.loader.version); + let AvailableBuilds = metaData.loader.map(build => build.version); + if (!version) return { error: `QuiltMC doesn't support Minecraft ${this.options.loader.version}` }; + + if (this.options.loader.build === 'latest') { + build = metaData.loader[0]; + } else if (this.options.loader.build === 'recommended') { + build = metaData.loader.find(build => !build.version.includes('beta')); + } else { + build = metaData.loader.find(loader => loader.version === this.options.loader.build); + } + + if (!build) return { error: `QuiltMC Loader ${this.options.loader.build} not fond, Available builds: ${AvailableBuilds.join(', ')}` }; + + let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); + let json = await nodeFetch(url).then(res => res.json()).catch(err => err); + return json + } + + async downloadLibraries(json) { + let { libraries } = json; + let downloader = new download(); + let files: any = []; + let check = 0; + let size = 0; + + for (let lib of libraries) { + if (lib.rules) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + let file = {} + let libInfo = getPathLibraries(lib.name); + let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); + let pathLibFile = path.resolve(pathLib, libInfo.name); + + if (!fs.existsSync(pathLibFile)) { + let url = `${lib.url}${libInfo.path}/${libInfo.name}` + let sizeFile = 0 + + let res: any = await downloader.checkURL(url); + if (res.status === 200) { + sizeFile = res.size; + size += res.size; + } + + file = { + url: url, + folder: pathLib, + path: `${pathLib}/${libInfo.name}`, + name: libInfo.name, + size: sizeFile + } + files.push(file); + } + this.emit('check', check++, libraries.length, 'libraries'); + } + + if (files.length > 0) { + downloader.on("progress", (DL: any, totDL: any) => { + this.emit("progress", DL, totDL, 'libraries'); + }); + + await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); + } + return libraries + } +} \ No newline at end of file diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index ad047eb3..8e032680 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import path from 'path'; -import crypto from 'crypto'; +import { getFileHash } from '../utils/Index.js' export default class MinecraftBundle { options: any; @@ -29,18 +29,19 @@ export default class MinecraftBundle { if (fs.existsSync(file.path)) { if (this.options.ignored.find(ignored => ignored == file.path.split("/").slice(-1)[0])) continue - if (file.sha1) if (!(await this.checkSHA1(file.path, file.sha1))) todownload.push(file); - } else todownload.push(file); + if (file.sha1) { + if (await getFileHash(file.path) != file.sha1) { + todownload.push(file); + } + } + + } else { + todownload.push(file); + } } return todownload; } - async checkSHA1(file: string, sha1: string) { - const hex = crypto.createHash('sha1').update(fs.readFileSync(file)).digest('hex') - if (hex == sha1) return true; - return false; - } - async getTotalSize(bundle: any) { let todownload = 0; for (let file of bundle) { @@ -48,4 +49,67 @@ export default class MinecraftBundle { } return todownload; } + + async checkFiles(bundle: any) { + let instancePath = '' + let instanceFolder = [] + if (this.options.instance) { + if (!fs.existsSync(`${this.options.path}/instances`)) fs.mkdirSync(`${this.options.path}/instances`, { recursive: true }); + instancePath = `/instances/${this.options.instance}` + instanceFolder = fs.readdirSync(`${this.options.path}/instances`).filter(dir => dir != this.options.instance) + } + let files = this.getFiles(this.options.path); + let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`)] + + for (let instances of instanceFolder) { + ignoredfiles.push(...this.getFiles(`${this.options.path}/instances/${instances}`)); + } + + for (let file of this.options.ignored) { + file = (`${this.options.path}${instancePath}/${file}`) + if (fs.existsSync(file)) { + if (fs.statSync(file).isDirectory()) { + ignoredfiles.push(...this.getFiles(file)); + } else if (fs.statSync(file).isFile()) { + ignoredfiles.push(file); + } + } + } + + ignoredfiles.forEach(file => this.options.ignored.push((file))); + bundle.forEach(file => ignoredfiles.push((file.path))); + files = files.filter(file => ignoredfiles.indexOf(file) < 0); + + for (let file of files) { + try { + if (fs.statSync(file).isDirectory()) { + fs.rmdirSync(file); + } else { + fs.unlinkSync(file); + let folder = file.split("/").slice(0, -1).join("/"); + while (true) { + if (folder == this.options.path) break; + let content = fs.readdirSync(folder); + if (content.length == 0) fs.rmdirSync(folder); + folder = folder.split("/").slice(0, -1).join("/"); + } + } + } catch (e) { + continue; + } + } + } + + getFiles(path: any, file = []) { + if (fs.existsSync(path)) { + let files = fs.readdirSync(path); + if (files.length == 0) file.push(path); + for (let i in files) { + let name = `${path}/${files[i]}`; + if (fs.statSync(name).isDirectory()) this.getFiles(name, file); + else file.push(name); + } + } + return file; + } } \ No newline at end of file diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 5ca38b0f..2576ca8a 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -105,67 +105,4 @@ export default class Libraries { } return natives; } - - async checkFiles(bundle: any) { - let instancePath = '' - let instanceFolder = [] - if (this.options.instance) { - if (!fs.existsSync(`${this.options.path}/instances`)) fs.mkdirSync(`${this.options.path}/instances`, { recursive: true }); - instancePath = `/instances/${this.options.instance}` - instanceFolder = fs.readdirSync(`${this.options.path}/instances`).filter(dir => dir != this.options.instance) - } - let files = this.getFiles(this.options.path); - let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`)] - - for (let instances of instanceFolder) { - ignoredfiles.push(...this.getFiles(`${this.options.path}/instances/${instances}`)); - } - - for (let file of this.options.ignored) { - file = (`${this.options.path}${instancePath}/${file}`) - if (fs.existsSync(file)) { - if (fs.statSync(file).isDirectory()) { - ignoredfiles.push(...this.getFiles(file)); - } else if (fs.statSync(file).isFile()) { - ignoredfiles.push(file); - } - } - } - - ignoredfiles.forEach(file => this.options.ignored.push((file))); - bundle.forEach(file => ignoredfiles.push((file.path))); - files = files.filter(file => ignoredfiles.indexOf(file) < 0); - - for (let file of files) { - try { - if (fs.statSync(file).isDirectory()) { - fs.rmdirSync(file); - } else { - fs.unlinkSync(file); - let folder = file.split("/").slice(0, -1).join("/"); - while (true) { - if (folder == this.options.path) break; - let content = fs.readdirSync(folder); - if (content.length == 0) fs.rmdirSync(folder); - folder = folder.split("/").slice(0, -1).join("/"); - } - } - } catch (e) { - continue; - } - } - } - - getFiles(path: any, file = []) { - if (fs.existsSync(path)) { - let files = fs.readdirSync(path); - if (files.length == 0) file.push(path); - for (let i in files) { - let name = `${path}/${files[i]}`; - if (fs.statSync(name).isDirectory()) this.getFiles(name, file); - else file.push(name); - } - } - return file; - } } \ No newline at end of file diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 4141872c..1f60f107 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -4,7 +4,7 @@ */ import { EventEmitter } from 'events'; -const loaderDownloader = require('minecraft-loader'); +import loaderDownloader from '../Minecraft-Loader/index.js' export default class MinecraftLoader { @@ -20,9 +20,7 @@ export default class MinecraftLoader { async GetLoader(version: any, javaPath: any) { let loader = new loaderDownloader({ path: `${this.options.path}/loader/${this.options.loader.type}`, - timeout: this.options.timeout, downloadFileMultiple: this.options.downloadFileMultiple, - autoClean: true, loader: { type: this.options.loader.type, version: version, diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index 0f352fe1..660dfc23 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -23,6 +23,31 @@ export default class download { this.emit = EventEmitter.prototype.emit; } + async downloadFile(url: string, path: string, fileName: string) { + if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true }); + const writer = fs.createWriteStream(path + '/' + fileName); + const response = await nodeFetch(url); + const size = response.headers.get('content-length'); + let downloaded = 0; + return new Promise((resolve: any, reject: any) => { + response.body.on('data', (chunk) => { + downloaded += chunk.length; + this.emit('progress', downloaded, size); + writer.write(chunk); + }); + + response.body.on('end', () => { + writer.end(); + resolve(); + }); + + response.body.on('error', (err) => { + this.emit('error', err); + reject(err); + }); + }) + } + async downloadFileMultiple(files: downloadOptions, size: number, limit: number = 1, timeout: number = 10000) { if (limit > files.length) limit = files.length; let completed = 0; @@ -92,4 +117,35 @@ export default class download { }, 100); }); } + + async checkURL(url: string, timeout = 10000) { + return await new Promise(async (resolve, reject) => { + await nodeFetch(url, { method: 'HEAD', timeout: timeout }).then(res => { + if (res.status === 200) { + resolve({ + size: parseInt(res.headers.get('content-length')), + status: res.status + }) + } + }) + reject(false); + }); + } + + async checkMirror(baseURL: string, mirrors: any) { + for (let mirror of mirrors) { + let url = `${mirror}/${baseURL}`; + let res: any = await this.checkURL(url).then(res => res).catch(err => false); + + if (res?.status == 200) { + return { + url: url, + size: res.size, + status: res.status + } + break; + } continue; + } + return false; + } } \ No newline at end of file diff --git a/src/utils/Index.ts b/src/utils/Index.ts index aa299dc2..9fc4eb4d 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -3,6 +3,10 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ +import crypto from 'crypto'; +import fs from 'fs'; +import admZip from 'adm-zip'; + function getPathLibraries(main: any, nativeString?: any, forceExt?: any) { let libSplit = main.split(':') let fileName = libSplit[3] ? `${libSplit[2]}-${libSplit[3]}` : libSplit[2]; @@ -14,8 +18,79 @@ function getPathLibraries(main: any, nativeString?: any, forceExt?: any) { }; } +async function getFileHash(filePath: string, algorithm: string = 'sha1') { + let shasum = crypto.createHash(algorithm); + + let file = fs.createReadStream(filePath); + file.on('data', data => { + shasum.update(data); + }); + + let hash = await new Promise(resolve => { + file.on('end', () => { + resolve(shasum.digest('hex')); + }); + }); + return hash; +} + function isold(json: any) { return json.assets === 'legacy' || json.assets === 'pre-1.6' } -export { getPathLibraries, isold }; \ No newline at end of file +function loader(type: string) { + if (type === 'forge') { + return { + metaData: 'https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json', + meta: 'https://files.minecraftforge.net/net/minecraftforge/forge/${build}/meta.json', + promotions: 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json', + install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer.jar' + + } + } else if (type === 'fabric') { + return { + metaData: 'https://meta.fabricmc.net/v2/versions', + json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' + } + } else if (type === 'quilt') { + return { + metaData: 'https://meta.quiltmc.org/v3/versions', + json: 'https://meta.quiltmc.org/v3/versions/loader/${version}/${build}/profile/json' + } + } +} + + +let mirrors = [ + "https://maven.minecraftforge.net", + "https://maven.creeperhost.net", + "https://libraries.minecraft.net" +] + +async function getFileFromJar(jar: string, file: string = null, path: string = null) { + let fileReturn: any = [] + let zip = new admZip(jar); + let entries = zip.getEntries(); + + return new Promise(resolve => { + for (let entry of entries) { + if (!entry.isDirectory && !path) { + if (entry.entryName == file) fileReturn = entry.getData(); + } + + if (!entry.isDirectory && entry.entryName.includes(path)) { + fileReturn.push(entry.entryName) + } + } + resolve(fileReturn); + }); +} + +export { + getPathLibraries, + isold, + getFileHash, + mirrors, + loader, + getFileFromJar +}; \ No newline at end of file diff --git a/test/index.js b/test/index.js index 84e123f6..fcc32820 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.20.1', + version: '1.16.5', detached: false, downloadFileMultiple: 30, From 9ac59d9cecc3e622eb94a88e72bebc922e7b8f19 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:54:05 +0200 Subject: [PATCH 002/185] Update index.js --- test/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index fcc32820..91e077d3 100755 --- a/test/index.js +++ b/test/index.js @@ -27,13 +27,13 @@ let mc timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.16.5', + version: '1.20.1', detached: false, downloadFileMultiple: 30, loader: { - type: 'forge', - build: 'latest', + type: 'fabric', + build: '0.14.22', enable: true }, From 44a5addcb4b13bae248f8eaed8e05bbe53ffe725 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:50:31 +0200 Subject: [PATCH 003/185] add support neo-forge --- package-lock.json | 4 +- package.json | 2 +- src/Launch.ts | 11 +- src/Minecraft-Loader/index.ts | 57 ++++- src/Minecraft-Loader/loader/fabric/fabric.ts | 2 +- src/Minecraft-Loader/loader/forge/forge.ts | 4 +- .../loader/neoForge/neoForge.ts | 226 ++++++++++++++++++ src/Minecraft-Loader/loader/quilt/quilt.ts | 2 +- .../{loader/forge => }/patcher.ts | 9 +- src/Minecraft/Minecraft-Arguments.ts | 17 +- src/Minecraft/Minecraft-Loader.ts | 2 +- src/utils/Index.ts | 7 +- test/3D/index.html | 14 -- test/3D/index.js | 24 -- test/index.js | 27 ++- tsconfig.json | 6 +- 16 files changed, 344 insertions(+), 70 deletions(-) create mode 100644 src/Minecraft-Loader/loader/neoForge/neoForge.ts rename src/Minecraft-Loader/{loader/forge => }/patcher.ts (93%) delete mode 100755 test/3D/index.html delete mode 100755 test/3D/index.js diff --git a/package-lock.json b/package-lock.json index c710d178..11301c28 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.0-beta.1", + "version": "3.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.0-beta.1", + "version": "3.6.0", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index fea4beb0..7240516a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.0-beta.1", + "version": "3.6.0", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Launch.ts b/src/Launch.ts index ac667776..3a2f2f06 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -45,8 +45,8 @@ export default class Launch { downloadFileMultiple: opt?.downloadFileMultiple || 3, loader: { - type: opt?.loader?.type || null, - build: opt?.loader?.build || 'latest', + type: opt?.loader?.type?.toLowerCase() || null, + build: opt?.loader?.build?.toLowerCase() || 'latest', enable: opt?.loader?.enable || false, }, @@ -89,10 +89,11 @@ export default class Launch { let Arguments: any = [ ...minecraftArguments.jvm, - ...loaderArguments.jvm, ...minecraftArguments.classpath, - ...loaderArguments.game, - ...minecraftArguments.game + ...loaderArguments.jvm, + minecraftArguments.mainClass, + ...minecraftArguments.game, + ...loaderArguments.game ] let java: any = this.options.javaPath ? this.options.javaPath : minecraftJava.path; diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 565b76c3..023e9bac 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -5,6 +5,7 @@ import { loader } from '../utils/Index.js'; import Forge from './loader/forge/forge.js'; +import NeoForge from './loader/neoForge/neoForge.js'; import Fabric from './loader/fabric/fabric.js'; import Quilt from './loader/quilt/quilt.js'; @@ -18,7 +19,7 @@ export default class Loader { on: any; emit: any; - constructor(options) { + constructor(options: any) { this.options = options this.on = EventEmitter.prototype.on; this.emit = EventEmitter.prototype.emit; @@ -32,6 +33,10 @@ export default class Loader { let forge = await this.forge(Loader); if (forge.error) return this.emit('error', forge); this.emit('json', forge); + } else if (this.options.loader.type === 'neoforge') { + let neoForge = await this.neoForge(Loader); + if (neoForge.error) return this.emit('error', neoForge); + this.emit('json', neoForge); } else if (this.options.loader.type === 'fabric') { let fabric = await this.fabric(Loader); if (fabric.error) return this.emit('error', fabric); @@ -66,7 +71,7 @@ export default class Loader { }); // download installer - let installer = await forge.donwloadInstaller(Loader); + let installer = await forge.downloadInstaller(Loader); if (installer.error) return installer; // extract install profile @@ -91,6 +96,52 @@ export default class Loader { return profile.version; } + async neoForge(Loader: any) { + let neoForge = new NeoForge(this.options); + + // set event + neoForge.on('check', (progress, size, element) => { + this.emit('check', progress, size, element); + }); + + neoForge.on('progress', (progress, size, element) => { + this.emit('progress', progress, size, element); + }); + + neoForge.on('extract', (element) => { + this.emit('extract', element); + }); + + neoForge.on('patch', patch => { + this.emit('patch', patch); + }); + + // download installer + let installer = await neoForge.downloadInstaller(Loader); + if (installer.error) return installer; + + // extract install profile + let profile: any = await neoForge.extractProfile(installer.filePath); + if (profile.error) return profile + let destination = path.resolve(this.options.path, 'versions', profile.version.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + + //extract universal jar + let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath); + if (universal.error) return universal; + + // download libraries + let libraries: any = await neoForge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; + + // patch forge if nessary + let patch: any = await neoForge.patchneoForge(profile.install); + if (patch.error) return patch; + + return profile.version; + } + async fabric(Loader: any) { let fabric = new Fabric(this.options); @@ -116,6 +167,8 @@ export default class Loader { return json; } + + async quilt(Loader: any) { let quilt = new Quilt(this.options); diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts index a7e9eb14..d8124a75 100644 --- a/src/Minecraft-Loader/loader/fabric/fabric.ts +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -36,7 +36,7 @@ export default class FabricMC { build = metaData.loader.find(loader => loader.version === this.options.loader.build); } - if (!build) return { error: `Fabric Loader ${this.options.loader.build} not fond, Available builds: ${AvailableBuilds.join(', ')}` }; + if (!build) return { error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); let json = await nodeFetch(url).then(res => res.json()).catch(err => err); diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index ea332b28..9afc18ec 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -5,7 +5,7 @@ import { getPathLibraries, getFileHash, mirrors, getFileFromJar } from '../../../utils/Index.js'; import download from '../../../utils/Downloader.js'; -import forgePatcher from './patcher.js' +import forgePatcher from '../../patcher.js' import nodeFetch from 'node-fetch' import fs from 'fs' @@ -23,7 +23,7 @@ export default class ForgeMC { this.emit = EventEmitter.prototype.emit; } - async donwloadInstaller(Loader: any) { + async downloadInstaller(Loader: any) { let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; let AvailableBuilds = metaData; let forgeURL = Loader.install diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts new file mode 100644 index 00000000..fbf0271a --- /dev/null +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -0,0 +1,226 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { getPathLibraries, getFileHash, mirrors, getFileFromJar } from '../../../utils/Index.js'; +import download from '../../../utils/Downloader.js'; +import neoForgePatcher from '../../patcher.js' + +import nodeFetch from 'node-fetch' +import fs from 'fs' +import path from 'path' +import { EventEmitter } from 'events'; + +export default class NeoForgeMC { + options: any; + on: any; + emit: any; + + constructor(options = {}) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async downloadInstaller(Loader: any) { + let build: string + let neoForgeURL = Loader.install + let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + + let versions = metaData.versions.filter(version => version.includes(`${this.options.loader.version}-`)); + if (!versions.length) return { error: `NeoForge doesn't support Minecraft ${this.options.loader.version}` }; + + if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { + build = versions[versions.length - 1]; + } else build = versions.find(loader => loader === this.options.loader.build); + + if (!build) return { error: `NeoForge Loader ${this.options.loader.build} not found, Available builds: ${versions.join(', ')}` }; + + neoForgeURL = neoForgeURL.replaceAll(/\${version}/g, build); + + + let pathFolder = path.resolve(this.options.path, 'neoForge'); + let filePath = path.resolve(pathFolder, `forge-${build}-installer.jar`); + + if (!fs.existsSync(filePath)) { + if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); + let downloadForge = new download(); + + downloadForge.on('progress', (downloaded, size) => { + this.emit('progress', downloaded, size, `forge-${build}-installer.jar`); + }); + + await downloadForge.downloadFile(neoForgeURL, pathFolder, `forge-${build}-installer.jar`); + } + + return { filePath }; + } + + async extractProfile(pathInstaller: any) { + let neoForgeJSON: any = {} + + let file: any = await getFileFromJar(pathInstaller, 'install_profile.json') + let neoForgeJsonOrigin = JSON.parse(file); + + if (!neoForgeJsonOrigin) return { error: { message: 'Invalid neoForge installer' } }; + if (neoForgeJsonOrigin.install) { + neoForgeJSON.install = neoForgeJsonOrigin.install; + neoForgeJSON.version = neoForgeJsonOrigin.versionInfo; + } else { + neoForgeJSON.install = neoForgeJsonOrigin; + let file: any = await getFileFromJar(pathInstaller, path.basename(neoForgeJSON.install.json)) + neoForgeJSON.version = JSON.parse(file); + } + + return neoForgeJSON; + } + + async extractUniversalJar(profile: any, pathInstaller: any) { + let skipneoForgeFilter = true + + if (profile.filePath) { + let fileInfo = getPathLibraries(profile.path) + this.emit('extract', `Extracting ${fileInfo.name}...`); + + let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) + if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); + + let file: any = await getFileFromJar(pathInstaller, profile.filePath) + fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) + } else if (profile.path) { + let fileInfo = getPathLibraries(profile.path) + let listFile: any = await getFileFromJar(pathInstaller, null, `maven/${fileInfo.path}`) + + await Promise.all( + listFile.map(async (files: any) => { + let fileName = files.split('/') + this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); + let file: any = await getFileFromJar(pathInstaller, files) + let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) + if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); + fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) + }) + ); + } else { + skipneoForgeFilter = false + } + + if (profile.processors?.length) { + let universalPath = profile.libraries.find(v => { + return (v.name || '').startsWith('net.neoforged:forge') + }) + + let client: any = await getFileFromJar(pathInstaller, 'data/client.lzma'); + let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') + let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) + + if (!fs.existsSync(pathFile)) fs.mkdirSync(pathFile, { recursive: true }); + fs.writeFileSync(`${pathFile}/${fileInfo.name}`, client, { mode: 0o777 }) + this.emit('extract', `Extracting ${fileInfo.name}...`); + } + + return skipneoForgeFilter + } + + async downloadLibraries(profile: any, skipneoForgeFilter: any) { + let { libraries } = profile.version; + let downloader = new download(); + let check = 0; + let files: any = []; + let size = 0; + + if (profile.install.libraries) libraries = libraries.concat(profile.install.libraries); + + libraries = libraries.filter((library, index, self) => index === self.findIndex(t => t.name === library.name)) + + let skipneoForge = [ + 'net.minecraftforge:neoforged:', + 'net.minecraftforge:minecraftforge:' + ] + + for (let lib of libraries) { + if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + if (lib.rules) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + let file = {} + let libInfo = getPathLibraries(lib.name); + let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); + let pathLibFile = path.resolve(pathLib, libInfo.name); + + if (!fs.existsSync(pathLibFile)) { + let url + let sizeFile = 0 + + let baseURL = `${libInfo.path}/${libInfo.name}`; + let response: any = await downloader.checkMirror(baseURL, mirrors) + + if (response?.status === 200) { + size += response.size; + sizeFile = response.size; + url = response.url; + } else if (lib.downloads?.artifact) { + url = lib.downloads.artifact.url + size += lib.downloads.artifact.size; + sizeFile = lib.downloads.artifact.size; + } else { + url = null + } + + if (url == null || !url) { + return { error: `Impossible to download ${libInfo.name}` }; + } + + file = { + url: url, + folder: pathLib, + path: `${pathLib}/${libInfo.name}`, + name: libInfo.name, + size: sizeFile + } + files.push(file); + } + this.emit('check', check++, libraries.length, 'libraries'); + } + + if (files.length > 0) { + downloader.on("progress", (DL, totDL) => { + this.emit("progress", DL, totDL, 'libraries'); + }); + + await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); + } + return libraries + } + + async patchneoForge(profile: any) { + if (profile?.processors?.length) { + let patcher: any = new neoForgePatcher(this.options); + let config: any = {} + + patcher.on('patch', data => { + this.emit('patch', data); + }); + + patcher.on('error', data => { + this.emit('error', data); + }); + + if (!patcher.check(profile)) { + 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 + } +} \ No newline at end of file diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts index 1e6f550a..1e418d9a 100644 --- a/src/Minecraft-Loader/loader/quilt/quilt.ts +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -39,7 +39,7 @@ export default class Quilt { build = metaData.loader.find(loader => loader.version === this.options.loader.build); } - if (!build) return { error: `QuiltMC Loader ${this.options.loader.build} not fond, Available builds: ${AvailableBuilds.join(', ')}` }; + if (!build) return { error: `QuiltMC Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); let json = await nodeFetch(url).then(res => res.json()).catch(err => err); diff --git a/src/Minecraft-Loader/loader/forge/patcher.ts b/src/Minecraft-Loader/patcher.ts similarity index 93% rename from src/Minecraft-Loader/loader/forge/patcher.ts rename to src/Minecraft-Loader/patcher.ts index 8c46214c..b8de04e5 100644 --- a/src/Minecraft-Loader/loader/forge/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -3,7 +3,7 @@ import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; -import { getPathLibraries, getFileFromJar } from '../../../utils/Index.js'; +import { getPathLibraries, getFileFromJar } from '../utils/Index.js'; @@ -101,9 +101,10 @@ export default class forgePatcher { setArgument(arg: any, profile: any, config: any) { let finalArg = arg.replace('{', '').replace('}', ''); - let universalPath = profile.libraries.find(v => - (v.name || '').startsWith('net.minecraftforge:forge') - ) + let universalPath = profile.libraries.find(v => { + if (this.options.loader.type === 'forge') return (v.name || '').startsWith('net.minecraftforge:forge') + if (this.options.loader.type === 'neoforge') return (v.name || '').startsWith('net.neoforged:forge') + }) if (profile.data[finalArg]) { if (finalArg === 'BINPATCH') { diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 36a41e67..79613a31 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -23,7 +23,8 @@ export default class MinecraftArguments { return { game: game, jvm: jvm, - classpath: classpath + classpath: classpath.classpath, + mainClass: classpath.mainClass } } @@ -88,12 +89,11 @@ export default class MinecraftArguments { '-Dfml.ignoreInvalidMinecraftCertificates=true' ] - if (process.platform == 'darwin') { - if (!json.minecraftArguments) { - jvm.push(opts[process.platform]) - } + if (!json.minecraftArguments) { + jvm.push(opts[process.platform]) } + if (json.nativesList) { jvm.push(`-Djava.library.path=${this.options.path}/versions/${json.id}/natives`) } @@ -131,10 +131,11 @@ export default class MinecraftArguments { } classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) - return [ + return {classpath:[ `-cp`, classPath.join(process.platform === 'win32' ? ';' : ':'), - loaderJson ? loaderJson.mainClass : json.mainClass - ] + + ], + mainClass: loaderJson ? loaderJson.mainClass : json.mainClass} } } diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 1f60f107..a96c7fc2 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -82,7 +82,7 @@ export default class MinecraftLoader { if (moddeArguments.jvm) Arguments.jvm = moddeArguments.jvm.map(jvm => { return jvm .replace(/\${version_name}/g, version) - .replace(/\${library_directory}/g, `${this.options.path}/loader/${this.options.loader.type}/libraries`) + .replace(/\${library_directory}/g, `${this.options.path}/loader/${this.options.loader.type.toLowerCase()}/libraries`) .replace(/\${classpath_separator}/g, process.platform === 'win32' ? ';' : ':'); }) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 9fc4eb4d..c530f358 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -45,7 +45,11 @@ function loader(type: string) { meta: 'https://files.minecraftforge.net/net/minecraftforge/forge/${build}/meta.json', promotions: 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json', install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer.jar' - + } + } else if (type === 'neoforge') { + return { + metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', + install: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar' } } else if (type === 'fabric') { return { @@ -63,6 +67,7 @@ function loader(type: string) { let mirrors = [ "https://maven.minecraftforge.net", + "https://maven.neoforged.net/releases", "https://maven.creeperhost.net", "https://libraries.minecraft.net" ] diff --git a/test/3D/index.html b/test/3D/index.html deleted file mode 100755 index ad267ef5..00000000 --- a/test/3D/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - My first three.js app - - - - - - - \ No newline at end of file diff --git a/test/3D/index.js b/test/3D/index.js deleted file mode 100755 index 724de854..00000000 --- a/test/3D/index.js +++ /dev/null @@ -1,24 +0,0 @@ -let skinUrl = 'http://textures.minecraft.net/texture/ac3de5b50fb6174da51032677ead046295486bf682b3ea73e0617a210c4b4f46' - -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera(75, 1000 / 500, 0.1, 100); - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize(1000, 500); -document.body.appendChild(renderer.domElement); - -const geometry = new THREE.BoxGeometry(1, 1, 1); -const material = new THREE.MeshBasicMaterial({ color: '#F1F1F1'}); -const cube = new THREE.Mesh(geometry, material); -scene.add(cube); - -camera.position.z = 2; - -function animate() { - requestAnimationFrame(animate); - cube.rotation.x += 0.01; - cube.rotation.y += 0.05; - renderer.render(scene, camera); -}; - -animate(); \ No newline at end of file diff --git a/test/index.js b/test/index.js index 91e077d3..f9bcc974 100755 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,4 @@ +const { spawn } = require('child_process'); const { Microsoft, Launch, Mojang } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -32,8 +33,8 @@ let mc downloadFileMultiple: 30, loader: { - type: 'fabric', - build: '0.14.22', + type: 'neoForge', + build: 'latest', enable: true }, @@ -107,3 +108,25 @@ let mc console.log(err); }); })() + +// const VERSIONS_ENDPOINT = 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge' +// const DOWNLOAD_URL = 'https://maven.neoforged.net/net/neoforged/forge'; + +// (async () => { +// let versionJson; + +// const response = await fetch(VERSIONS_ENDPOINT); +// versionJson = await response.json(); + +// if (versionJson) { +// let { versions } = versionJson; +// console.log(versions); + +// versions = versions[1]; + +// const installerUrl = `${DOWNLOAD_URL}/${versions}/forge-${versions}-installer.jar`; + +// console.log(installerUrl); + +// } +// })() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 076ada6f..a473eadc 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,10 @@ "sourceMap": false, "target": "ES2020", "module": "CommonJS", - "lib": ["ES2021"], - "declaration": true, + "lib": [ + "ES2021" + ], + "declaration": true, "outDir": "build", "esModuleInterop": true } From 415a0c874a85b51d391a9db261ef03ca971da0cb Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:25:17 +0200 Subject: [PATCH 004/185] add support legacyfabric --- src/Minecraft-Loader/index.ts | 28 ++++++ .../loader/legacyfabric/legacyFabric.ts | 94 +++++++++++++++++++ src/utils/Downloader.ts | 3 +- src/utils/Index.ts | 5 + test/index.js | 4 +- 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 023e9bac..14cca954 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -7,6 +7,7 @@ import { loader } from '../utils/Index.js'; import Forge from './loader/forge/forge.js'; import NeoForge from './loader/neoForge/neoForge.js'; import Fabric from './loader/fabric/fabric.js'; +import LegacyFabric from './loader/legacyfabric/legacyFabric.js'; import Quilt from './loader/quilt/quilt.js'; @@ -41,6 +42,10 @@ export default class Loader { let fabric = await this.fabric(Loader); if (fabric.error) return this.emit('error', fabric); this.emit('json', fabric); + } else if (this.options.loader.type === 'legacyfabric') { + let legacyFabric = await this.legacyFabric(Loader); + if (legacyFabric.error) return this.emit('error', legacyFabric); + this.emit('json', legacyFabric); } else if (this.options.loader.type === 'quilt') { let quilt = await this.quilt(Loader); if (quilt.error) return this.emit('error', quilt); @@ -167,7 +172,30 @@ export default class Loader { return json; } + async legacyFabric(Loader: any) { + let legacyFabric = new LegacyFabric(this.options); + // set event + legacyFabric.on('check', (progress, size, element) => { + this.emit('check', progress, size, element); + }); + + legacyFabric.on('progress', (progress, size, element) => { + this.emit('progress', progress, size, element); + }); + + // download Json + let json = await legacyFabric.downloadJson(Loader); + if (json.error) return json; + let destination = path.resolve(this.options.path, 'versions', json.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + + // download libraries + await legacyFabric.downloadLibraries(json); + + return json; + } async quilt(Loader: any) { let quilt = new Quilt(this.options); diff --git a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts new file mode 100644 index 00000000..d8124a75 --- /dev/null +++ b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts @@ -0,0 +1,94 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +import { getPathLibraries } from '../../../utils/Index.js'; +import download from '../../../utils/Downloader.js'; + +import nodeFetch from 'node-fetch' +import fs from 'fs' +import path from'path' +import { EventEmitter } from 'events'; + +export default class FabricMC { + options: any; + on: any; + emit: any; + + constructor(options = {}) { + this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; + } + + async downloadJson(Loader) { + let build + let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + + let version = metaData.game.find(version => version.version === this.options.loader.version); + let AvailableBuilds = metaData.loader.map(build => build.version); + if (!version) return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; + + if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { + build = metaData.loader[0]; + } else { + build = metaData.loader.find(loader => loader.version === this.options.loader.build); + } + + if (!build) return { error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; + + let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); + let json = await nodeFetch(url).then(res => res.json()).catch(err => err); + return json + } + + async downloadLibraries(json) { + let { libraries } = json; + let downloader = new download(); + let files:any = []; + let check = 0; + let size = 0; + + for (let lib of libraries) { + if (lib.rules) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } + let file = {} + let libInfo = getPathLibraries(lib.name); + let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); + let pathLibFile = path.resolve(pathLib, libInfo.name); + + if (!fs.existsSync(pathLibFile)) { + let url = `${lib.url}${libInfo.path}/${libInfo.name}` + let sizeFile = 0 + + let res:any = await downloader.checkURL(url); + if (res.status === 200) { + sizeFile = res.size; + size += res.size; + } + + file = { + url: url, + folder: pathLib, + path: `${pathLib}/${libInfo.name}`, + name: libInfo.name, + size: sizeFile + } + files.push(file); + } + this.emit('check', check++, libraries.length, 'libraries'); + } + + if (files.length > 0) { + downloader.on("progress", (DL, totDL) => { + this.emit("progress", DL, totDL, 'libraries'); + }); + + await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); + } + return libraries + } +} \ No newline at end of file diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index 660dfc23..a85b97e6 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -27,8 +27,9 @@ export default class download { if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true }); const writer = fs.createWriteStream(path + '/' + fileName); const response = await nodeFetch(url); - const size = response.headers.get('content-length'); + let size = response.headers.get('content-length'); let downloaded = 0; + return new Promise((resolve: any, reject: any) => { response.body.on('data', (chunk) => { downloaded += chunk.length; diff --git a/src/utils/Index.ts b/src/utils/Index.ts index c530f358..8891e313 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -56,6 +56,11 @@ function loader(type: string) { metaData: 'https://meta.fabricmc.net/v2/versions', json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' } + } else if (type === 'legacyfabric') { + return { + metaData: 'https://meta.legacyfabric.net/v2/versions', + json: 'https://meta.legacyfabric.net/v2/versions/loader/${version}/${build}/profile/json' + } } else if (type === 'quilt') { return { metaData: 'https://meta.quiltmc.org/v3/versions', diff --git a/test/index.js b/test/index.js index f9bcc974..18725b62 100755 --- a/test/index.js +++ b/test/index.js @@ -28,12 +28,12 @@ let mc timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.20.1', + version: '1.12.2', detached: false, downloadFileMultiple: 30, loader: { - type: 'neoForge', + type: 'legacyfabric', build: 'latest', enable: true }, From ddfc121e5d0e3cf232d35135b9a7c30c9b0c6af5 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:57:16 +0200 Subject: [PATCH 005/185] hide prive info console (token ...) --- src/Launch.ts | 10 ++++++++-- test/index.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 3a2f2f06..c6814565 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -100,9 +100,15 @@ export default class Launch { let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); - let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) + let argumentsLogs: string = Arguments.join(' ') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '__HIDDEN_TOKEN__') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '__HIDDEN_TOKEN__') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '__HIDDEN_UUID__') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '__HIDDEN_XUID__') + argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') + this.emit('data', `Launching with arguments ${argumentsLogs}`); - this.emit('data', `Launching with arguments ${Arguments.join(' ')}`) + let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) diff --git a/test/index.js b/test/index.js index 18725b62..8de363dd 100755 --- a/test/index.js +++ b/test/index.js @@ -28,12 +28,12 @@ let mc timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.12.2', + version: '1.20.1', detached: false, downloadFileMultiple: 30, loader: { - type: 'legacyfabric', + type: 'Forge', build: 'latest', enable: true }, From b6e887e3592ef14ac19156ba6a1c937dec50cd37 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:58:06 +0200 Subject: [PATCH 006/185] publish 3.6.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11301c28..84b3465f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.0", + "version": "3.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.0", + "version": "3.6.1", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 7240516a..7edd54d9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.0", + "version": "3.6.1", "types": "./build/Index.d.ts", "exports": { ".": { From 31899c3372d3d22aacfcabdd5589523d2a8555e4 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 13 Aug 2023 00:09:14 +0200 Subject: [PATCH 007/185] Update index.js --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 8de363dd..f9bcc974 100755 --- a/test/index.js +++ b/test/index.js @@ -33,7 +33,7 @@ let mc downloadFileMultiple: 30, loader: { - type: 'Forge', + type: 'neoForge', build: 'latest', enable: true }, From aa93c7b43dc77ee66f6cf4bf043d2ac4c9803e4e Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 13 Aug 2023 00:10:44 +0200 Subject: [PATCH 008/185] Update Launch.ts --- src/Launch.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index c6814565..5bf6e834 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -101,10 +101,10 @@ export default class Launch { if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); let argumentsLogs: string = Arguments.join(' ') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '__HIDDEN_TOKEN__') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '__HIDDEN_TOKEN__') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '__HIDDEN_UUID__') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '__HIDDEN_XUID__') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') this.emit('data', `Launching with arguments ${argumentsLogs}`); From 0ddc5be79e6ad352f8f99092a9ba413681b01456 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:25:24 +0200 Subject: [PATCH 009/185] fix --- src/Minecraft/Minecraft-Assets.ts | 3 ++- test/index.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Minecraft/Minecraft-Assets.ts b/src/Minecraft/Minecraft-Assets.ts index 7d47d8ed..9bf38caa 100755 --- a/src/Minecraft/Minecraft-Assets.ts +++ b/src/Minecraft/Minecraft-Assets.ts @@ -40,7 +40,8 @@ export default class MinecraftAssets { } copyAssets(json: any) { - let legacyDirectory = `${this.options.path}/resources`; + let legacyDirectory: string = `${this.options.path}/resources`; + if (this.options.instance) legacyDirectory = `${this.options.path}/instances/${this.options.instance}/resources`; let pathAssets = `${this.options.path}/assets/indexes/${json.assets}.json`; if (!fs.existsSync(pathAssets)) return; let assets = JSON.parse(fs.readFileSync(pathAssets, 'utf-8')); diff --git a/test/index.js b/test/index.js index f9bcc974..707d7782 100755 --- a/test/index.js +++ b/test/index.js @@ -28,12 +28,12 @@ let mc timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.20.1', + version: '1.4.7', detached: false, downloadFileMultiple: 30, loader: { - type: 'neoForge', + type: 'legacyFabric', build: 'latest', enable: true }, From 5ef62caed67054a3805b649528ed9628d97bc7a9 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 20 Aug 2023 22:48:38 +0200 Subject: [PATCH 010/185] change path java --- src/Minecraft/Minecraft-Java.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 8af5e865..6860b859 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -42,7 +42,7 @@ export default class java { if (info.type == "directory") continue; if (!info.downloads) continue; let file: any = {}; - file.path = `runtime/${version}/${path.replace(toDelete, "")}`; + file.path = `runtime/${version}-${process.platform}/${path.replace(toDelete, "")}`; file.executable = info.executable; file.sha1 = info.downloads.raw.sha1; file.size = info.downloads.raw.size; @@ -52,7 +52,7 @@ export default class java { } return { files: files, - path: path.resolve(this.options.path, `runtime/${version}/bin/java${process.platform == "win32" ? ".exe" : ""}`).replace(/\\/g, "/"), + path: path.resolve(this.options.path, `runtime/${version}-${process.platform}/bin/java${process.platform == "win32" ? ".exe" : ""}`), }; } } \ No newline at end of file From 9962457c6db6f60946f62a107680507de85c5799 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 22 Aug 2023 04:36:46 +0200 Subject: [PATCH 011/185] publish 3.6.2 --- README.md | 8 +++++--- package-lock.json | 4 ++-- package.json | 2 +- test/index.js | 8 ++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dec625ec..69e27635 100755 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ async function main() { main() ``` --- -
- -[

discord](https://discord.gg/e9q7Yr2cuQ) +

+ + + +

diff --git a/package-lock.json b/package-lock.json index 84b3465f..66e89901 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.1", + "version": "3.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.1", + "version": "3.6.2", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 7edd54d9..c03c922b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.1", + "version": "3.6.2", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/test/index.js b/test/index.js index 707d7782..8331d481 100755 --- a/test/index.js +++ b/test/index.js @@ -23,17 +23,17 @@ let mc } let opt = { - // url: 'http://craftdium.ml/launcherSelvania/files?instance=hypixel', + url: 'http://launcher.luuxis.fr/files=?instance=PokeMoonX', authenticator: mc, timeout: 10000, path: './.Minecraft', - instance: 'Hypixel', - version: '1.4.7', + instance: 'PokeMoonX', + version: '1.16.5', detached: false, downloadFileMultiple: 30, loader: { - type: 'legacyFabric', + type: 'forge', build: 'latest', enable: true }, From 1394daf5d6f7767d4f3c88ff79a4f3e43db1d8c5 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 22 Aug 2023 04:41:25 +0200 Subject: [PATCH 012/185] publish 3.6.2 --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 69e27635..6da61b20 100755 --- a/README.md +++ b/README.md @@ -2,13 +2,16 @@ NodeJS Module for Minecraft launcher
[![Number](https://img.shields.io/npm/v/minecraft-java-core?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -
[![Install](https://img.shields.io/npm/dm/minecraft-java-core.svg?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -
[![size](https://img.shields.io/github/languages/code-size/luuxis/minecraft-java-core?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -
[![sizeinstall](https://badgen.net/packagephobia/install/minecraft-java-core)](https://npmjs.com/minecraft-java-core) +

+ + + +

+ --- ## Avantages :dizzy: - Auto check & downloading compatible java version @@ -121,9 +124,3 @@ async function main() { main() ``` ---- -

- - - -

From d86d8635fd06e9feaa8141b0f3bc43d2f0271416 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:24:36 +0200 Subject: [PATCH 013/185] fix path files verify --- src/Launch.ts | 2 +- src/Minecraft/Minecraft-Bundle.ts | 19 ++++++------------- src/utils/Index.ts | 4 ++-- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 5bf6e834..6ef3c959 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -71,7 +71,7 @@ export default class Launch { if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 - if (this.options.downloadFileMultiple > 20) this.options.downloadFileMultiple = 20 + if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 if (!this.options.loader.enable) this.options.loader = false; this.start(); } diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index 8e032680..0286e1bc 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -28,16 +28,15 @@ export default class MinecraftBundle { } if (fs.existsSync(file.path)) { - if (this.options.ignored.find(ignored => ignored == file.path.split("/").slice(-1)[0])) continue + if (this.options.ignored.find(ignored => ignored == file.path.replaceAll(`${this.options.path}/`, ""))) continue + if (file.sha1) { if (await getFileHash(file.path) != file.sha1) { todownload.push(file); } } - } else { - todownload.push(file); - } + } else todownload.push(file); } return todownload; } @@ -52,19 +51,13 @@ export default class MinecraftBundle { async checkFiles(bundle: any) { let instancePath = '' - let instanceFolder = [] if (this.options.instance) { if (!fs.existsSync(`${this.options.path}/instances`)) fs.mkdirSync(`${this.options.path}/instances`, { recursive: true }); instancePath = `/instances/${this.options.instance}` - instanceFolder = fs.readdirSync(`${this.options.path}/instances`).filter(dir => dir != this.options.instance) } - let files = this.getFiles(this.options.path); + let files = this.options.instance ? this.getFiles(`${this.options.path}/instances/${this.options.instance}`) : this.getFiles(this.options.path); let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`)] - for (let instances of instanceFolder) { - ignoredfiles.push(...this.getFiles(`${this.options.path}/instances/${instances}`)); - } - for (let file of this.options.ignored) { file = (`${this.options.path}${instancePath}/${file}`) if (fs.existsSync(file)) { @@ -83,14 +76,14 @@ export default class MinecraftBundle { for (let file of files) { try { if (fs.statSync(file).isDirectory()) { - fs.rmdirSync(file); + fs.rmSync(file, { recursive: true }); } else { fs.unlinkSync(file); let folder = file.split("/").slice(0, -1).join("/"); while (true) { if (folder == this.options.path) break; let content = fs.readdirSync(folder); - if (content.length == 0) fs.rmdirSync(folder); + if (content.length == 0) fs.rmSync(folder); folder = folder.split("/").slice(0, -1).join("/"); } } diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 8891e313..7fb407ab 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -88,8 +88,8 @@ async function getFileFromJar(jar: string, file: string = null, path: string = n if (entry.entryName == file) fileReturn = entry.getData(); } - if (!entry.isDirectory && entry.entryName.includes(path)) { - fileReturn.push(entry.entryName) + if (!entry.isDirectory && entry.entryName.includes(path) && path) { + fileReturn.push(entry.entryName); } } resolve(fileReturn); From 339d75792edc61c13822d30f11a092d6214f8ee1 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:24:56 +0200 Subject: [PATCH 014/185] publish 3.6.3 --- package-lock.json | 4 ++-- package.json | 2 +- test/index.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66e89901..815fcb50 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.2", + "version": "3.6.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.2", + "version": "3.6.3", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index c03c922b..03d46f00 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.2", + "version": "3.6.3", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/test/index.js b/test/index.js index 8331d481..16561bf4 100755 --- a/test/index.js +++ b/test/index.js @@ -23,7 +23,7 @@ let mc } let opt = { - url: 'http://launcher.luuxis.fr/files=?instance=PokeMoonX', + url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', authenticator: mc, timeout: 10000, path: './.Minecraft', @@ -33,12 +33,12 @@ let mc downloadFileMultiple: 30, loader: { - type: 'forge', + type: 'Forge', build: 'latest', enable: true }, - verify: false, + verify: true, ignored: [ 'config', 'essential', From a2946ed251e78f30a710b5f00a5549db378e9b6c Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:36:17 +0200 Subject: [PATCH 015/185] del test neoforge --- .gitignore | 1 + test/index.js | 31 ++++--------------------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 08dc1a24..453cb7e9 100755 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ files .Minecraft test/*.json webfiles/instances/* +.DS_Store diff --git a/test/index.js b/test/index.js index 16561bf4..16486a83 100755 --- a/test/index.js +++ b/test/index.js @@ -1,5 +1,4 @@ -const { spawn } = require('child_process'); -const { Microsoft, Launch, Mojang } = require('../build/Index'); +const { Microsoft, Launch } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -23,12 +22,12 @@ let mc } let opt = { - url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', + //url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', authenticator: mc, timeout: 10000, path: './.Minecraft', instance: 'PokeMoonX', - version: '1.16.5', + version: '1.20.1', detached: false, downloadFileMultiple: 30, @@ -107,26 +106,4 @@ let mc launch.on('error', err => { console.log(err); }); -})() - -// const VERSIONS_ENDPOINT = 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge' -// const DOWNLOAD_URL = 'https://maven.neoforged.net/net/neoforged/forge'; - -// (async () => { -// let versionJson; - -// const response = await fetch(VERSIONS_ENDPOINT); -// versionJson = await response.json(); - -// if (versionJson) { -// let { versions } = versionJson; -// console.log(versions); - -// versions = versions[1]; - -// const installerUrl = `${DOWNLOAD_URL}/${versions}/forge-${versions}-installer.jar`; - -// console.log(installerUrl); - -// } -// })() \ No newline at end of file +})() \ No newline at end of file From 1e9b5407e6f98b72bb253039feb83cd5a1f48602 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:23:35 +0200 Subject: [PATCH 016/185] add suport minecraft 1.0 > 1.18 arm --- src/Launch.ts | 2 ++ src/Minecraft/Minecraft-Java.ts | 20 +++++++++++++------- test/index.js | 14 +++++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 6ef3c959..817593be 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -128,6 +128,8 @@ export default class Launch { let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); let gameJava: any = this.options.javaPath ? { files: [] } : await new javaMinecraft(this.options).GetJsonJava(json); + if (gameJava.error) return gameJava + let filesList: any = await bundle.checkBundle([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); if (filesList.length > 0) { diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 6860b859..854a37bf 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -23,18 +23,23 @@ export default class java { if (os.platform() == "win32") { let arch = { x64: "windows-x64", ia32: "windows-x86" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].version.name}` - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].manifest.url).then(res => res.json())).files) + version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` + if (version.includes('undefined')) return { error: true, message: "Java not found" }; + javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) } else if (os.platform() == "darwin") { - let arch = { x64: "mac-os", arm64: "mac-os-arm64" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].version.name}` - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].manifest.url).then(res => res.json())).files) + let arch = { x64: "mac-os", arm64: "mac-os" } + version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` + if (version.includes('undefined')) return { error: true, message: "Java not found" }; + javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) } else if (os.platform() == "linux") { let arch = { x64: "linux", ia32: "linux-i386" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].version.name}` - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0].manifest.url).then(res => res.json())).files) + version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` + if (version.includes('undefined')) return { error: true, message: "Java not found" }; + javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) } else return console.log("OS not supported"); + if (!javaVersionsJson) return { error: true, message: "Java not found" }; + let java = javaVersionsJson.find(file => file[0].endsWith(process.platform == "win32" ? "bin/javaw.exe" : "bin/java"))[0]; let toDelete = java.replace(process.platform == "win32" ? "bin/javaw.exe" : "bin/java", ""); @@ -54,5 +59,6 @@ export default class java { files: files, path: path.resolve(this.options.path, `runtime/${version}-${process.platform}/bin/java${process.platform == "win32" ? ".exe" : ""}`), }; + } } \ No newline at end of file diff --git a/test/index.js b/test/index.js index 16486a83..8981c259 100755 --- a/test/index.js +++ b/test/index.js @@ -22,12 +22,12 @@ let mc } let opt = { - //url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', + url: 'https://launcher.luuxis.fr/files/?instance=hypixel', authenticator: mc, timeout: 10000, path: './.Minecraft', - instance: 'PokeMoonX', - version: '1.20.1', + instance: 'Hypixel', + version: '1.8.9', detached: false, downloadFileMultiple: 30, @@ -55,10 +55,10 @@ let mc javaPath: null, - screen: { - width: 1600, - height: 900 - }, + //screen: { + // width: 1600, + // height: 900 + //}, memory: { min: '4G', From 7582e03b73846234d82e1b58e09d25f54d3eebaa Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:30:18 +0200 Subject: [PATCH 017/185] pulish 3.6.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 815fcb50..311f71dd 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.3", + "version": "3.6.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.3", + "version": "3.6.4", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 03d46f00..e19363e0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.3", + "version": "3.6.4", "types": "./build/Index.d.ts", "exports": { ".": { From fa8410e669a499c65a4a41bf3bbf01290230133d Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:05:18 +0200 Subject: [PATCH 018/185] test --- src/Launch.ts | 1 + src/Minecraft/Minecraft-Java.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Launch.ts b/src/Launch.ts index 817593be..219125fd 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -43,6 +43,7 @@ export default class Launch { instance: opt?.instance || null, detached: opt?.detached || false, downloadFileMultiple: opt?.downloadFileMultiple || 3, + armEnabledMac: opt?.armEnabledMac || true, loader: { type: opt?.loader?.type?.toLowerCase() || null, diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 854a37bf..d3d98464 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -27,7 +27,7 @@ export default class java { if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) } else if (os.platform() == "darwin") { - let arch = { x64: "mac-os", arm64: "mac-os" } + let arch = { x64: "mac-os", arm64: this.options.armEnabledMac ? "mac-os-arm64" : "mac-os" } version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) From 3320283541870fb63d8f8b1b2c6faf8668faa78e Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:57:29 +0200 Subject: [PATCH 019/185] add suport mac silicn --- package-lock.json | 10 ++++++++++ package.json | 1 + src/Launch.ts | 2 +- src/Minecraft-Loader/patcher.ts | 2 -- src/Minecraft/Minecraft-Java.ts | 2 +- src/utils/Index.ts | 2 +- test/index.js | 9 +++++---- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 311f71dd..5c7a1c3a 100755 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "tslib": "^2.4.1" }, "devDependencies": { + "@types/adm-zip": "^0.5.2", "@types/node": "^18.11.13", "@types/node-fetch": "^2.6.2", "rimraf": "^3.0.2", @@ -29,6 +30,15 @@ "node": ">=0.1.90" } }, + "node_modules/@types/adm-zip": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.2.tgz", + "integrity": "sha512-33OTTnnW3onOE6HJuoqsi7T7Ojupz7zO/Vs5ddRNVCYQnu4lg05RqH/pr9eidHGvGyYfdO4uPO9cvegAMixBCQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", diff --git a/package.json b/package.json index e19363e0..ac6f790d 100755 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "tslib": "^2.4.1" }, "devDependencies": { + "@types/adm-zip": "^0.5.2", "@types/node": "^18.11.13", "@types/node-fetch": "^2.6.2", "rimraf": "^3.0.2", diff --git a/src/Launch.ts b/src/Launch.ts index 219125fd..cd972be6 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -43,7 +43,7 @@ export default class Launch { instance: opt?.instance || null, detached: opt?.detached || false, downloadFileMultiple: opt?.downloadFileMultiple || 3, - armEnabledMac: opt?.armEnabledMac || true, + intelEnabledMac: opt?.intelEnabledMac ? true : false, loader: { type: opt?.loader?.type?.toLowerCase() || null, diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index b8de04e5..bc9452de 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -5,8 +5,6 @@ import { EventEmitter } from 'events'; import { getPathLibraries, getFileFromJar } from '../utils/Index.js'; - - export default class forgePatcher { options: any; on: any; diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index d3d98464..2a0a6fc3 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -27,7 +27,7 @@ export default class java { if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) } else if (os.platform() == "darwin") { - let arch = { x64: "mac-os", arm64: this.options.armEnabledMac ? "mac-os-arm64" : "mac-os" } + let arch = { x64: "mac-os", arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" } version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 7fb407ab..5877aca2 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -82,7 +82,7 @@ async function getFileFromJar(jar: string, file: string = null, path: string = n let zip = new admZip(jar); let entries = zip.getEntries(); - return new Promise(resolve => { + return await new Promise(resolve => { for (let entry of entries) { if (!entry.isDirectory && !path) { if (entry.entryName == file) fileReturn = entry.getData(); diff --git a/test/index.js b/test/index.js index 8981c259..d4eb3fc0 100755 --- a/test/index.js +++ b/test/index.js @@ -22,17 +22,18 @@ let mc } let opt = { - url: 'https://launcher.luuxis.fr/files/?instance=hypixel', + url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', authenticator: mc, timeout: 10000, path: './.Minecraft', instance: 'Hypixel', - version: '1.8.9', + version: '1.16.5', detached: false, + intelEnabledMac: false, downloadFileMultiple: 30, loader: { - type: 'Forge', + type: 'forge', build: 'latest', enable: true }, @@ -92,7 +93,7 @@ let mc }) launch.on('patch', patch => { - console.log(patch); + //console.log(patch); }); launch.on('data', (e) => { From 3c7048484e29c034bf3706983a04960310467b92 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:35:52 +0200 Subject: [PATCH 020/185] fix error --- src/Launch.ts | 80 ++++++++++++++++++++++++++------------------------- test/index.js | 6 ++-- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index cd972be6..43ff9c51 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -22,9 +22,45 @@ import { isold } from './utils/Index.js'; import Downloader from './utils/Downloader.js'; +type loader = { + type?: string, + build?: string, + enable?: boolean +} + +type screen = { + width?: number, + height?: number, + fullscreen?: boolean +} + +type memory = { + min?: string, + max?: string +} + +type LaunchOPTS = { + url: string | null, + authenticator: any, + timeout: number, + path: string, + version: string, + instance: string, + detached: boolean, + downloadFileMultiple: number, + intelEnabledMac: boolean, + loader: loader, + verify: boolean, + ignored: string[], + JVM_ARGS: string[], + GAME_ARGS: string[], + javaPath: string, + screen: screen, + memory: memory +}; export default class Launch { - options: any; + options: LaunchOPTS; on: any; emit: any; @@ -33,47 +69,13 @@ export default class Launch { this.emit = EventEmitter.prototype.emit; } - async Launch(opt: any) { - this.options = { - url: opt?.url || null, - authenticator: opt?.authenticator || null, - timeout: opt?.timeout || 10000, - path: path.resolve(opt?.path || '.Minecraft').replace(/\\/g, '/'), - version: opt?.version || 'latest_release', - instance: opt?.instance || null, - detached: opt?.detached || false, - downloadFileMultiple: opt?.downloadFileMultiple || 3, - intelEnabledMac: opt?.intelEnabledMac ? true : false, - - loader: { - type: opt?.loader?.type?.toLowerCase() || null, - build: opt?.loader?.build?.toLowerCase() || 'latest', - enable: opt?.loader?.enable || false, - }, - - verify: opt?.verify || false, - ignored: opt?.ignored || [], - JVM_ARGS: opt?.JVM_ARGS || [], - GAME_ARGS: opt?.GAME_ARGS || [], - - javaPath: opt?.javaPath || null, - - screen: { - width: opt?.screen?.width || null, - height: opt?.screen?.height || null, - fullscreen: opt?.screen?.fullscreen || false, - }, - - memory: { - min: opt?.memory?.min || '1G', - max: opt?.memory?.max || '2G' - } - } + async Launch(opt: LaunchOPTS) { + this.options = opt; + this.options.path = path.resolve(this.options.path) if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 - if (!this.options.loader.enable) this.options.loader = false; this.start(); } @@ -156,7 +158,7 @@ export default class Launch { await downloader.downloadFileMultiple(filesList, totsize, this.options.downloadFileMultiple, this.options.timeout); } - if (this.options.loader) { + if (this.options.loader.enable === true) { let loaderInstall = new loaderMinecraft(this.options) loaderInstall.on('extract', (extract: any) => { diff --git a/test/index.js b/test/index.js index d4eb3fc0..c037843b 100755 --- a/test/index.js +++ b/test/index.js @@ -26,10 +26,10 @@ let mc authenticator: mc, timeout: 10000, path: './.Minecraft', - instance: 'Hypixel', + instance: 'PokeMoonX', version: '1.16.5', detached: false, - intelEnabledMac: false, + intelEnabledMac: true, downloadFileMultiple: 30, loader: { @@ -93,7 +93,7 @@ let mc }) launch.on('patch', patch => { - //console.log(patch); + console.log(patch); }); launch.on('data', (e) => { From eeaedf8324958300c3eccfb02ed2d025436cd587 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:36:07 +0200 Subject: [PATCH 021/185] publish 3.7.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c7a1c3a..79648427 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.6.4", + "version": "3.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.6.4", + "version": "3.7.0", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index ac6f790d..2beea29d 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.6.4", + "version": "3.7.0", "types": "./build/Index.d.ts", "exports": { ".": { From 68e26620f5f41e6239dd64beda9b0d5f8d702a40 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:35:11 +0200 Subject: [PATCH 022/185] fix error 3.7.0 --- src/Index.ts | 1 - src/Launch.ts | 61 +++++++++++++++++++++++++++++++++++++++++---------- test/index.js | 12 +++++----- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/Index.ts b/src/Index.ts index cb838cf8..ae6dfd3d 100755 --- a/src/Index.ts +++ b/src/Index.ts @@ -8,7 +8,6 @@ import Microsoft from './Authenticator/Microsoft.js'; import * as Mojang from './Authenticator/Mojang.js'; import Status from './StatusServer/status.js'; - export { AZauth as AZauth, Launch as Launch, diff --git a/src/Launch.ts b/src/Launch.ts index 43ff9c51..cbc8eb96 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -9,17 +9,14 @@ import fs from 'fs'; import { spawn } from 'child_process'; import jsonMinecraft from './Minecraft/Minecraft-Json.js'; - import librariesMinecraft from './Minecraft/Minecraft-Libraries.js'; import assetsMinecraft from './Minecraft/Minecraft-Assets.js'; import loaderMinecraft from './Minecraft/Minecraft-Loader.js'; import javaMinecraft from './Minecraft/Minecraft-Java.js'; - import bundleMinecraft from './Minecraft/Minecraft-Bundle.js'; import argumentsMinecraft from './Minecraft/Minecraft-Arguments.js'; import { isold } from './utils/Index.js'; - import Downloader from './utils/Downloader.js'; type loader = { @@ -42,13 +39,13 @@ type memory = { type LaunchOPTS = { url: string | null, authenticator: any, - timeout: number, + timeout?: number, path: string, version: string, - instance: string, - detached: boolean, - downloadFileMultiple: number, - intelEnabledMac: boolean, + instance?: string, + detached?: boolean, + downloadFileMultiple?: number, + intelEnabledMac?: boolean, loader: loader, verify: boolean, ignored: string[], @@ -70,15 +67,57 @@ export default class Launch { } async Launch(opt: LaunchOPTS) { - this.options = opt; - - this.options.path = path.resolve(this.options.path) + const defaultOptions: LaunchOPTS = { + url: null, + authenticator: null, + timeout: 10000, + path: '.Minecraft', + version: 'latest_release', + instance: null, + detached: false, + intelEnabledMac: false, + downloadFileMultiple: 3, + + loader: { + type: null, + build: 'latest', + enable: false, + }, + + verify: false, + ignored: [], + JVM_ARGS: [], + GAME_ARGS: [], + + javaPath: null, + + screen: { + width: null, + height: null, + fullscreen: false, + }, + + memory: { + min: '1G', + max: '2G' + }, + ...opt, + }; + + this.options = defaultOptions; + + this.options.path = path.resolve(this.options.path).replace(/\\/g, '/'); + if (this.options.loader.type) { + this.options.loader.type = this.options.loader.type.toLowerCase() + this.options.loader.build = this.options.loader.build.toLowerCase() + } if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 this.start(); } + async start() { let data: any = await this.DownloadGame(); if (data.error) return this.emit('error', data); diff --git a/test/index.js b/test/index.js index c037843b..9380af6f 100755 --- a/test/index.js +++ b/test/index.js @@ -29,11 +29,11 @@ let mc instance: 'PokeMoonX', version: '1.16.5', detached: false, - intelEnabledMac: true, + intelEnabledMac: false, downloadFileMultiple: 30, loader: { - type: 'forge', + type: 'Forge', build: 'latest', enable: true }, @@ -56,10 +56,10 @@ let mc javaPath: null, - //screen: { - // width: 1600, - // height: 900 - //}, + screen: { + width: 1600, + height: 900 + }, memory: { min: '4G', From 3635c58c2780dcc9fdf042cacedfc1f7f69d5d4b Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:35:29 +0200 Subject: [PATCH 023/185] publish 3.7.1 stable --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79648427..a5a268ab 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.7.0", + "version": "3.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.7.0", + "version": "3.7.1", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 2beea29d..5fd416c9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.7.0", + "version": "3.7.1", "types": "./build/Index.d.ts", "exports": { ".": { From 670436e2943ba98c51973bf51e650b63f6148fb1 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:31:51 +0200 Subject: [PATCH 024/185] fix remove file --- src/Minecraft/Minecraft-Bundle.ts | 10 +++++----- src/Minecraft/Minecraft-Libraries.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index 0286e1bc..dd7af516 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -17,7 +17,7 @@ export default class MinecraftBundle { let todownload = []; for (let file of bundle) { - if (!file.path) continue; + if (!file.path) continue file.path = path.resolve(this.options.path, file.path).replace(/\\/g, "/"); file.folder = file.path.split("/").slice(0, -1).join("/"); @@ -28,12 +28,12 @@ export default class MinecraftBundle { } if (fs.existsSync(file.path)) { - if (this.options.ignored.find(ignored => ignored == file.path.replaceAll(`${this.options.path}/`, ""))) continue + let replaceName = `${this.options.path}/` + if (this.options.instance) replaceName = `${this.options.path}/instances/${this.options.instance}/` + if (this.options.ignored.find(ignored => ignored == file.path.replaceAll(replaceName, ""))) continue if (file.sha1) { - if (await getFileHash(file.path) != file.sha1) { - todownload.push(file); - } + if (await getFileHash(file.path) != file.sha1) todownload.push(file); } } else todownload.push(file); diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 2576ca8a..532b70a2 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -78,7 +78,7 @@ export default class Libraries { sha1: asset.hash, size: asset.size, type: path.split("/")[0], - path: this.options.instance ? `${this.options.path}/instances/${this.options.instance}/${path}` : path, + path: this.options.instance ? `instances/${this.options.instance}/${path}` : path, url: asset.url }); } From c76be2ff02370c10a69b64a5d5d4150d86362aea Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:32:14 +0200 Subject: [PATCH 025/185] fix arg --- src/Launch.ts | 2 +- src/Minecraft/Minecraft-Arguments.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index cbc8eb96..d346d4ee 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -76,7 +76,7 @@ export default class Launch { instance: null, detached: false, intelEnabledMac: false, - downloadFileMultiple: 3, + downloadFileMultiple: 5, loader: { type: null, diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 79613a31..3f5b7f6b 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -43,7 +43,7 @@ export default class MinecraftArguments { '${auth_uuid}': this.authenticator.uuid, '${auth_xuid}': this.authenticator.meta.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, - '${user_type}': this.authenticator.meta.type, + '${user_type}': this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type, '${version_name}': json.id, '${assets_index_name}': json.assetIndex.id, '${game_directory}': this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path, From 97088f5060ab141b92afd32373e49840507d2a10 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:32:32 +0200 Subject: [PATCH 026/185] publish 3.7.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5a268ab..8b31a041 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.7.1", + "version": "3.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.7.1", + "version": "3.7.2", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 5fd416c9..35182097 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.7.1", + "version": "3.7.2", "types": "./build/Index.d.ts", "exports": { ".": { From bf2697ea22597d103a5f067c597f55c38bccf905 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:56:30 +0200 Subject: [PATCH 027/185] fix error --- package-lock.json | 4 ++-- package.json | 2 +- src/Minecraft/Minecraft-Arguments.ts | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b31a041..63dbcd38 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.7.2", + "version": "3.7.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.7.2", + "version": "3.7.3", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 35182097..e4f57bbb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.7.2", + "version": "3.7.3", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 3f5b7f6b..367d0e6d 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -30,12 +30,17 @@ export default class MinecraftArguments { async GetGameArguments(json: any, loaderJson: any) { let game = json.minecraftArguments ? json.minecraftArguments.split(' ') : json.arguments.game; + let userType: String + if (loaderJson) { let gameLoader = loaderJson.minecraftArguments ? loaderJson.minecraftArguments.split(' ') : []; game = game.concat(gameLoader); - game = game.filter((item: any, index: any, self: any) => index === self.findIndex((res: any) => res == item)) + game = game.filter((item: String, index: Number, self: any) => index === self.findIndex((res: String) => res == item)) } + if (json.id.startsWith('1.16')) userType = 'Xbox' + else userType = this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type + let table = { '${auth_access_token}': this.authenticator.access_token, '${auth_session}': this.authenticator.access_token, @@ -43,7 +48,7 @@ export default class MinecraftArguments { '${auth_uuid}': this.authenticator.uuid, '${auth_xuid}': this.authenticator.meta.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, - '${user_type}': this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type, + '${user_type}': userType, '${version_name}': json.id, '${assets_index_name}': json.assetIndex.id, '${game_directory}': this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path, From d7022cf488f25f21f572c2cd37221b3366f092fc Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 3 Nov 2023 14:53:19 +0100 Subject: [PATCH 028/185] init commit --- .gitignore | 2 +- src/Minecraft-Loader/loader/forge/forge.ts | 15 ++++++----- .../loader/neoForge/neoForge.ts | 2 +- src/Minecraft/Minecraft-Arguments.ts | 25 ++++++++++++++----- src/utils/Index.ts | 4 ++- test/index.js | 18 ++++++------- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 453cb7e9..4b911503 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules build files -.Minecraft +Minecraft test/*.json webfiles/instances/* .DS_Store diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 9afc18ec..241b5279 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -26,7 +26,9 @@ export default class ForgeMC { async downloadInstaller(Loader: any) { let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; let AvailableBuilds = metaData; - let forgeURL = Loader.install + let forgeURL: String; + let ext: String; + let hashFileOrigin: String; if (!metaData) return { error: `Forge ${this.options.loader.version} not supported` }; let build @@ -46,13 +48,15 @@ export default class ForgeMC { metaData = metaData.filter(b => b === build)[0]; if (!metaData) return { error: `Build ${build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - forgeURL = forgeURL.replace(/\${version}/g, metaData); + + // forgeURL = forgeURL.replace(/\${version}/g, metaData); let urlMeta = Loader.meta.replace(/\${build}/g, metaData); - let pathFolder = path.resolve(this.options.path, 'forge'); - let filePath = path.resolve(pathFolder, `forge-${metaData}-installer.jar`); + // let pathFolder = path.resolve(this.options.path, 'forge'); + // let filePath = path.resolve(pathFolder, `forge-${metaData}-installer.jar`); let meta = await nodeFetch(urlMeta).then(res => res.json()); + console.log(Object.entries(meta).map(([key, value]) => ({ key, value }))); if (!fs.existsSync(filePath)) { if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); let downloadForge = new download(); @@ -61,11 +65,10 @@ export default class ForgeMC { this.emit('progress', downloaded, size, `forge-${metaData}-installer.jar`); }); - await downloadForge.downloadFile(forgeURL, pathFolder, `forge-${metaData}-installer.jar`); + await downloadForge.downloadFile(forgeURL, pathFolder, `forge-${metaData}-installer.${ext}`); } let hashFileDownload = await getFileHash(filePath, 'md5'); - let hashFileOrigin = meta?.classifiers?.installer?.jar; if (hashFileDownload !== hashFileOrigin) { fs.rmSync(filePath); diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index fbf0271a..47710ffb 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -3,7 +3,7 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ -import { getPathLibraries, getFileHash, mirrors, getFileFromJar } from '../../../utils/Index.js'; +import { getPathLibraries, mirrors, getFileFromJar } from '../../../utils/Index.js'; import download from '../../../utils/Downloader.js'; import neoForgePatcher from '../../patcher.js' diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 367d0e6d..88035d9d 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -3,6 +3,9 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ +import fs from 'fs'; +import os from 'os' + import { getPathLibraries, isold } from '../utils/Index.js'; let MojangLib = { win32: "windows", darwin: "osx", linux: "linux" }; @@ -103,6 +106,14 @@ export default class MinecraftArguments { jvm.push(`-Djava.library.path=${this.options.path}/versions/${json.id}/natives`) } + if (os.platform() == "darwin") { + let pathAssets = `${this.options.path}/assets/indexes/${json.assets}.json`; + let assets = JSON.parse(fs.readFileSync(pathAssets, 'utf-8')); + let icon = assets.objects['icons/minecraft.icns'].hash + + jvm.push(`-Xdock:name=Minecraft`) + jvm.push(`-Xdock:icon=${this.options.path}/assets/objects/${icon.substring(0, 2)}/${icon}`) + } jvm.push(...this.options.JVM_ARGS) return jvm; @@ -136,11 +147,13 @@ export default class MinecraftArguments { } classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) - return {classpath:[ - `-cp`, - classPath.join(process.platform === 'win32' ? ';' : ':'), - - ], - mainClass: loaderJson ? loaderJson.mainClass : json.mainClass} + return { + classpath: [ + `-cp`, + classPath.join(process.platform === 'win32' ? ';' : ':'), + + ], + mainClass: loaderJson ? loaderJson.mainClass : json.mainClass + } } } diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 5877aca2..68dcbfbd 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -44,7 +44,9 @@ function loader(type: string) { metaData: 'https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json', meta: 'https://files.minecraftforge.net/net/minecraftforge/forge/${build}/meta.json', promotions: 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json', - install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer.jar' + install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer', + universal: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-universal', + client: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-client', } } else if (type === 'neoforge') { return { diff --git a/test/index.js b/test/index.js index 9380af6f..c33b714f 100755 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -const { Microsoft, Launch } = require('../build/Index'); +const { Microsoft, Launch, Mojang } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -22,23 +22,23 @@ let mc } let opt = { - url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', + // url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', authenticator: mc, timeout: 10000, - path: './.Minecraft', - instance: 'PokeMoonX', - version: '1.16.5', + path: './Minecraft', + // instance: 'PokeMoonX', + version: '1.2.5', detached: false, - intelEnabledMac: false, + intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'Forge', + type: 'forge', build: 'latest', enable: true }, - verify: true, + verify: false, ignored: [ 'config', 'essential', @@ -57,7 +57,7 @@ let mc javaPath: null, screen: { - width: 1600, + width: 1500, height: 900 }, From fae56da9165ebc45a8b17095fa908edf06b95dbd Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:10:57 +0100 Subject: [PATCH 029/185] test --- src/Launch.ts | 3 +- src/Minecraft-Loader/index.ts | 39 +++++++++++--------- src/Minecraft-Loader/loader/forge/forge.ts | 43 ++++++++++++++++------ src/Minecraft/Minecraft-Loader.ts | 3 +- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index d346d4ee..90814799 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -220,7 +220,8 @@ export default class Launch { .then((data: any) => data) .catch((err: any) => err); if (jsonLoader.error) return jsonLoader; - loaderJson = jsonLoader; + if (jsonLoader.ext === 'zip') loaderJson = json; + else loaderJson = jsonLoader; } if (this.options.verify) await bundle.checkFiles([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 14cca954..6079e365 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -57,7 +57,6 @@ export default class Loader { async forge(Loader: any) { let forge = new Forge(this.options); - // set event forge.on('check', (progress, size, element) => { this.emit('check', progress, size, element); @@ -79,26 +78,32 @@ export default class Loader { let installer = await forge.downloadInstaller(Loader); if (installer.error) return installer; - // extract install profile - let profile: any = await forge.extractProfile(installer.filePath); - if (profile.error) return profile - let destination = path.resolve(this.options.path, 'versions', profile.version.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + if (installer.ext == 'jar') { + // extract install profile + let profile: any = await forge.extractProfile(installer.filePath); + if (profile.error) return profile + let destination = path.resolve(this.options.path, 'versions', profile.version.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); - // extract universal jar - let universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); - if (universal.error) return universal; + // extract universal jar + let universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); + if (universal.error) return universal; - // download libraries - let libraries: any = await forge.downloadLibraries(profile, universal); - if (libraries.error) return libraries; + // download libraries + let libraries: any = await forge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; - // patch forge if nessary - let patch: any = await forge.patchForge(profile.install); - if (patch.error) return patch; + // patch forge if nessary + let patch: any = await forge.patchForge(profile.install); + if (patch.error) return patch; - return profile.version; + return profile.version; + } else { + await forge.createJar(installer.filePath); + let profile: any = await forge.createProfile(); + return profile; + } } async neoForge(Loader: any) { diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 241b5279..6c6031b2 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -8,6 +8,7 @@ import download from '../../../utils/Downloader.js'; import forgePatcher from '../../patcher.js' import nodeFetch from 'node-fetch' +import AdmZip from 'adm-zip'; import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; @@ -27,7 +28,7 @@ export default class ForgeMC { let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; let AvailableBuilds = metaData; let forgeURL: String; - let ext: String; + let ext; let hashFileOrigin: String; if (!metaData) return { error: `Forge ${this.options.loader.version} not supported` }; @@ -49,23 +50,37 @@ export default class ForgeMC { if (!metaData) return { error: `Build ${build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - // forgeURL = forgeURL.replace(/\${version}/g, metaData); - let urlMeta = Loader.meta.replace(/\${build}/g, metaData); + let meta = await nodeFetch(Loader.meta.replace(/\${build}/g, metaData)).then(res => res.json()); + let installerType = Object.keys(meta.classifiers).find((key: String) => key == 'installer'); + let clientType = Object.keys(meta.classifiers).find((key: String) => key == 'client'); + let universalType = Object.keys(meta.classifiers).find((key: String) => key == 'universal'); + + if (installerType) { + forgeURL = forgeURL = Loader.install.replace(/\${version}/g, metaData); + ext = Object.keys(meta.classifiers.installer)[0]; + hashFileOrigin = meta.classifiers.installer[ext]; + } else if (clientType) { + forgeURL = Loader.client.replace(/\${version}/g, metaData); + ext = Object.keys(meta.classifiers.client)[0]; + hashFileOrigin = meta.classifiers.client[ext]; + } else if (universalType) { + forgeURL = Loader.universal.replace(/\${version}/g, metaData); + ext = Object.keys(meta.classifiers.universal)[0]; + hashFileOrigin = meta.classifiers.universal[ext]; + } - // let pathFolder = path.resolve(this.options.path, 'forge'); - // let filePath = path.resolve(pathFolder, `forge-${metaData}-installer.jar`); - let meta = await nodeFetch(urlMeta).then(res => res.json()); + let pathFolder = path.resolve(this.options.path, 'forge'); + let filePath = path.resolve(pathFolder, (`${forgeURL}.${ext}`).split('/').pop()); - console.log(Object.entries(meta).map(([key, value]) => ({ key, value }))); if (!fs.existsSync(filePath)) { if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); let downloadForge = new download(); downloadForge.on('progress', (downloaded, size) => { - this.emit('progress', downloaded, size, `forge-${metaData}-installer.jar`); + this.emit('progress', downloaded, size, (`${forgeURL}.${ext}`).split('/').pop()); }); - await downloadForge.downloadFile(forgeURL, pathFolder, `forge-${metaData}-installer.${ext}`); + await downloadForge.downloadFile(`${forgeURL}.${ext}`, pathFolder, (`${forgeURL}.${ext}`).split('/').pop()); } let hashFileDownload = await getFileHash(filePath, 'md5'); @@ -74,7 +89,7 @@ export default class ForgeMC { fs.rmSync(filePath); return { error: 'Invalid hash' }; } - return { filePath, metaData } + return { filePath, metaData, ext } } async extractProfile(pathInstaller: any) { @@ -241,7 +256,13 @@ export default class ForgeMC { await patcher.patcher(profile, config); } } - return true } + + async createJar(pathInstaller: any) { + console.log(this.options) + + } + + async createProfile() { } } \ No newline at end of file diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index a96c7fc2..bbbefaf7 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -32,16 +32,17 @@ export default class MinecraftLoader { } } }); + return await new Promise((resolve, reject) => { loader.install(); loader.on('json', (json: any) => { let loaderJson = json; + if (json.ext === 'zip') return resolve(loaderJson); loaderJson.libraries = loaderJson.libraries.map((lib: any) => { lib.loader = `${this.options.path}/loader/${this.options.loader.type}`; return lib; }); - resolve(loaderJson); }); From 260712262dcd72c5a238ce9a0c758181e85dc1b1 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:11:39 +0100 Subject: [PATCH 030/185] add old forge versions --- src/Launch.ts | 7 +++-- src/Minecraft-Loader/index.ts | 11 ++++--- src/Minecraft-Loader/loader/forge/forge.ts | 36 ++++++++++++++-------- src/Minecraft/Minecraft-Arguments.ts | 10 ++++-- src/Minecraft/Minecraft-Libraries.ts | 7 ++--- src/Minecraft/Minecraft-Loader.ts | 1 - src/utils/Index.ts | 22 +++++++++++-- test/index.js | 2 ++ webfiles/php/scandir.php | 3 +- 9 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 90814799..2d443079 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -47,6 +47,7 @@ type LaunchOPTS = { downloadFileMultiple?: number, intelEnabledMac?: boolean, loader: loader, + mcp: any, verify: boolean, ignored: string[], JVM_ARGS: string[], @@ -84,6 +85,8 @@ export default class Launch { enable: false, }, + mcp: null, + verify: false, ignored: [], JVM_ARGS: [], @@ -107,6 +110,7 @@ export default class Launch { this.options = defaultOptions; this.options.path = path.resolve(this.options.path).replace(/\\/g, '/'); + this.options.mcp = this.options.mcp ? path.resolve(`${this.options.path}/${this.options.mcp}`).replace(/\\/g, '/'): null; if (this.options.loader.type) { this.options.loader.type = this.options.loader.type.toLowerCase() this.options.loader.build = this.options.loader.build.toLowerCase() @@ -220,8 +224,7 @@ export default class Launch { .then((data: any) => data) .catch((err: any) => err); if (jsonLoader.error) return jsonLoader; - if (jsonLoader.ext === 'zip') loaderJson = json; - else loaderJson = jsonLoader; + loaderJson = jsonLoader; } if (this.options.verify) await bundle.checkFiles([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 6079e365..e4c8e334 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -100,8 +100,11 @@ export default class Loader { return profile.version; } else { - await forge.createJar(installer.filePath); - let profile: any = await forge.createProfile(); + let profile: any = await forge.createProfile(installer.id,installer.filePath); + if (profile.error) return profile + let destination = path.resolve(this.options.path, 'versions', profile.id) + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.id}.json`), JSON.stringify(profile, null, 4)); return profile; } } @@ -138,7 +141,7 @@ export default class Loader { fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); //extract universal jar - let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath); + let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); if (universal.error) return universal; // download libraries @@ -146,7 +149,7 @@ export default class Loader { if (libraries.error) return libraries; // patch forge if nessary - let patch: any = await neoForge.patchneoForge(profile.install); + let patch: any = await neoForge.patchneoForge(profile.install, installer.oldAPI); if (patch.error) return patch; return profile.version; diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 6c6031b2..3eead53f 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -3,12 +3,11 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ -import { getPathLibraries, getFileHash, mirrors, getFileFromJar } from '../../../utils/Index.js'; +import { getPathLibraries, getFileHash, mirrors, getFileFromJar, createZIP } from '../../../utils/Index.js'; import download from '../../../utils/Downloader.js'; import forgePatcher from '../../patcher.js' import nodeFetch from 'node-fetch' -import AdmZip from 'adm-zip'; import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; @@ -28,7 +27,7 @@ export default class ForgeMC { let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; let AvailableBuilds = metaData; let forgeURL: String; - let ext; + let ext: String; let hashFileOrigin: String; if (!metaData) return { error: `Forge ${this.options.loader.version} not supported` }; @@ -58,15 +57,17 @@ export default class ForgeMC { if (installerType) { forgeURL = forgeURL = Loader.install.replace(/\${version}/g, metaData); ext = Object.keys(meta.classifiers.installer)[0]; - hashFileOrigin = meta.classifiers.installer[ext]; + hashFileOrigin = meta.classifiers.installer[`${ext}`]; } else if (clientType) { forgeURL = Loader.client.replace(/\${version}/g, metaData); ext = Object.keys(meta.classifiers.client)[0]; - hashFileOrigin = meta.classifiers.client[ext]; + hashFileOrigin = meta.classifiers.client[`${ext}`]; } else if (universalType) { forgeURL = Loader.universal.replace(/\${version}/g, metaData); ext = Object.keys(meta.classifiers.universal)[0]; - hashFileOrigin = meta.classifiers.universal[ext]; + hashFileOrigin = meta.classifiers.universal[`${ext}`]; + } else { + return { error: 'Invalid forge installer' }; } let pathFolder = path.resolve(this.options.path, 'forge'); @@ -89,7 +90,7 @@ export default class ForgeMC { fs.rmSync(filePath); return { error: 'Invalid hash' }; } - return { filePath, metaData, ext } + return { filePath, metaData, ext, id: `forge-${build}` }; } async extractProfile(pathInstaller: any) { @@ -259,10 +260,21 @@ export default class ForgeMC { return true } - async createJar(pathInstaller: any) { - console.log(this.options) - - } + async createProfile(id: any, pathInstaller: any) { + let forgeFiles: any = await getFileFromJar(pathInstaller) + let minecraftJar: any = await getFileFromJar(this.options.loader.config.minecraftJar) + let data: any = await createZIP([...minecraftJar, ...forgeFiles], 'META-INF'); + + let destination = path.resolve(this.options.path, 'versions', id); - async createProfile() { } + let profile = JSON.parse(fs.readFileSync(this.options.loader.config.minecraftJson, 'utf-8')) + profile.libraries = []; + profile.id = id; + profile.isOldForge = true; + profile.jarPath = path.resolve(destination, `${id}.jar`); + + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${id}.jar`), data, { mode: 0o777 }); + return profile + } } \ No newline at end of file diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 88035d9d..302af909 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -101,7 +101,6 @@ export default class MinecraftArguments { jvm.push(opts[process.platform]) } - if (json.nativesList) { jvm.push(`-Djava.library.path=${this.options.path}/versions/${json.id}/natives`) } @@ -145,7 +144,14 @@ export default class MinecraftArguments { classPath.push(`${this.options.path}/libraries/${path.path}/${path.name}`) } } - classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) + + if (loaderJson?.isOldForge) { + classPath.push(loaderJson?.jarPath) + } else if (this.options.mcp) { + classPath.push(this.options.mcp) + } else { + classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) + } return { classpath: [ diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 532b70a2..5f8e2881 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -49,13 +49,12 @@ export default class Libraries { }); } - let clientjar = this.json.downloads.client; libraries.push({ - sha1: clientjar.sha1, - size: clientjar.size, + sha1: this.json.downloads.client.sha1, + size: this.json.downloads.client.size, path: `versions/${this.json.id}/${this.json.id}.jar`, type: "Libraries", - url: clientjar.url + url: this.json.downloads.client.url }); libraries.push({ diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index bbbefaf7..7e5fd4b3 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -38,7 +38,6 @@ export default class MinecraftLoader { loader.on('json', (json: any) => { let loaderJson = json; - if (json.ext === 'zip') return resolve(loaderJson); loaderJson.libraries = loaderJson.libraries.map((lib: any) => { lib.loader = `${this.options.path}/loader/${this.options.loader.type}`; return lib; diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 68dcbfbd..d1d7aaf9 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -50,8 +50,10 @@ function loader(type: string) { } } else if (type === 'neoforge') { return { - metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', - install: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar' + legacyMetaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', + metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge', + legacyInstall: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar', + install: 'https://maven.neoforged.net/net/neoforged/neoforge/${version}/neoforge-${version}-installer.jar' } } else if (type === 'fabric') { return { @@ -88,6 +90,7 @@ async function getFileFromJar(jar: string, file: string = null, path: string = n for (let entry of entries) { if (!entry.isDirectory && !path) { if (entry.entryName == file) fileReturn = entry.getData(); + if (!file) fileReturn.push({ name: entry.entryName, data: entry.getData() }); } if (!entry.isDirectory && entry.entryName.includes(path) && path) { @@ -98,11 +101,24 @@ async function getFileFromJar(jar: string, file: string = null, path: string = n }); } +async function createZIP(files: any, ignored: any = null) { + let zip = new admZip(); + + return await new Promise(resolve => { + for (let entry of files) { + if (ignored && entry.name.includes(ignored)) continue; + zip.addFile(entry.name, entry.data); + } + resolve(zip.toBuffer()); + }); +} + export { getPathLibraries, isold, getFileHash, mirrors, loader, - getFileFromJar + getFileFromJar, + createZIP }; \ No newline at end of file diff --git a/test/index.js b/test/index.js index c33b714f..c4d01072 100755 --- a/test/index.js +++ b/test/index.js @@ -38,6 +38,8 @@ let mc enable: true }, + mcp: 'mcp/test.jar', + verify: false, ignored: [ 'config', diff --git a/webfiles/php/scandir.php b/webfiles/php/scandir.php index a26254cf..7312a3c3 100755 --- a/webfiles/php/scandir.php +++ b/webfiles/php/scandir.php @@ -22,7 +22,6 @@ function scanFolder($dir) { $filePath = $dir . '/' . $filename; if ($filename == "php") continue; if (is_dir($filePath)) $result[] = $filename; - } return $result; } @@ -37,7 +36,7 @@ function dirToArray($dir) { $url_req = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); $url = "http://$_SERVER[HTTP_HOST]$url_req$dir/$path"; - $res[] = array("url" => $url, "size" => $size, "hash" => $hash, "path" => $path); + $res[] = array("url" => $url, "size" => $size, "hash" => $hash, "path" => $path); } return str_replace("\\", "", json_encode($res)); } From f5360ad9d7397e430a574b65d9e75fc6d5e66fcb Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:12:40 +0100 Subject: [PATCH 031/185] publish alpah test --- package-lock.json | 4 +-- package.json | 2 +- src/Minecraft-Loader/index.ts | 2 +- .../loader/neoForge/neoForge.ts | 35 ++++++++++++------- src/Minecraft-Loader/patcher.ts | 8 ++--- test/index.js | 8 ++--- 6 files changed, 34 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63dbcd38..676ab159 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.7.3", + "version": "3.8.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.7.3", + "version": "3.8.0-alpha.2", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index e4f57bbb..6f58e596 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.7.3", + "version": "3.8.0-alpha.2", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index e4c8e334..89231a9c 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -138,7 +138,7 @@ export default class Loader { if (profile.error) return profile let destination = path.resolve(this.options.path, 'versions', profile.version.id) if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile, null, 4)); //extract universal jar let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 47710ffb..1115fec3 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -24,11 +24,20 @@ export default class NeoForgeMC { } async downloadInstaller(Loader: any) { - let build: string - let neoForgeURL = Loader.install + let build: string; + let neoForgeURL: string; + let oldAPI: boolean = true; + let legacyMetaData = await nodeFetch(Loader.legacyMetaData).then(res => res.json()); let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); - let versions = metaData.versions.filter(version => version.includes(`${this.options.loader.version}-`)); + let versions = legacyMetaData.versions.filter(version => version.includes(`${this.options.loader.version}-`)); + + if (!versions.length) { + let minecraftVersion = `${this.options.loader.version.split('.')[1]}.${this.options.loader.version.split('.')[2]}`; + versions = metaData.versions.filter(version => version.startsWith(minecraftVersion)); + oldAPI = false; + } + if (!versions.length) return { error: `NeoForge doesn't support Minecraft ${this.options.loader.version}` }; if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { @@ -37,24 +46,24 @@ export default class NeoForgeMC { if (!build) return { error: `NeoForge Loader ${this.options.loader.build} not found, Available builds: ${versions.join(', ')}` }; - neoForgeURL = neoForgeURL.replaceAll(/\${version}/g, build); - + if (oldAPI) neoForgeURL = Loader.legacyInstall.replaceAll(/\${version}/g, build); + else neoForgeURL = Loader.install.replaceAll(/\${version}/g, build); let pathFolder = path.resolve(this.options.path, 'neoForge'); - let filePath = path.resolve(pathFolder, `forge-${build}-installer.jar`); + let filePath = path.resolve(pathFolder, `neoForge-${build}-installer.jar`); if (!fs.existsSync(filePath)) { if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); let downloadForge = new download(); downloadForge.on('progress', (downloaded, size) => { - this.emit('progress', downloaded, size, `forge-${build}-installer.jar`); + this.emit('progress', downloaded, size, `neoForge-${build}-installer.jar`); }); - await downloadForge.downloadFile(neoForgeURL, pathFolder, `forge-${build}-installer.jar`); + await downloadForge.downloadFile(neoForgeURL, pathFolder, `neoForge-${build}-installer.jar`); } - return { filePath }; + return { filePath, oldAPI }; } async extractProfile(pathInstaller: any) { @@ -76,7 +85,7 @@ export default class NeoForgeMC { return neoForgeJSON; } - async extractUniversalJar(profile: any, pathInstaller: any) { + async extractUniversalJar(profile: any, pathInstaller: any, oldAPI: any) { let skipneoForgeFilter = true if (profile.filePath) { @@ -108,7 +117,7 @@ export default class NeoForgeMC { if (profile.processors?.length) { let universalPath = profile.libraries.find(v => { - return (v.name || '').startsWith('net.neoforged:forge') + return (v.name || '').startsWith(oldAPI ? 'net.neoforged:forge' : 'net.neoforged:neoforge') }) let client: any = await getFileFromJar(pathInstaller, 'data/client.lzma'); @@ -198,7 +207,7 @@ export default class NeoForgeMC { return libraries } - async patchneoForge(profile: any) { + async patchneoForge(profile: any, oldAPI: any) { if (profile?.processors?.length) { let patcher: any = new neoForgePatcher(this.options); let config: any = {} @@ -218,7 +227,7 @@ export default class NeoForgeMC { minecraftJson: this.options.loader.config.minecraftJson } - await patcher.patcher(profile, config); + await patcher.patcher(profile, config, oldAPI); } } return true diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index bc9452de..2d8f3e94 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -16,7 +16,7 @@ export default class forgePatcher { this.emit = EventEmitter.prototype.emit; } - async patcher(profile: any, config: any) { + async patcher(profile: any, config: any, neoForgeOld: boolean = true) { let { processors } = profile; for (let key in processors) { @@ -29,7 +29,7 @@ export default class forgePatcher { let jar = getPathLibraries(processor.jar) let filePath = path.resolve(this.options.path, 'libraries', jar.path, jar.name) - let args = processor.args.map(arg => this.setArgument(arg, profile, config)).map(arg => this.computePath(arg)); + let args = processor.args.map(arg => this.setArgument(arg, profile, config, neoForgeOld)).map(arg => this.computePath(arg)); let classPaths = processor.classpath.map(cp => { let classPath = getPathLibraries(cp) return `"${path.join(this.options.path, 'libraries', `${classPath.path}/${classPath.name}`)}"` @@ -97,11 +97,11 @@ export default class forgePatcher { return true; } - setArgument(arg: any, profile: any, config: any) { + setArgument(arg: any, profile: any, config: any, neoForgeOld) { let finalArg = arg.replace('{', '').replace('}', ''); let universalPath = profile.libraries.find(v => { if (this.options.loader.type === 'forge') return (v.name || '').startsWith('net.minecraftforge:forge') - if (this.options.loader.type === 'neoforge') return (v.name || '').startsWith('net.neoforged:forge') + if (this.options.loader.type === 'neoforge') return (v.name || '').startsWith(neoForgeOld ? 'net.neoforged:forge' : 'net.neoforged:neoforge') }) if (profile.data[finalArg]) { diff --git a/test/index.js b/test/index.js index c4d01072..a3e6192e 100755 --- a/test/index.js +++ b/test/index.js @@ -26,16 +26,16 @@ let mc authenticator: mc, timeout: 10000, path: './Minecraft', - // instance: 'PokeMoonX', - version: '1.2.5', + instance: 'PokeMoonX', + version: '1.20.1', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'forge', + type: 'neoforge', build: 'latest', - enable: true + enable: false }, mcp: 'mcp/test.jar', From a52b46af28b5a6f802b36f7ed2ce61cbb322822b Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:32:30 +0100 Subject: [PATCH 032/185] Update index.js --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index a3e6192e..1d6ee4db 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.20.1', + version: '1.8.8', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, From f31dbf90c7a9db0d15a71579d3b115d287504988 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:02:21 +0100 Subject: [PATCH 033/185] publish stable 3.8.1 --- package-lock.json | 4 ++-- package.json | 2 +- src/Minecraft-Loader/loader/forge/forge.ts | 20 ++++++++++++----- src/utils/Index.ts | 26 ++++++++++++++++++++-- test/index.js | 8 +++---- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 676ab159..948e3d9d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.8.0-alpha.2", + "version": "3.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.8.0-alpha.2", + "version": "3.8.1", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index 6f58e596..c1c95fa9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.8.0-alpha.2", + "version": "3.8.1", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 3eead53f..58336104 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -11,6 +11,9 @@ import nodeFetch from 'node-fetch' import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; +import { skipLibrary } from '../../../utils/Index.js'; + +let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; export default class ForgeMC { options: any; @@ -176,24 +179,31 @@ export default class ForgeMC { ] for (let lib of libraries) { + let natives = null; + if (skipForgeFilter && skipForge.find(libs => lib.name.includes(libs))) { this.emit('check', check++, libraries.length, 'libraries'); continue; } - if (lib.rules) { + + if (skipLibrary(lib)) { this.emit('check', check++, libraries.length, 'libraries'); continue; } + + if (lib.natives) { + natives = lib.natives[Lib[process.platform]]; + } + let file = {} - let libInfo = getPathLibraries(lib.name); + let libInfo = getPathLibraries(lib.name, natives ? `-${natives}` : ''); let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); let pathLibFile = path.resolve(pathLib, libInfo.name); if (!fs.existsSync(pathLibFile)) { let url - let sizeFile = 0 - - let baseURL = `${libInfo.path}/${libInfo.name}`; + let sizeFile = 0; + let baseURL = natives ? `${libInfo.path}/` : `${libInfo.path}/${libInfo.name}`; let response: any = await downloader.checkMirror(baseURL, mirrors) if (response?.status === 200) { diff --git a/src/utils/Index.ts b/src/utils/Index.ts index d1d7aaf9..125116f8 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -78,7 +78,8 @@ let mirrors = [ "https://maven.minecraftforge.net", "https://maven.neoforged.net/releases", "https://maven.creeperhost.net", - "https://libraries.minecraft.net" + "https://libraries.minecraft.net", + "https://repo1.maven.org/maven2" ] async function getFileFromJar(jar: string, file: string = null, path: string = null) { @@ -113,6 +114,26 @@ async function createZIP(files: any, ignored: any = null) { }); } +function skipLibrary(lib) { + let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; + + let skip = false; + if (lib.rules) { + skip = true; + lib.rules.forEach(({ action, os, features }) => { + if (features) return true; + if (action === 'allow' && ((os && os.name === Lib[process.platform]) || !os)) { + skip = false; + } + + if (action === 'disallow' && ((os && os.name === Lib[process.platform]) || !os)) { + skip = true; + } + }); + } + return skip; + } + export { getPathLibraries, isold, @@ -120,5 +141,6 @@ export { mirrors, loader, getFileFromJar, - createZIP + createZIP, + skipLibrary }; \ No newline at end of file diff --git a/test/index.js b/test/index.js index 1d6ee4db..27ea949e 100755 --- a/test/index.js +++ b/test/index.js @@ -27,19 +27,17 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.8.8', + version: '1.6.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'neoforge', + type: 'forge', build: 'latest', - enable: false + enable: true }, - mcp: 'mcp/test.jar', - verify: false, ignored: [ 'config', From 7ec019b0c7158ff10ad773c619ed17648326c8ee Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 18 Nov 2023 14:15:59 +0100 Subject: [PATCH 034/185] test --- src/Launch.ts | 9 +++++++-- src/Minecraft-Loader/index.ts | 2 +- test/index.js | 14 +++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 2d443079..89571a15 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -108,13 +108,18 @@ export default class Launch { }; this.options = defaultOptions; - this.options.path = path.resolve(this.options.path).replace(/\\/g, '/'); - this.options.mcp = this.options.mcp ? path.resolve(`${this.options.path}/${this.options.mcp}`).replace(/\\/g, '/'): null; + + if (this.options.mcp) { + if (this.options.instance) this.options.mcp = `${this.options.path}/instances/${this.options.instance}/${this.options.mcp}` + else this.options.mcp = path.resolve(`${this.options.path}/${this.options.mcp}`).replace(/\\/g, '/') + } + if (this.options.loader.type) { this.options.loader.type = this.options.loader.type.toLowerCase() this.options.loader.build = this.options.loader.build.toLowerCase() } + if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 89231a9c..e4c8e334 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -138,7 +138,7 @@ export default class Loader { if (profile.error) return profile let destination = path.resolve(this.options.path, 'versions', profile.version.id) if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile, null, 4)); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); //extract universal jar let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); diff --git a/test/index.js b/test/index.js index 27ea949e..805e59a0 100755 --- a/test/index.js +++ b/test/index.js @@ -22,7 +22,7 @@ let mc } let opt = { - // url: 'http://launcher.luuxis.fr/files?instance=PokeMoonX', + // url: 'https://launcher.luuxis.fr/files/?instance=hypixel', authenticator: mc, timeout: 10000, path: './Minecraft', @@ -35,10 +35,10 @@ let mc loader: { type: 'forge', build: 'latest', - enable: true + enable: true, }, - verify: false, + verify: true, ignored: [ 'config', 'essential', @@ -56,10 +56,10 @@ let mc javaPath: null, - screen: { - width: 1500, - height: 900 - }, + // screen: { + // width: 1500, + // height: 900 + // }, memory: { min: '4G', From dd77830c10a0b3c449d170ab9fcca7bfd20c2286 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <99091642+Luuxis-tuto@users.noreply.github.com> Date: Sat, 30 Dec 2023 06:52:19 +0100 Subject: [PATCH 035/185] add windows ARM64 --- src/Minecraft/Minecraft-Java.ts | 12 ++++++++---- test/index.js | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 2a0a6fc3..911bb6f2 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -16,26 +16,30 @@ export default class java { async GetJsonJava(jsonversion: any) { let version: any; let files: any = []; + let archOS: String; let javaVersionsJson = await nodeFetch("https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json").then(res => res.json()) if (!jsonversion.javaVersion) jsonversion = "jre-legacy" else jsonversion = jsonversion.javaVersion.component if (os.platform() == "win32") { - let arch = { x64: "windows-x64", ia32: "windows-x86" } + let arch = { x64: "windows-x64", ia32: "windows-x86", arm64: "windows-arm64" } version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) + archOS = arch[os.arch()] } else if (os.platform() == "darwin") { - let arch = { x64: "mac-os", arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" } + let arch = { x64: "mac-os", arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" } version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) + archOS = arch[os.arch()] } else if (os.platform() == "linux") { let arch = { x64: "linux", ia32: "linux-i386" } version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` if (version.includes('undefined')) return { error: true, message: "Java not found" }; javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) + archOS = arch[os.arch()] } else return console.log("OS not supported"); if (!javaVersionsJson) return { error: true, message: "Java not found" }; @@ -47,7 +51,7 @@ export default class java { if (info.type == "directory") continue; if (!info.downloads) continue; let file: any = {}; - file.path = `runtime/${version}-${process.platform}/${path.replace(toDelete, "")}`; + file.path = `runtime/${version}-${archOS}/${path.replace(toDelete, "")}`; file.executable = info.executable; file.sha1 = info.downloads.raw.sha1; file.size = info.downloads.raw.size; @@ -57,7 +61,7 @@ export default class java { } return { files: files, - path: path.resolve(this.options.path, `runtime/${version}-${process.platform}/bin/java${process.platform == "win32" ? ".exe" : ""}`), + path: path.resolve(this.options.path, `runtime/${version}-${archOS}/bin/java`), }; } diff --git a/test/index.js b/test/index.js index 805e59a0..49a63a9a 100755 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -const { Microsoft, Launch, Mojang } = require('../build/Index'); +const { Microsoft, Launch } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -27,13 +27,13 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.6.4', + version: '1.20.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'forge', + type: 'neoforge', build: 'latest', enable: true, }, From 505187cfee84da20af59d415d2e3b614a3f53c26 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:02:43 +0100 Subject: [PATCH 036/185] add rootforge path --- src/Launch.ts | 2 ++ src/Minecraft/Minecraft-Loader.ts | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 89571a15..dec9bd85 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -20,6 +20,7 @@ import { isold } from './utils/Index.js'; import Downloader from './utils/Downloader.js'; type loader = { + rootPath?: boolean, type?: string, build?: string, enable?: boolean @@ -80,6 +81,7 @@ export default class Launch { downloadFileMultiple: 5, loader: { + rootPath: false, type: null, build: 'latest', enable: false, diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 7e5fd4b3..8a6dddc3 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -11,15 +11,18 @@ export default class MinecraftLoader { options: any; on: any; emit: any; + loaderPath: string; constructor(options: any) { this.options = options; this.on = EventEmitter.prototype.on; this.emit = EventEmitter.prototype.emit; + if (this.options.loader.rootPath) this.loaderPath = this.options.path; + else this.loaderPath = `${this.options.path}/loader/${this.options.loader.type}`; } async GetLoader(version: any, javaPath: any) { let loader = new loaderDownloader({ - path: `${this.options.path}/loader/${this.options.loader.type}`, + path: this.loaderPath, downloadFileMultiple: this.options.downloadFileMultiple, loader: { type: this.options.loader.type, @@ -32,14 +35,14 @@ export default class MinecraftLoader { } } }); - + return await new Promise((resolve, reject) => { loader.install(); loader.on('json', (json: any) => { let loaderJson = json; loaderJson.libraries = loaderJson.libraries.map((lib: any) => { - lib.loader = `${this.options.path}/loader/${this.options.loader.type}`; + lib.loader = this.loaderPath; return lib; }); resolve(loaderJson); @@ -82,7 +85,7 @@ export default class MinecraftLoader { if (moddeArguments.jvm) Arguments.jvm = moddeArguments.jvm.map(jvm => { return jvm .replace(/\${version_name}/g, version) - .replace(/\${library_directory}/g, `${this.options.path}/loader/${this.options.loader.type.toLowerCase()}/libraries`) + .replace(/\${library_directory}/g, `${this.loaderPath}/libraries`) .replace(/\${classpath_separator}/g, process.platform === 'win32' ? ';' : ':'); }) From 875b020fa03852ea03316fb51e3f86a93b793ae7 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:03:02 +0100 Subject: [PATCH 037/185] publish 3.8.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 948e3d9d..51440f41 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.8.1", + "version": "3.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.8.1", + "version": "3.8.2", "license": "CCANC", "dependencies": { "adm-zip": "^0.5.9", diff --git a/package.json b/package.json index c1c95fa9..611de615 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.8.1", + "version": "3.8.2", "types": "./build/Index.d.ts", "exports": { ".": { From 2bb563c97a153e30dd4aa56b802df1dcb91bd204 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:03:41 +0100 Subject: [PATCH 038/185] fix start forge 1.20.3 > 1.20.4 --- src/Minecraft-Loader/loader/forge/forge.ts | 6 ++++-- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 58336104..d5af3feb 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -182,8 +182,10 @@ export default class ForgeMC { let natives = null; if (skipForgeFilter && skipForge.find(libs => lib.name.includes(libs))) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; + if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } } if (skipLibrary(lib)) { diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 1115fec3..ca202e70 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -150,8 +150,10 @@ export default class NeoForgeMC { for (let lib of libraries) { if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; + if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { + this.emit('check', check++, libraries.length, 'libraries'); + continue; + } } if (lib.rules) { this.emit('check', check++, libraries.length, 'libraries'); From 68e55ba86627258dd170af9e0c48265af0dd4b90 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:03:50 +0100 Subject: [PATCH 039/185] Update Minecraft-Arguments.ts --- src/Minecraft/Minecraft-Arguments.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 302af909..6d0af575 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -52,7 +52,7 @@ export default class MinecraftArguments { '${auth_xuid}': this.authenticator.meta.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, '${user_type}': userType, - '${version_name}': json.id, + '${version_name}': loaderJson ? loaderJson.id : json.id, '${assets_index_name}': json.assetIndex.id, '${game_directory}': this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path, '${assets_root}': isold(json) ? `${this.options.path}/resources` : `${this.options.path}/assets`, @@ -94,7 +94,10 @@ export default class MinecraftArguments { '-XX:G1ReservePercent=20', '-XX:MaxGCPauseMillis=50', '-XX:G1HeapRegionSize=32M', - '-Dfml.ignoreInvalidMinecraftCertificates=true' + '-Dfml.ignoreInvalidMinecraftCertificates=true', + `-Djna.tmpdir=${this.options.path}/versions/${json.id}/natives`, + `-Dorg.lwjgl.system.SharedLibraryExtractPath=${this.options.path}/versions/${json.id}/natives`, + `-Dio.netty.native.workdir=${this.options.path}/versions/${json.id}/natives` ] if (!json.minecraftArguments) { @@ -147,7 +150,7 @@ export default class MinecraftArguments { if (loaderJson?.isOldForge) { classPath.push(loaderJson?.jarPath) - } else if (this.options.mcp) { + } else if (this.options.mcp) { classPath.push(this.options.mcp) } else { classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) From 913792f0150c18fae82b6c252e36ddda4d44f270 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Fri, 5 Jan 2024 21:29:23 +0100 Subject: [PATCH 040/185] fix --- src/Launch.ts | 5 +++-- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 4 ++-- src/Minecraft/Minecraft-Loader.ts | 3 +-- test/index.js | 7 ++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index dec9bd85..6e330b8f 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -20,7 +20,7 @@ import { isold } from './utils/Index.js'; import Downloader from './utils/Downloader.js'; type loader = { - rootPath?: boolean, + path?: string, type?: string, build?: string, enable?: boolean @@ -81,7 +81,7 @@ export default class Launch { downloadFileMultiple: 5, loader: { - rootPath: false, + path: './loader', type: null, build: 'latest', enable: false, @@ -125,6 +125,7 @@ export default class Launch { if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 + if (typeof this.options.loader.path !== 'string') this.options.loader.path = './loader' this.start(); } diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index ca202e70..17afe3a7 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -150,10 +150,10 @@ export default class NeoForgeMC { for (let lib of libraries) { if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { - if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { + // if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { this.emit('check', check++, libraries.length, 'libraries'); continue; - } + // } } if (lib.rules) { this.emit('check', check++, libraries.length, 'libraries'); diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 8a6dddc3..1cede670 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -16,8 +16,7 @@ export default class MinecraftLoader { this.options = options; this.on = EventEmitter.prototype.on; this.emit = EventEmitter.prototype.emit; - if (this.options.loader.rootPath) this.loaderPath = this.options.path; - else this.loaderPath = `${this.options.path}/loader/${this.options.loader.type}`; + this.loaderPath = `${this.options.path}/${this.options.loader.path}`; } async GetLoader(version: any, javaPath: any) { diff --git a/test/index.js b/test/index.js index 49a63a9a..306afb56 100755 --- a/test/index.js +++ b/test/index.js @@ -27,15 +27,16 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.20.4', + version: '1.20.2', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { + path: './loader', type: 'neoforge', - build: 'latest', - enable: true, + build: '20.2.86', + enable: true }, verify: true, From b3e71195b1356d1137689cc68359f26d80933850 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:13:29 +0100 Subject: [PATCH 041/185] Refactor JavaDownloader to use modern JS features --- src/Launch.ts | 4 +- src/Minecraft/Minecraft-Java.ts | 66 ++++++++++++++++----------------- test/index.js | 4 +- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 6e330b8f..73d9dcbd 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -125,7 +125,7 @@ export default class Launch { if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 - if (typeof this.options.loader.path !== 'string') this.options.loader.path = './loader' + if (typeof this.options.loader.path !== 'string') this.options.loader.path = `./loader/${this.options.loader.type}`; this.start(); } @@ -180,7 +180,7 @@ export default class Launch { let gameLibraries: any = await libraries.Getlibraries(json); let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); - let gameJava: any = this.options.javaPath ? { files: [] } : await new javaMinecraft(this.options).GetJsonJava(json); + let gameJava: any = this.options.javaPath ? { files: [] } : await new javaMinecraft(this.options).getJavaFiles(json); if (gameJava.error) return gameJava diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 911bb6f2..a0a553e9 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -7,51 +7,47 @@ import os from 'os'; import nodeFetch from 'node-fetch'; import path from 'path'; -export default class java { +export default class JavaDownloader { options: any; + constructor(options: any) { this.options = options; } - async GetJsonJava(jsonversion: any) { - let version: any; - let files: any = []; - let archOS: String; - let javaVersionsJson = await nodeFetch("https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json").then(res => res.json()) + async getJavaFiles(jsonversion: any) { + const archMapping = { + win32: { x64: 'windows-x64', ia32: 'windows-x86', arm64: 'windows-arm64' }, + darwin: { x64: 'mac-os', arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" }, + linux: { x64: 'linux', ia32: 'linux-i386' } + }; + + const osPlatform = os.platform(); + const arch = os.arch(); + const osArchMapping = archMapping[osPlatform]; + const javaVersion = jsonversion.javaVersion?.component || 'jre-legacy'; + let files = [] + + if (!osArchMapping) return { error: true, message: 'OS not supported' }; - if (!jsonversion.javaVersion) jsonversion = "jre-legacy" - else jsonversion = jsonversion.javaVersion.component + const archOs: any = osArchMapping[arch]; + const javaVersionsJson = await nodeFetch(`https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json`).then(res => res.json()); - if (os.platform() == "win32") { - let arch = { x64: "windows-x64", ia32: "windows-x86", arm64: "windows-arm64" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` - if (version.includes('undefined')) return { error: true, message: "Java not found" }; - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) - archOS = arch[os.arch()] - } else if (os.platform() == "darwin") { - let arch = { x64: "mac-os", arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` - if (version.includes('undefined')) return { error: true, message: "Java not found" }; - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) - archOS = arch[os.arch()] - } else if (os.platform() == "linux") { - let arch = { x64: "linux", ia32: "linux-i386" } - version = `jre-${javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.version?.name}` - if (version.includes('undefined')) return { error: true, message: "Java not found" }; - javaVersionsJson = Object.entries((await nodeFetch(javaVersionsJson[`${arch[os.arch()]}`][jsonversion][0]?.manifest?.url).then(res => res.json())).files) - archOS = arch[os.arch()] - } else return console.log("OS not supported"); + const versionName = javaVersionsJson[archOs]?.[javaVersion]?.[0]?.version?.name; - if (!javaVersionsJson) return { error: true, message: "Java not found" }; + if (!versionName) return { error: true, message: 'Java not found' }; - let java = javaVersionsJson.find(file => file[0].endsWith(process.platform == "win32" ? "bin/javaw.exe" : "bin/java"))[0]; - let toDelete = java.replace(process.platform == "win32" ? "bin/javaw.exe" : "bin/java", ""); + const manifestUrl = javaVersionsJson[archOs][javaVersion][0]?.manifest?.url; + const manifest = await nodeFetch(manifestUrl).then(res => res.json()); + const javaFiles: any = Object.entries(manifest.files); - for (let [path, info] of javaVersionsJson) { + const java = javaFiles.find(([path]) => path.endsWith(process.platform === 'win32' ? 'bin/javaw.exe' : 'bin/java'))[0]; + const toDelete = java.replace(process.platform === 'win32' ? 'bin/javaw.exe' : 'bin/java', ''); + + for (let [path, info] of javaFiles) { if (info.type == "directory") continue; if (!info.downloads) continue; let file: any = {}; - file.path = `runtime/${version}-${archOS}/${path.replace(toDelete, "")}`; + file.path = `runtime/jre-${versionName}-${archOs}/${path.replace(toDelete, "")}`; file.executable = info.executable; file.sha1 = info.downloads.raw.sha1; file.size = info.downloads.raw.size; @@ -59,10 +55,10 @@ export default class java { file.type = "Java"; files.push(file); } + return { - files: files, - path: path.resolve(this.options.path, `runtime/${version}-${archOS}/bin/java`), + files, + path: path.resolve(this.options.path, `runtime/jre-${versionName}-${archOs}/bin/java`), }; - } } \ No newline at end of file diff --git a/test/index.js b/test/index.js index 306afb56..634bd764 100755 --- a/test/index.js +++ b/test/index.js @@ -33,9 +33,9 @@ let mc downloadFileMultiple: 30, loader: { - path: './loader', + // path: './loader', type: 'neoforge', - build: '20.2.86', + build: 'latest', enable: true }, From 842027685eee849336f97456a669d26013bc6c66 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 19:01:14 +0100 Subject: [PATCH 042/185] test --- package-lock.json | 88 +++++++++++++++++++ package.json | 3 + src/Launch.ts | 8 +- src/Minecraft-Loader/loader/forge/forge.ts | 18 ++-- .../loader/neoForge/neoForge.ts | 14 +-- src/Minecraft-Loader/patcher.ts | 4 +- src/Minecraft/Minecraft-Java.ts | 82 ++++++++++++++++- src/utils/Index.ts | 36 ++++---- test/index.js | 4 +- 9 files changed, 216 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51440f41..add9512f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "3.8.2", "license": "CCANC", "dependencies": { + "7zip-bin": "^5.2.0", "adm-zip": "^0.5.9", + "node-7z": "^3.0.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", "tslib": "^2.4.1" @@ -17,6 +19,7 @@ "devDependencies": { "@types/adm-zip": "^0.5.2", "@types/node": "^18.11.13", + "@types/node-7z": "^2.1.8", "@types/node-fetch": "^2.6.2", "rimraf": "^3.0.2", "typescript": "^4.9.4" @@ -45,6 +48,15 @@ "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", "dev": true }, + "node_modules/@types/node-7z": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@types/node-7z/-/node-7z-2.1.8.tgz", + "integrity": "sha512-VjiU7yEbczNc3EFKN4GJcAUqAMkn92P/92r6ARjMSXEdixunMD9lC79mTX81vKxTlNYXuvCJ7zvnzlDbFTt2Vw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node-fetch": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", @@ -55,6 +67,11 @@ "form-data": "^3.0.0" } }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==" + }, "node_modules/adm-zip": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", @@ -124,6 +141,22 @@ "node": ">=0.4.0" } }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -207,6 +240,31 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.defaultsdeep": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", + "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==" + }, + "node_modules/lodash.defaultto": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/lodash.defaultto/-/lodash.defaultto-4.14.0.tgz", + "integrity": "sha512-G6tizqH6rg4P5j32Wy4Z3ZIip7OfG8YWWlPFzUFGcYStH1Ld0l1tWs6NevEQNEDnO1M3NZYjuHuraaFSN5WqeQ==" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" + }, + "node_modules/lodash.negate": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.negate/-/lodash.negate-3.0.2.tgz", + "integrity": "sha512-JGJYYVslKYC0tRMm/7igfdHulCjoXjoganRNWM8AgS+RXfOvFnPkOveDhPI65F9aAypCX9QEEQoBqWf7Q6uAeA==" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -240,11 +298,33 @@ "node": "*" } }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/node-7z": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-7z/-/node-7z-3.0.0.tgz", + "integrity": "sha512-KIznWSxIkOYO/vOgKQfJEaXd7rgoFYKZbaurainCEdMhYc7V7mRHX+qdf2HgbpQFcdJL/Q6/XOPrDLoBeTfuZA==", + "dependencies": { + "debug": "^4.3.2", + "lodash.defaultsdeep": "^4.6.1", + "lodash.defaultto": "^4.14.0", + "lodash.flattendeep": "^4.4.0", + "lodash.isempty": "^4.4.0", + "lodash.negate": "^3.0.2", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -264,6 +344,14 @@ } } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index 611de615..7e220a6e 100755 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "author": "Luuxis", "license": "CCANC", "dependencies": { + "7zip-bin": "^5.2.0", "adm-zip": "^0.5.9", + "node-7z": "^3.0.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", "tslib": "^2.4.1" @@ -39,6 +41,7 @@ "devDependencies": { "@types/adm-zip": "^0.5.2", "@types/node": "^18.11.13", + "@types/node-7z": "^2.1.8", "@types/node-fetch": "^2.6.2", "rimraf": "^3.0.2", "typescript": "^4.9.4" diff --git a/src/Launch.ts b/src/Launch.ts index 73d9dcbd..7019d328 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -176,11 +176,17 @@ export default class Launch { let libraries = new librariesMinecraft(this.options) let bundle = new bundleMinecraft(this.options) + let java = new javaMinecraft(this.options) + + java.on('progress', (progress: any, size: any, element: any) => { + this.emit('progress', progress, size, element); + }); let gameLibraries: any = await libraries.Getlibraries(json); let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); - let gameJava: any = this.options.javaPath ? { files: [] } : await new javaMinecraft(this.options).getJavaFiles(json); + let gameJava: any = this.options.javaPath ? { files: [] } : await java.getJavaFiles(json); + if (gameJava.error) return gameJava diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index d5af3feb..cd8c2342 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -3,7 +3,7 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ -import { getPathLibraries, getFileHash, mirrors, getFileFromJar, createZIP } from '../../../utils/Index.js'; +import { getPathLibraries, getFileHash, mirrors, getFileFromArchive, createZIP } from '../../../utils/Index.js'; import download from '../../../utils/Downloader.js'; import forgePatcher from '../../patcher.js' @@ -99,7 +99,7 @@ export default class ForgeMC { async extractProfile(pathInstaller: any) { let forgeJSON: any = {} - let file: any = await getFileFromJar(pathInstaller, 'install_profile.json') + let file: any = await getFileFromArchive(pathInstaller, 'install_profile.json') let forgeJsonOrigin = JSON.parse(file); if (!forgeJsonOrigin) return { error: { message: 'Invalid forge installer' } }; @@ -108,7 +108,7 @@ export default class ForgeMC { forgeJSON.version = forgeJsonOrigin.versionInfo; } else { forgeJSON.install = forgeJsonOrigin; - let file: any = await getFileFromJar(pathInstaller, path.basename(forgeJSON.install.json)) + let file: any = await getFileFromArchive(pathInstaller, path.basename(forgeJSON.install.json)) forgeJSON.version = JSON.parse(file); } @@ -125,17 +125,17 @@ export default class ForgeMC { let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - let file: any = await getFileFromJar(pathInstaller, profile.filePath) + let file: any = await getFileFromArchive(pathInstaller, profile.filePath) fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) } else if (profile.path) { let fileInfo = getPathLibraries(profile.path) - let listFile: any = await getFileFromJar(pathInstaller, null, `maven/${fileInfo.path}`) + let listFile: any = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`) await Promise.all( listFile.map(async (files: any) => { let fileName = files.split('/') this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); - let file: any = await getFileFromJar(pathInstaller, files) + let file: any = await getFileFromArchive(pathInstaller, files) let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) @@ -150,7 +150,7 @@ export default class ForgeMC { return (v.name || '').startsWith('net.minecraftforge:forge') }) - let client: any = await getFileFromJar(pathInstaller, 'data/client.lzma'); + let client: any = await getFileFromArchive(pathInstaller, 'data/client.lzma'); let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) @@ -273,8 +273,8 @@ export default class ForgeMC { } async createProfile(id: any, pathInstaller: any) { - let forgeFiles: any = await getFileFromJar(pathInstaller) - let minecraftJar: any = await getFileFromJar(this.options.loader.config.minecraftJar) + let forgeFiles: any = await getFileFromArchive(pathInstaller) + let minecraftJar: any = await getFileFromArchive(this.options.loader.config.minecraftJar) let data: any = await createZIP([...minecraftJar, ...forgeFiles], 'META-INF'); let destination = path.resolve(this.options.path, 'versions', id); diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 17afe3a7..88f483ce 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -3,7 +3,7 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ -import { getPathLibraries, mirrors, getFileFromJar } from '../../../utils/Index.js'; +import { getPathLibraries, mirrors, getFileFromArchive } from '../../../utils/Index.js'; import download from '../../../utils/Downloader.js'; import neoForgePatcher from '../../patcher.js' @@ -69,7 +69,7 @@ export default class NeoForgeMC { async extractProfile(pathInstaller: any) { let neoForgeJSON: any = {} - let file: any = await getFileFromJar(pathInstaller, 'install_profile.json') + let file: any = await getFileFromArchive(pathInstaller, 'install_profile.json') let neoForgeJsonOrigin = JSON.parse(file); if (!neoForgeJsonOrigin) return { error: { message: 'Invalid neoForge installer' } }; @@ -78,7 +78,7 @@ export default class NeoForgeMC { neoForgeJSON.version = neoForgeJsonOrigin.versionInfo; } else { neoForgeJSON.install = neoForgeJsonOrigin; - let file: any = await getFileFromJar(pathInstaller, path.basename(neoForgeJSON.install.json)) + let file: any = await getFileFromArchive(pathInstaller, path.basename(neoForgeJSON.install.json)) neoForgeJSON.version = JSON.parse(file); } @@ -95,17 +95,17 @@ export default class NeoForgeMC { let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - let file: any = await getFileFromJar(pathInstaller, profile.filePath) + let file: any = await getFileFromArchive(pathInstaller, profile.filePath) fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) } else if (profile.path) { let fileInfo = getPathLibraries(profile.path) - let listFile: any = await getFileFromJar(pathInstaller, null, `maven/${fileInfo.path}`) + let listFile: any = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`) await Promise.all( listFile.map(async (files: any) => { let fileName = files.split('/') this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); - let file: any = await getFileFromJar(pathInstaller, files) + let file: any = await getFileFromArchive(pathInstaller, files) let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) @@ -120,7 +120,7 @@ export default class NeoForgeMC { return (v.name || '').startsWith(oldAPI ? 'net.neoforged:forge' : 'net.neoforged:neoforge') }) - let client: any = await getFileFromJar(pathInstaller, 'data/client.lzma'); + let client: any = await getFileFromArchive(pathInstaller, 'data/client.lzma'); let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index 2d8f3e94..02baa8f7 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -3,7 +3,7 @@ import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; -import { getPathLibraries, getFileFromJar } from '../utils/Index.js'; +import { getPathLibraries, getFileFromArchive } from '../utils/Index.js'; export default class forgePatcher { options: any; @@ -132,7 +132,7 @@ export default class forgePatcher { } async readJarManifest(jarPath: string) { - let extraction: any = await getFileFromJar(jarPath, 'META-INF/MANIFEST.MF'); + let extraction: any = await getFileFromArchive(jarPath, 'META-INF/MANIFEST.MF'); if (extraction) return (extraction.toString("utf8")).split('Main-Class: ')[1].split('\r\n')[0]; return null; diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index a0a553e9..fd49c88b 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -6,12 +6,23 @@ import os from 'os'; import nodeFetch from 'node-fetch'; import path from 'path'; +import fs from 'fs'; +import EventEmitter from 'events'; +import Seven from 'node-7z'; +import sevenBin from '7zip-bin' + +import { getFileHash } from '../utils/Index.js'; +import downloader from '../utils/Downloader.js'; export default class JavaDownloader { options: any; + on: any; + emit: any; constructor(options: any) { this.options = options; + this.on = EventEmitter.prototype.on; + this.emit = EventEmitter.prototype.emit; } async getJavaFiles(jsonversion: any) { @@ -27,14 +38,14 @@ export default class JavaDownloader { const javaVersion = jsonversion.javaVersion?.component || 'jre-legacy'; let files = [] - if (!osArchMapping) return { error: true, message: 'OS not supported' }; + if (!osArchMapping) return await this.getJavaFilesArm64(jsonversion); const archOs: any = osArchMapping[arch]; const javaVersionsJson = await nodeFetch(`https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json`).then(res => res.json()); const versionName = javaVersionsJson[archOs]?.[javaVersion]?.[0]?.version?.name; - if (!versionName) return { error: true, message: 'Java not found' }; + if (!versionName) return await this.getJavaFilesArm64(jsonversion); const manifestUrl = javaVersionsJson[archOs][javaVersion][0]?.manifest?.url; const manifest = await nodeFetch(manifestUrl).then(res => res.json()); @@ -61,4 +72,71 @@ export default class JavaDownloader { path: path.resolve(this.options.path, `runtime/jre-${versionName}-${archOs}/bin/java`), }; } + + async getJavaFilesArm64(jsonversion: any) { + const majorVersion = jsonversion.javaVersion?.majorVersion || '8'; + const javaVersion = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot` + const javaVersionsJson = await nodeFetch(javaVersion).then(res => res.json()); + + const java = javaVersionsJson.find((file: any) => { + if (file.binary.image_type === 'jre') { + if (file.binary.architecture === (os.arch() === 'x64' ? 'x64' : 'aarch64')) { + if (file.binary.os === os.platform()) { + return true; + } + } + } + }); + + if (!java) return { error: true, message: "No Java found" }; + + const checksum = java.binary.package.checksum; + const url = java.binary.package.link; + const fileName = java.binary.package.name; + const version = java.release_name; + const image_type = java.binary.image_type; + const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); + const filePath = path.resolve(this.options.path, `runtime/jre-${majorVersion}/${fileName}`); + + if (fs.existsSync(filePath)) { + if (await getFileHash(filePath, 'sha256') !== checksum) fs.unlinkSync(filePath); + } + + if (!fs.existsSync(filePath)) { + if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); + let download = new downloader(); + + download.on('progress', (downloaded, size) => { + this.emit('progress', downloaded, size, fileName); + }); + + await download.downloadFile(url, pathFolder, fileName); + } + + if (await getFileHash(filePath, 'sha256') !== checksum) return { error: true, message: "Java checksum failed" }; + + // extract file tar. + await this.tarExtract(filePath, pathFolder); + console.log("Java extracted"); + + return { + files: [], + path: `${pathFolder}/${version}-${image_type}/bin/java`, + }; + } + + async tarExtract(archiveFilePath: string, extractionPath: string) { + return new Promise((resolve, reject) => { + if (process.platform !== 'win32') fs.chmodSync(sevenBin.path7za, '755'); + const myStream = Seven.extract(archiveFilePath, extractionPath, { + $progress: true, + $bin: sevenBin.path7za, + recursive: true + }) + + myStream.on('end', () => resolve); + myStream.on('progress', (progress: any) => console.log(progress) ) + myStream.on('error', (err: any) => reject(err)); + }); + } } \ No newline at end of file diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 125116f8..020b6062 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -82,7 +82,7 @@ let mirrors = [ "https://repo1.maven.org/maven2" ] -async function getFileFromJar(jar: string, file: string = null, path: string = null) { +async function getFileFromArchive(jar: string, file: string = null, path: string = null) { let fileReturn: any = [] let zip = new admZip(jar); let entries = zip.getEntries(); @@ -116,23 +116,23 @@ async function createZIP(files: any, ignored: any = null) { function skipLibrary(lib) { let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; - - let skip = false; - if (lib.rules) { - skip = true; - lib.rules.forEach(({ action, os, features }) => { - if (features) return true; - if (action === 'allow' && ((os && os.name === Lib[process.platform]) || !os)) { - skip = false; - } - - if (action === 'disallow' && ((os && os.name === Lib[process.platform]) || !os)) { - skip = true; - } - }); - } - return skip; + + let skip = false; + if (lib.rules) { + skip = true; + lib.rules.forEach(({ action, os, features }) => { + if (features) return true; + if (action === 'allow' && ((os && os.name === Lib[process.platform]) || !os)) { + skip = false; + } + + if (action === 'disallow' && ((os && os.name === Lib[process.platform]) || !os)) { + skip = true; + } + }); } + return skip; +} export { getPathLibraries, @@ -140,7 +140,7 @@ export { getFileHash, mirrors, loader, - getFileFromJar, + getFileFromArchive, createZIP, skipLibrary }; \ No newline at end of file diff --git a/test/index.js b/test/index.js index 634bd764..b784df1a 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.20.2', + version: '1.12.2', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, @@ -36,7 +36,7 @@ let mc // path: './loader', type: 'neoforge', build: 'latest', - enable: true + enable: false }, verify: true, From e55549e57036b021a70e9d9ac688fff4667ecd10 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:57:31 +0100 Subject: [PATCH 043/185] Add getJavaOther method for alternative Java retrieval --- src/Launch.ts | 22 ++++--- src/Minecraft/Minecraft-Java.ts | 103 +++++++++++++++++++------------- test/index.js | 8 ++- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 7019d328..acd1a9ad 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -37,6 +37,11 @@ type memory = { max?: string } +type javaOPTS = { + path?: string, + version?: number +} + type LaunchOPTS = { url: string | null, authenticator: any, @@ -53,7 +58,7 @@ type LaunchOPTS = { ignored: string[], JVM_ARGS: string[], GAME_ARGS: string[], - javaPath: string, + java: javaOPTS, screen: screen, memory: memory }; @@ -94,7 +99,10 @@ export default class Launch { JVM_ARGS: [], GAME_ARGS: [], - javaPath: null, + java: { + path: null, + version: null + }, screen: { width: null, @@ -150,7 +158,7 @@ export default class Launch { ...loaderArguments.game ] - let java: any = this.options.javaPath ? this.options.javaPath : minecraftJava.path; + let java: any = this.options.java.path ? this.options.java.path : minecraftJava.path; let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); @@ -178,14 +186,14 @@ export default class Launch { let bundle = new bundleMinecraft(this.options) let java = new javaMinecraft(this.options) - java.on('progress', (progress: any, size: any, element: any) => { - this.emit('progress', progress, size, element); + java.on('extract', (progress: any) => { + this.emit('extract', progress) }); let gameLibraries: any = await libraries.Getlibraries(json); let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); - let gameJava: any = this.options.javaPath ? { files: [] } : await java.getJavaFiles(json); + let gameJava: any = this.options.java.path ? { files: [] } : await java.getJavaFiles(json); if (gameJava.error) return gameJava @@ -234,7 +242,7 @@ export default class Launch { this.emit('patch', patch); }); - let jsonLoader = await loaderInstall.GetLoader(version, this.options.javaPath ? this.options.javaPath : gameJava.path) + let jsonLoader = await loaderInstall.GetLoader(version, this.options.java.path ? this.options.java.path : gameJava.path) .then((data: any) => data) .catch((err: any) => err); if (jsonLoader.error) return jsonLoader; diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index fd49c88b..3be6c2d8 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -26,6 +26,7 @@ export default class JavaDownloader { } async getJavaFiles(jsonversion: any) { + if (this.options.java.version) return await this.getJavaOther(jsonversion,this.options.java.version); const archMapping = { win32: { x64: 'windows-x64', ia32: 'windows-x86', arm64: 'windows-arm64' }, darwin: { x64: 'mac-os', arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" }, @@ -38,14 +39,14 @@ export default class JavaDownloader { const javaVersion = jsonversion.javaVersion?.component || 'jre-legacy'; let files = [] - if (!osArchMapping) return await this.getJavaFilesArm64(jsonversion); + if (!osArchMapping) return await this.getJavaOther(jsonversion); const archOs: any = osArchMapping[arch]; const javaVersionsJson = await nodeFetch(`https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json`).then(res => res.json()); const versionName = javaVersionsJson[archOs]?.[javaVersion]?.[0]?.version?.name; - if (!versionName) return await this.getJavaFilesArm64(jsonversion); + if (!versionName) return await this.getJavaOther(jsonversion); const manifestUrl = javaVersionsJson[archOs][javaVersion][0]?.manifest?.url; const manifest = await nodeFetch(manifestUrl).then(res => res.json()); @@ -73,31 +74,52 @@ export default class JavaDownloader { }; } - async getJavaFilesArm64(jsonversion: any) { - const majorVersion = jsonversion.javaVersion?.majorVersion || '8'; - const javaVersion = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot` - const javaVersionsJson = await nodeFetch(javaVersion).then(res => res.json()); - - const java = javaVersionsJson.find((file: any) => { - if (file.binary.image_type === 'jre') { - if (file.binary.architecture === (os.arch() === 'x64' ? 'x64' : 'aarch64')) { - if (file.binary.os === os.platform()) { - return true; - } - } - } - }); + async getJavaOther(jsonversion: any, versionDownload?: any) { + const majorVersion = jsonversion.javaVersion?.component || versionDownload ? versionDownload : '8'; + console.log(majorVersion) + const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; + const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); + const { platform, arch } = this.getPlatformArch(); + + const java = javaVersions.find(file => + file.binary.image_type === 'jre' && + file.binary.architecture === arch && + file.binary.os === platform); if (!java) return { error: true, message: "No Java found" }; - const checksum = java.binary.package.checksum; - const url = java.binary.package.link; - const fileName = java.binary.package.name; - const version = java.release_name; + const { checksum, link: url, name: fileName } = java.binary.package; + const { release_name: version } = java; const image_type = java.binary.image_type; const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); - const filePath = path.resolve(this.options.path, `runtime/jre-${majorVersion}/${fileName}`); + const filePath = path.resolve(pathFolder, fileName); + + await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); + + let javaPath = `${pathFolder}/${version}-${image_type}/bin/java`; + if (platform == 'mac') javaPath = `${pathFolder}/${version}-${image_type}/Contents/Home/bin/java`; + + if (!fs.existsSync(javaPath)) { + await this.extract(filePath, pathFolder); + await this.extract(filePath.replace('.gz', ''), pathFolder); + if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); + if (platform !== 'windows') fs.chmodSync(javaPath, 0o755); + } + + return { + files: [], + path: javaPath, + }; + } + + getPlatformArch() { + return { + platform: { win32: 'windows', darwin: 'mac', linux: 'linux' }[os.platform()], + arch: { x64: 'x64', ia32: 'x32', arm64: 'aarch64', arm: 'arm' }[os.arch()] + }; + } + async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }) { if (fs.existsSync(filePath)) { if (await getFileHash(filePath, 'sha256') !== checksum) fs.unlinkSync(filePath); } @@ -113,30 +135,27 @@ export default class JavaDownloader { await download.downloadFile(url, pathFolder, fileName); } - if (await getFileHash(filePath, 'sha256') !== checksum) return { error: true, message: "Java checksum failed" }; - - // extract file tar. - await this.tarExtract(filePath, pathFolder); - console.log("Java extracted"); - - return { - files: [], - path: `${pathFolder}/${version}-${image_type}/bin/java`, - }; + if (await getFileHash(filePath, 'sha256') !== checksum) { + return { error: true, message: "Java checksum failed" }; + } } - - async tarExtract(archiveFilePath: string, extractionPath: string) { + extract(filePath, destPath) { return new Promise((resolve, reject) => { - if (process.platform !== 'win32') fs.chmodSync(sevenBin.path7za, '755'); - const myStream = Seven.extract(archiveFilePath, extractionPath, { - $progress: true, + const extract = Seven.extractFull(filePath, destPath, { $bin: sevenBin.path7za, - recursive: true + recursive: true, + $progress: true, + }) + extract.on('end', () => { + resolve(true) + }) + extract.on('error', (err) => { + reject(err) + }) + + extract.on('progress', (progress) => { + if (progress.percent > 0) this.emit('extract', progress.percent); }) - - myStream.on('end', () => resolve); - myStream.on('progress', (progress: any) => console.log(progress) ) - myStream.on('error', (err: any) => reject(err)); - }); + }) } } \ No newline at end of file diff --git a/test/index.js b/test/index.js index b784df1a..d88dad54 100755 --- a/test/index.js +++ b/test/index.js @@ -27,13 +27,12 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.12.2', + version: '1.20', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - // path: './loader', type: 'neoforge', build: 'latest', enable: false @@ -55,7 +54,10 @@ let mc JVM_ARGS: [], GAME_ARGS: [], - javaPath: null, + java: { + // path: 'java', + version: null, + }, // screen: { // width: 1500, From 7b7d3544a8aea6fad6148e5dbbf3ef62c96c0367 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:16:55 +0100 Subject: [PATCH 044/185] add type download --- src/Launch.ts | 10 ++++++++-- src/Minecraft/Minecraft-Java.ts | 11 +++++------ test/index.js | 5 +++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index acd1a9ad..85d7d81a 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -39,7 +39,8 @@ type memory = { type javaOPTS = { path?: string, - version?: number + version?: number, + type?: string } type LaunchOPTS = { @@ -101,7 +102,8 @@ export default class Launch { java: { path: null, - version: null + version: null, + type: 'jre', }, screen: { @@ -186,6 +188,10 @@ export default class Launch { let bundle = new bundleMinecraft(this.options) let java = new javaMinecraft(this.options) + java.on('progress', (progress: any, size: any, element: any) => { + this.emit('progress', progress, size, element) + }); + java.on('extract', (progress: any) => { this.emit('extract', progress) }); diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 3be6c2d8..4dfeae02 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -26,7 +26,7 @@ export default class JavaDownloader { } async getJavaFiles(jsonversion: any) { - if (this.options.java.version) return await this.getJavaOther(jsonversion,this.options.java.version); + if (this.options.java.version) return await this.getJavaOther(jsonversion, this.options.java.version); const archMapping = { win32: { x64: 'windows-x64', ia32: 'windows-x86', arm64: 'windows-arm64' }, darwin: { x64: 'mac-os', arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" }, @@ -76,13 +76,12 @@ export default class JavaDownloader { async getJavaOther(jsonversion: any, versionDownload?: any) { const majorVersion = jsonversion.javaVersion?.component || versionDownload ? versionDownload : '8'; - console.log(majorVersion) const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); const { platform, arch } = this.getPlatformArch(); const java = javaVersions.find(file => - file.binary.image_type === 'jre' && + file.binary.image_type === this.options.java.type && file.binary.architecture === arch && file.binary.os === platform); @@ -96,8 +95,8 @@ export default class JavaDownloader { await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); - let javaPath = `${pathFolder}/${version}-${image_type}/bin/java`; - if (platform == 'mac') javaPath = `${pathFolder}/${version}-${image_type}/Contents/Home/bin/java`; + let javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/bin/java`; + if (platform == 'mac') javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/Contents/Home/bin/java`; if (!fs.existsSync(javaPath)) { await this.extract(filePath, pathFolder); @@ -115,7 +114,7 @@ export default class JavaDownloader { getPlatformArch() { return { platform: { win32: 'windows', darwin: 'mac', linux: 'linux' }[os.platform()], - arch: { x64: 'x64', ia32: 'x32', arm64: 'aarch64', arm: 'arm' }[os.arch()] + arch: { x64: 'x64', ia32: 'x32', arm64: this.options.intelEnabledMac ? "x64" : "aarch64", arm: 'arm' }[os.arch()] }; } diff --git a/test/index.js b/test/index.js index d88dad54..7400e6dd 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.20', + version: '1.12.2', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, @@ -56,7 +56,8 @@ let mc java: { // path: 'java', - version: null, + version: 16, + type: 'jdk', }, // screen: { From 306e43a62969c514b572761798d0dc3bab1fb62f Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:29:19 +0100 Subject: [PATCH 045/185] fix error download --- src/Minecraft/Minecraft-Java.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 4dfeae02..336b3ba1 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -93,7 +93,14 @@ export default class JavaDownloader { const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); const filePath = path.resolve(pathFolder, fileName); - await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); + await this.verifyAndDownloadFile({ + filePath, + pathFolder, + fileName, + url, + checksum, + pathExtract: `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}` + }); let javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/bin/java`; if (platform == 'mac') javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/Contents/Home/bin/java`; @@ -113,14 +120,26 @@ export default class JavaDownloader { getPlatformArch() { return { - platform: { win32: 'windows', darwin: 'mac', linux: 'linux' }[os.platform()], - arch: { x64: 'x64', ia32: 'x32', arm64: this.options.intelEnabledMac ? "x64" : "aarch64", arm: 'arm' }[os.arch()] + platform: { + win32: 'windows', + darwin: 'mac', + linux: 'linux' + }[os.platform()], + arch: { + x64: 'x64', + ia32: 'x32', + arm64: this.options.intelEnabledMac && os.platform() == 'darwin' ? "x64" : "aarch64", + arm: 'arm' + }[os.arch()] }; } - async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }) { + async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum, pathExtract }) { if (fs.existsSync(filePath)) { - if (await getFileHash(filePath, 'sha256') !== checksum) fs.unlinkSync(filePath); + if (await getFileHash(filePath, 'sha256') !== checksum) { + fs.unlinkSync(filePath); + fs.rmdirSync(pathExtract, { recursive: true }); + } } if (!fs.existsSync(filePath)) { @@ -137,7 +156,9 @@ export default class JavaDownloader { if (await getFileHash(filePath, 'sha256') !== checksum) { return { error: true, message: "Java checksum failed" }; } + } + extract(filePath, destPath) { return new Promise((resolve, reject) => { const extract = Seven.extractFull(filePath, destPath, { From cf483d435402a2aeb5c05d45a5170543bb1d3308 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:35:12 +0100 Subject: [PATCH 046/185] Update Minecraft-Java.ts --- src/Minecraft/Minecraft-Java.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 336b3ba1..6b9911fc 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -75,7 +75,7 @@ export default class JavaDownloader { } async getJavaOther(jsonversion: any, versionDownload?: any) { - const majorVersion = jsonversion.javaVersion?.component || versionDownload ? versionDownload : '8'; + const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion; const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); const { platform, arch } = this.getPlatformArch(); From 57aa160daa3e95d5a81ab44f9053353606126e9e Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:35:16 +0100 Subject: [PATCH 047/185] Update index.js --- test/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 7400e6dd..04391758 100755 --- a/test/index.js +++ b/test/index.js @@ -55,8 +55,8 @@ let mc GAME_ARGS: [], java: { - // path: 'java', - version: 16, + path: null, + version: null, type: 'jdk', }, From e35bb2fe2bbf5d6085b4e78d6f1f52c1cb80b7c1 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:52:35 +0100 Subject: [PATCH 048/185] Create meta.zip --- test/meta.zip | Bin 0 -> 83109 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/meta.zip diff --git a/test/meta.zip b/test/meta.zip new file mode 100644 index 0000000000000000000000000000000000000000..7da4b003d8663fec055fc8884c876414317a10e2 GIT binary patch literal 83109 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 Date: Tue, 23 Jan 2024 11:15:42 +0100 Subject: [PATCH 049/185] fix --- src/Launch.ts | 9 +-------- src/Minecraft-Loader/index.ts | 9 +++------ src/Minecraft-Loader/loader/fabric/fabric.ts | 13 +++++-------- src/Minecraft-Loader/loader/forge/forge.ts | 7 ++----- .../loader/legacyfabric/legacyFabric.ts | 13 +++++-------- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 11 ++++------- src/Minecraft-Loader/loader/quilt/quilt.ts | 7 ++----- src/Minecraft-Loader/patcher.ts | 7 ++----- src/Minecraft/Minecraft-Java.ts | 7 ++----- src/Minecraft/Minecraft-Loader.ts | 1 - src/utils/Downloader.ts | 9 +-------- test/index.js | 2 +- 12 files changed, 28 insertions(+), 67 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 85d7d81a..e9f21593 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -64,15 +64,8 @@ type LaunchOPTS = { memory: memory }; -export default class Launch { +export default class Launch extends EventEmitter { options: LaunchOPTS; - on: any; - emit: any; - - constructor() { - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; - } async Launch(opt: LaunchOPTS) { const defaultOptions: LaunchOPTS = { diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index e4c8e334..5891606e 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -15,15 +15,12 @@ import { EventEmitter } from 'events'; import fs from 'fs' import path from 'path' -export default class Loader { +export default class Loader extends EventEmitter { options: any; - on: any; - emit: any; constructor(options: any) { + super(); this.options = options - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async install() { @@ -100,7 +97,7 @@ export default class Loader { return profile.version; } else { - let profile: any = await forge.createProfile(installer.id,installer.filePath); + let profile: any = await forge.createProfile(installer.id, installer.filePath); if (profile.error) return profile let destination = path.resolve(this.options.path, 'versions', profile.id) if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts index d8124a75..3615df8e 100644 --- a/src/Minecraft-Loader/loader/fabric/fabric.ts +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -8,18 +8,15 @@ import download from '../../../utils/Downloader.js'; import nodeFetch from 'node-fetch' import fs from 'fs' -import path from'path' +import path from 'path' import { EventEmitter } from 'events'; -export default class FabricMC { +export default class FabricMC extends EventEmitter { options: any; - on: any; - emit: any; constructor(options = {}) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async downloadJson(Loader) { @@ -46,7 +43,7 @@ export default class FabricMC { async downloadLibraries(json) { let { libraries } = json; let downloader = new download(); - let files:any = []; + let files: any = []; let check = 0; let size = 0; @@ -64,7 +61,7 @@ export default class FabricMC { let url = `${lib.url}${libInfo.path}/${libInfo.name}` let sizeFile = 0 - let res:any = await downloader.checkURL(url); + let res: any = await downloader.checkURL(url); if (res.status === 200) { sizeFile = res.size; size += res.size; diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index cd8c2342..906b956b 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -15,15 +15,12 @@ import { skipLibrary } from '../../../utils/Index.js'; let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; -export default class ForgeMC { +export default class ForgeMC extends EventEmitter { options: any; - on: any; - emit: any; constructor(options = {}) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async downloadInstaller(Loader: any) { diff --git a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts index d8124a75..3615df8e 100644 --- a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts +++ b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts @@ -8,18 +8,15 @@ import download from '../../../utils/Downloader.js'; import nodeFetch from 'node-fetch' import fs from 'fs' -import path from'path' +import path from 'path' import { EventEmitter } from 'events'; -export default class FabricMC { +export default class FabricMC extends EventEmitter { options: any; - on: any; - emit: any; constructor(options = {}) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async downloadJson(Loader) { @@ -46,7 +43,7 @@ export default class FabricMC { async downloadLibraries(json) { let { libraries } = json; let downloader = new download(); - let files:any = []; + let files: any = []; let check = 0; let size = 0; @@ -64,7 +61,7 @@ export default class FabricMC { let url = `${lib.url}${libInfo.path}/${libInfo.name}` let sizeFile = 0 - let res:any = await downloader.checkURL(url); + let res: any = await downloader.checkURL(url); if (res.status === 200) { sizeFile = res.size; size += res.size; diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 88f483ce..c0fc5a65 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -12,15 +12,12 @@ import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; -export default class NeoForgeMC { +export default class NeoForgeMC extends EventEmitter { options: any; - on: any; - emit: any; constructor(options = {}) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async downloadInstaller(Loader: any) { @@ -151,8 +148,8 @@ export default class NeoForgeMC { for (let lib of libraries) { if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { // if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; + this.emit('check', check++, libraries.length, 'libraries'); + continue; // } } if (lib.rules) { diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts index 1e418d9a..10b9a581 100644 --- a/src/Minecraft-Loader/loader/quilt/quilt.ts +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -11,16 +11,13 @@ import fs from 'fs' import path from 'path' import { EventEmitter } from 'events'; -export default class Quilt { +export default class Quilt extends EventEmitter { options: any; versionMinecraft: any; - on: any; - emit: any; constructor(options = {}) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async downloadJson(Loader: any) { diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index 02baa8f7..c03f3fbc 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -5,15 +5,12 @@ import { EventEmitter } from 'events'; import { getPathLibraries, getFileFromArchive } from '../utils/Index.js'; -export default class forgePatcher { +export default class forgePatcher extends EventEmitter { options: any; - on: any; - emit: any; constructor(options: any) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async patcher(profile: any, config: any, neoForgeOld: boolean = true) { diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 6b9911fc..fa76b717 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -14,15 +14,12 @@ import sevenBin from '7zip-bin' import { getFileHash } from '../utils/Index.js'; import downloader from '../utils/Downloader.js'; -export default class JavaDownloader { +export default class JavaDownloader extends EventEmitter { options: any; - on: any; - emit: any; constructor(options: any) { + super(); this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; } async getJavaFiles(jsonversion: any) { diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 1cede670..ee8f64dd 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -6,7 +6,6 @@ import { EventEmitter } from 'events'; import loaderDownloader from '../Minecraft-Loader/index.js' - export default class MinecraftLoader { options: any; on: any; diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index a85b97e6..e9990e29 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -14,14 +14,7 @@ interface downloadOptions { folder: string } -export default class download { - on: any; - emit: any; - - constructor() { - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; - } +export default class download extends EventEmitter { async downloadFile(url: string, path: string, fileName: string) { if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true }); diff --git a/test/index.js b/test/index.js index 04391758..762dc900 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: '1.12.2', + version: 's', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, From a1b9cb37ae12c9d512086afe5fd4c6b29b8f6de5 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:02:03 +0100 Subject: [PATCH 050/185] test --- src/Minecraft/Minecraft-Json.ts | 2 +- test/api.txt | 14 ++++++++++++++ test/index.js | 12 ++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 test/api.txt diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index 4a72d381..29a1505f 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -14,7 +14,7 @@ export default class Json { async GetInfoVersion() { let version: string = this.options.version; - let data: any = await nodeFetch(`https://launchermeta.mojang.com/mc/game/version_manifest_v2.json?_t=${new Date().toISOString()}`); + let data: any = await nodeFetch(`https://raw.githubusercontent.com/theofficialgman/piston-meta-arm64/main/mc/game/version_manifest_noncompact.json`); data = await data.json(); if (version == 'latest_release' || version == 'r' || version == 'lr') { diff --git a/test/api.txt b/test/api.txt new file mode 100644 index 00000000..a07e8f14 --- /dev/null +++ b/test/api.txt @@ -0,0 +1,14 @@ +https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar +https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl-glfw/lwjgl-glfw-natives-linux-arm64.jar +https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.3.1/lwjgl-jemalloc-patched-natives-linux-arm64.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl-openal/lwjgl-openal-natives-linux-arm64.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl-opengl/lwjgl-opengl-natives-linux-arm64.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl-stb/lwjgl-stb-natives-linux-arm64.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl-tinyfd/lwjgl-tinyfd-natives-linux-arm64.jar +https://build.lwjgl.org/release/3.3.1/bin/lwjgl/lwjgl-natives-linux-arm64.jar \ No newline at end of file diff --git a/test/index.js b/test/index.js index 762dc900..dca329bc 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', instance: 'PokeMoonX', - version: 's', + version: '1.20.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, @@ -35,7 +35,7 @@ let mc loader: { type: 'neoforge', build: 'latest', - enable: false + enable: true }, verify: true, @@ -60,10 +60,10 @@ let mc type: 'jdk', }, - // screen: { - // width: 1500, - // height: 900 - // }, + screen: { + width: 1500, + height: 900 + }, memory: { min: '4G', From cb76cd74ab447855a22aaae8e6fe4e523123cea6 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:19:29 +0100 Subject: [PATCH 051/185] add natives linux fo ARM --- .../arm32/2.9.4-nightly-20150209.json | 96 +++++++ assets/natives-linuxARM/arm32/3.1.2.json | 202 +++++++++++++ assets/natives-linuxARM/arm32/3.1.6.json | 202 +++++++++++++ assets/natives-linuxARM/arm32/3.2.1.json | 202 +++++++++++++ assets/natives-linuxARM/arm32/3.2.2.json | 235 +++++++++++++++ assets/natives-linuxARM/arm32/3.3.1.json | 270 ++++++++++++++++++ assets/natives-linuxARM/arm32/3.3.2.json | 270 ++++++++++++++++++ .../arm64/2.9.4-nightly-20150209.json | 96 +++++++ assets/natives-linuxARM/arm64/3.1.2.json | 202 +++++++++++++ assets/natives-linuxARM/arm64/3.1.6.json | 202 +++++++++++++ assets/natives-linuxARM/arm64/3.2.1.json | 202 +++++++++++++ assets/natives-linuxARM/arm64/3.2.2.json | 235 +++++++++++++++ assets/natives-linuxARM/arm64/3.3.1.json | 270 ++++++++++++++++++ assets/natives-linuxARM/arm64/3.3.2.json | 270 ++++++++++++++++++ 14 files changed, 2954 insertions(+) create mode 100644 assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json create mode 100644 assets/natives-linuxARM/arm32/3.1.2.json create mode 100644 assets/natives-linuxARM/arm32/3.1.6.json create mode 100644 assets/natives-linuxARM/arm32/3.2.1.json create mode 100644 assets/natives-linuxARM/arm32/3.2.2.json create mode 100644 assets/natives-linuxARM/arm32/3.3.1.json create mode 100644 assets/natives-linuxARM/arm32/3.3.2.json create mode 100644 assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json create mode 100644 assets/natives-linuxARM/arm64/3.1.2.json create mode 100644 assets/natives-linuxARM/arm64/3.1.6.json create mode 100644 assets/natives-linuxARM/arm64/3.2.1.json create mode 100644 assets/natives-linuxARM/arm64/3.2.2.json create mode 100644 assets/natives-linuxARM/arm64/3.3.1.json create mode 100644 assets/natives-linuxARM/arm64/3.3.2.json diff --git a/assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json b/assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json new file mode 100644 index 00000000..9a6bd338 --- /dev/null +++ b/assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json @@ -0,0 +1,96 @@ +{ + "libraries": [ + { + "downloads": { + "classifiers": { + "natives-linux": { + "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar", + "sha1": "f3c455b71c5146acb5f8a9513247fc06db182fd5", + "size": 4521, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-2.9.4/jinput-platform-2.0.5-natives-linux.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "net.java.jinput:jinput-platform:2.0.5", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar", + "sha1": "c2e322bbec2345f1b93b96000f93e3a4c3b2bf96", + "size": 216945, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-2.9.4/jinput-2.0.5.jar" + } + }, + "name": "net.java.jinput:jinput:2.0.5" + }, + { + "downloads": { + "artifact": { + "path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar", + "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6", + "size": 7508, + "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar" + } + }, + "name": "net.java.jutils:jutils:1.0.0" + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "fa483e540a9a753a5ffbb23dcf7879a5bf752611", + "size": 475177, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-2.9.4/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar", + "sha1": "697517568c68e78ae0b4544145af031c81082dfe", + "size": 1047168, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209" + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar", + "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0", + "size": 173887, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-2.9.4/lwjgl_util-2.9.4-nightly-20150209.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209" + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.1.2.json b/assets/natives-linuxARM/arm32/3.1.2.json new file mode 100644 index 00000000..a9577cde --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.1.2.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "d2df40234fd576e708feee78bccadaf02c5712a3", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "d2df40234fd576e708feee78bccadaf02c5712a3", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "1d4e6397a4ba0df30f7477ee5ba308d4dad3387e", + "size": 59165, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9b0884b8fc763ebf9abe2fd747feaf6bc6968d8c", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "9b0884b8fc763ebf9abe2fd747feaf6bc6968d8c", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "e3c8c26f433efc2e135b9f02a275742825560bfc", + "size": 134237, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "71cbf1f0f60dc7a9c522c5b76e7e62453ca7c77c", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "71cbf1f0f60dc7a9c522c5b76e7e62453ca7c77c", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "fafb2af9deeb64b52a1642ae853493e1f13f98a2", + "size": 398418, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9a6b6e74b41c15b70964a79a32d4a9b04f614d9f", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "9a6b6e74b41c15b70964a79a32d4a9b04f614d9f", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "a7922f4e7be6aae0483432cac5ee7da7b0748346", + "size": 65526, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "f978d3bf16a4ba09dafeb3d5563786cb7002129c", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "f978d3bf16a4ba09dafeb3d5563786cb7002129c", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "0c1dc55fed2fe336c256206a2b49fc54107115f5", + "size": 80186, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "bc9e649120027fc78cfebabc17b00ba50e16a86a", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "bc9e649120027fc78cfebabc17b00ba50e16a86a", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bdf6e31bf49d466cc497444f1be31dbd522e108a", + "size": 143311, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.1.6.json b/assets/natives-linuxARM/arm32/3.1.6.json new file mode 100644 index 00000000..a9577cde --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.1.6.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "d2df40234fd576e708feee78bccadaf02c5712a3", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "d2df40234fd576e708feee78bccadaf02c5712a3", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "1d4e6397a4ba0df30f7477ee5ba308d4dad3387e", + "size": 59165, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9b0884b8fc763ebf9abe2fd747feaf6bc6968d8c", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "9b0884b8fc763ebf9abe2fd747feaf6bc6968d8c", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "e3c8c26f433efc2e135b9f02a275742825560bfc", + "size": 134237, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-jemalloc-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "71cbf1f0f60dc7a9c522c5b76e7e62453ca7c77c", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "71cbf1f0f60dc7a9c522c5b76e7e62453ca7c77c", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "fafb2af9deeb64b52a1642ae853493e1f13f98a2", + "size": 398418, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-openal-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9a6b6e74b41c15b70964a79a32d4a9b04f614d9f", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "9a6b6e74b41c15b70964a79a32d4a9b04f614d9f", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "a7922f4e7be6aae0483432cac5ee7da7b0748346", + "size": 65526, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-opengl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "f978d3bf16a4ba09dafeb3d5563786cb7002129c", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "f978d3bf16a4ba09dafeb3d5563786cb7002129c", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "0c1dc55fed2fe336c256206a2b49fc54107115f5", + "size": 80186, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-glfw-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "bc9e649120027fc78cfebabc17b00ba50e16a86a", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "bc9e649120027fc78cfebabc17b00ba50e16a86a", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bdf6e31bf49d466cc497444f1be31dbd522e108a", + "size": 143311, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.1.6/lwjgl-stb-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.2.1.json b/assets/natives-linuxARM/arm32/3.2.1.json new file mode 100644 index 00000000..7cfebef2 --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.2.1.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "c1f8d244dda855a936e136844a5c80611a5f36fe", + "size": 314536, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "c1f8d244dda855a936e136844a5c80611a5f36fe", + "size": 314536, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "a50f6e12091dccd4901e783b168b428f7653d254", + "size": 65481, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "f8f7801d8b82af7705f06c40bbea16b066479531", + "size": 37712, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "f8f7801d8b82af7705f06c40bbea16b066479531", + "size": 37712, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "e19e402887625f15a982426156976badcdffaadc", + "size": 134237, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-jemalloc-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "3dcb4c151a8ccf65f50f4aee9f6ff30a79bb7439", + "size": 79683, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "3dcb4c151a8ccf65f50f4aee9f6ff30a79bb7439", + "size": 79683, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "0dce4b9a444fcb0a4e5b75ea758b0094049daea3", + "size": 398418, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-openal-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "2350cc2bd1fe80e14ff2c39a81477b57dccfe09a", + "size": 939248, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "2350cc2bd1fe80e14ff2c39a81477b57dccfe09a", + "size": 939248, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "a6d3ff86fe3e07bd055032e93ce3b8952574a427", + "size": 56387, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-opengl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "f245bcd89c484e05b524c25ea9935b6aa1e6d7ac", + "size": 116839, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "f245bcd89c484e05b524c25ea9935b6aa1e6d7ac", + "size": 116839, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "14533fb79a7077b2eb1d156067956d2da9f3403f", + "size": 80186, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-glfw-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "acd384013d30ea9ddddd805e9a5996b2b6058c5c", + "size": 105432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "acd384013d30ea9ddddd805e9a5996b2b6058c5c", + "size": 105432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bc5bb97dbb328df0c82375637030f692a161d082", + "size": 144685, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.1/lwjgl-stb-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.1", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.2.2.json b/assets/natives-linuxARM/arm32/3.2.2.json new file mode 100644 index 00000000..e19ae72a --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.2.2.json @@ -0,0 +1,235 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "16ea3934fca417368250d1ddac01a30c1809d317", + "size": 318413, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "16ea3934fca417368250d1ddac01a30c1809d317", + "size": 318413, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "6bd0b37fef777a309936a72dc7f63126e8c79ea5", + "size": 90296, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "8224ae2e8fc6d8e1a0fc7d84dc917aa3c440620c", + "size": 33790, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "8224ae2e8fc6d8e1a0fc7d84dc917aa3c440620c", + "size": 33790, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "9163a2a5559ef87bc13ead8fea84417ea3928748", + "size": 134237, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-jemalloc-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "304f0571fd5971621ee6da86a4c1e90f6f52e2ee", + "size": 79582, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "304f0571fd5971621ee6da86a4c1e90f6f52e2ee", + "size": 79582, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "ecbc981fdd996492a1f6334f003ed62e5a8c0cd5", + "size": 398418, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-openal-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9762ae928d02147e716cd82e929b74a97ea9600a", + "size": 937609, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "9762ae928d02147e716cd82e929b74a97ea9600a", + "size": 937609, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "3af5599c74dd76dd8dbb567b3f9b4963a6abeed5", + "size": 56388, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-opengl-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "99e9a39fa8ed4167e3ff9e04d47eb32c9e69804d", + "size": 108691, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "99e9a39fa8ed4167e3ff9e04d47eb32c9e69804d", + "size": 108691, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "4265f2fbe3b9d642591165165a17cf406cf7b98e", + "size": 80186, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-glfw-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ea979b0af45b8e689f5f47c989aa8550c148d8a2", + "size": 104075, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "ea979b0af45b8e689f5f47c989aa8550c148d8a2", + "size": 104075, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "ec9d70aaebd0ff76dfeecf8f00b56118bf3706b1", + "size": 149387, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-stb-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "a8c09f5b7fa24bd53ec329c231b566497a163d5b", + "size": 5571, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-tinyfd.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "a8c09f5b7fa24bd53ec329c231b566497a163d5b", + "size": 5571, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-tinyfd.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "82d16054ada6633297a3108fb6d8bae98800c76f", + "size": 41663, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm32/raw/lwjgl-3.2.2/lwjgl-tinyfd-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.2.2", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.3.1.json b/assets/natives-linuxARM/arm32/3.3.1.json new file mode 100644 index 00000000..a76adcaa --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.3.1.json @@ -0,0 +1,270 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "816d935933f2dd743074c4e717cc25b55720f294", + "size": 104027, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "e502700e6a1a0d02bddb8b4ef85afcdc15c88358", + "size": 125778, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "a96a6d6cb3876d7813fcee53c3c24f246aeba3b3", + "size": 136157, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "f5858d34e06053b1866858fed7a685cf0c6b5926", + "size": 32306, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "ffbe35d7fa5ec9b7eca136a7c71f24d4025a510b", + "size": 400129, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "9c563bf7c10b71c6609b9f96a7c7859bdf05d21f", + "size": 85417, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "e3550fa91097fd56e361b4370fa822220fef3595", + "size": 58474, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "1a827bc02651fa44d32f424c380edc6d53f94a62", + "size": 1274449, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "b08226bab162c06ae69337d8a1b0ee0a3fdf0b90", + "size": 153889, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "22cb295464f44068add8443204ec8c85fd379cbe", + "size": 103489, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "d53d331e859217a61298fcbcf8d79137f3df345c", + "size": 48061, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + }, + "sources": { + "sha1": "4784c20508b51386ce9d572632524a5bf47ccb40", + "size": 5530, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "41a3c1dd15d6b964eb8196dde69720a3e3e5e969", + "size": 82374, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "e918fb595d1ca293a68807a9da8b519ea348a67a", + "size": 572854, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.3.1", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm32/3.3.2.json b/assets/natives-linuxARM/arm32/3.3.2.json new file mode 100644 index 00000000..360f5c24 --- /dev/null +++ b/assets/natives-linuxARM/arm32/3.3.2.json @@ -0,0 +1,270 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "757920418805fb90bfebb3d46b1d9e7669fca2eb", + "size": 135828, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "757920418805fb90bfebb3d46b1d9e7669fca2eb", + "size": 135828, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "5907d9a6b7c44fb0612a63bb1cff5992588f65be", + "size": 110067, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "0bdd2ae91adb35fd7809b7ecf2d677546b26fe4f", + "size": 126160, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "877e17e39ebcd58a9c956dc3b5b777813de0873a", + "size": 43233, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "877e17e39ebcd58a9c956dc3b5b777813de0873a", + "size": 43233, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "9367437ce192e4d6f5725d53d85520644c0b0d6f", + "size": 177571, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "1d953086a319cfb09d0703e50011849a95ab2277", + "size": 32303, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ae5357ed6d934546d3533993ea84c0cfb75eed95", + "size": 108230, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "ae5357ed6d934546d3533993ea84c0cfb75eed95", + "size": 108230, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "7c82bbc33ef49ee4094b216c940db564b2998224", + "size": 503352, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "534195bc70b8ff83c270c1d618cdafe76e9be2c4", + "size": 100606, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ee8e95be0b438602038bc1f02dc5e3d011b1b216", + "size": 928871, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "ee8e95be0b438602038bc1f02dc5e3d011b1b216", + "size": 928871, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "821f9a2d1d583c44893f42b96f6977682b48a99b", + "size": 59265, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "1301ff0d9814ac96d7020f5912d5f0f72c039fca", + "size": 1275908, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "a2550795014d622b686e9caac50b14baa87d2c70", + "size": 118874, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "a2550795014d622b686e9caac50b14baa87d2c70", + "size": 118874, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "ca9333da184aade20757151f4615f1e27ca521ae", + "size": 154928, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "dda437f20ae0c920c1c744984b4093889982b994", + "size": 103496, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9f65c248dd77934105274fcf8351abb75b34327c", + "size": 13404, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "9f65c248dd77934105274fcf8351abb75b34327c", + "size": 13404, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "807e220913aa0740449ff90d3b3d825cf5f359ed", + "size": 48788, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + }, + "sources": { + "sha1": "bd33407b8cdbac1161c759656034d283f4708051", + "size": 5527, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "4421d94af68e35dcaa31737a6fc59136a1e61b94", + "size": 786196, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "4421d94af68e35dcaa31737a6fc59136a1e61b94", + "size": 786196, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "afcbfaaa46f217e98a6da4208550f71de1f2a225", + "size": 89347, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm32.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "aff949f8180d6d1e26a47c7a2bca8163f5b987fe", + "size": 624340, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.3.2", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json b/assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json new file mode 100644 index 00000000..aa3324da --- /dev/null +++ b/assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json @@ -0,0 +1,96 @@ +{ + "libraries": [ + { + "downloads": { + "classifiers": { + "natives-linux": { + "path": "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar", + "sha1": "42b388ccb7c63cec4e9f24f4dddef33325f8b212", + "size": 10932, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-2.9.4/jinput-platform-2.0.5-natives-linux.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "net.java.jinput:jinput-platform:2.0.5", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "path": "net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar", + "sha1": "47f50f20c60495069c5a3cab65f5f87b44c1069e", + "size": 216970, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-2.9.4/jinput-2.0.5.jar" + } + }, + "name": "net.java.jinput:jinput:2.0.5" + }, + { + "downloads": { + "artifact": { + "path": "net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar", + "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6", + "size": 7508, + "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar" + } + }, + "name": "net.java.jutils:jutils:1.0.0" + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "63ac7da0f4a4785c7eadc0f8edc1e9dcc4dd08cb", + "size": 579979, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-2.9.4/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar", + "sha1": "697517568c68e78ae0b4544145af031c81082dfe", + "size": 1047168, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209" + }, + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar", + "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0", + "size": 173887, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-2.9.4/lwjgl_util-2.9.4-nightly-20150209.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209" + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/3.1.2.json b/assets/natives-linuxARM/arm64/3.1.2.json new file mode 100644 index 00000000..fab1028f --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.1.2.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "55b9dbe63745835ddde8b4c22be8da6520e8274f", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "55b9dbe63745835ddde8b4c22be8da6520e8274f", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "68c8151938f33c702528208d32084e1c18b2dc9e", + "size": 54802, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "6e9ee82494343aee0737c391e151d0147c992584", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "6e9ee82494343aee0737c391e151d0147c992584", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "762d7d80c9cdf3a3f3fc80c8a5f86612255edfe0", + "size": 156343, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc-patched-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "fa243387070b806da104e3746828968b07ca737f", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "fa243387070b806da104e3746828968b07ca737f", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "5d1f9e7c633044a5700f2a80454d2a717251f675", + "size": 469432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "862a9e64741dfab2f3a48638ebd58c35ba5e43cd", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "862a9e64741dfab2f3a48638ebd58c35ba5e43cd", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "f9dec4cedbe2d98a92dd933d030c7e3efa99411b", + "size": 67010, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "b3ae7bad5b7e7d771ba9423521fc10f4ed67bbab", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "b3ae7bad5b7e7d771ba9423521fc10f4ed67bbab", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "645aeac6de1deb1b77c7cf3abe84674f77ef1aea", + "size": 85072, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "688e32efb1ad7a09b60f0f4b7131e3dc33b286ca", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "688e32efb1ad7a09b60f0f4b7131e3dc33b286ca", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "7852b75b40a470face01cbb9f37ebc5e715944bd", + "size": 192935, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/3.1.6.json b/assets/natives-linuxARM/arm64/3.1.6.json new file mode 100644 index 00000000..fab1028f --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.1.6.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "55b9dbe63745835ddde8b4c22be8da6520e8274f", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "55b9dbe63745835ddde8b4c22be8da6520e8274f", + "size": 300107, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "68c8151938f33c702528208d32084e1c18b2dc9e", + "size": 54802, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.1.6/lwjgl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "6e9ee82494343aee0737c391e151d0147c992584", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "6e9ee82494343aee0737c391e151d0147c992584", + "size": 39899, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "762d7d80c9cdf3a3f3fc80c8a5f86612255edfe0", + "size": 156343, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-jemalloc-patched-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.1.6/lwjgl-jemalloc-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "fa243387070b806da104e3746828968b07ca737f", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "fa243387070b806da104e3746828968b07ca737f", + "size": 78718, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "5d1f9e7c633044a5700f2a80454d2a717251f675", + "size": 469432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-openal-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.1.6/lwjgl-openal-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "862a9e64741dfab2f3a48638ebd58c35ba5e43cd", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "862a9e64741dfab2f3a48638ebd58c35ba5e43cd", + "size": 830047, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "f9dec4cedbe2d98a92dd933d030c7e3efa99411b", + "size": 67010, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-opengl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.1.6/lwjgl-opengl-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "b3ae7bad5b7e7d771ba9423521fc10f4ed67bbab", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "b3ae7bad5b7e7d771ba9423521fc10f4ed67bbab", + "size": 114229, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "645aeac6de1deb1b77c7cf3abe84674f77ef1aea", + "size": 85072, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-glfw-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.1.6/lwjgl-glfw-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.1.6", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "688e32efb1ad7a09b60f0f4b7131e3dc33b286ca", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6" + }, + { + "downloads": { + "artifact": { + "sha1": "688e32efb1ad7a09b60f0f4b7131e3dc33b286ca", + "size": 104372, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "7852b75b40a470face01cbb9f37ebc5e715944bd", + "size": 192935, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.1.6/lwjgl-stb-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.1.6/lwjgl-stb-3.1.6-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.1.6", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/3.2.1.json b/assets/natives-linuxARM/arm64/3.2.1.json new file mode 100644 index 00000000..c395b0bd --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.2.1.json @@ -0,0 +1,202 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "4add49f642c6f6d0bf51e1b6fea388d5101267b9", + "size": 314115, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "4add49f642c6f6d0bf51e1b6fea388d5101267b9", + "size": 314115, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "03c691efeac999530f4b350312a5a9d85e52cf89", + "size": 60186, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.2.1/lwjgl-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "5621e055b542f6caab937b8346de5fd02105f9b2", + "size": 37712, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "5621e055b542f6caab937b8346de5fd02105f9b2", + "size": 37712, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "762d7d80c9cdf3a3f3fc80c8a5f86612255edfe0", + "size": 156343, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-jemalloc-patched-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.1/lwjgl-jemalloc-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "b6ef8efb60c888a14feebcd09addc265f1eca1a2", + "size": 79683, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "b6ef8efb60c888a14feebcd09addc265f1eca1a2", + "size": 79683, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "222a583cbfdcc3a81d5d9ad807c3d68196c8ec52", + "size": 469432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-openal-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.1/lwjgl-openal-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "282139e3a8d1402fc25e18eb6a05eb090a1e81b4", + "size": 939248, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "282139e3a8d1402fc25e18eb6a05eb090a1e81b4", + "size": 939248, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "1d5e0092e54efd3f100f2c307261155573fa78f6", + "size": 56110, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-opengl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.1/lwjgl-opengl-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ccc4ba7e8521b5981343bcdbc3cf6492f35a5aa5", + "size": 116839, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "ccc4ba7e8521b5981343bcdbc3cf6492f35a5aa5", + "size": 116839, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "d52b2a26fafb6d4eebeef74ee4f6c3ebd65004b2", + "size": 85072, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-glfw-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.1/lwjgl-glfw-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "e6898a15a8067c89771ae2949bea8f5ff7874fcc", + "size": 105432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.1" + }, + { + "downloads": { + "artifact": { + "sha1": "e6898a15a8067c89771ae2949bea8f5ff7874fcc", + "size": 105432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "b8f7ace2cb31887254bbcde88e0dc705f5de2c3f", + "size": 193998, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.1/lwjgl-stb-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.1/lwjgl-stb-3.2.1-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.1", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/3.2.2.json b/assets/natives-linuxARM/arm64/3.2.2.json new file mode 100644 index 00000000..d15ca007 --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.2.2.json @@ -0,0 +1,235 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "360899386df83d6a8407844a94478607af937f97", + "size": 318833, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "360899386df83d6a8407844a94478607af937f97", + "size": 318833, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-core.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "612efd57d12b2e48e554858eb35e7e2eb46ebb4c", + "size": 87121, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "cc04eec29b2fa8c298791af9800a3766d9617954", + "size": 33790, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "cc04eec29b2fa8c298791af9800a3766d9617954", + "size": 33790, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-jemalloc.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "762d7d80c9cdf3a3f3fc80c8a5f86612255edfe0", + "size": 156343, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-jemalloc-patched-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "6dfce9dc6a9629c75b2ae01a8df7e7be80ba0261", + "size": 79582, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "6dfce9dc6a9629c75b2ae01a8df7e7be80ba0261", + "size": 79582, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-openal.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "948e415b5b2a2c650c25b377a4a9f443b21ce92e", + "size": 469432, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-openal-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "198bc2f72e0b2eb401eb6f5999aea52909b31ac4", + "size": 937609, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "198bc2f72e0b2eb401eb6f5999aea52909b31ac4", + "size": 937609, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-opengl.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bd40897077bf7d12f562da898b18ac2c68e1f9d7", + "size": 56109, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-opengl-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "155d175037efc76630940c197ca6dea2b17d7e18", + "size": 108691, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "155d175037efc76630940c197ca6dea2b17d7e18", + "size": 108691, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-glfw.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "074ad243761147df0d060fbefc814614d2ff75cc", + "size": 85072, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-glfw-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "46a5735f3eb9d17eb5dcbdd5afa194066d2a6555", + "size": 104075, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "46a5735f3eb9d17eb5dcbdd5afa194066d2a6555", + "size": 104075, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-stb.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "077efa7d7ea41b32df5c6078e912e724cccd06db", + "size": 202038, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-stb-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.2.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "3a75b9811607633bf33c978f53964df1534a4bc1", + "size": 5571, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-tinyfd.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.2.2" + }, + { + "downloads": { + "artifact": { + "sha1": "3a75b9811607633bf33c978f53964df1534a4bc1", + "size": 5571, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-tinyfd.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "37c744ca289b5d7ae155d79e39029488b3254e5b", + "size": 37893, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.2.2/lwjgl-tinyfd-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2-natives-linux.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.2.2", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file diff --git a/assets/natives-linuxARM/arm64/3.3.1.json b/assets/natives-linuxARM/arm64/3.3.1.json new file mode 100644 index 00000000..f9bbf8e0 --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.3.1.json @@ -0,0 +1,270 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "513eb39b866d0fe131a18d5c517087805433b029", + "size": 112350, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "e502700e6a1a0d02bddb8b4ef85afcdc15c88358", + "size": 125778, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "749be48a9b86ee2c3a2da5fd77511208adcfb33b", + "size": 159993, + "url": "https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.3.1/lwjgl-jemalloc-patched-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "f5858d34e06053b1866858fed7a685cf0c6b5926", + "size": 32306, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "cf4e303257e82981b8b2e31bba3d7f8f7b8f42b2", + "size": 470743, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "9c563bf7c10b71c6609b9f96a7c7859bdf05d21f", + "size": 85417, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "1c528fb258a6e63e8fceb4482d8db0f3af10a634", + "size": 57908, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "1a827bc02651fa44d32f424c380edc6d53f94a62", + "size": 1274449, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "8e8348a1813aad7f30aaf75ea197151ebb7beba9", + "size": 205491, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "22cb295464f44068add8443204ec8c85fd379cbe", + "size": 103489, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "964f628b7a82fd909def086c0dd9a4b84bb259ae", + "size": 42654, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar" + }, + "sources": { + "sha1": "4784c20508b51386ce9d572632524a5bf47ccb40", + "size": 5530, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.3.1" + }, + { + "downloads": { + "artifact": { + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "b597401014acb7196c76d97e15a6288f54f1f692", + "size": 86308, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux.jar" + }, + "sources": { + "sha1": "e918fb595d1ca293a68807a9da8b519ea348a67a", + "size": 572854, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.3.1", + "natives": { + "linux": "natives-linux" + } + } + ] +} diff --git a/assets/natives-linuxARM/arm64/3.3.2.json b/assets/natives-linuxARM/arm64/3.3.2.json new file mode 100644 index 00000000..37cb2ba3 --- /dev/null +++ b/assets/natives-linuxARM/arm64/3.3.2.json @@ -0,0 +1,270 @@ +{ + "libraries": [ + { + "downloads": { + "artifact": { + "sha1": "757920418805fb90bfebb3d46b1d9e7669fca2eb", + "size": 135828, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "757920418805fb90bfebb3d46b1d9e7669fca2eb", + "size": 135828, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bc49e64bae0f7ff103a312ee8074a34c4eb034c7", + "size": 120168, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "0bdd2ae91adb35fd7809b7ecf2d677546b26fe4f", + "size": 126160, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-glfw:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "877e17e39ebcd58a9c956dc3b5b777813de0873a", + "size": 43233, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "877e17e39ebcd58a9c956dc3b5b777813de0873a", + "size": 43233, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "5249f18a9ae20ea86c5816bc3107a888ce7a17d2", + "size": 206402, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "1d953086a319cfb09d0703e50011849a95ab2277", + "size": 32303, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ae5357ed6d934546d3533993ea84c0cfb75eed95", + "size": 108230, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "ae5357ed6d934546d3533993ea84c0cfb75eed95", + "size": 108230, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "22408980cc579709feaf9acb807992d3ebcf693f", + "size": 590865, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "534195bc70b8ff83c270c1d618cdafe76e9be2c4", + "size": 100606, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-openal:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "ee8e95be0b438602038bc1f02dc5e3d011b1b216", + "size": 928871, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "ee8e95be0b438602038bc1f02dc5e3d011b1b216", + "size": 928871, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "bb9eb56da6d1d549d6a767218e675e36bc568eb9", + "size": 58627, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "1301ff0d9814ac96d7020f5912d5f0f72c039fca", + "size": 1275908, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-opengl:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "a2550795014d622b686e9caac50b14baa87d2c70", + "size": 118874, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "a2550795014d622b686e9caac50b14baa87d2c70", + "size": 118874, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "11a380c37b0f03cb46db235e064528f84d736ff7", + "size": 207419, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "dda437f20ae0c920c1c744984b4093889982b994", + "size": 103496, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-stb:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "9f65c248dd77934105274fcf8351abb75b34327c", + "size": 13404, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "9f65c248dd77934105274fcf8351abb75b34327c", + "size": 13404, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "93f8c5bc1984963cd79109891fb5a9d1e580373e", + "size": 43381, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar" + }, + "sources": { + "sha1": "bd33407b8cdbac1161c759656034d283f4708051", + "size": 5527, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2", + "natives": { + "linux": "natives-linux" + } + }, + { + "downloads": { + "artifact": { + "sha1": "4421d94af68e35dcaa31737a6fc59136a1e61b94", + "size": 786196, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar" + } + }, + "name": "org.lwjgl:lwjgl:3.3.2" + }, + { + "downloads": { + "artifact": { + "sha1": "4421d94af68e35dcaa31737a6fc59136a1e61b94", + "size": 786196, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar" + }, + "classifiers": { + "natives-linux": { + "sha1": "8bd89332c90a90e6bc4aa997a25c05b7db02c90a", + "size": 90795, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar", + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux.jar" + }, + "sources": { + "sha1": "aff949f8180d6d1e26a47c7a2bca8163f5b987fe", + "size": 624340, + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-sources.jar" + } + } + }, + "name": "org.lwjgl:lwjgl:3.3.2", + "natives": { + "linux": "natives-linux" + } + } + ] +} \ No newline at end of file From 8d7db403dfee74aa2350419e6a9bedd31a047cf0 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:08:54 +0100 Subject: [PATCH 052/185] . --- package-lock.json | 55 ++++++++++++++++++++++++++++---- src/Minecraft/Minecraft-Json.ts | 10 ++++-- test/api.txt | 14 -------- test/index.js | 8 ++--- test/meta.zip | Bin 83109 -> 0 bytes 5 files changed, 61 insertions(+), 26 deletions(-) delete mode 100644 test/api.txt delete mode 100644 test/meta.zip diff --git a/package-lock.json b/package-lock.json index add9512f..dab8d90d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "7zip-bin": "^5.2.0", "adm-zip": "^0.5.9", + "axios": "^1.6.5", "node-7z": "^3.0.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", @@ -88,8 +89,30 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -119,7 +142,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -161,7 +183,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -174,6 +195,25 @@ "node": "> 0.1.90" } }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -269,7 +309,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -278,7 +317,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -385,6 +423,11 @@ "node": ">= 6.0.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index 29a1505f..ec969490 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -3,7 +3,10 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ +import MinecraftNativeLinuxARM from './Minecraft-Native-Linux-ARM.js'; + import nodeFetch from 'node-fetch'; +import os from 'os'; export default class Json { options: any; @@ -14,7 +17,7 @@ export default class Json { async GetInfoVersion() { let version: string = this.options.version; - let data: any = await nodeFetch(`https://raw.githubusercontent.com/theofficialgman/piston-meta-arm64/main/mc/game/version_manifest_noncompact.json`); + let data: any = await nodeFetch(`https://launchermeta.mojang.com/mc/game/version_manifest_v2.json?_t=${new Date().toISOString()}`); data = await data.json(); if (version == 'latest_release' || version == 'r' || version == 'lr') { @@ -31,9 +34,12 @@ export default class Json { message: `Minecraft ${version} is not found.` }; + let json: any = await nodeFetch(data.url).then(res => res.json()); + if (os.platform() == 'linux' && os.arch().startsWith('arm')) json = new MinecraftNativeLinuxARM(this.options).ProcessJson(json); + return { InfoVersion: data, - json: await nodeFetch(data.url).then(res => res.json()), + json: json, version: version }; } diff --git a/test/api.txt b/test/api.txt deleted file mode 100644 index a07e8f14..00000000 --- a/test/api.txt +++ /dev/null @@ -1,14 +0,0 @@ -https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar -https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl-glfw/lwjgl-glfw-natives-linux-arm64.jar -https://github.com/theofficialgman/lwjgl3-binaries-arm64/raw/lwjgl-3.3.1/lwjgl-jemalloc-patched-natives-linux-arm64.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl-openal/lwjgl-openal-natives-linux-arm64.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl-opengl/lwjgl-opengl-natives-linux-arm64.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl-stb/lwjgl-stb-natives-linux-arm64.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl-tinyfd/lwjgl-tinyfd-natives-linux-arm64.jar -https://build.lwjgl.org/release/3.3.1/bin/lwjgl/lwjgl-natives-linux-arm64.jar \ No newline at end of file diff --git a/test/index.js b/test/index.js index dca329bc..5fae94fa 100755 --- a/test/index.js +++ b/test/index.js @@ -26,8 +26,8 @@ let mc authenticator: mc, timeout: 10000, path: './Minecraft', - instance: 'PokeMoonX', - version: '1.20.4', + // instance: 'PokeMoonX', + version: '1.7.10', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, @@ -35,7 +35,7 @@ let mc loader: { type: 'neoforge', build: 'latest', - enable: true + enable: false }, verify: true, @@ -57,7 +57,7 @@ let mc java: { path: null, version: null, - type: 'jdk', + type: 'jre', }, screen: { diff --git a/test/meta.zip b/test/meta.zip deleted file mode 100644 index 7da4b003d8663fec055fc8884c876414317a10e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83109 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 Date: Tue, 23 Jan 2024 14:33:36 +0100 Subject: [PATCH 053/185] test --- src/Launch.ts | 24 ++++++++++++------------ src/Minecraft/Minecraft-Java.ts | 8 ++++---- src/Minecraft/Minecraft-Json.ts | 2 +- test/index.js | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index e9f21593..9ea5a3c4 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -157,18 +157,18 @@ export default class Launch extends EventEmitter { let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); - let argumentsLogs: string = Arguments.join(' ') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') - this.emit('data', `Launching with arguments ${argumentsLogs}`); - - let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) - minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) + // let argumentsLogs: string = Arguments.join(' ') + // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') + // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') + // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') + // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') + // argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') + // this.emit('data', `Launching with arguments ${argumentsLogs}`); + + // let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) + // minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) + // minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) + // minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) } async DownloadGame() { diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index fa76b717..b916f43f 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -16,7 +16,6 @@ import downloader from '../utils/Downloader.js'; export default class JavaDownloader extends EventEmitter { options: any; - constructor(options: any) { super(); this.options = options; @@ -105,7 +104,7 @@ export default class JavaDownloader extends EventEmitter { if (!fs.existsSync(javaPath)) { await this.extract(filePath, pathFolder); await this.extract(filePath.replace('.gz', ''), pathFolder); - if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); + // if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); if (platform !== 'windows') fs.chmodSync(javaPath, 0o755); } @@ -156,8 +155,8 @@ export default class JavaDownloader extends EventEmitter { } - extract(filePath, destPath) { - return new Promise((resolve, reject) => { + async extract(filePath, destPath) { + return await new Promise((resolve, reject) => { const extract = Seven.extractFull(filePath, destPath, { $bin: sevenBin.path7za, recursive: true, @@ -167,6 +166,7 @@ export default class JavaDownloader extends EventEmitter { resolve(true) }) extract.on('error', (err) => { + console.log(err) reject(err) }) diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index ec969490..8ae2c8dd 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -3,7 +3,7 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ -import MinecraftNativeLinuxARM from './Minecraft-Native-Linux-ARM.js'; +import MinecraftNativeLinuxARM from './Minecraft-Lwjgl-Native.js'; import nodeFetch from 'node-fetch'; import os from 'os'; diff --git a/test/index.js b/test/index.js index 5fae94fa..ca525952 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', // instance: 'PokeMoonX', - version: '1.7.10', + version: '1.8.9', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, From e7ddd2603341aa61c5cf06257e8f7e8aa4c1c0f4 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:12:02 +0100 Subject: [PATCH 054/185] Update Minecraft-Loader.ts --- src/Minecraft/Minecraft-Loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index ee8f64dd..2f2f0b85 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -17,7 +17,7 @@ export default class MinecraftLoader { this.emit = EventEmitter.prototype.emit; this.loaderPath = `${this.options.path}/${this.options.loader.path}`; } - + async GetLoader(version: any, javaPath: any) { let loader = new loaderDownloader({ path: this.loaderPath, From 6ac1a06118910c7b394ca506a5026e25297f1087 Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:16:32 +0100 Subject: [PATCH 055/185] Update package-lock.json --- package-lock.json | 55 ++++++----------------------------------------- 1 file changed, 6 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index dab8d90d..add9512f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "7zip-bin": "^5.2.0", "adm-zip": "^0.5.9", - "axios": "^1.6.5", "node-7z": "^3.0.0", "node-fetch": "^2.6.9", "prompt": "^1.2.1", @@ -89,30 +88,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", @@ -142,6 +119,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -183,6 +161,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -195,25 +174,6 @@ "node": "> 0.1.90" } }, - "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -309,6 +269,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -317,6 +278,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -423,11 +385,6 @@ "node": ">= 6.0.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", From 23a725a67eded08dd8773241b0c26ec62141732d Mon Sep 17 00:00:00 2001 From: Luuxis-tuto <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:42:46 +0100 Subject: [PATCH 056/185] fix java --- .gitignore | 3 +-- src/Launch.ts | 24 ++++++++++++------------ src/Minecraft/Minecraft-Bundle.ts | 2 +- src/Minecraft/Minecraft-Java.ts | 5 +++-- src/Minecraft/Minecraft-Lwjgl-Native.ts | 15 +++++++++++++++ test/index.js | 6 +++--- 6 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 src/Minecraft/Minecraft-Lwjgl-Native.ts diff --git a/.gitignore b/.gitignore index 4b911503..6bd17f77 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ node_modules build -files -Minecraft +test/Minecraft test/*.json webfiles/instances/* .DS_Store diff --git a/src/Launch.ts b/src/Launch.ts index 9ea5a3c4..d74428d9 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -157,18 +157,18 @@ export default class Launch extends EventEmitter { let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); - // let argumentsLogs: string = Arguments.join(' ') - // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') - // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') - // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') - // argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') - // argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') - // this.emit('data', `Launching with arguments ${argumentsLogs}`); - - // let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) - // minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) - // minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) - // minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) + let argumentsLogs: string = Arguments.join(' ') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') + this.emit('data', `Launching with arguments ${argumentsLogs}`); + + let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) + minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) } async DownloadGame() { diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index dd7af516..6fa5d99b 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -56,7 +56,7 @@ export default class MinecraftBundle { instancePath = `/instances/${this.options.instance}` } let files = this.options.instance ? this.getFiles(`${this.options.path}/instances/${this.options.instance}`) : this.getFiles(this.options.path); - let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`)] + let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`), ...this.getFiles(`${this.options.path}/runtime`)] for (let file of this.options.ignored) { file = (`${this.options.path}${instancePath}/${file}`) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index b916f43f..aae8ccfd 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -104,7 +104,7 @@ export default class JavaDownloader extends EventEmitter { if (!fs.existsSync(javaPath)) { await this.extract(filePath, pathFolder); await this.extract(filePath.replace('.gz', ''), pathFolder); - // if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); + if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); if (platform !== 'windows') fs.chmodSync(javaPath, 0o755); } @@ -155,8 +155,9 @@ export default class JavaDownloader extends EventEmitter { } - async extract(filePath, destPath) { + async extract(filePath, destPath) { return await new Promise((resolve, reject) => { + if (os.platform() !== 'win32') fs.chmodSync(sevenBin.path7za, 0o755); const extract = Seven.extractFull(filePath, destPath, { $bin: sevenBin.path7za, recursive: true, diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts new file mode 100644 index 00000000..98f2674d --- /dev/null +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -0,0 +1,15 @@ +/** + * @author Luuxis + * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + */ + +export default class MinecraftLoader { + options: any; + constructor(options: any) { + this.options = options; + } + + async ProcessJson(version: any) { + return version; + } +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index ca525952..9be04905 100755 --- a/test/index.js +++ b/test/index.js @@ -27,7 +27,7 @@ let mc timeout: 10000, path: './Minecraft', // instance: 'PokeMoonX', - version: '1.8.9', + version: '1.20.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, @@ -56,8 +56,8 @@ let mc java: { path: null, - version: null, - type: 'jre', + version: 21, + type: 'jdk', }, screen: { From 0d1e1d026c77109b3771c651864ff964b38cb6a0 Mon Sep 17 00:00:00 2001 From: Luuxis Date: Wed, 24 Jan 2024 12:25:44 +0100 Subject: [PATCH 057/185] test --- src/Minecraft/Minecraft-Json.ts | 2 +- src/Minecraft/Minecraft-Lwjgl-Native.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index 8ae2c8dd..25db0da9 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -35,7 +35,7 @@ export default class Json { }; let json: any = await nodeFetch(data.url).then(res => res.json()); - if (os.platform() == 'linux' && os.arch().startsWith('arm')) json = new MinecraftNativeLinuxARM(this.options).ProcessJson(json); + if (os.platform() == 'linux' && os.arch().startsWith('arm')) json = await new MinecraftNativeLinuxARM(this.options).ProcessJson(json); return { InfoVersion: data, diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts index 98f2674d..891fda8f 100644 --- a/src/Minecraft/Minecraft-Lwjgl-Native.ts +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -10,6 +10,7 @@ export default class MinecraftLoader { } async ProcessJson(version: any) { + console.log(version.libraries) return version; } } \ No newline at end of file From 164b447eb949441455aa8f7964518eff754bab79 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:18:52 +0000 Subject: [PATCH 058/185] add support linux arm --- .../aarch/2.9.4.json} | 0 .../arm32 => LWJGL/aarch}/3.1.2.json | 0 .../arm32 => LWJGL/aarch}/3.1.6.json | 0 .../arm32 => LWJGL/aarch}/3.2.1.json | 0 .../arm32 => LWJGL/aarch}/3.2.2.json | 0 .../arm32 => LWJGL/aarch}/3.3.1.json | 0 .../arm32 => LWJGL/aarch}/3.3.2.json | 0 .../aarch64/2.9.4.json} | 0 .../arm64 => LWJGL/aarch64}/3.1.2.json | 0 .../arm64 => LWJGL/aarch64}/3.1.6.json | 0 .../arm64 => LWJGL/aarch64}/3.2.1.json | 0 .../arm64 => LWJGL/aarch64}/3.2.2.json | 0 .../arm64 => LWJGL/aarch64}/3.3.1.json | 0 .../arm64 => LWJGL/aarch64}/3.3.2.json | 0 src/Launch.ts | 24 ++++----- src/Minecraft/Minecraft-Java.ts | 2 +- src/Minecraft/Minecraft-Lwjgl-Native.ts | 52 ++++++++++++++++++- test/index.js | 10 ++-- 18 files changed, 68 insertions(+), 20 deletions(-) rename assets/{natives-linuxARM/arm32/2.9.4-nightly-20150209.json => LWJGL/aarch/2.9.4.json} (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.1.2.json (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.1.6.json (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.2.1.json (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.2.2.json (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.3.1.json (100%) rename assets/{natives-linuxARM/arm32 => LWJGL/aarch}/3.3.2.json (100%) rename assets/{natives-linuxARM/arm64/2.9.4-nightly-20150209.json => LWJGL/aarch64/2.9.4.json} (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.1.2.json (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.1.6.json (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.2.1.json (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.2.2.json (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.3.1.json (100%) rename assets/{natives-linuxARM/arm64 => LWJGL/aarch64}/3.3.2.json (100%) diff --git a/assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json b/assets/LWJGL/aarch/2.9.4.json similarity index 100% rename from assets/natives-linuxARM/arm32/2.9.4-nightly-20150209.json rename to assets/LWJGL/aarch/2.9.4.json diff --git a/assets/natives-linuxARM/arm32/3.1.2.json b/assets/LWJGL/aarch/3.1.2.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.1.2.json rename to assets/LWJGL/aarch/3.1.2.json diff --git a/assets/natives-linuxARM/arm32/3.1.6.json b/assets/LWJGL/aarch/3.1.6.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.1.6.json rename to assets/LWJGL/aarch/3.1.6.json diff --git a/assets/natives-linuxARM/arm32/3.2.1.json b/assets/LWJGL/aarch/3.2.1.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.2.1.json rename to assets/LWJGL/aarch/3.2.1.json diff --git a/assets/natives-linuxARM/arm32/3.2.2.json b/assets/LWJGL/aarch/3.2.2.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.2.2.json rename to assets/LWJGL/aarch/3.2.2.json diff --git a/assets/natives-linuxARM/arm32/3.3.1.json b/assets/LWJGL/aarch/3.3.1.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.3.1.json rename to assets/LWJGL/aarch/3.3.1.json diff --git a/assets/natives-linuxARM/arm32/3.3.2.json b/assets/LWJGL/aarch/3.3.2.json similarity index 100% rename from assets/natives-linuxARM/arm32/3.3.2.json rename to assets/LWJGL/aarch/3.3.2.json diff --git a/assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json b/assets/LWJGL/aarch64/2.9.4.json similarity index 100% rename from assets/natives-linuxARM/arm64/2.9.4-nightly-20150209.json rename to assets/LWJGL/aarch64/2.9.4.json diff --git a/assets/natives-linuxARM/arm64/3.1.2.json b/assets/LWJGL/aarch64/3.1.2.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.1.2.json rename to assets/LWJGL/aarch64/3.1.2.json diff --git a/assets/natives-linuxARM/arm64/3.1.6.json b/assets/LWJGL/aarch64/3.1.6.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.1.6.json rename to assets/LWJGL/aarch64/3.1.6.json diff --git a/assets/natives-linuxARM/arm64/3.2.1.json b/assets/LWJGL/aarch64/3.2.1.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.2.1.json rename to assets/LWJGL/aarch64/3.2.1.json diff --git a/assets/natives-linuxARM/arm64/3.2.2.json b/assets/LWJGL/aarch64/3.2.2.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.2.2.json rename to assets/LWJGL/aarch64/3.2.2.json diff --git a/assets/natives-linuxARM/arm64/3.3.1.json b/assets/LWJGL/aarch64/3.3.1.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.3.1.json rename to assets/LWJGL/aarch64/3.3.1.json diff --git a/assets/natives-linuxARM/arm64/3.3.2.json b/assets/LWJGL/aarch64/3.3.2.json similarity index 100% rename from assets/natives-linuxARM/arm64/3.3.2.json rename to assets/LWJGL/aarch64/3.3.2.json diff --git a/src/Launch.ts b/src/Launch.ts index d74428d9..e9f21593 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -157,18 +157,18 @@ export default class Launch extends EventEmitter { let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); - let argumentsLogs: string = Arguments.join(' ') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') - this.emit('data', `Launching with arguments ${argumentsLogs}`); - - let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) - minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) + let argumentsLogs: string = Arguments.join(' ') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') + this.emit('data', `Launching with arguments ${argumentsLogs}`); + + let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) + minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) } async DownloadGame() { diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index aae8ccfd..f577c8c7 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -72,7 +72,7 @@ export default class JavaDownloader extends EventEmitter { async getJavaOther(jsonversion: any, versionDownload?: any) { const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion; - const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; + const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion ? majorVersion : 8}/hotspot`; const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); const { platform, arch } = this.getPlatformArch(); diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts index 891fda8f..b6343278 100644 --- a/src/Minecraft/Minecraft-Lwjgl-Native.ts +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -3,14 +3,62 @@ * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ */ +import path from 'path'; +import fs from 'fs'; +import os from 'os'; + + export default class MinecraftLoader { options: any; constructor(options: any) { this.options = options; } - + async ProcessJson(version: any) { - console.log(version.libraries) + let archMapping: any = { arm64: "aarch64", arm: 'aarch' }[os.arch()] + let pathLWJGL = path.join(__dirname, `../../assets/LWJGL/${archMapping}`); + + let versionJinput = version.libraries.find((lib: any) => { + if (lib.name.startsWith("net.java.jinput:jinput-platform:")) { + return true; + } else if (lib.name.startsWith("net.java.jinput:jinput:")) { + return true; + } + })?.name.split(":").pop(); + + let versionLWJGL = version.libraries.find((lib: any) => { + if (lib.name.startsWith("org.lwjgl:lwjgl:")) { + return true; + } else if (lib.name.startsWith("org.lwjgl.lwjgl:lwjgl:")) { + return true; + } + })?.name.split(":").pop(); + + + if (versionJinput) { + version.libraries = version.libraries.filter((lib: any) => { + if (lib.name.includes("jinput")) return false + return true; + }); + } + + if (versionLWJGL) { + version.libraries = version.libraries.filter((lib: any) => { + if (lib.name.includes("lwjgl")) return false; + return true; + }); + + if (versionLWJGL.includes('2.9')){ + let versionLWJGLNatives = JSON.parse(fs.readFileSync(path.join(pathLWJGL, '2.9.4.json'), 'utf-8')); + version.libraries.push(...versionLWJGLNatives.libraries); + } else { + let versionLWJGLNatives = JSON.parse(fs.readFileSync(path.join(pathLWJGL, `${versionLWJGL}.json`), 'utf-8')); + version.libraries.push(...versionLWJGLNatives.libraries); + } + + + } + return version; } } \ No newline at end of file diff --git a/test/index.js b/test/index.js index 9be04905..9f3b7a8a 100755 --- a/test/index.js +++ b/test/index.js @@ -27,18 +27,18 @@ let mc timeout: 10000, path: './Minecraft', // instance: 'PokeMoonX', - version: '1.20.4', + version: '1.16.5', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'neoforge', + type: 'forge', build: 'latest', enable: false }, - verify: true, + verify: false, ignored: [ 'config', 'essential', @@ -56,8 +56,8 @@ let mc java: { path: null, - version: 21, - type: 'jdk', + version: null, + type: 'jre', }, screen: { From 0a6efcc808d3aa0ca0aef725d438c319f49e54ae Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:26:23 +0100 Subject: [PATCH 059/185] fix error (java.lang.IllegalStateException: Duplicate key) --- src/Minecraft/Minecraft-Arguments.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 6d0af575..481fab0f 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -156,6 +156,11 @@ export default class MinecraftArguments { classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) } + classPath = classPath.filter((url: string, index: number, self: string[]) => { + let lastSegment = url.substring(url.lastIndexOf('/') + 1); + return self.findIndex((u: string) => u.substring(u.lastIndexOf('/') + 1) === lastSegment) === index; + }); + return { classpath: [ `-cp`, From 93529e771261aece891bfd083d508b7f6d89d183 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:21:52 +0100 Subject: [PATCH 060/185] publish 3.8.3 --- README.md | 47 +++++++++++++------ package-lock.json | 4 +- package.json | 2 +- src/Authenticator/Microsoft.ts | 12 +++++ .../loader/neoForge/neoForge.ts | 4 +- test/index.js | 11 +++-- 6 files changed, 56 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6da61b20..b0aff7e3 100755 --- a/README.md +++ b/README.md @@ -48,35 +48,54 @@ const launch = new Launch(); async function main() { let opt = { + url: 'https://launcher.luuxis.fr/files/?instance=PokeMoonX', authenticator: await Mojang.login('Luuxis'), timeout: 10000, - path: './.Minecraft test', - version: '1.19.3', + path: './Minecraft', + instance: 'PokeMoonX', + version: '1.20.4', detached: false, - downloadFileMultiple: 100, + intelEnabledMac: true, + downloadFileMultiple: 30, loader: { + path: '', type: 'forge', build: 'latest', enable: true }, - verify: false, - ignored: ['loader', 'options.txt'], - args: [], - - javaPath: null, - java: true, + verify: true, + ignored: [ + 'config', + 'essential', + 'logs', + 'resourcepacks', + 'saves', + 'screenshots', + 'shaderpacks', + 'W-OVERFLOW', + 'options.txt', + 'optionsof.txt' + ], + + JVM_ARGS: [], + GAME_ARGS: [], + + java: { + path: null, + version: null, + type: 'jre', + }, screen: { - width: null, - height: null, - fullscreen: null, + width: 1500, + height: 900 }, memory: { - min: '2G', - max: '4G' + min: '4G', + max: '6G' } } diff --git a/package-lock.json b/package-lock.json index add9512f..985f0ec7 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.8.2", + "version": "3.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.8.2", + "version": "3.8.3", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 7e220a6e..0b3768a3 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.8.2", + "version": "3.8.3", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index d5a14e31..38b809aa 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -139,6 +139,18 @@ export default class Microsoft { return xsts } + let launch = await nodeFetch("https://api.minecraftservices.com/launcher/login", { + method: "POST", + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ xtoken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}`, platform: "PC_LAUNCHER"}) + }).then(res => res.json()).catch(err => { return { error: err } }); + if (launch.error) { + launch.errorType = "launch"; + return launch + } + let mcLogin = await nodeFetch("https://api.minecraftservices.com/authentication/login_with_xbox", { method: "POST", headers: { diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index c0fc5a65..ffb67980 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -147,10 +147,10 @@ export default class NeoForgeMC extends EventEmitter { for (let lib of libraries) { if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { - // if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { + if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { this.emit('check', check++, libraries.length, 'libraries'); continue; - // } + } } if (lib.rules) { this.emit('check', check++, libraries.length, 'libraries'); diff --git a/test/index.js b/test/index.js index 9f3b7a8a..39b8e084 100755 --- a/test/index.js +++ b/test/index.js @@ -27,15 +27,16 @@ let mc timeout: 10000, path: './Minecraft', // instance: 'PokeMoonX', - version: '1.16.5', + version: '1.20.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'forge', + path: '', + type: 'neoforge', build: 'latest', - enable: false + enable: true }, verify: false, @@ -56,7 +57,7 @@ let mc java: { path: null, - version: null, + version: 21, type: 'jre', }, @@ -111,4 +112,4 @@ let mc launch.on('error', err => { console.log(err); }); -})() \ No newline at end of file +})(); From 8fe0978a493822217c5b0a7f2d449d8e83e4f362 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sat, 17 Feb 2024 14:00:33 +0100 Subject: [PATCH 061/185] add info accont microsoft --- src/Authenticator/Microsoft.ts | 6 +++++- src/Minecraft/Minecraft-Arguments.ts | 2 +- test/index.js | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 38b809aa..62c4f624 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -177,11 +177,15 @@ export default class Microsoft { refresh_token: oauth2.refresh_token, user_properties: '{}', meta: { - xuid: xboxAccount.DisplayClaims.xui[0].xid, type: "Xbox", access_token_expires_in: mcLogin.expires_in + Math.floor(Date.now() / 1000), demo: profile.error ? true : false }, + xboxAccount: { + xuid: xboxAccount.DisplayClaims.xui[0].xid, + gamertag: xboxAccount.DisplayClaims.xui[0].gtg, + ageGroup: xboxAccount.DisplayClaims.xui[0].agg, + }, profile: { skins: profile.skins, capes: profile.capes diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 481fab0f..c63ca862 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -49,7 +49,7 @@ export default class MinecraftArguments { '${auth_session}': this.authenticator.access_token, '${auth_player_name}': this.authenticator.name, '${auth_uuid}': this.authenticator.uuid, - '${auth_xuid}': this.authenticator.meta.xuid || this.authenticator.access_token, + '${auth_xuid}': this.authenticator.xboxAccount.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, '${user_type}': userType, '${version_name}': loaderJson ? loaderJson.id : json.id, diff --git a/test/index.js b/test/index.js index 39b8e084..578a5765 100755 --- a/test/index.js +++ b/test/index.js @@ -34,7 +34,7 @@ let mc loader: { path: '', - type: 'neoforge', + type: 'forge', build: 'latest', enable: true }, @@ -57,7 +57,7 @@ let mc java: { path: null, - version: 21, + version: 8, type: 'jre', }, From 27115768c82a45861752134ef0251f678012f7c2 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:29:45 +0100 Subject: [PATCH 062/185] fix xbox game pass --- .gitignore | 2 +- src/Authenticator/Microsoft.ts | 41 ++++++++++++++++++++++------------ test/index.js | 9 ++++---- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 6bd17f77..82791e76 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules build test/Minecraft -test/*.json +test/*.json* webfiles/instances/* .DS_Store diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 62c4f624..0a4ddcc8 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -57,27 +57,26 @@ export default class Microsoft { async refresh(acc: any) { let timeStamp = Math.floor(Date.now() / 1000) - let oauth2 = await nodeFetch("https://login.live.com/oauth20_token.srf", { - method: "POST", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` - }).then(res => res.json()).catch(err => { return { error: err } });; - if (oauth2.error) { - oauth2.errorType = "oauth2"; - return oauth2 - }; if (timeStamp < (acc?.meta?.access_token_expires_in - 7200)) { let profile = await this.getProfile(acc) - acc.refresh_token = oauth2.refresh_token acc.profile = { skins: profile.skins, capes: profile.capes } return acc } else { + let oauth2 = await nodeFetch("https://login.live.com/oauth20_token.srf", { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` + }).then(res => res.json()).catch(err => { return { error: err } });; + if (oauth2.error) { + oauth2.errorType = "oauth2"; + return oauth2 + }; return await this.getAccount(oauth2) } } @@ -144,7 +143,7 @@ export default class Microsoft { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ xtoken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}`, platform: "PC_LAUNCHER"}) + body: JSON.stringify({ xtoken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}`, platform: "PC_LAUNCHER" }) }).then(res => res.json()).catch(err => { return { error: err } }); if (launch.error) { launch.errorType = "launch"; @@ -163,6 +162,20 @@ export default class Microsoft { return mcLogin } + let hasGame = await nodeFetch("https://api.minecraftservices.com/entitlements/mcstore", { + method: "GET", + headers: { + 'Authorization': `Bearer ${mcLogin.access_token}` + } + }).then(res => res.json()); + + if (!hasGame.items.find(i => i.name == "product_minecraft" || i.name == "game_minecraft")) { + return { + error: "You don't own the game", + errorType: "game" + } + } + let profile = await this.getProfile(mcLogin); if (profile.error) { profile.errorType = "profile"; @@ -184,7 +197,7 @@ export default class Microsoft { xboxAccount: { xuid: xboxAccount.DisplayClaims.xui[0].xid, gamertag: xboxAccount.DisplayClaims.xui[0].gtg, - ageGroup: xboxAccount.DisplayClaims.xui[0].agg, + ageGroup: xboxAccount.DisplayClaims.xui[0].agg }, profile: { skins: profile.skins, diff --git a/test/index.js b/test/index.js index 578a5765..7747ac72 100755 --- a/test/index.js +++ b/test/index.js @@ -26,16 +26,15 @@ let mc authenticator: mc, timeout: 10000, path: './Minecraft', - // instance: 'PokeMoonX', + instance: 'PokeMoonX', version: '1.20.4', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - path: '', - type: 'forge', - build: 'latest', + type: 'fabric', + build: 'Recommended', enable: true }, @@ -57,7 +56,7 @@ let mc java: { path: null, - version: 8, + version: 21, type: 'jre', }, From a0e51e470713a0b3748247956a08f0a78d3d3efd Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:30:35 +0100 Subject: [PATCH 063/185] publish 3.9.0 stable --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 985f0ec7..5fa8020f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.8.3", + "version": "3.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.8.3", + "version": "3.9.0", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 0b3768a3..5be559d7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.8.3", + "version": "3.9.0", "types": "./build/Index.d.ts", "exports": { ".": { From 71ac67059101e062695c7af00cd725baaf8703dd Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 5 May 2024 01:43:44 +0200 Subject: [PATCH 064/185] fix error start game if xuid not found --- src/Minecraft/Minecraft-Arguments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index c63ca862..d42ed308 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -49,7 +49,7 @@ export default class MinecraftArguments { '${auth_session}': this.authenticator.access_token, '${auth_player_name}': this.authenticator.name, '${auth_uuid}': this.authenticator.uuid, - '${auth_xuid}': this.authenticator.xboxAccount.xuid || this.authenticator.access_token, + '${auth_xuid}': this.authenticator.xboxAccount?.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, '${user_type}': userType, '${version_name}': loaderJson ? loaderJson.id : json.id, From 1d551c1a828d574357c809d19136cabb20bbdd5d Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 5 May 2024 01:45:42 +0200 Subject: [PATCH 065/185] add skin from AZauth --- src/Authenticator/AZauth.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 819e4c2d..c01cae38 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -50,6 +50,11 @@ export default class AZauth { meta: { online: false, type: 'AZauth', + }, + profile: { + skin: [ + await this.skin(response.uuid), + ] } } } @@ -87,6 +92,11 @@ export default class AZauth { meta: { online: false, type: 'AZauth', + }, + profile: { + skin: [ + await this.skin(response.uuid), + ] } } } @@ -102,4 +112,25 @@ export default class AZauth { if (auth.error) return false; return true } + + async skin(uuid: string) { + let response: any = await nodeFetch(`https://poke-universe.fr/api/skin-api/skins/${uuid}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + }) + + if (response.status == 404) { + return { + url: `https://poke-universe.fr/api/skin-api/skins/${uuid}`, + } + } + + response = await response.buffer() + return { + url: `https://poke-universe.fr/api/skin-api/skins/${uuid}`, + base64: "data:image/png;base64," + response.toString('base64') + } + } } \ No newline at end of file From 2a780900e922cee6a8a96d20ea66f492b9f336ec Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 5 May 2024 01:45:59 +0200 Subject: [PATCH 066/185] publish 3.10.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5fa8020f..20add963 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.9.0", + "version": "3.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.9.0", + "version": "3.10.0", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 5be559d7..ce5c80bc 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.9.0", + "version": "3.10.0", "types": "./build/Index.d.ts", "exports": { ".": { From 78e97e0f2d326f049c2e1737d69ab351fcf7f838 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 5 May 2024 01:53:41 +0200 Subject: [PATCH 067/185] fix skin > skins --- package-lock.json | 4 ++-- package.json | 2 +- src/Authenticator/AZauth.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 20add963..2aab9392 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.10.0", + "version": "3.10.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.10.0", + "version": "3.10.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index ce5c80bc..202b7e74 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.10.0", + "version": "3.10.1", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index c01cae38..cf7d8ef3 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -52,7 +52,7 @@ export default class AZauth { type: 'AZauth', }, profile: { - skin: [ + skins: [ await this.skin(response.uuid), ] } @@ -94,7 +94,7 @@ export default class AZauth { type: 'AZauth', }, profile: { - skin: [ + skins: [ await this.skin(response.uuid), ] } From 925a51d1ea8a375f1e9d14bd354cfbf774c89f14 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 9 May 2024 19:49:49 +0200 Subject: [PATCH 068/185] fix skin azAuth API --- package-lock.json | 4 ++-- package.json | 2 +- src/Authenticator/AZauth.ts | 9 ++++++--- test/AZauth.js | 9 +++++---- test/index.js | 20 ++++++++++---------- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2aab9392..572a7839 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.10.1", + "version": "3.10.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.10.1", + "version": "3.10.2", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 202b7e74..465d7057 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.10.1", + "version": "3.10.2", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index cf7d8ef3..25bab02a 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -7,8 +7,11 @@ import nodeFetch from 'node-fetch'; export default class AZauth { url: string; + skinAPI: string; + constructor(url: string) { this.url = `${url}/api/auth`; + this.skinAPI = `${url}/api/skin-api/skins`; } async login(username: string, password: string, A2F: any = null) { @@ -114,7 +117,7 @@ export default class AZauth { } async skin(uuid: string) { - let response: any = await nodeFetch(`https://poke-universe.fr/api/skin-api/skins/${uuid}`, { + let response: any = await nodeFetch(`${this.skinAPI}/${uuid}`, { method: 'GET', headers: { 'Content-Type': 'application/json' @@ -123,13 +126,13 @@ export default class AZauth { if (response.status == 404) { return { - url: `https://poke-universe.fr/api/skin-api/skins/${uuid}`, + url: `${this.skinAPI}/${uuid}` } } response = await response.buffer() return { - url: `https://poke-universe.fr/api/skin-api/skins/${uuid}`, + url: `${this.skinAPI}/${uuid}`, base64: "data:image/png;base64," + response.toString('base64') } } diff --git a/test/AZauth.js b/test/AZauth.js index cd6445dc..dbb261e8 100755 --- a/test/AZauth.js +++ b/test/AZauth.js @@ -1,7 +1,7 @@ const prompt = require('prompt') const { AZauth, Launch } = require('../build/Index'); const launch = new Launch(); -const auth = new AZauth('http://craftdium.ml/test'); +const auth = new AZauth('https://poke-universe.fr'); const fs = require('fs'); let mc @@ -47,9 +47,10 @@ async function main() { // url: 'https://luuxis.fr/api/user/893bbc-a0bc41-da8568-ef56dd-7f2df8/files', authenticator: mc, timeout: 10000, - path: './.Minecraft', - version: '1.19.3', + path: './Minecraft', + version: '1.16.5', detached: false, + intelEnabledMac: true, downloadFileMultiple: 10, loader: { @@ -77,7 +78,7 @@ async function main() { } } - await launch.Launch(opt); + // await launch.Launch(opt); launch.on('extract', extract => { console.log(extract); diff --git a/test/index.js b/test/index.js index 7747ac72..86e0823a 100755 --- a/test/index.js +++ b/test/index.js @@ -22,19 +22,19 @@ let mc } let opt = { - // url: 'https://launcher.luuxis.fr/files/?instance=hypixel', + url: 'http://launcher.poke-universe.fr/files?instance=poke-universe', authenticator: mc, timeout: 10000, + instance: 'test', path: './Minecraft', - instance: 'PokeMoonX', - version: '1.20.4', + version: '1.16.5', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'fabric', - build: 'Recommended', + type: 'forge', + build: 'latest', enable: true }, @@ -56,14 +56,14 @@ let mc java: { path: null, - version: 21, + version: null, type: 'jre', }, - screen: { - width: 1500, - height: 900 - }, + // screen: { + // width: 1500, + // height: 900 + // }, memory: { min: '4G', From ec0a5fa487d549fb6dac83259f42e7a6509205e2 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 13 May 2024 20:48:44 +0200 Subject: [PATCH 069/185] fix skin --- src/Authenticator/AZauth.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 25bab02a..0b50918c 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -46,6 +46,7 @@ export default class AZauth { name: response.username, user_properties: '{}', user_info: { + id: response.id, banned: response.banned, money: response.money, role: response.role @@ -88,6 +89,7 @@ export default class AZauth { name: response.username, user_properties: '{}', user_info: { + id: response.id, banned: response.banned, money: response.money, role: response.role @@ -98,7 +100,7 @@ export default class AZauth { }, profile: { skins: [ - await this.skin(response.uuid), + await this.skin(response.id), ] } } From 62f3f88787131a6f9b1ec42a4238d931e4771633 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 13 May 2024 20:50:02 +0200 Subject: [PATCH 070/185] publish 3.10.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 572a7839..4d84d1a9 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.10.2", + "version": "3.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.10.2", + "version": "3.10.3", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 465d7057..7a91badb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.10.2", + "version": "3.10.3", "types": "./build/Index.d.ts", "exports": { ".": { From efa48b85f86f9896b4f29dab69acf19aa07df84b Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Mon, 13 May 2024 21:10:11 +0200 Subject: [PATCH 071/185] fix --- package-lock.json | 4 ++-- package.json | 2 +- src/Authenticator/AZauth.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d84d1a9..e10f0c23 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.10.3", + "version": "3.10.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.10.3", + "version": "3.10.4", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 7a91badb..b92e6e51 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.10.3", + "version": "3.10.4", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 0b50918c..b6975cf9 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -57,7 +57,7 @@ export default class AZauth { }, profile: { skins: [ - await this.skin(response.uuid), + await this.skin(response.id), ] } } From 6cb8d7b262a6b01fe4c0751fd86d0b9e17764bc2 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Sun, 30 Jun 2024 16:01:19 +0200 Subject: [PATCH 072/185] Update neoForge.ts --- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index ffb67980..e903c427 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -30,7 +30,8 @@ export default class NeoForgeMC extends EventEmitter { let versions = legacyMetaData.versions.filter(version => version.includes(`${this.options.loader.version}-`)); if (!versions.length) { - let minecraftVersion = `${this.options.loader.version.split('.')[1]}.${this.options.loader.version.split('.')[2]}`; + let minecraftVersion = `${this.options.loader.version.split('.')[1]}.${this.options.loader.version.split('.')[2] || 0}`; + console.log(minecraftVersion); versions = metaData.versions.filter(version => version.startsWith(minecraftVersion)); oldAPI = false; } From 9e320631eedeecf161d456a74a5eae3a664dada6 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:35:55 +0200 Subject: [PATCH 073/185] Refactor: Utilize path.join for loaderPath construction --- src/Minecraft/Minecraft-Loader.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 2f2f0b85..a07c61d4 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events'; import loaderDownloader from '../Minecraft-Loader/index.js' +import path from 'path' export default class MinecraftLoader { options: any; @@ -15,7 +16,7 @@ export default class MinecraftLoader { this.options = options; this.on = EventEmitter.prototype.on; this.emit = EventEmitter.prototype.emit; - this.loaderPath = `${this.options.path}/${this.options.loader.path}`; + this.loaderPath = path.join(this.options.path, this.options.loader.path); } async GetLoader(version: any, javaPath: any) { From af2b4e24e72916e5e64cdceec890e824e12ae73e Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:50:28 +0200 Subject: [PATCH 074/185] fix dupli lib class path --- src/Minecraft/Minecraft-Arguments.ts | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index d42ed308..60476a0a 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -124,10 +124,20 @@ export default class MinecraftArguments { async GetClassPath(json: any, loaderJson: any) { let classPath: any = [] let libraries: any = json.libraries; + let seenSegments = new Set(); if (loaderJson?.libraries) libraries = loaderJson.libraries.concat(libraries); libraries = libraries.filter((library: any, index: any, self: any) => index === self.findIndex((res: any) => res.name === library.name)) + libraries = Object.values(libraries.reduce((acc, lib) => { + const libName = lib.name.replace(/:(\d+[\d.:]*)(@.*)?$/, ':'); + const version = lib.name.match(/:(\d+[\d.:]*)/)[1]; + + if (!acc[libName] || acc[libName].name.match(/:(\d+[\d.:]*)/)[1] < version) acc[libName] = lib; + return acc; + }, {})); + + for (let lib of libraries) { if (lib.natives) { let native = lib.natives[MojangLib[process.platform]]; @@ -139,26 +149,27 @@ export default class MinecraftArguments { } } - - let path = getPathLibraries(lib.name) + let path = getPathLibraries(lib.name); if (lib.loader) { - classPath.push(`${lib.loader}/libraries/${path.path}/${path.name}`) + classPath.push(`${lib.loader}/libraries/${path.path}/${path.name}`); } else { - classPath.push(`${this.options.path}/libraries/${path.path}/${path.name}`) + classPath.push(`${this.options.path}/libraries/${path.path}/${path.name}`); } } if (loaderJson?.isOldForge) { - classPath.push(loaderJson?.jarPath) + classPath.push(loaderJson?.jarPath); } else if (this.options.mcp) { - classPath.push(this.options.mcp) + classPath.push(this.options.mcp); } else { - classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`) + classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`); } - classPath = classPath.filter((url: string, index: number, self: string[]) => { - let lastSegment = url.substring(url.lastIndexOf('/') + 1); - return self.findIndex((u: string) => u.substring(u.lastIndexOf('/') + 1) === lastSegment) === index; + classPath = classPath.filter((url: string) => { + const lastSegment = url.substring(url.lastIndexOf('/') + 1); + if (seenSegments.has(lastSegment)) return false; + seenSegments.add(lastSegment); + return true; }); return { From 1c7f5b12537a35762779f8de1d3ef42eb126ea14 Mon Sep 17 00:00:00 2001 From: andre <61792917+luuxis@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:50:43 +0200 Subject: [PATCH 075/185] publish 3.11.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e10f0c23..a1733d06 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.10.4", + "version": "3.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.10.4", + "version": "3.11.0", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index b92e6e51..bded8ca7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.10.4", + "version": "3.11.0", "types": "./build/Index.d.ts", "exports": { ".": { From b13e33bdfaebeffc97e3e7df565476efe1960985 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:48:00 +0200 Subject: [PATCH 076/185] publish 3.11.1-beta.1 --- package-lock.json | 4 +- package.json | 2 +- src/Launch.ts | 2 +- .../loader/neoForge/neoForge.ts | 5 +-- src/Minecraft/Minecraft-Arguments.ts | 39 ++++++++----------- src/Minecraft/Minecraft-Java.ts | 3 +- src/Minecraft/Minecraft-Loader.ts | 2 +- src/Minecraft/Minecraft-Lwjgl-Native.ts | 4 +- src/utils/Downloader.ts | 1 - test/index.js | 9 +++-- 10 files changed, 31 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1733d06..51731386 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.0", + "version": "3.11.1-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.0", + "version": "3.11.1-beta.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index bded8ca7..113e841b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.0", + "version": "3.11.1-beta.1", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Launch.ts b/src/Launch.ts index e9f21593..5322c864 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -161,7 +161,7 @@ export default class Launch extends EventEmitter { argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xboxAccount.xuid, '????????') argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') this.emit('data', `Launching with arguments ${argumentsLogs}`); diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index e903c427..5758190b 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -31,7 +31,6 @@ export default class NeoForgeMC extends EventEmitter { if (!versions.length) { let minecraftVersion = `${this.options.loader.version.split('.')[1]}.${this.options.loader.version.split('.')[2] || 0}`; - console.log(minecraftVersion); versions = metaData.versions.filter(version => version.startsWith(minecraftVersion)); oldAPI = false; } @@ -149,8 +148,8 @@ export default class NeoForgeMC extends EventEmitter { for (let lib of libraries) { if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; + this.emit('check', check++, libraries.length, 'libraries'); + continue; } } if (lib.rules) { diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 60476a0a..391efa2b 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -122,21 +122,12 @@ export default class MinecraftArguments { } async GetClassPath(json: any, loaderJson: any) { - let classPath: any = [] + let librariesList: string[] = [] + let classPath: string[] = [] let libraries: any = json.libraries; - let seenSegments = new Set(); if (loaderJson?.libraries) libraries = loaderJson.libraries.concat(libraries); - libraries = libraries.filter((library: any, index: any, self: any) => index === self.findIndex((res: any) => res.name === library.name)) - - libraries = Object.values(libraries.reduce((acc, lib) => { - const libName = lib.name.replace(/:(\d+[\d.:]*)(@.*)?$/, ':'); - const version = lib.name.match(/:(\d+[\d.:]*)/)[1]; - - if (!acc[libName] || acc[libName].name.match(/:(\d+[\d.:]*)/)[1] < version) acc[libName] = lib; - return acc; - }, {})); - + libraries = libraries.filter((library: any, index: any, self: any) => index === self.findIndex((res: any) => res.name === library.name)); for (let lib of libraries) { if (lib.natives) { @@ -151,32 +142,34 @@ export default class MinecraftArguments { let path = getPathLibraries(lib.name); if (lib.loader) { - classPath.push(`${lib.loader}/libraries/${path.path}/${path.name}`); + librariesList.push(`${lib.loader}/libraries/${path.path}/${path.name}`); } else { - classPath.push(`${this.options.path}/libraries/${path.path}/${path.name}`); + librariesList.push(`${this.options.path}/libraries/${path.path}/${path.name}`); } } if (loaderJson?.isOldForge) { - classPath.push(loaderJson?.jarPath); + librariesList.push(loaderJson?.jarPath); } else if (this.options.mcp) { - classPath.push(this.options.mcp); + librariesList.push(this.options.mcp); } else { - classPath.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`); + librariesList.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`); } - classPath = classPath.filter((url: string) => { - const lastSegment = url.substring(url.lastIndexOf('/') + 1); - if (seenSegments.has(lastSegment)) return false; - seenSegments.add(lastSegment); - return true; + + classPath = librariesList.filter((path: string) => { + let lib = path.split('/').pop(); + if (lib && !classPath.includes(lib)) { + classPath.push(lib); + return true; + } + return false; }); return { classpath: [ `-cp`, classPath.join(process.platform === 'win32' ? ';' : ':'), - ], mainClass: loaderJson ? loaderJson.mainClass : json.mainClass } diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index f577c8c7..dab01ae6 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -33,7 +33,7 @@ export default class JavaDownloader extends EventEmitter { const arch = os.arch(); const osArchMapping = archMapping[osPlatform]; const javaVersion = jsonversion.javaVersion?.component || 'jre-legacy'; - let files = [] + let files = []; if (!osArchMapping) return await this.getJavaOther(jsonversion); @@ -167,7 +167,6 @@ export default class JavaDownloader extends EventEmitter { resolve(true) }) extract.on('error', (err) => { - console.log(err) reject(err) }) diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index a07c61d4..775803e0 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -18,7 +18,7 @@ export default class MinecraftLoader { this.emit = EventEmitter.prototype.emit; this.loaderPath = path.join(this.options.path, this.options.loader.path); } - + async GetLoader(version: any, javaPath: any) { let loader = new loaderDownloader({ path: this.loaderPath, diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts index b6343278..e83f6bea 100644 --- a/src/Minecraft/Minecraft-Lwjgl-Native.ts +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -15,7 +15,7 @@ export default class MinecraftLoader { } async ProcessJson(version: any) { - let archMapping: any = { arm64: "aarch64", arm: 'aarch' }[os.arch()] + let archMapping: any = { arm64: "aarch64", arm: 'aarch' }[os.arch()] let pathLWJGL = path.join(__dirname, `../../assets/LWJGL/${archMapping}`); let versionJinput = version.libraries.find((lib: any) => { @@ -48,7 +48,7 @@ export default class MinecraftLoader { return true; }); - if (versionLWJGL.includes('2.9')){ + if (versionLWJGL.includes('2.9')) { let versionLWJGLNatives = JSON.parse(fs.readFileSync(path.join(pathLWJGL, '2.9.4.json'), 'utf-8')); version.libraries.push(...versionLWJGLNatives.libraries); } else { diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index e9990e29..2b535827 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -15,7 +15,6 @@ interface downloadOptions { } export default class download extends EventEmitter { - async downloadFile(url: string, path: string, fileName: string) { if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true }); const writer = fs.createWriteStream(path + '/' + fileName); diff --git a/test/index.js b/test/index.js index 86e0823a..104e3735 100755 --- a/test/index.js +++ b/test/index.js @@ -22,20 +22,21 @@ let mc } let opt = { - url: 'http://launcher.poke-universe.fr/files?instance=poke-universe', + url: 'http://launcher.luuxis.fr/files?instance=TEST', authenticator: mc, timeout: 10000, instance: 'test', path: './Minecraft', - version: '1.16.5', + version: '1.21', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'forge', + type: 'neoforge', build: 'latest', - enable: true + enable: true, + path: './', }, verify: false, From 6075484cddfc30774bdff93b88ecd90fcb9e67c5 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Oct 2024 00:48:20 +0200 Subject: [PATCH 077/185] Refactor and optimize TypeScript code for improved efficiency and readability --- src/Minecraft/Minecraft-Java.ts | 125 +++++++++++++++++--------------- 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index dab01ae6..987aea2b 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -70,77 +70,87 @@ export default class JavaDownloader extends EventEmitter { }; } + async getJavaOther(jsonversion: any, versionDownload?: any) { - const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion; - const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion ? majorVersion : 8}/hotspot`; + const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; + const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); + const { platform, arch } = this.getPlatformArch(); - const java = javaVersions.find(file => - file.binary.image_type === this.options.java.type && - file.binary.architecture === arch && - file.binary.os === platform); + const java = javaVersions.find(({ binary }) => + binary.image_type === this.options.java.type && + binary.architecture === arch && + binary.os === platform + ); if (!java) return { error: true, message: "No Java found" }; const { checksum, link: url, name: fileName } = java.binary.package; - const { release_name: version } = java; - const image_type = java.binary.image_type; const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); - const filePath = path.resolve(pathFolder, fileName); - - await this.verifyAndDownloadFile({ - filePath, - pathFolder, - fileName, - url, - checksum, - pathExtract: `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}` - }); + const filePath = path.join(pathFolder, fileName); - let javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/bin/java`; - if (platform == 'mac') javaPath = `${pathFolder}/${version}${image_type === 'jre' ? '-jre' : ''}/Contents/Home/bin/java`; + await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); + + let javaPath = path.join(pathFolder, 'bin', 'java'); + if (platform === 'mac') javaPath = path.join(pathFolder, 'Contents', 'Home', 'bin', 'java'); if (!fs.existsSync(javaPath)) { await this.extract(filePath, pathFolder); - await this.extract(filePath.replace('.gz', ''), pathFolder); - if (fs.existsSync(filePath.replace('.gz', ''))) fs.unlinkSync(filePath.replace('.gz', '')); + fs.unlinkSync(filePath); + + if (filePath.endsWith('.tar.gz')) { + const tarFilePath = filePath.replace('.gz', ''); + await this.extract(tarFilePath, pathFolder); + if (fs.existsSync(tarFilePath)) fs.unlinkSync(tarFilePath); + } + + const extractedItems = fs.readdirSync(pathFolder); + if (extractedItems.length === 1) { + const extractedFolder = path.join(pathFolder, extractedItems[0]); + const stat = fs.statSync(extractedFolder); + if (stat.isDirectory()) { + const subItems = fs.readdirSync(extractedFolder); + for (const item of subItems) { + const srcPath = path.join(extractedFolder, item); + const destPath = path.join(pathFolder, item); + fs.renameSync(srcPath, destPath); + } + fs.rmdirSync(extractedFolder); + } + } + if (platform !== 'windows') fs.chmodSync(javaPath, 0o755); } - return { - files: [], - path: javaPath, - }; + return { files: [], path: javaPath }; } getPlatformArch() { - return { - platform: { - win32: 'windows', - darwin: 'mac', - linux: 'linux' - }[os.platform()], - arch: { - x64: 'x64', - ia32: 'x32', - arm64: this.options.intelEnabledMac && os.platform() == 'darwin' ? "x64" : "aarch64", - arm: 'arm' - }[os.arch()] - }; + const platformMap = { win32: 'windows', darwin: 'mac', linux: 'linux' }; + const archMap = { x64: 'x64', ia32: 'x32', arm64: 'aarch64', arm: 'arm' }; + const platform = platformMap[os.platform()] || os.platform(); + let arch = archMap[os.arch()] || os.arch(); + + if (os.platform() === 'darwin' && os.arch() === 'arm64' && this.options.intelEnabledMac) { + arch = 'x64'; + } + + return { platform, arch }; } - async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum, pathExtract }) { + async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }) { if (fs.existsSync(filePath)) { - if (await getFileHash(filePath, 'sha256') !== checksum) { + const existingChecksum = await getFileHash(filePath, 'sha256'); + if (existingChecksum !== checksum) { fs.unlinkSync(filePath); - fs.rmdirSync(pathExtract, { recursive: true }); + fs.rmSync(pathFolder, { recursive: true, force: true }); } } if (!fs.existsSync(filePath)) { - if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); - let download = new downloader(); + fs.mkdirSync(pathFolder, { recursive: true }); + const download = new downloader(); download.on('progress', (downloaded, size) => { this.emit('progress', downloaded, size, fileName); @@ -149,30 +159,27 @@ export default class JavaDownloader extends EventEmitter { await download.downloadFile(url, pathFolder, fileName); } - if (await getFileHash(filePath, 'sha256') !== checksum) { - return { error: true, message: "Java checksum failed" }; + const downloadedChecksum = await getFileHash(filePath, 'sha256'); + if (downloadedChecksum !== checksum) { + throw new Error("Java checksum failed"); } - } - async extract(filePath, destPath) { - return await new Promise((resolve, reject) => { - if (os.platform() !== 'win32') fs.chmodSync(sevenBin.path7za, 0o755); + async extract(filePath: string, destPath: string) { + if (os.platform() !== 'win32') fs.chmodSync(sevenBin.path7za, 0o755); + + await new Promise((resolve, reject) => { const extract = Seven.extractFull(filePath, destPath, { $bin: sevenBin.path7za, recursive: true, $progress: true, - }) - extract.on('end', () => { - resolve(true) - }) - extract.on('error', (err) => { - reject(err) - }) + }); + extract.on('end', () => resolve()); + extract.on('error', (err) => reject(err)); extract.on('progress', (progress) => { if (progress.percent > 0) this.emit('extract', progress.percent); - }) - }) + }); + }); } } \ No newline at end of file From b21a31c6280a2e6e51a129ebc45264b9af5bd4e3 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Oct 2024 00:49:12 +0200 Subject: [PATCH 078/185] publish 1.11.1 stable --- package-lock.json | 4 ++-- package.json | 2 +- src/Minecraft/Minecraft-Arguments.ts | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51731386..ba2f800b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.1-beta.1", + "version": "3.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.1-beta.1", + "version": "3.11.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 113e841b..59c51ea9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.1-beta.1", + "version": "3.11.1", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 391efa2b..69a92d33 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -130,6 +130,8 @@ export default class MinecraftArguments { libraries = libraries.filter((library: any, index: any, self: any) => index === self.findIndex((res: any) => res.name === library.name)); for (let lib of libraries) { + if (lib.loader && lib.name.startsWith('org.apache.logging.log4j:log4j-slf4j2-impl')) continue; + if (lib.natives) { let native = lib.natives[MojangLib[process.platform]]; if (!native) native = lib.natives[process.platform]; From d664458b3596841d40639dd2407a398950b74016 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Oct 2024 01:00:59 +0200 Subject: [PATCH 079/185] fix uuid error start game --- src/Launch.ts | 8 ++++---- src/Minecraft/Minecraft-Arguments.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index 5322c864..47474b70 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -158,10 +158,10 @@ export default class Launch extends EventEmitter { if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); let argumentsLogs: string = Arguments.join(' ') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.access_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.client_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.uuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator.xboxAccount.xuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.access_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.client_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.uuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.xboxAccount?.xuid, '????????') argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') this.emit('data', `Launching with arguments ${argumentsLogs}`); diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 69a92d33..3c108710 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -49,7 +49,7 @@ export default class MinecraftArguments { '${auth_session}': this.authenticator.access_token, '${auth_player_name}': this.authenticator.name, '${auth_uuid}': this.authenticator.uuid, - '${auth_xuid}': this.authenticator.xboxAccount?.xuid || this.authenticator.access_token, + '${auth_xuid}': this.authenticator?.xboxAccount?.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, '${user_type}': userType, '${version_name}': loaderJson ? loaderJson.id : json.id, From 806d93cc704247aef02b5bb6e350efa65a092c73 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Oct 2024 01:01:47 +0200 Subject: [PATCH 080/185] publish 3.11.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba2f800b..c680a03c 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.1", + "version": "3.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.1", + "version": "3.11.2", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 59c51ea9..bd7873af 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.1", + "version": "3.11.2", "types": "./build/Index.d.ts", "exports": { ".": { From 111726ec9ff5f87883ae1919f436c2687148b885 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:38:38 +0200 Subject: [PATCH 081/185] add .github --- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 71 +++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ 3 files changed, 77 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..1b9f3199 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: ['luuxis'] \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..8c9d5677 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,71 @@ +name: Signaler un bug +description: Vous avez rencontré un bug? Signalez-le ici +title: "[Bug] " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Un grand merci d'avance pour votre aide. Néanmoins, nous avons besoin d'un certain nombre d'informations, pour nous aider. + - type: checkboxes + attributes: + label: "Liste des vérifications à faire avant de valider l'ouverture du signalement de bug" + description: Assurez que vous avez complété ce qui suit, dans le cas contraire, votre rapport peut être refusé + options: + - label: J'ai réussi à reproduire le bug sur le Selvania Launcher (sans mes modifications) + required: true + - label: Mon code respecte la licence Creative Commons Zero v1.0 Universal + required: true + - label: Mon code respecte les conditions d'utilisation du Selvania Launcher + required: true + - label: J'arrive à reproduire le bug sur la dernière version du Selvania Launcher + required: true + - type: dropdown + attributes: + label: Système d'exploitation + options: + - Windows + - macOS + - Linux (Basé sur Debian/Ubuntu) + - Linux (Autres) + validations: + required: true + - type: input + attributes: + label: Version du système d'exploitation + placeholder: "Exemple: Windows 11 Professionnel 21H2 Build 22000.739" + validations: + required: true + - type: input + attributes: + label: Hash du commit sur lequel le bug est rencontré + placeholder: 84d7881b67ecf6088205eca6723bfb19bf2a5f0d + - type: textarea + attributes: + label: Comportement attendu + description: Une description de ce qui devrait se passer + placeholder: Le launcher devrait... + validations: + required: true + - type: textarea + attributes: + label: Comportement actuel + description: Une description de ce qui se passe avec le bug + validations: + required: true + - type: textarea + attributes: + label: Instructions pour reproduire le but + placeholder: | + 1. Ouvrir le launcher + 2. Aller dans le menu xyz + 3. Cliquer sur abc + 4. Observer + validations: + required: true + - type: textarea + attributes: + label: Notes additionnelles + placeholder: Détails supplémentaires concernant le bug, tout ce qui pourrait être utile + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..574a5e0f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Nous rejoindre sur Discord, pour toutes questions ou demandes + url: http://discord.luuxis.fr + about: Veuillez ne pas ouvrir d'issue autre que pour signaler des bugs From 5206317f431152c16973cbdd1a2e960deb040dd9 Mon Sep 17 00:00:00 2001 From: xllifi Date: Sun, 20 Oct 2024 12:39:25 +1000 Subject: [PATCH 082/185] Add `Launch` docs, export `launchOPTS` --- src/Launch.ts | 130 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/Launch.ts b/src/Launch.ts index 47474b70..c72bca69 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -20,47 +20,175 @@ import { isold } from './utils/Index.js'; import Downloader from './utils/Downloader.js'; type loader = { + /** + * Path to loader directory. Relative to absolute path to Minecraft's root directory (config option `path`). + * + * If `undefined`, defaults to `.minecraft/loader/`. + * + * Example: `'fabricfiles'`. + */ path?: string, + /** + * Loader type. + * + * Acceptable values: `'forge'`, `'neoforge'`, `'fabric'`, `'legacyfabric'`, `'quilt'`. + */ type?: string, + /** + * Loader build (version). + * + * Acceptable values: `'latest'`, `'recommended'`, actual version. + * + * Example: `'0.16.3'` + */ build?: string, + /** + * Should the launcher use a loader? + */ enable?: boolean } +/** + * Screen options. + */ type screen = { width?: number, height?: number, + /** + * Should Minecraft be started in fullscreen mode? + */ fullscreen?: boolean } +/** + * Memory limits + */ type memory = { + /** + * Sets the `-Xms` JVM argument. This is the initial memory usage. + */ min?: string, + /** + * Sets the `-Xmx` JVM argument. This is the limit of memory usage. + */ max?: string } +/** + * Java download options + */ type javaOPTS = { + /** + * Absolute path to Java binaries directory. + * + * If set, expects Java to be already downloaded. If `undefined`, downloads Java and sets it automatically. + * + * Example: `'C:\Program Files\Eclipse Adoptium\jdk-21.0.2.13-hotspot\bin'` + */ path?: string, + /** + * Java version number. + * + * If set, fetched from https://api.adoptium.net. + * If `undefined`, fetched from [Mojang](https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json). + * + * Example: `21` + */ version?: number, + /** + * Java image type. Acceptable values: `'jdk'`, `'jre'`, `'testimage'`, `'debugimage'`, `'staticlibs'`, `'sources'`, `'sbom'`. + * + * Using `jre` is recommended since it only has what's needed. + */ type?: string } -type LaunchOPTS = { +/** + * Launch options. + */ +export type LaunchOPTS = { + /** + * URL to the launcher backend. Refer to [Selvania Launcher Wiki](https://github.com/luuxis/Selvania-Launcher/blob/master/docs/wiki_EN-US.md) for setup instructions. + */ url: string | null, + /** + * Something to Authenticate the player. + * + * Refer to `Mojang`, `Microsoft` or `AZauth` classes. + * + * Example: `await Mojang.login('Luuxis')` + */ authenticator: any, + /** + * Connection timeout in milliseconds. + */ timeout?: number, + /** + * Absolute path to Minecraft's root directory. + * + * Example: `'%appdata%/.minecraft'` + */ path: string, + /** + * Minecraft version. + * + * Example: `'1.20.4'` + */ version: string, + /** + * Path to instance directory. Relative to absolute path to Minecraft's root directory (config option `path`). + * This separates game files (e.g. versions, libraries, assets) from game data (e.g. worlds, resourcepacks, options). + * + * Example: `'PokeMoonX'` + */ instance?: string, + /** + * Should Minecraft process be independent of launcher? + */ detached?: boolean, + /** + * How many concurrent downloads can be in progress at once. + */ downloadFileMultiple?: number, intelEnabledMac?: boolean, + /** + * Loader config + */ loader: loader, + /** + * MCPathcer directory. (idk actually luuxis please verify this) + * + * If `instance` if set, relative to it. + * If `instance` is `undefined`, relative to `path`. + */ mcp: any, + /** + * Should game files be verified each launch? + */ verify: boolean, + /** + * Files to ignore from instance. (idk actually luuxis please verify this) + */ ignored: string[], + /** + * Custom JVM arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#JVM_Arguments) + */ JVM_ARGS: string[], + /** + * Custom game arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#Game_Arguments) + */ GAME_ARGS: string[], + /** + * Java options. + */ java: javaOPTS, + /** + * Screen options. + */ screen: screen, + /** + * Memory limit options. + */ memory: memory }; From 5fbb49d88f866fed9f308440d408677b026e8ba1 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 20 Oct 2024 09:35:40 +0200 Subject: [PATCH 083/185] Fix slash error with AzAuth https://github.com/luuxis/minecraft-java-core/pull/23 --- src/Authenticator/AZauth.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index b6975cf9..43416d92 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -10,8 +10,8 @@ export default class AZauth { skinAPI: string; constructor(url: string) { - this.url = `${url}/api/auth`; - this.skinAPI = `${url}/api/skin-api/skins`; + this.url = new URL('/api/auth', url).toString(); + this.skinAPI = new URL('/api/skin-api/skins', url).toString(); } async login(username: string, password: string, A2F: any = null) { @@ -49,7 +49,8 @@ export default class AZauth { id: response.id, banned: response.banned, money: response.money, - role: response.role + role: response.role, + verified: response.email_verified }, meta: { online: false, @@ -92,7 +93,8 @@ export default class AZauth { id: response.id, banned: response.banned, money: response.money, - role: response.role + role: response.role, + verified: response.email_verified }, meta: { online: false, From 62f4c8d8e7cec2a7ef393557909c8f00572c91b1 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:50:35 +0100 Subject: [PATCH 084/185] fix redownload java if use adoptium --- src/Minecraft/Minecraft-Java.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 987aea2b..f1efa260 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -90,12 +90,11 @@ export default class JavaDownloader extends EventEmitter { const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); const filePath = path.join(pathFolder, fileName); - await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); - let javaPath = path.join(pathFolder, 'bin', 'java'); if (platform === 'mac') javaPath = path.join(pathFolder, 'Contents', 'Home', 'bin', 'java'); if (!fs.existsSync(javaPath)) { + await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); await this.extract(filePath, pathFolder); fs.unlinkSync(filePath); From 753a9f649d9180bc1cb19edc015042f62d00358d Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:50:52 +0100 Subject: [PATCH 085/185] publish 3.11.3 --- package-lock.json | 4 ++-- package.json | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c680a03c..4ff18a2f 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.2", + "version": "3.11.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.2", + "version": "3.11.3", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index bd7873af..0d40e56e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.2", + "version": "3.11.3", "types": "./build/Index.d.ts", "exports": { ".": { @@ -11,7 +11,8 @@ "description": "A library starting minecraft game NW.js and Electron.js", "scripts": { "dev": "rimraf ./build && tsc -w", - "build": "rimraf ./build && tsc" + "build": "rimraf ./build && tsc", + "prepublishOnly": "npm i && npm run build" }, "files": [ "assets/**", From 4b698378e4cc3b1f00e8dc2883597f449d1d12a8 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 9 Nov 2024 23:23:52 +0100 Subject: [PATCH 086/185] Update index.js --- test/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/index.js b/test/index.js index 104e3735..0bcfac8a 100755 --- a/test/index.js +++ b/test/index.js @@ -22,19 +22,19 @@ let mc } let opt = { - url: 'http://launcher.luuxis.fr/files?instance=TEST', + // url: 'http://www.pokefree.fr/launcher_pokefree/files?instance=PokeFree', authenticator: mc, timeout: 10000, - instance: 'test', + instance: 'PokeFree', path: './Minecraft', - version: '1.21', + version: '1.16.5', detached: false, intelEnabledMac: true, downloadFileMultiple: 30, loader: { - type: 'neoforge', - build: 'latest', + type: 'forge', + build: '1.16.5-36.2.35', enable: true, path: './', }, From 0e88f64f58c4db08de094de1036d9c23c73ea5e1 Mon Sep 17 00:00:00 2001 From: xllifi Date: Sun, 20 Oct 2024 12:41:01 +1000 Subject: [PATCH 087/185] Optimize "other" Java lookup - use Adoptium API. --- src/Minecraft/Minecraft-Java.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index f1efa260..49ab1c3d 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -73,16 +73,15 @@ export default class JavaDownloader extends EventEmitter { async getJavaOther(jsonversion: any, versionDownload?: any) { const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; - const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot`; - const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); - const { platform, arch } = this.getPlatformArch(); + const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot?` + new URLSearchParams({ + image_type: this.options.java.type, + architecture: arch, + os: platform + }).toString(); + const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); - const java = javaVersions.find(({ binary }) => - binary.image_type === this.options.java.type && - binary.architecture === arch && - binary.os === platform - ); + const java = javaVersions[0]; if (!java) return { error: true, message: "No Java found" }; From 4989b36a2a12fc4d5179d3e12efef359d3eab229 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:13:16 +0100 Subject: [PATCH 088/185] update dep --- package-lock.json | 714 ++++++++++++++++++++++++++++++++++++++-------- package.json | 20 +- 2 files changed, 598 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ff18a2f..7d75c500 100755 --- a/package-lock.json +++ b/package-lock.json @@ -10,107 +10,185 @@ "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", - "adm-zip": "^0.5.9", + "adm-zip": "^0.5.16", "node-7z": "^3.0.0", - "node-fetch": "^2.6.9", - "prompt": "^1.2.1", - "tslib": "^2.4.1" + "node-fetch": "^2.7.0", + "prompt": "^1.3.0", + "tslib": "^2.8.1" }, "devDependencies": { - "@types/adm-zip": "^0.5.2", - "@types/node": "^18.11.13", - "@types/node-7z": "^2.1.8", - "@types/node-fetch": "^2.6.2", - "rimraf": "^3.0.2", - "typescript": "^4.9.4" + "@types/adm-zip": "^0.5.7", + "@types/node": "^22.10.2", + "@types/node-7z": "^2.1.10", + "@types/node-fetch": "^2.6.12", + "rimraf": "^6.0.1", + "typescript": "^5.7.2" } }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", "engines": { "node": ">=0.1.90" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@types/adm-zip": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.2.tgz", - "integrity": "sha512-33OTTnnW3onOE6HJuoqsi7T7Ojupz7zO/Vs5ddRNVCYQnu4lg05RqH/pr9eidHGvGyYfdO4uPO9cvegAMixBCQ==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", + "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "18.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", - "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", - "dev": true + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } }, "node_modules/@types/node-7z": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@types/node-7z/-/node-7z-2.1.8.tgz", - "integrity": "sha512-VjiU7yEbczNc3EFKN4GJcAUqAMkn92P/92r6ARjMSXEdixunMD9lC79mTX81vKxTlNYXuvCJ7zvnzlDbFTt2Vw==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@types/node-7z/-/node-7z-2.1.10.tgz", + "integrity": "sha512-LdfuQcGAKsLafyM96+F8VekToCuGQnFy9DMM0UdS6f5pEnaP5kAixp3TQPc1NJU4C6IwLwnednktYBjp/z2LRw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" + "form-data": "^4.0.0" } }, "node_modules/7zip-bin": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", - "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==" + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "license": "MIT" }, "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", "engines": { - "node": ">=6.0" + "node": ">=12.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -120,6 +198,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -127,11 +206,20 @@ "node": ">= 0.8" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, "node_modules/cycle": { "version": "1.0.3", @@ -142,11 +230,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -162,10 +251,25 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -174,11 +278,29 @@ "node": "> 0.1.90" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -188,88 +310,121 @@ "node": ">= 6" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.defaultsdeep": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==" + "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", + "license": "MIT" }, "node_modules/lodash.defaultto": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/lodash.defaultto/-/lodash.defaultto-4.14.0.tgz", - "integrity": "sha512-G6tizqH6rg4P5j32Wy4Z3ZIip7OfG8YWWlPFzUFGcYStH1Ld0l1tWs6NevEQNEDnO1M3NZYjuHuraaFSN5WqeQ==" + "integrity": "sha512-G6tizqH6rg4P5j32Wy4Z3ZIip7OfG8YWWlPFzUFGcYStH1Ld0l1tWs6NevEQNEDnO1M3NZYjuHuraaFSN5WqeQ==", + "license": "MIT" }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "license": "MIT" }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "license": "MIT" }, "node_modules/lodash.negate": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.negate/-/lodash.negate-3.0.2.tgz", - "integrity": "sha512-JGJYYVslKYC0tRMm/7igfdHulCjoXjoganRNWM8AgS+RXfOvFnPkOveDhPI65F9aAypCX9QEEQoBqWf7Q6uAeA==" + "integrity": "sha512-JGJYYVslKYC0tRMm/7igfdHulCjoXjoganRNWM8AgS+RXfOvFnPkOveDhPI65F9aAypCX9QEEQoBqWf7Q6uAeA==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -279,6 +434,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -287,31 +443,48 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" }, "node_modules/node-7z": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/node-7z/-/node-7z-3.0.0.tgz", "integrity": "sha512-KIznWSxIkOYO/vOgKQfJEaXd7rgoFYKZbaurainCEdMhYc7V7mRHX+qdf2HgbpQFcdJL/Q6/XOPrDLoBeTfuZA==", + "license": "ISC", "dependencies": { "debug": "^4.3.2", "lodash.defaultsdeep": "^4.6.1", @@ -326,9 +499,10 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -348,32 +522,50 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "dependencies": { - "wrappy": "1" + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/prompt": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "license": "MIT", "dependencies": { "@colors/colors": "1.5.0", "async": "3.2.3", @@ -389,6 +581,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "license": "ISC", "dependencies": { "mute-stream": "~0.0.4" }, @@ -400,20 +593,62 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "license": "Apache 2.0", "engines": { "node": ">= 0.4.0" } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -423,51 +658,185 @@ "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", "engines": { "node": "*" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/winston": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", + "license": "MIT", "dependencies": { "async": "^2.6.4", "colors": "1.0.x", @@ -484,15 +853,108 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 0d40e56e..e6e74fda 100755 --- a/package.json +++ b/package.json @@ -33,19 +33,19 @@ "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", - "adm-zip": "^0.5.9", + "adm-zip": "^0.5.16", "node-7z": "^3.0.0", - "node-fetch": "^2.6.9", - "prompt": "^1.2.1", - "tslib": "^2.4.1" + "node-fetch": "^2.7.0", + "prompt": "^1.3.0", + "tslib": "^2.8.1" }, "devDependencies": { - "@types/adm-zip": "^0.5.2", - "@types/node": "^18.11.13", - "@types/node-7z": "^2.1.8", - "@types/node-fetch": "^2.6.2", - "rimraf": "^3.0.2", - "typescript": "^4.9.4" + "@types/adm-zip": "^0.5.7", + "@types/node": "^22.10.2", + "@types/node-7z": "^2.1.10", + "@types/node-fetch": "^2.6.12", + "rimraf": "^6.0.1", + "typescript": "^5.7.2" }, "bugs": { "url": "https://github.com/luuxis/minecraft-java-core/issues" From f0a03caec4ab27ad6161b99b09c8daabd83a5649 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:06:22 +0100 Subject: [PATCH 089/185] refactor: Convert patcher.ts to a typed TypeScript class - Introduce interfaces for config, profile, and patcher options - Rename class to ForgePatcher and remove old code - Emit patch logs and errors via EventEmitter --- src/Minecraft-Loader/patcher.ts | 298 ++++++++++++++++++-------------- 1 file changed, 165 insertions(+), 133 deletions(-) diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index c03f3fbc..cae284c3 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -1,137 +1,169 @@ import { spawn } from 'child_process'; -import fs from 'fs' -import path from 'path' +import fs from 'fs'; +import path from 'path'; import { EventEmitter } from 'events'; - import { getPathLibraries, getFileFromArchive } from '../utils/Index.js'; -export default class forgePatcher extends EventEmitter { - options: any; - - constructor(options: any) { - super(); - this.options = options; - } - - async patcher(profile: any, config: any, neoForgeOld: boolean = true) { - let { processors } = profile; - - for (let key in processors) { - if (Object.prototype.hasOwnProperty.call(processors, key)) { - let processor = processors[key]; - if (processor?.sides && !(processor?.sides || []).includes('client')) { - continue; - } - - let jar = getPathLibraries(processor.jar) - let filePath = path.resolve(this.options.path, 'libraries', jar.path, jar.name) - - let args = processor.args.map(arg => this.setArgument(arg, profile, config, neoForgeOld)).map(arg => this.computePath(arg)); - let classPaths = processor.classpath.map(cp => { - let classPath = getPathLibraries(cp) - return `"${path.join(this.options.path, 'libraries', `${classPath.path}/${classPath.name}`)}"` - }); - let mainClass = await this.readJarManifest(filePath); - - await new Promise((resolve: any) => { - const ps = spawn( - `"${path.resolve(config.java)}"`, - [ - '-classpath', - [`"${filePath}"`, ...classPaths].join(path.delimiter), - mainClass, - ...args - ], { shell: true } - ); - - ps.stdout.on('data', data => { - this.emit('patch', data.toString('utf-8')) - }); - - ps.stderr.on('data', data => { - this.emit('patch', data.toString('utf-8')) - }); - - ps.on('close', code => { - if (code !== 0) { - this.emit('error', `Forge patcher exited with code ${code}`); - resolve(); - } - resolve(); - }); - }); - } - } - - } - - check(profile: any) { - let files = []; - let { processors } = profile; - - for (let key in processors) { - if (Object.prototype.hasOwnProperty.call(processors, key)) { - let processor = processors[key]; - if (processor?.sides && !(processor?.sides || []).includes('client')) continue; - - processor.args.map(arg => { - let finalArg = arg.replace('{', '').replace('}', ''); - if (profile.data[finalArg]) { - if (finalArg === 'BINPATCH') return - files.push(profile.data[finalArg].client) - } - }) - } - } - - files = files.filter((item, index) => files.indexOf(item) === index); - - for (let file of files) { - let libMCP = getPathLibraries(file.replace('[', '').replace(']', '')) - file = `${path.resolve(this.options.path, 'libraries', `${libMCP.path}/${libMCP.name}`)}`; - if (!fs.existsSync(file)) return false - } - return true; - } - - setArgument(arg: any, profile: any, config: any, neoForgeOld) { - let finalArg = arg.replace('{', '').replace('}', ''); - let universalPath = profile.libraries.find(v => { - if (this.options.loader.type === 'forge') return (v.name || '').startsWith('net.minecraftforge:forge') - if (this.options.loader.type === 'neoforge') return (v.name || '').startsWith(neoForgeOld ? 'net.neoforged:forge' : 'net.neoforged:neoforge') - }) - - if (profile.data[finalArg]) { - if (finalArg === 'BINPATCH') { - let clientdata = getPathLibraries(profile.path || universalPath.name) - return `"${path - .join(this.options.path, 'libraries', `${clientdata.path}/${clientdata.name}`) - .replace('.jar', '-clientdata.lzma')}"`; - } - return profile.data[finalArg].client; - } - - return arg - .replace('{SIDE}', `client`) - .replace('{ROOT}', `"${path.dirname(path.resolve(this.options.path, 'forge'))}"`) - .replace('{MINECRAFT_JAR}', `"${config.minecraft}"`) - .replace('{MINECRAFT_VERSION}', `"${config.minecraftJson}"`) - .replace('{INSTALLER}', `"${this.options.path}/libraries"`) - .replace('{LIBRARY_DIR}', `"${this.options.path}/libraries"`); - } - - computePath(arg: any) { - if (arg[0] === '[') { - let libMCP = getPathLibraries(arg.replace('[', '').replace(']', '')) - return `"${path.join(this.options.path, 'libraries', `${libMCP.path}/${libMCP.name}`)}"`; - } - return arg; - } - - async readJarManifest(jarPath: string) { - let extraction: any = await getFileFromArchive(jarPath, 'META-INF/MANIFEST.MF'); - - if (extraction) return (extraction.toString("utf8")).split('Main-Class: ')[1].split('\r\n')[0]; - return null; - } -} \ No newline at end of file +interface ForgePatcherOptions { + path: string; + loader: { + type: 'forge' | 'neoforge'; + }; +} + +interface Config { + java: string; + minecraft: string; + minecraftJson: string; +} + +interface ProfileData { + client: string; + [key: string]: any; +} + +interface Processor { + jar: string; + args: string[]; + classpath: string[]; + sides?: string[]; +} + +interface Profile { + data: Record; + libraries: Array<{ name: string }>; + processors: Record; + path?: string; +} + +export default class ForgePatcher extends EventEmitter { + private readonly options: ForgePatcherOptions; + + constructor(options: ForgePatcherOptions) { + super(); + this.options = options; + } + + public async patcher(profile: Profile, config: Config, neoForgeOld: boolean = true): Promise { + const { processors } = profile; + + for (const [_, processor] of Object.entries(processors)) { + if (processor.sides && !processor.sides.includes('client')) continue; + + const jarInfo = getPathLibraries(processor.jar); + const jarPath = path.resolve(this.options.path, 'libraries', jarInfo.path, jarInfo.name); + + const args = processor.args + .map(arg => this.setArgument(arg, profile, config, neoForgeOld)) + .map(arg => this.computePath(arg)); + + const classPaths = processor.classpath.map(cp => { + const cpInfo = getPathLibraries(cp); + return `"${path.join(this.options.path, 'libraries', cpInfo.path, cpInfo.name)}"`; + }); + + const mainClass = await this.readJarManifest(jarPath); + if (!mainClass) { + this.emit('error', `Impossible de déterminer la classe principale dans le JAR: ${jarPath}`); + continue; + } + + await new Promise((resolve) => { + const spawned = spawn( + `"${path.resolve(config.java)}"`, + [ + '-classpath', + [`"${jarPath}"`, ...classPaths].join(path.delimiter), + mainClass, + ...args + ], + { shell: true } + ); + + spawned.stdout.on('data', data => { + this.emit('patch', data.toString('utf-8')); + }); + + spawned.stderr.on('data', data => { + this.emit('patch', data.toString('utf-8')); + }); + + spawned.on('close', code => { + if (code !== 0) { + this.emit('error', `Le patcher Forge s'est terminé avec le code ${code}`); + } + resolve(); + }); + }); + } + } + + public check(profile: Profile): boolean { + const { processors } = profile; + let files: string[] = []; + + for (const processor of Object.values(processors)) { + if (processor.sides && !processor.sides.includes('client')) continue; + + processor.args.forEach(arg => { + const finalArg = arg.replace('{', '').replace('}', ''); + if (profile.data[finalArg]) { + if (finalArg === 'BINPATCH') return; + files.push(profile.data[finalArg].client); + } + }); + } + + files = Array.from(new Set(files)); + + for (const file of files) { + const lib = getPathLibraries(file.replace('[', '').replace(']', '')); + const filePath = path.resolve(this.options.path, 'libraries', lib.path, lib.name); + if (!fs.existsSync(filePath)) return false; + } + return true; + } + + private setArgument(arg: string, profile: Profile, config: Config, neoForgeOld: boolean): string { + const finalArg = arg.replace('{', '').replace('}', ''); + + const universalLib = profile.libraries.find(lib => { + if (this.options.loader.type === 'forge') return lib.name.startsWith('net.minecraftforge:forge'); + else return lib.name.startsWith(neoForgeOld ? 'net.neoforged:forge' : 'net.neoforged:neoforge'); + }); + + if (profile.data[finalArg]) { + if (finalArg === 'BINPATCH') { + const jarInfo = getPathLibraries(profile.path || (universalLib?.name ?? '')); + return `"${path.join(this.options.path, 'libraries', jarInfo.path, jarInfo.name).replace('.jar', '-clientdata.lzma')}"`; + } + return profile.data[finalArg].client; + } + + return arg + .replace('{SIDE}', 'client') + .replace('{ROOT}', `"${path.dirname(path.resolve(this.options.path, 'forge'))}"`) + .replace('{MINECRAFT_JAR}', `"${config.minecraft}"`) + .replace('{MINECRAFT_VERSION}', `"${config.minecraftJson}"`) + .replace('{INSTALLER}', `"${path.join(this.options.path, 'libraries')}"`) + .replace('{LIBRARY_DIR}', `"${path.join(this.options.path, 'libraries')}"`); + } + + private computePath(arg: string): string { + if (arg.startsWith('[')) { + const libInfo = getPathLibraries(arg.replace('[', '').replace(']', '')); + return `"${path.join(this.options.path, 'libraries', libInfo.path, libInfo.name)}"`; + } + return arg; + } + + private async readJarManifest(jarPath: string): Promise { + const manifestContent = await getFileFromArchive(jarPath, 'META-INF/MANIFEST.MF'); + if (!manifestContent) return null; + + const content = manifestContent.toString(); + const mainClassLine = content.split('Main-Class: ')[1]; + if (!mainClassLine) return null; + return mainClassLine.split('\r\n')[0]; + } +} From ffebc590e95c36e1ba48706ad496f77ccc8c34e9 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:07:23 +0100 Subject: [PATCH 090/185] publish 3.11.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d75c500..564348e0 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.3", + "version": "3.11.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.3", + "version": "3.11.4", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index e6e74fda..de8394f9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.3", + "version": "3.11.4", "types": "./build/Index.d.ts", "exports": { ".": { From e8c04135a6563aa3433fe6e4255da9f79e8a9451 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 091/185] refactor: restructure AZauth code and introduce AZauthUser interface --- src/Authenticator/AZauth.ts | 343 ++++++++++++++++++++++-------------- 1 file changed, 206 insertions(+), 137 deletions(-) diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 43416d92..017eb3aa 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -1,143 +1,212 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import nodeFetch from 'node-fetch'; +// This interface defines the structure of the user object +// returned by the AZauth service. You can adapt it to your needs. +interface AZauthUser { + access_token?: string; + client_token?: string; + uuid?: string; + name?: string; + user_properties?: string; + user_info?: { + id?: string; + banned?: boolean; + money?: number; + role?: string; + verified?: boolean; + }; + meta?: { + online: boolean; + type: string; + }; + profile?: { + skins: Array<{ + url: string; + base64?: string; + }>; + }; + // Error-related fields + error?: boolean; + reason?: string; + message?: string; + A2F?: boolean; +} + export default class AZauth { - url: string; - skinAPI: string; - - constructor(url: string) { - this.url = new URL('/api/auth', url).toString(); - this.skinAPI = new URL('/api/skin-api/skins', url).toString(); - } - - async login(username: string, password: string, A2F: any = null) { - let response = await nodeFetch(`${this.url}/authenticate`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - email: username, - password: password, - code: A2F - }), - }).then((res: any) => res.json()) - - if (response.status == 'pending' && response.reason == '2fa') { - return { A2F: true }; - } - - if (response.status == 'error') { - return { - error: true, - reason: response.reason, - message: response.message - }; - } - - return { - access_token: response.access_token, - client_token: response.uuid, - uuid: response.uuid, - name: response.username, - user_properties: '{}', - user_info: { - id: response.id, - banned: response.banned, - money: response.money, - role: response.role, - verified: response.email_verified - }, - meta: { - online: false, - type: 'AZauth', - }, - profile: { - skins: [ - await this.skin(response.id), - ] - } - } - } - - async verify(user: any) { - let response = await nodeFetch(`${this.url}/verify`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - access_token: user.access_token - }), - }).then((res: any) => res.json()) - - if (response.status == 'error') { - return { - error: true, - reason: response.reason, - message: response.message - }; - } - - return { - access_token: response.access_token, - client_token: response.uuid, - uuid: response.uuid, - name: response.username, - user_properties: '{}', - user_info: { - id: response.id, - banned: response.banned, - money: response.money, - role: response.role, - verified: response.email_verified - }, - meta: { - online: false, - type: 'AZauth', - }, - profile: { - skins: [ - await this.skin(response.id), - ] - } - } - } - - async signout(user: any) { - let auth = await nodeFetch(`${this.url}/logout`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - access_token: user.access_token - }), - }).then((res: any) => res.json()) - if (auth.error) return false; - return true - } - - async skin(uuid: string) { - let response: any = await nodeFetch(`${this.skinAPI}/${uuid}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - }) - - if (response.status == 404) { - return { - url: `${this.skinAPI}/${uuid}` - } - } - - response = await response.buffer() - return { - url: `${this.skinAPI}/${uuid}`, - base64: "data:image/png;base64," + response.toString('base64') - } - } -} \ No newline at end of file + private url: string; + private skinAPI: string; + + /** + * The constructor prepares the authentication and skin URLs from the base URL. + * @param url The base URL of the AZauth server + */ + constructor(url: string) { + // '/api/auth' for authentication, '/api/skin-api/skins' for skin data + this.url = new URL('/api/auth', url).toString(); + this.skinAPI = new URL('/api/skin-api/skins', url).toString(); + } + + /** + * Authenticates a user using their username/email and password. + * Optionally, a 2FA code can be provided. + * + * @param username The email or username for authentication + * @param password The password + * @param A2F Optional 2FA code + * @returns A Promise that resolves to an AZauthUser object + */ + public async login(username: string, password: string, A2F: string | null = null): Promise { + const response = await nodeFetch(`${this.url}/authenticate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + email: username, + password, + code: A2F + }) + }); + + const data = await response.json(); + + // If the server indicates that 2FA is required + if (data.status === 'pending' && data.reason === '2fa') { + return { A2F: true }; + } + + // If the server returns an error status + if (data.status === 'error') { + return { + error: true, + reason: data.reason, + message: data.message + }; + } + + // If authentication is successful, return the complete user object + return { + access_token: data.access_token, + client_token: data.uuid, + uuid: data.uuid, + name: data.username, + user_properties: '{}', + user_info: { + id: data.id, + banned: data.banned, + money: data.money, + role: data.role, + verified: data.email_verified + }, + meta: { + online: false, + type: 'AZauth' + }, + profile: { + skins: [await this.skin(data.id)] + } + }; + } + + /** + * Verifies an existing session (e.g., for refreshing tokens). + * @param user An AZauthUser object containing at least the access token + * @returns A Promise that resolves to an updated AZauthUser object or an error object + */ + public async verify(user: AZauthUser): Promise { + const response = await nodeFetch(`${this.url}/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + access_token: user.access_token + }) + }); + + const data = await response.json(); + + // If the server indicates an error + if (data.status === 'error') { + return { + error: true, + reason: data.reason, + message: data.message + }; + } + + // Return the updated user session object + return { + access_token: data.access_token, + client_token: data.uuid, + uuid: data.uuid, + name: data.username, + user_properties: '{}', + user_info: { + id: data.id, + banned: data.banned, + money: data.money, + role: data.role, + verified: data.email_verified + }, + meta: { + online: false, + type: 'AZauth' + }, + profile: { + skins: [await this.skin(data.id)] + } + }; + } + + /** + * Logs out a user from the AZauth service (invalidates the token). + * @param user The AZauthUser object with a valid access token + * @returns A Promise that resolves to true if logout is successful, otherwise false + */ + public async signout(user: AZauthUser): Promise { + const response = await nodeFetch(`${this.url}/logout`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + access_token: user.access_token + }) + }); + + const data = await response.json(); + if (data.error) return false; + return true; + } + + /** + * Retrieves the skin of a user by their ID (UUID). + * If the skin exists, returns both the direct URL and a base64-encoded PNG. + * If the skin doesn't exist, only the URL is returned. + * + * @param uuid The UUID or ID of the user + * @returns A Promise resolving to an object with the skin URL (and optional base64 data) + */ + private async skin(uuid: string): Promise<{ url: string; base64?: string }> { + let response = await nodeFetch(`${this.skinAPI}/${uuid}`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }); + + // If the skin is not found (404), return only the URL + if (response.status === 404) { + return { + url: `${this.skinAPI}/${uuid}` + }; + } + + // Otherwise, convert the skin image to a base64-encoded string + const buffer = await response.buffer(); + return { + url: `${this.skinAPI}/${uuid}`, + base64: `data:image/png;base64,${buffer.toString('base64')}` + }; + } +} From 4f3a7075be4370faad7993fd468ecc0885dbf954 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 092/185] chore: update license header and code style --- src/Authenticator/GUI/Electron.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Authenticator/GUI/Electron.ts b/src/Authenticator/GUI/Electron.ts index db0d6f70..f3aaa84b 100755 --- a/src/Authenticator/GUI/Electron.ts +++ b/src/Authenticator/GUI/Electron.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ const path = require('path') From 56bb708b99a4ad04767e0d2f31fb31bbd369a5fc Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 093/185] chore: update license header and code style --- src/Authenticator/GUI/NW.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Authenticator/GUI/NW.ts b/src/Authenticator/GUI/NW.ts index 0985f61a..0175e9d0 100755 --- a/src/Authenticator/GUI/NW.ts +++ b/src/Authenticator/GUI/NW.ts @@ -1,9 +1,10 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ -'use strict'; import path from 'path'; const defaultProperties = { From f481b5d9fd43788de37b13abc84b45b0f716282b Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 094/185] chore: update license header and code style --- src/Authenticator/GUI/Terminal.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Authenticator/GUI/Terminal.ts b/src/Authenticator/GUI/Terminal.ts index 0349fb4c..b794775e 100755 --- a/src/Authenticator/GUI/Terminal.ts +++ b/src/Authenticator/GUI/Terminal.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import prompt from 'prompt'; From 900560143a25f1018cad185abafd0f0d17e627ca Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 095/185] refactor: reorganize MS auth flow and add typed interfaces --- src/Authenticator/Microsoft.ts | 609 ++++++++++++++++++++------------- 1 file changed, 377 insertions(+), 232 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 0a4ddcc8..7e36fb67 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -1,241 +1,386 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import nodeFetch from 'node-fetch'; import crypto from 'crypto'; +// Possible client types (Electron, NW.js, or terminal usage) +export type MicrosoftClientType = 'electron' | 'nwjs' | 'terminal'; -export default class Microsoft { - client_id: string; - type: 'electron' | 'nwjs' | 'terminal'; - - constructor(client_id: string) { - if (client_id === '' || !client_id) client_id = '00000000402b5328'; - this.client_id = client_id; - - if (!!process && !!process.versions && !!process.versions.electron) { - this.type = 'electron'; - } else if (!!process && !!process.versions && !!process.versions.nw) { - this.type = 'nwjs'; - } else { - this.type = 'terminal'; - } - } - - async getAuth(type: string, url: string) { - if (!url) url = `https://login.live.com/oauth20_authorize.srf?client_id=${this.client_id}&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=XboxLive.signin%20offline_access&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account`; - if (!type) type = this.type; - - if (type == "electron") { - let usercode = await (require('./GUI/Electron.js'))(url) - if (usercode === "cancel") return false; - else return await this.url(usercode); - } else if (type == "nwjs") { - let usercode = await (require('./GUI/NW.js'))(url) - if (usercode === "cancel") return false; - else return await this.url(usercode); - } else if (type == "terminal") { - let usercode = await (require('./GUI/Terminal.js'))(url) - if (usercode === "cancel") return false; - else return await this.url(usercode); - } - } - - async url(code: string) { - let oauth2 = await nodeFetch("https://login.live.com/oauth20_token.srf", { - method: "POST", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=https://login.live.com/oauth20_desktop.srf` - }).then(res => res.json()).catch(err => { return { error: err } });; - if (oauth2.error) return oauth2; - return await this.getAccount(oauth2) - } - - async refresh(acc: any) { - let timeStamp = Math.floor(Date.now() / 1000) - - if (timeStamp < (acc?.meta?.access_token_expires_in - 7200)) { - let profile = await this.getProfile(acc) - acc.profile = { - skins: profile.skins, - capes: profile.capes - } - return acc - } else { - let oauth2 = await nodeFetch("https://login.live.com/oauth20_token.srf", { - method: "POST", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` - }).then(res => res.json()).catch(err => { return { error: err } });; - if (oauth2.error) { - oauth2.errorType = "oauth2"; - return oauth2 - }; - return await this.getAccount(oauth2) - } - } - - async getAccount(oauth2: any) { - let xbl = await nodeFetch("https://user.auth.xboxlive.com/user/authenticate", { - method: "post", - body: JSON.stringify({ - Properties: { - AuthMethod: "RPS", - SiteName: "user.auth.xboxlive.com", - RpsTicket: "d=" + oauth2.access_token - }, - RelyingParty: "http://auth.xboxlive.com", - TokenType: "JWT" - }), - headers: { "Content-Type": "application/json", Accept: "application/json" }, - }).then(res => res.json()).catch(err => { return { error: err } });; - if (xbl.error) { - xbl.errorType = "xbl"; - return xbl - } - - let xsts = await nodeFetch("https://xsts.auth.xboxlive.com/xsts/authorize", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - Properties: { - SandboxId: "RETAIL", - UserTokens: [xbl.Token] - }, - RelyingParty: "rp://api.minecraftservices.com/", - TokenType: "JWT" - }) - }).then(res => res.json()); - if (xsts.error) { - xsts.errorType = "xsts"; - return xsts - } - - let xboxAccount = await nodeFetch("https://xsts.auth.xboxlive.com/xsts/authorize", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - Properties: { - SandboxId: "RETAIL", - UserTokens: [xbl.Token] - }, - RelyingParty: "http://xboxlive.com", - TokenType: "JWT" - }) - }).then(res => res.json()).catch(err => { return { error: err } }); - if (xsts.error) { - xsts.errorType = "xboxAccount"; - return xsts - } - - let launch = await nodeFetch("https://api.minecraftservices.com/launcher/login", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ xtoken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}`, platform: "PC_LAUNCHER" }) - }).then(res => res.json()).catch(err => { return { error: err } }); - if (launch.error) { - launch.errorType = "launch"; - return launch - } - - let mcLogin = await nodeFetch("https://api.minecraftservices.com/authentication/login_with_xbox", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ "identityToken": `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}` }) - }).then(res => res.json()).catch(err => { return { error: err } }); - if (mcLogin.error) { - mcLogin.errorType = "mcLogin"; - return mcLogin - } - - let hasGame = await nodeFetch("https://api.minecraftservices.com/entitlements/mcstore", { - method: "GET", - headers: { - 'Authorization': `Bearer ${mcLogin.access_token}` - } - }).then(res => res.json()); - - if (!hasGame.items.find(i => i.name == "product_minecraft" || i.name == "game_minecraft")) { - return { - error: "You don't own the game", - errorType: "game" - } - } - - let profile = await this.getProfile(mcLogin); - if (profile.error) { - profile.errorType = "profile"; - return profile - } - - return { - access_token: mcLogin.access_token, - client_token: crypto.randomBytes(16).toString('hex'), - uuid: profile.id, - name: profile.name, - refresh_token: oauth2.refresh_token, - user_properties: '{}', - meta: { - type: "Xbox", - access_token_expires_in: mcLogin.expires_in + Math.floor(Date.now() / 1000), - demo: profile.error ? true : false - }, - xboxAccount: { - xuid: xboxAccount.DisplayClaims.xui[0].xid, - gamertag: xboxAccount.DisplayClaims.xui[0].gtg, - ageGroup: xboxAccount.DisplayClaims.xui[0].agg - }, - profile: { - skins: profile.skins, - capes: profile.capes - } - } - } - - async getProfile(mcLogin: any) { - let profile = await nodeFetch("https://api.minecraftservices.com/minecraft/profile", { - method: "GET", - headers: { - 'Authorization': `Bearer ${mcLogin.access_token}` - } - }).then(res => res.json()).catch(err => { return { error: err } });; - if (profile.error) return profile - - let skins = profile.skins; - let capes = profile.capes; - - for (let skin of skins) { - skin.base64 = `data:image/png;base64,${await getBass64(skin.url)}` - } - for (let cape of capes) { - cape.base64 = `data:image/png;base64,${await getBass64(cape.url)}` - } - - return { - id: profile.id, - name: profile.name, - skins: profile.skins || [], - capes: profile.capes || [] - } - } +// Basic structure for a Minecraft profile, with optional base64 fields +export interface MinecraftSkin { + id?: string; + state?: string; + url?: string; + variant?: string; + alias?: string; + base64?: string; // We add base64 representation after fetching +} + +export interface MinecraftProfile { + id: string; + name: string; + skins?: MinecraftSkin[]; + capes?: MinecraftSkin[]; +} + +// Structure for errors returned by the different steps in authentication +export interface AuthError { + error: string; + errorType?: string; + [key: string]: any; +} + +// Main structure for successful authentication +export interface AuthResponse { + access_token: string; + client_token: string; + uuid: string; + name: string; + refresh_token: string; + user_properties: string; + meta: { + type: 'Xbox'; + access_token_expires_in: number; + demo: boolean; + }; + xboxAccount: { + xuid: string; + gamertag: string; + ageGroup: string; + }; + profile: { + skins?: MinecraftSkin[]; + capes?: MinecraftSkin[]; + }; +} + +// Utility function to fetch and convert an image to base64 +async function getBase64(url: string): Promise { + const response = await nodeFetch(url); + const buffer = await response.buffer(); + return buffer.toString('base64'); } -async function getBass64(url: string) { - let response = await nodeFetch(url); - let buffer = await response.buffer(); - return buffer.toString('base64'); -} \ No newline at end of file +export default class Microsoft { + public client_id: string; + public type: MicrosoftClientType; + + /** + * Creates a Microsoft auth instance. + * @param client_id Your Microsoft OAuth client ID (default: '00000000402b5328' if none provided). + */ + constructor(client_id: string) { + if (!client_id) { + client_id = '00000000402b5328'; + } + this.client_id = client_id; + + // Determine if we're running under Electron, NW.js, or just in a terminal + if (typeof process !== 'undefined' && process.versions && process.versions.electron) { + this.type = 'electron'; + } else if (typeof process !== 'undefined' && process.versions && process.versions.nw) { + this.type = 'nwjs'; + } else { + this.type = 'terminal'; + } + } + + /** + * Opens a GUI (Electron or NW.js) or uses terminal approach to fetch an OAuth2 code, + * and then retrieves user information from Microsoft if successful. + * + * @param type The environment to open the OAuth window. Defaults to the auto-detected type. + * @param url The full OAuth2 authorization URL. If not provided, a default is used. + * @returns An object with user data on success, or false if canceled. + */ + public async getAuth(type?: MicrosoftClientType, url?: string): Promise { + const finalType = type || this.type; + const finalUrl = url || + `https://login.live.com/oauth20_authorize.srf?client_id=${this.client_id}&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=XboxLive.signin%20offline_access&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account`; + + // Dynamically require different GUI modules depending on environment + let userCode: string | 'cancel'; + switch (finalType) { + case 'electron': + userCode = await (require('./GUI/Electron.js'))(finalUrl); + break; + case 'nwjs': + userCode = await (require('./GUI/NW.js'))(finalUrl); + break; + case 'terminal': + userCode = await (require('./GUI/Terminal.js'))(finalUrl); + break; + default: + return false; + } + + if (userCode === 'cancel') { + return false; + } + + // Exchange the code for an OAuth2 token, then retrieve account data + return this.exchangeCodeForToken(userCode); + } + + /** + * Exchanges an OAuth2 authorization code for an access token, then retrieves account information. + * @param code The OAuth2 authorization code returned by Microsoft. + * @returns The authenticated user data or an error object. + */ + private async exchangeCodeForToken(code: string): Promise { + try { + const response = await nodeFetch('https://login.live.com/oauth20_token.srf', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=https://login.live.com/oauth20_desktop.srf` + }); + const oauth2 = await response.json(); + + if (oauth2.error) { + return { error: oauth2.error, errorType: 'oauth2', ...oauth2 }; + } + return this.getAccount(oauth2); + } catch (err: any) { + return { error: err.message, errorType: 'network' }; + } + } + + /** + * Refreshes the user's session if the token has expired or is about to expire. + * Otherwise, simply fetches the user's profile. + * + * @param acc A previously obtained AuthResponse object. + * @returns Updated AuthResponse (with new token if needed) or an error object. + */ + public async refresh(acc: AuthResponse | any): Promise { + const timeStamp = Math.floor(Date.now() / 1000); + + // If the token is still valid for at least 2 more hours, just re-fetch the profile + if (timeStamp < (acc?.meta?.access_token_expires_in - 7200)) { + const updatedProfile = await this.getProfile({ access_token: acc.access_token }); + if ('error' in updatedProfile) { + // If there's an error, return it directly + return updatedProfile; + } + acc.profile = { + skins: updatedProfile.skins, + capes: updatedProfile.capes + }; + return acc; + } + + // Otherwise, refresh the token + try { + const response = await nodeFetch('https://login.live.com/oauth20_token.srf', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` + }); + const oauth2 = await response.json(); + + if (oauth2.error) { + return { error: oauth2.error, errorType: 'oauth2', ...oauth2 }; + } + // Retrieve account data with the new tokens + return this.getAccount(oauth2); + } catch (err: any) { + return { error: err.message, errorType: 'network' }; + } + } + + /** + * Retrieves and assembles the full account details (Xbox Live, XSTS, Minecraft). + * @param oauth2 The token object returned by the Microsoft OAuth endpoint. + * @returns A fully populated AuthResponse object or an error. + */ + private async getAccount(oauth2: any): Promise { + // 1. Authenticate with Xbox Live + const xblResponse = await this.fetchJSON('https://user.auth.xboxlive.com/user/authenticate', { + method: 'POST', + headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, + body: JSON.stringify({ + Properties: { + AuthMethod: 'RPS', + SiteName: 'user.auth.xboxlive.com', + RpsTicket: `d=${oauth2.access_token}` + }, + RelyingParty: 'http://auth.xboxlive.com', + TokenType: 'JWT' + }) + }); + if (xblResponse.error) { + return { ...xblResponse, errorType: 'xbl' }; + } + + // 2. Authorize with XSTS for Minecraft services + const xstsResponse = await this.fetchJSON('https://xsts.auth.xboxlive.com/xsts/authorize', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + Properties: { + SandboxId: 'RETAIL', + UserTokens: [xblResponse.Token] + }, + RelyingParty: 'rp://api.minecraftservices.com/', + TokenType: 'JWT' + }) + }); + if (xstsResponse.error) { + return { ...xstsResponse, errorType: 'xsts' }; + } + + // 3. Authorize for the standard Xbox Live realm (useful for xuid/gamertag) + const xboxAccount = await this.fetchJSON('https://xsts.auth.xboxlive.com/xsts/authorize', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + Properties: { + SandboxId: 'RETAIL', + UserTokens: [xblResponse.Token] + }, + RelyingParty: 'http://xboxlive.com', + TokenType: 'JWT' + }) + }); + if (xboxAccount.error) { + return { ...xboxAccount, errorType: 'xboxAccount' }; + } + + // 4. Get a launcher token from Minecraft services + const launchResponse = await this.fetchJSON('https://api.minecraftservices.com/launcher/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + xtoken: `XBL3.0 x=${xblResponse.DisplayClaims.xui[0].uhs};${xstsResponse.Token}`, + platform: 'PC_LAUNCHER' + }) + }); + if (launchResponse.error) { + return { ...launchResponse, errorType: 'launch' }; + } + + // 5. Login with Xbox token to get a Minecraft token + const mcLogin = await this.fetchJSON('https://api.minecraftservices.com/authentication/login_with_xbox', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + identityToken: `XBL3.0 x=${xblResponse.DisplayClaims.xui[0].uhs};${xstsResponse.Token}` + }) + }); + if (mcLogin.error) { + return { ...mcLogin, errorType: 'mcLogin' }; + } + + // 6. Check if the account has purchased Minecraft + const hasGame = await this.fetchJSON('https://api.minecraftservices.com/entitlements/mcstore', { + method: 'GET', + headers: { + Authorization: `Bearer ${mcLogin.access_token}` + } + }); + if (!hasGame.items?.find((i: any) => i.name === 'product_minecraft' || i.name === 'game_minecraft')) { + return { + error: "You don't own the game", + errorType: 'game' + }; + } + + // 7. Fetch the user profile (skins, capes, etc.) + const profile = await this.getProfile(mcLogin); + if ('error' in profile) { + return { ...profile, errorType: 'profile' }; + } + + // Build and return the final AuthResponse object + return { + access_token: mcLogin.access_token, + client_token: crypto.randomBytes(16).toString('hex'), + uuid: profile.id, + name: profile.name, + refresh_token: oauth2.refresh_token, + user_properties: '{}', + meta: { + type: 'Xbox', + access_token_expires_in: mcLogin.expires_in + Math.floor(Date.now() / 1000), + demo: false // If there's an error retrieving the profile, you can set this to true + }, + xboxAccount: { + xuid: xboxAccount.DisplayClaims.xui[0].xid, + gamertag: xboxAccount.DisplayClaims.xui[0].gtg, + ageGroup: xboxAccount.DisplayClaims.xui[0].agg + }, + profile: { + skins: profile.skins, + capes: profile.capes + } + }; + } + + /** + * Fetches the Minecraft profile (including skins and capes) for a given access token, + * then converts each skin/cape URL to base64. + * + * @param mcLogin An object containing `access_token` to call the Minecraft profile API. + * @returns The user's Minecraft profile or an error object. + */ + public async getProfile(mcLogin: { access_token: string }): Promise { + try { + const response = await nodeFetch('https://api.minecraftservices.com/minecraft/profile', { + method: 'GET', + headers: { + Authorization: `Bearer ${mcLogin.access_token}` + } + }); + const profile = await response.json(); + + if (profile.error) { + return { error: profile.error }; + } + + // Convert each skin and cape to base64 + if (Array.isArray(profile.skins)) { + for (const skin of profile.skins) { + if (skin.url) { + skin.base64 = `data:image/png;base64,${await getBase64(skin.url)}`; + } + } + } + if (Array.isArray(profile.capes)) { + for (const cape of profile.capes) { + if (cape.url) { + cape.base64 = `data:image/png;base64,${await getBase64(cape.url)}`; + } + } + } + + return { + id: profile.id, + name: profile.name, + skins: profile.skins || [], + capes: profile.capes || [] + }; + } catch (err: any) { + return { error: err.message }; + } + } + + /** + * A helper method to perform fetch and parse JSON. + * @param url The endpoint URL. + * @param options fetch options (method, headers, body, etc.). + * @returns The parsed JSON or an object with an error field if something goes wrong. + */ + private async fetchJSON(url: string, options: Record): Promise { + try { + const response = await nodeFetch(url, options); + return response.json(); + } catch (err: any) { + return { error: err.message }; + } + } +} From dc1097362f308f13036509bccf8ea24b7961a7f3 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 096/185] refactor: unify code style and improve doc usage --- src/Authenticator/Mojang.ts | 220 ++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 109 deletions(-) diff --git a/src/Authenticator/Mojang.ts b/src/Authenticator/Mojang.ts index 57d84cd2..45605134 100755 --- a/src/Authenticator/Mojang.ts +++ b/src/Authenticator/Mojang.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import crypto from 'crypto'; @@ -10,132 +12,132 @@ let api_url = 'https://authserver.mojang.com'; async function login(username: string, password?: string) { - let UUID = crypto.randomBytes(16).toString('hex'); - if (!password) { - return { - access_token: UUID, - client_token: UUID, - uuid: UUID, - name: username, - user_properties: '{}', - meta: { - online: false, - type: 'Mojang' - } - } - } + let UUID = crypto.randomBytes(16).toString('hex'); + if (!password) { + return { + access_token: UUID, + client_token: UUID, + uuid: UUID, + name: username, + user_properties: '{}', + meta: { + online: false, + type: 'Mojang' + } + } + } - let message = await nodeFetch(`${api_url}/authenticate`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - agent: { - name: "Minecraft", - version: 1 - }, - username, - password, - clientToken: UUID, - requestUser: true - }) - }).then(res => res.json()); + let message = await nodeFetch(`${api_url}/authenticate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + agent: { + name: "Minecraft", + version: 1 + }, + username, + password, + clientToken: UUID, + requestUser: true + }) + }).then(res => res.json()); - if (message.error) { - return message; - }; - let user = { - access_token: message.accessToken, - client_token: message.clientToken, - uuid: message.selectedProfile.id, - name: message.selectedProfile.name, - user_properties: '{}', - meta: { - online: true, - type: 'Mojang' - } - } - return user; + if (message.error) { + return message; + }; + let user = { + access_token: message.accessToken, + client_token: message.clientToken, + uuid: message.selectedProfile.id, + name: message.selectedProfile.name, + user_properties: '{}', + meta: { + online: true, + type: 'Mojang' + } + } + return user; } async function refresh(acc: any) { - let message = await nodeFetch(`${api_url}/refresh`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - accessToken: acc.access_token, - clientToken: acc.client_token, - requestUser: true - }) - }).then(res => res.json()); + let message = await nodeFetch(`${api_url}/refresh`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + accessToken: acc.access_token, + clientToken: acc.client_token, + requestUser: true + }) + }).then(res => res.json()); - if (message.error) { - return message; - }; + if (message.error) { + return message; + }; - let user = { - access_token: message.accessToken, - client_token: message.clientToken, - uuid: message.selectedProfile.id, - name: message.selectedProfile.name, - user_properties: '{}', - meta: { - online: true, - type: 'Mojang' - } - } - return user; + let user = { + access_token: message.accessToken, + client_token: message.clientToken, + uuid: message.selectedProfile.id, + name: message.selectedProfile.name, + user_properties: '{}', + meta: { + online: true, + type: 'Mojang' + } + } + return user; } async function validate(acc: any) { - let message = await nodeFetch(`${api_url}/validate`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - accessToken: acc.access_token, - clientToken: acc.client_token, - }) - }); + let message = await nodeFetch(`${api_url}/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + accessToken: acc.access_token, + clientToken: acc.client_token, + }) + }); - if (message.status == 204) { - return true; - } else { - return false; - } + if (message.status == 204) { + return true; + } else { + return false; + } } async function signout(acc: any) { - let message = await nodeFetch(`${api_url}/invalidate`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - accessToken: acc.access_token, - clientToken: acc.client_token, - }) - }).then(res => res.text()); + let message = await nodeFetch(`${api_url}/invalidate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + accessToken: acc.access_token, + clientToken: acc.client_token, + }) + }).then(res => res.text()); - if (message == "") { - return true; - } else { - return false; - } + if (message == "") { + return true; + } else { + return false; + } } function ChangeAuthApi(url: string) { - api_url = url + api_url = url } export { - login as login, - refresh as refresh, - validate as validate, - signout as signout, - ChangeAuthApi as ChangeAuthApi + login as login, + refresh as refresh, + validate as validate, + signout as signout, + ChangeAuthApi as ChangeAuthApi } \ No newline at end of file From af568209f7a7ea31730f51fde73b29f8b8d384ac Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 097/185] chore: update license header --- src/Index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Index.ts b/src/Index.ts index ae6dfd3d..fa5a5ff8 100755 --- a/src/Index.ts +++ b/src/Index.ts @@ -1,7 +1,10 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ + import AZauth from './Authenticator/AZauth.js'; import Launch from './Launch.js'; import Microsoft from './Authenticator/Microsoft.js'; From 7e33711428ddd77039a3b8d1379151178e585c03 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 098/185] refactor: refactor launching process and add TypeScript-style definitions --- src/Launch.ts | 696 +++++++++++++++++++++++++------------------------- 1 file changed, 350 insertions(+), 346 deletions(-) diff --git a/src/Launch.ts b/src/Launch.ts index c72bca69..40b41771 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import { EventEmitter } from 'events'; @@ -20,375 +22,377 @@ import { isold } from './utils/Index.js'; import Downloader from './utils/Downloader.js'; type loader = { - /** - * Path to loader directory. Relative to absolute path to Minecraft's root directory (config option `path`). - * - * If `undefined`, defaults to `.minecraft/loader/`. - * - * Example: `'fabricfiles'`. - */ - path?: string, - /** - * Loader type. - * - * Acceptable values: `'forge'`, `'neoforge'`, `'fabric'`, `'legacyfabric'`, `'quilt'`. - */ - type?: string, - /** - * Loader build (version). - * - * Acceptable values: `'latest'`, `'recommended'`, actual version. - * - * Example: `'0.16.3'` - */ - build?: string, - /** - * Should the launcher use a loader? - */ - enable?: boolean + /** + * Path to loader directory. Relative to absolute path to Minecraft's root directory (config option `path`). + * + * If `undefined`, defaults to `.minecraft/loader/`. + * + * Example: `'fabricfiles'`. + */ + path?: string, + /** + * Loader type. + * + * Acceptable values: `'forge'`, `'neoforge'`, `'fabric'`, `'legacyfabric'`, `'quilt'`. + */ + type?: string, + /** + * Loader build (version). + * + * Acceptable values: `'latest'`, `'recommended'`, actual version. + * + * Example: `'0.16.3'` + */ + build?: string, + /** + * Should the launcher use a loader? + */ + enable?: boolean } /** * Screen options. */ type screen = { - width?: number, - height?: number, - /** - * Should Minecraft be started in fullscreen mode? - */ - fullscreen?: boolean + width?: number, + height?: number, + /** + * Should Minecraft be started in fullscreen mode? + */ + fullscreen?: boolean } /** * Memory limits */ type memory = { - /** - * Sets the `-Xms` JVM argument. This is the initial memory usage. - */ - min?: string, - /** - * Sets the `-Xmx` JVM argument. This is the limit of memory usage. - */ - max?: string + /** + * Sets the `-Xms` JVM argument. This is the initial memory usage. + */ + min?: string, + /** + * Sets the `-Xmx` JVM argument. This is the limit of memory usage. + */ + max?: string } /** * Java download options */ type javaOPTS = { - /** - * Absolute path to Java binaries directory. - * - * If set, expects Java to be already downloaded. If `undefined`, downloads Java and sets it automatically. - * - * Example: `'C:\Program Files\Eclipse Adoptium\jdk-21.0.2.13-hotspot\bin'` - */ - path?: string, - /** - * Java version number. - * - * If set, fetched from https://api.adoptium.net. - * If `undefined`, fetched from [Mojang](https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json). - * - * Example: `21` - */ - version?: number, - /** - * Java image type. Acceptable values: `'jdk'`, `'jre'`, `'testimage'`, `'debugimage'`, `'staticlibs'`, `'sources'`, `'sbom'`. - * - * Using `jre` is recommended since it only has what's needed. - */ - type?: string + /** + * Absolute path to Java binaries directory. + * + * If set, expects Java to be already downloaded. If `undefined`, downloads Java and sets it automatically. + * + * Example: `'C:\Program Files\Eclipse Adoptium\jdk-21.0.2.13-hotspot\bin'` + */ + path?: string, + /** + * Java version number. + * + * If set, fetched from https://api.adoptium.net. + * If `undefined`, fetched from [Mojang](https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json). + * + * Example: `21` + */ + version?: string, + /** + * Java image type. Acceptable values: `'jdk'`, `'jre'`, `'testimage'`, `'debugimage'`, `'staticlibs'`, `'sources'`, `'sbom'`. + * + * Using `jre` is recommended since it only has what's needed. + */ + type: string } /** * Launch options. */ export type LaunchOPTS = { - /** - * URL to the launcher backend. Refer to [Selvania Launcher Wiki](https://github.com/luuxis/Selvania-Launcher/blob/master/docs/wiki_EN-US.md) for setup instructions. - */ - url: string | null, - /** - * Something to Authenticate the player. - * - * Refer to `Mojang`, `Microsoft` or `AZauth` classes. - * - * Example: `await Mojang.login('Luuxis')` - */ - authenticator: any, - /** - * Connection timeout in milliseconds. - */ - timeout?: number, - /** - * Absolute path to Minecraft's root directory. - * - * Example: `'%appdata%/.minecraft'` - */ - path: string, - /** - * Minecraft version. - * - * Example: `'1.20.4'` - */ - version: string, - /** - * Path to instance directory. Relative to absolute path to Minecraft's root directory (config option `path`). - * This separates game files (e.g. versions, libraries, assets) from game data (e.g. worlds, resourcepacks, options). - * - * Example: `'PokeMoonX'` - */ - instance?: string, - /** - * Should Minecraft process be independent of launcher? - */ - detached?: boolean, - /** - * How many concurrent downloads can be in progress at once. - */ - downloadFileMultiple?: number, - intelEnabledMac?: boolean, - /** - * Loader config - */ - loader: loader, - /** - * MCPathcer directory. (idk actually luuxis please verify this) - * - * If `instance` if set, relative to it. - * If `instance` is `undefined`, relative to `path`. - */ - mcp: any, - /** - * Should game files be verified each launch? - */ - verify: boolean, - /** - * Files to ignore from instance. (idk actually luuxis please verify this) - */ - ignored: string[], - /** - * Custom JVM arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#JVM_Arguments) - */ - JVM_ARGS: string[], - /** - * Custom game arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#Game_Arguments) - */ - GAME_ARGS: string[], - /** - * Java options. - */ - java: javaOPTS, - /** - * Screen options. - */ - screen: screen, - /** - * Memory limit options. - */ - memory: memory + /** + * URL to the launcher backend. Refer to [Selvania Launcher Wiki](https://github.com/luuxis/Selvania-Launcher/blob/master/docs/wiki_EN-US.md) for setup instructions. + */ + url?: string | null, + /** + * Something to Authenticate the player. + * + * Refer to `Mojang`, `Microsoft` or `AZauth` classes. + * + * Example: `await Mojang.login('Luuxis')` + */ + authenticator: any, + /** + * Connection timeout in milliseconds. + */ + timeout?: number, + /** + * Absolute path to Minecraft's root directory. + * + * Example: `'%appdata%/.minecraft'` + */ + path: string, + /** + * Minecraft version. + * + * Example: `'1.20.4'` + */ + version: string, + /** + * Path to instance directory. Relative to absolute path to Minecraft's root directory (config option `path`). + * This separates game files (e.g. versions, libraries, assets) from game data (e.g. worlds, resourcepacks, options). + * + * Example: `'PokeMoonX'` + */ + instance?: string, + /** + * Should Minecraft process be independent of launcher? + */ + detached?: boolean, + /** + * How many concurrent downloads can be in progress at once. + */ + downloadFileMultiple?: number, + intelEnabledMac?: boolean, + /** + * Loader config + */ + loader: loader, + /** + * MCPathcer directory. (idk actually luuxis please verify this) + * + * If `instance` if set, relative to it. + * If `instance` is `undefined`, relative to `path`. + */ + mcp: any, + /** + * Should game files be verified each launch? + */ + verify: boolean, + /** + * Files to ignore from instance. (idk actually luuxis please verify this) + */ + ignored: string[], + /** + * Custom JVM arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#JVM_Arguments) + */ + JVM_ARGS: string[], + /** + * Custom game arguments. Read more on [wiki.vg](https://wiki.vg/Launching_the_game#Game_Arguments) + */ + GAME_ARGS: string[], + /** + * Java options. + */ + java: javaOPTS, + /** + * Screen options. + */ + screen: screen, + /** + * Memory limit options. + */ + memory: memory }; export default class Launch extends EventEmitter { - options: LaunchOPTS; - - async Launch(opt: LaunchOPTS) { - const defaultOptions: LaunchOPTS = { - url: null, - authenticator: null, - timeout: 10000, - path: '.Minecraft', - version: 'latest_release', - instance: null, - detached: false, - intelEnabledMac: false, - downloadFileMultiple: 5, - - loader: { - path: './loader', - type: null, - build: 'latest', - enable: false, - }, - - mcp: null, - - verify: false, - ignored: [], - JVM_ARGS: [], - GAME_ARGS: [], - - java: { - path: null, - version: null, - type: 'jre', - }, - - screen: { - width: null, - height: null, - fullscreen: false, - }, - - memory: { - min: '1G', - max: '2G' - }, - ...opt, - }; - - this.options = defaultOptions; - this.options.path = path.resolve(this.options.path).replace(/\\/g, '/'); - - if (this.options.mcp) { - if (this.options.instance) this.options.mcp = `${this.options.path}/instances/${this.options.instance}/${this.options.mcp}` - else this.options.mcp = path.resolve(`${this.options.path}/${this.options.mcp}`).replace(/\\/g, '/') - } - - if (this.options.loader.type) { - this.options.loader.type = this.options.loader.type.toLowerCase() - this.options.loader.build = this.options.loader.build.toLowerCase() - } - - if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); - if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 - if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 - if (typeof this.options.loader.path !== 'string') this.options.loader.path = `./loader/${this.options.loader.type}`; - this.start(); - } - - - async start() { - let data: any = await this.DownloadGame(); - if (data.error) return this.emit('error', data); - let { minecraftJson, minecraftLoader, minecraftVersion, minecraftJava } = data; - - let minecraftArguments: any = await new argumentsMinecraft(this.options).GetArguments(minecraftJson, minecraftLoader); - if (minecraftArguments.error) return this.emit('error', minecraftArguments); - - let loaderArguments: any = await new loaderMinecraft(this.options).GetArguments(minecraftLoader, minecraftVersion); - if (loaderArguments.error) return this.emit('error', loaderArguments); - - let Arguments: any = [ - ...minecraftArguments.jvm, - ...minecraftArguments.classpath, - ...loaderArguments.jvm, - minecraftArguments.mainClass, - ...minecraftArguments.game, - ...loaderArguments.game - ] - - let java: any = this.options.java.path ? this.options.java.path : minecraftJava.path; - let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; - if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); - - let argumentsLogs: string = Arguments.join(' ') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.access_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.client_token, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.uuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.xboxAccount?.xuid, '????????') - argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') - this.emit('data', `Launching with arguments ${argumentsLogs}`); - - let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) - minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) - minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) - } - - async DownloadGame() { - let InfoVersion = await new jsonMinecraft(this.options).GetInfoVersion(); - let loaderJson: any = null; - if (InfoVersion.error) return InfoVersion - let { json, version } = InfoVersion; - - let libraries = new librariesMinecraft(this.options) - let bundle = new bundleMinecraft(this.options) - let java = new javaMinecraft(this.options) - - java.on('progress', (progress: any, size: any, element: any) => { - this.emit('progress', progress, size, element) - }); - - java.on('extract', (progress: any) => { - this.emit('extract', progress) - }); - - let gameLibraries: any = await libraries.Getlibraries(json); - let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); - let gameAssets: any = await new assetsMinecraft(this.options).GetAssets(json); - let gameJava: any = this.options.java.path ? { files: [] } : await java.getJavaFiles(json); - - - if (gameJava.error) return gameJava - - let filesList: any = await bundle.checkBundle([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); - - if (filesList.length > 0) { - let downloader = new Downloader(); - let totsize = await bundle.getTotalSize(filesList); - - downloader.on("progress", (DL: any, totDL: any, element: any) => { - this.emit("progress", DL, totDL, element); - }); - - downloader.on("speed", (speed: any) => { - this.emit("speed", speed); - }); - - downloader.on("estimated", (time: any) => { - this.emit("estimated", time); - }); - - downloader.on("error", (e: any) => { - this.emit("error", e); - }); - - await downloader.downloadFileMultiple(filesList, totsize, this.options.downloadFileMultiple, this.options.timeout); - } - - if (this.options.loader.enable === true) { - let loaderInstall = new loaderMinecraft(this.options) - - loaderInstall.on('extract', (extract: any) => { - this.emit('extract', extract); - }); - - loaderInstall.on('progress', (progress: any, size: any, element: any) => { - this.emit('progress', progress, size, element); - }); - - loaderInstall.on('check', (progress: any, size: any, element: any) => { - this.emit('check', progress, size, element); - }); - - loaderInstall.on('patch', (patch: any) => { - this.emit('patch', patch); - }); - - let jsonLoader = await loaderInstall.GetLoader(version, this.options.java.path ? this.options.java.path : gameJava.path) - .then((data: any) => data) - .catch((err: any) => err); - if (jsonLoader.error) return jsonLoader; - loaderJson = jsonLoader; - } - - if (this.options.verify) await bundle.checkFiles([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); - - let natives = await libraries.natives(gameLibraries); - if (natives.length === 0) json.nativesList = false; - else json.nativesList = true; - - if (isold(json)) new assetsMinecraft(this.options).copyAssets(json); - - return { - minecraftJson: json, - minecraftLoader: loaderJson, - minecraftVersion: version, - minecraftJava: gameJava - } - } + options: LaunchOPTS; + + async Launch(opt: LaunchOPTS) { + const defaultOptions: LaunchOPTS = { + url: null, + authenticator: null, + timeout: 10000, + path: '.Minecraft', + version: 'latest_release', + instance: null, + detached: false, + intelEnabledMac: false, + downloadFileMultiple: 5, + + loader: { + path: './loader', + type: null, + build: 'latest', + enable: false, + }, + + mcp: null, + + verify: false, + ignored: [], + JVM_ARGS: [], + GAME_ARGS: [], + + java: { + path: null, + version: null, + type: 'jre', + }, + + screen: { + width: null, + height: null, + fullscreen: false, + }, + + memory: { + min: '1G', + max: '2G' + }, + ...opt, + }; + + this.options = defaultOptions; + this.options.path = path.resolve(this.options.path).replace(/\\/g, '/'); + + if (this.options.mcp) { + if (this.options.instance) this.options.mcp = `${this.options.path}/instances/${this.options.instance}/${this.options.mcp}` + else this.options.mcp = path.resolve(`${this.options.path}/${this.options.mcp}`).replace(/\\/g, '/') + } + + if (this.options.loader.type) { + this.options.loader.type = this.options.loader.type.toLowerCase() + this.options.loader.build = this.options.loader.build.toLowerCase() + } + + if (!this.options.authenticator) return this.emit("error", { error: "Authenticator not found" }); + if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 + if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 + if (typeof this.options.loader.path !== 'string') this.options.loader.path = `./loader/${this.options.loader.type}`; + this.start(); + } + + + async start() { + let data: any = await this.DownloadGame(); + if (data.error) return this.emit('error', data); + let { minecraftJson, minecraftLoader, minecraftVersion, minecraftJava } = data; + + let minecraftArguments: any = await new argumentsMinecraft(this.options).GetArguments(minecraftJson, minecraftLoader); + if (minecraftArguments.error) return this.emit('error', minecraftArguments); + + let loaderArguments: any = await new loaderMinecraft(this.options).GetArguments(minecraftLoader, minecraftVersion); + if (loaderArguments.error) return this.emit('error', loaderArguments); + + let Arguments: any = [ + ...minecraftArguments.jvm, + ...minecraftArguments.classpath, + ...loaderArguments.jvm, + minecraftArguments.mainClass, + ...minecraftArguments.game, + ...loaderArguments.game + ] + + let java: any = this.options.java.path ? this.options.java.path : minecraftJava.path; + let logs = this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path; + if (!fs.existsSync(logs)) fs.mkdirSync(logs, { recursive: true }); + + let argumentsLogs: string = Arguments.join(' ') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.access_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.client_token, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.uuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(this.options.authenticator?.xboxAccount?.xuid, '????????') + argumentsLogs = argumentsLogs.replaceAll(`${this.options.path}/`, '') + this.emit('data', `Launching with arguments ${argumentsLogs}`); + + let minecraftDebug = spawn(java, Arguments, { cwd: logs, detached: this.options.detached }) + minecraftDebug.stdout.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.stderr.on('data', (data) => this.emit('data', data.toString('utf-8'))) + minecraftDebug.on('close', (code) => this.emit('close', 'Minecraft closed')) + } + + async DownloadGame() { + let InfoVersion = await new jsonMinecraft(this.options).GetInfoVersion(); + let loaderJson: any = null; + if ('error' in InfoVersion) { + return this.emit('error', InfoVersion); + } + let { json, version } = InfoVersion; + + let libraries = new librariesMinecraft(this.options) + let bundle = new bundleMinecraft(this.options) + let java = new javaMinecraft(this.options) + + java.on('progress', (progress: any, size: any, element: any) => { + this.emit('progress', progress, size, element) + }); + + java.on('extract', (progress: any) => { + this.emit('extract', progress) + }); + + let gameLibraries: any = await libraries.Getlibraries(json); + let gameAssetsOther: any = await libraries.GetAssetsOthers(this.options.url); + let gameAssets: any = await new assetsMinecraft(this.options).getAssets(json); + let gameJava: any = this.options.java.path ? { files: [] } : await java.getJavaFiles(json); + + + if (gameJava.error) return gameJava + + let filesList: any = await bundle.checkBundle([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); + + if (filesList.length > 0) { + let downloader = new Downloader(); + let totsize = await bundle.getTotalSize(filesList); + + downloader.on("progress", (DL: any, totDL: any, element: any) => { + this.emit("progress", DL, totDL, element); + }); + + downloader.on("speed", (speed: any) => { + this.emit("speed", speed); + }); + + downloader.on("estimated", (time: any) => { + this.emit("estimated", time); + }); + + downloader.on("error", (e: any) => { + this.emit("error", e); + }); + + await downloader.downloadFileMultiple(filesList, totsize, this.options.downloadFileMultiple, this.options.timeout); + } + + if (this.options.loader.enable === true) { + let loaderInstall = new loaderMinecraft(this.options) + + loaderInstall.on('extract', (extract: any) => { + this.emit('extract', extract); + }); + + loaderInstall.on('progress', (progress: any, size: any, element: any) => { + this.emit('progress', progress, size, element); + }); + + loaderInstall.on('check', (progress: any, size: any, element: any) => { + this.emit('check', progress, size, element); + }); + + loaderInstall.on('patch', (patch: any) => { + this.emit('patch', patch); + }); + + let jsonLoader = await loaderInstall.GetLoader(version, this.options.java.path ? this.options.java.path : gameJava.path) + .then((data: any) => data) + .catch((err: any) => err); + if (jsonLoader.error) return jsonLoader; + loaderJson = jsonLoader; + } + + if (this.options.verify) await bundle.checkFiles([...gameLibraries, ...gameAssetsOther, ...gameAssets, ...gameJava.files]); + + let natives = await libraries.natives(gameLibraries); + if (natives.length === 0) json.nativesList = false; + else json.nativesList = true; + + if (isold(json)) new assetsMinecraft(this.options).copyAssets(json); + + return { + minecraftJson: json, + minecraftLoader: loaderJson, + minecraftVersion: version, + minecraftJava: gameJava + } + } } \ No newline at end of file From 6c31efeb7fe0cff1143c9e4a0b2a2646823362d6 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 099/185] refactor: reorganize loader architecture and code style --- src/Minecraft-Loader/index.ts | 547 ++++++++++++++++++++-------------- 1 file changed, 330 insertions(+), 217 deletions(-) diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 5891606e..5ff4ae92 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -1,230 +1,343 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ -import { loader } from '../utils/Index.js'; +import { EventEmitter } from 'events'; +import fs from 'fs'; +import path from 'path'; +import { loader as loaderFunction } from '../utils/Index.js'; + +// Loader sub-classes (Forge, NeoForge, etc.) +// Adjust the import paths based on your project's actual file structure. import Forge from './loader/forge/forge.js'; import NeoForge from './loader/neoForge/neoForge.js'; import Fabric from './loader/fabric/fabric.js'; import LegacyFabric from './loader/legacyfabric/legacyFabric.js'; import Quilt from './loader/quilt/quilt.js'; +/** + * Represents the user's selected loader type (Forge, Fabric, etc.). + * Extend or refine as your application requires. + */ +export type LoaderType = 'forge' | 'neoforge' | 'fabric' | 'legacyfabric' | 'quilt'; -import { EventEmitter } from 'events'; -import fs from 'fs' -import path from 'path' +/** + * Configuration for the loader (build, version, etc.). + * For instance: { type: "forge", version: "1.19.2", build: "latest" } + */ +export interface LoaderConfig { + type: LoaderType; + version: string; // Minecraft version + build: string; // e.g., "latest", "recommended", or a specific numeric build + config: { + javaPath: string; + minecraftJar: string; + minecraftJson: string; + }; + // Feel free to add additional fields if needed +} -export default class Loader extends EventEmitter { - options: any; - - constructor(options: any) { - super(); - this.options = options - } - - async install() { - let Loader = loader(this.options.loader.type); - if (!Loader) return this.emit('error', { error: `Loader ${this.options.loader.type} not found` }); - - if (this.options.loader.type === 'forge') { - let forge = await this.forge(Loader); - if (forge.error) return this.emit('error', forge); - this.emit('json', forge); - } else if (this.options.loader.type === 'neoforge') { - let neoForge = await this.neoForge(Loader); - if (neoForge.error) return this.emit('error', neoForge); - this.emit('json', neoForge); - } else if (this.options.loader.type === 'fabric') { - let fabric = await this.fabric(Loader); - if (fabric.error) return this.emit('error', fabric); - this.emit('json', fabric); - } else if (this.options.loader.type === 'legacyfabric') { - let legacyFabric = await this.legacyFabric(Loader); - if (legacyFabric.error) return this.emit('error', legacyFabric); - this.emit('json', legacyFabric); - } else if (this.options.loader.type === 'quilt') { - let quilt = await this.quilt(Loader); - if (quilt.error) return this.emit('error', quilt); - this.emit('json', quilt); - } else { - return this.emit('error', { error: `Loader ${this.options.loader.type} not found` }); - } - } - - async forge(Loader: any) { - let forge = new Forge(this.options); - // set event - forge.on('check', (progress, size, element) => { - this.emit('check', progress, size, element); - }); - - forge.on('progress', (progress, size, element) => { - this.emit('progress', progress, size, element); - }); - - forge.on('extract', (element) => { - this.emit('extract', element); - }); - - forge.on('patch', patch => { - this.emit('patch', patch); - }); - - // download installer - let installer = await forge.downloadInstaller(Loader); - if (installer.error) return installer; - - if (installer.ext == 'jar') { - // extract install profile - let profile: any = await forge.extractProfile(installer.filePath); - if (profile.error) return profile - let destination = path.resolve(this.options.path, 'versions', profile.version.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); - - // extract universal jar - let universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); - if (universal.error) return universal; - - // download libraries - let libraries: any = await forge.downloadLibraries(profile, universal); - if (libraries.error) return libraries; - - // patch forge if nessary - let patch: any = await forge.patchForge(profile.install); - if (patch.error) return patch; - - return profile.version; - } else { - let profile: any = await forge.createProfile(installer.id, installer.filePath); - if (profile.error) return profile - let destination = path.resolve(this.options.path, 'versions', profile.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.id}.json`), JSON.stringify(profile, null, 4)); - return profile; - } - } - - async neoForge(Loader: any) { - let neoForge = new NeoForge(this.options); - - // set event - neoForge.on('check', (progress, size, element) => { - this.emit('check', progress, size, element); - }); - - neoForge.on('progress', (progress, size, element) => { - this.emit('progress', progress, size, element); - }); - - neoForge.on('extract', (element) => { - this.emit('extract', element); - }); - - neoForge.on('patch', patch => { - this.emit('patch', patch); - }); - - // download installer - let installer = await neoForge.downloadInstaller(Loader); - if (installer.error) return installer; - - // extract install profile - let profile: any = await neoForge.extractProfile(installer.filePath); - if (profile.error) return profile - let destination = path.resolve(this.options.path, 'versions', profile.version.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); - - //extract universal jar - let universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); - if (universal.error) return universal; - - // download libraries - let libraries: any = await neoForge.downloadLibraries(profile, universal); - if (libraries.error) return libraries; - - // patch forge if nessary - let patch: any = await neoForge.patchneoForge(profile.install, installer.oldAPI); - if (patch.error) return patch; - - return profile.version; - } - - async fabric(Loader: any) { - let fabric = new Fabric(this.options); - - // set event - fabric.on('check', (progress, size, element) => { - this.emit('check', progress, size, element); - }); - - fabric.on('progress', (progress, size, element) => { - this.emit('progress', progress, size, element); - }); - - // download Json - let json = await fabric.downloadJson(Loader); - if (json.error) return json; - let destination = path.resolve(this.options.path, 'versions', json.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); - - // download libraries - await fabric.downloadLibraries(json); - - return json; - } - - async legacyFabric(Loader: any) { - let legacyFabric = new LegacyFabric(this.options); - - // set event - legacyFabric.on('check', (progress, size, element) => { - this.emit('check', progress, size, element); - }); - - legacyFabric.on('progress', (progress, size, element) => { - this.emit('progress', progress, size, element); - }); - - // download Json - let json = await legacyFabric.downloadJson(Loader); - if (json.error) return json; - let destination = path.resolve(this.options.path, 'versions', json.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); - - // download libraries - await legacyFabric.downloadLibraries(json); - - return json; - } - - async quilt(Loader: any) { - let quilt = new Quilt(this.options); - - // set event - quilt.on('check', (progress, size, element) => { - this.emit('check', progress, size, element); - }); - - quilt.on('progress', (progress, size, element) => { - this.emit('progress', progress, size, element); - }); +/** + * The overall options passed to our Loader class, + * containing path information and loader configuration. + */ +export interface LoaderOptions { + path: string; // Base directory for storing version files, etc. + loader: LoaderConfig; + [key: string]: any; // Allow additional fields as necessary +} - // download Json - let json = await quilt.downloadJson(Loader); - - if (json.error) return json; - let destination = path.resolve(this.options.path, 'versions', json.id) - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); +/** + * A generic type to represent the JSON objects returned by + * Forge, NeoForge, Fabric, etc., after an installation. + */ +export interface LoaderResult { + id?: string; // For example, "1.19.2-Forge" or "fabric-loader-1.14" + error?: string; // If an error occurs, we store a message here + [key: string]: any; // Additional fields depending on the loader +} - // // download libraries - await quilt.downloadLibraries(json); - - return json; - } -} \ No newline at end of file +/** + * The main Loader class that orchestrates installation of different + * Minecraft mod loaders (Forge, Fabric, LegacyFabric, Quilt, etc.). + * It extends EventEmitter to provide "check", "progress", "extract", "patch", and "error" events. + */ +export default class Loader extends EventEmitter { + private readonly options: LoaderOptions; + + constructor(options: LoaderOptions) { + super(); + this.options = options; + } + + /** + * Main entry point for installing the selected loader. + * Checks the loader type from `this.options.loader.type` and delegates to the appropriate method. + * Emits: + * - "error" if the loader is not found or if an installation step fails + * - "json" upon successful completion, returning the version JSON or loader info + */ + public async install(): Promise { + // Retrieve a loader definition from your `loaderFunction` + // (Presumably a function that returns metadata URLs, etc. based on the type.) + const LoaderData = loaderFunction(this.options.loader.type); + if (!LoaderData) { + this.emit('error', { error: `Loader ${this.options.loader.type} not found` }); + return; + } + + const loaderType = this.options.loader.type; + let result: LoaderResult | undefined; + + switch (loaderType) { + case 'forge': { + result = await this.forge(LoaderData); + break; + } + case 'neoforge': { + result = await this.neoForge(LoaderData); + break; + } + case 'fabric': { + result = await this.fabric(LoaderData); + break; + } + case 'legacyfabric': { + result = await this.legacyFabric(LoaderData); + break; + } + case 'quilt': { + result = await this.quilt(LoaderData); + break; + } + default: { + this.emit('error', { error: `Loader ${loaderType} not found` }); + return; + } + } + + // If there's an error property, emit it. Otherwise, emit the final JSON. + if (result && result.error) { + this.emit('error', result); + } else if (result) { + this.emit('json', result); + } + } + + /** + * Handles Forge installation by: + * 1. Downloading the installer + * 2. Depending on installer type, extracting an install profile or creating a merged Jar + * 3. Downloading required libraries + * 4. Patching Forge if necessary + * 5. Returns the final version JSON object or an error + */ + private async forge(LoaderData: any): Promise { + const forge = new Forge(this.options); + + // Forward Forge events + forge.on('check', (progress: number, size: number, element: string) => { + this.emit('check', progress, size, element); + }); + forge.on('progress', (progress: number, size: number, element: string) => { + this.emit('progress', progress, size, element); + }); + forge.on('extract', (element: string) => { + this.emit('extract', element); + }); + forge.on('patch', (patch: any) => { + this.emit('patch', patch); + }); + + // 1. Download installer + const installer: any = await forge.downloadInstaller(LoaderData); + if (installer.error) return installer; // e.g., { error: "..." } + + // 2. If the installer extension is ".jar", we do the standard "install_profile.json" approach + if ("ext" in installer && installer.ext === 'jar') { + const profile: any = await forge.extractProfile(installer.filePath); + if (profile.error) return profile; + + // Write the version JSON to disk + const destination = path.resolve(this.options.path, 'versions', profile.version.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + + // 3. Extract universal jar if needed + const universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); + if (universal.error) return universal; + + // 4. Download libraries + const libraries: any = await forge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; + + // 5. Patch Forge if necessary + const patch: any = await forge.patchForge(profile.install); + if (patch.error) return patch; + + return profile.version; + } else { + // For older Forge, create a merged jar + const profile = await forge.createProfile(installer.id, installer.filePath); + if (profile.error) return profile; + + const destination = path.resolve(this.options.path, 'versions', profile.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.id}.json`), JSON.stringify(profile, null, 4)); + + return profile; + } + } + + /** + * Manages installation flow for NeoForge: + * 1. Download the installer + * 2. Extract the install profile + * 3. Extract the universal jar + * 4. Download libraries + * 5. Patch if needed + */ + private async neoForge(LoaderData: any): Promise { + const neoForge = new NeoForge(this.options); + + // Forward events + neoForge.on('check', (progress: number, size: number, element: string) => { + this.emit('check', progress, size, element); + }); + neoForge.on('progress', (progress: number, size: number, element: string) => { + this.emit('progress', progress, size, element); + }); + neoForge.on('extract', (element: string) => { + this.emit('extract', element); + }); + neoForge.on('patch', (patch: any) => { + this.emit('patch', patch); + }); + + const installer = await neoForge.downloadInstaller(LoaderData); + if (installer.error) return installer; + + // Extract the main profile + const profile: any = await neoForge.extractProfile(installer.filePath); + if (profile.error) return profile; + + // Write version JSON + if ("version" in profile && "id" in profile.version) { + const destination = path.resolve(this.options.path, 'versions', profile.version.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + } + // Extract universal jar + const universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); + if (universal.error) return universal; + + // Download libraries + const libraries: any = await neoForge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; + + // Patch if needed + const patch: any = await neoForge.patchneoForge(profile.install, installer.oldAPI); + if (patch.error) return patch; + + if ("version" in profile) return profile.version; + } + + /** + * Installs Fabric: + * 1. Download the loader JSON + * 2. Save it as a version .json + * 3. Download required libraries + */ + private async fabric(LoaderData: any): Promise { + const fabric = new Fabric(this.options); + + // Forward events + fabric.on('check', (progress: number, size: number, element: string) => { + this.emit('check', progress, size, element); + }); + fabric.on('progress', (progress: number, size: number, element: string) => { + this.emit('progress', progress, size, element); + }); + + const json = await fabric.downloadJson(LoaderData); + if (json.error) return json; + + if ("id" in json) { + const destination = path.resolve(this.options.path, 'versions', json.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + } + + if ("libraries" in json) { + await fabric.downloadLibraries(json); + } + + return json; + } + + /** + * Installs Legacy Fabric: + * 1. Download JSON + * 2. Save version .json + * 3. Download libraries + */ + private async legacyFabric(LoaderData: any): Promise { + const legacyFabric = new LegacyFabric(this.options); + + // Forward events + legacyFabric.on('check', (progress: number, size: number, element: string) => { + this.emit('check', progress, size, element); + }); + legacyFabric.on('progress', (progress: number, size: number, element: string) => { + this.emit('progress', progress, size, element); + }); + + const json = await legacyFabric.downloadJson(LoaderData); + if (json.error) return json; + + if ("id" in json) { + const destination = path.resolve(this.options.path, 'versions', json.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + } + if ("libraries" in json) { + await legacyFabric.downloadLibraries(json); + } + return json; + } + + /** + * Installs Quilt: + * 1. Download the loader JSON + * 2. Write to a version file + * 3. Download required libraries + */ + private async quilt(LoaderData: any): Promise { + const quilt = new Quilt(this.options); + + // Forward events + quilt.on('check', (progress: number, size: number, element: string) => { + this.emit('check', progress, size, element); + }); + quilt.on('progress', (progress: number, size: number, element: string) => { + this.emit('progress', progress, size, element); + }); + + const json = await quilt.downloadJson(LoaderData); + if (json.error) return json; + + if ("id" in json) { + const destination = path.resolve(this.options.path, 'versions', json.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + } + if ("libraries" in json) { + await quilt.downloadLibraries(json); + } + + return json; + } +} From db08a5e2f9b5ae4c0c4eba73ac9c9ca5becc56b3 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 100/185] refactor: improve doc comments and add typed interfaces --- src/Minecraft-Loader/loader/fabric/fabric.ts | 284 +++++++++++++------ 1 file changed, 200 insertions(+), 84 deletions(-) diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts index 3615df8e..b5eb36fb 100644 --- a/src/Minecraft-Loader/loader/fabric/fabric.ts +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -1,91 +1,207 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ +import { EventEmitter } from 'events'; +import fs from 'fs'; +import path from 'path'; +import nodeFetch from 'node-fetch'; + import { getPathLibraries } from '../../../utils/Index.js'; -import download from '../../../utils/Downloader.js'; +import Downloader from '../../../utils/Downloader.js'; -import nodeFetch from 'node-fetch' -import fs from 'fs' -import path from 'path' -import { EventEmitter } from 'events'; +/** + * Represents the options needed by the FabricMC class. + * You can expand this if your code requires more specific fields. + */ +interface FabricOptions { + path: string; // Base path to your game or library folder + downloadFileMultiple?: number; // Max simultaneous downloads (if supported by Downloader) + loader: { + version: string; // Minecraft version + build: string; // Fabric build (e.g. "latest", "recommended", or a specific version) + }; +} +/** + * Represents the Loader object that holds metadata URLs and JSON paths. + * For instance, it might look like: + * { + * metaData: 'https://meta.fabricmc.net/v2/versions', + * json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' + * } + */ +interface LoaderObject { + metaData: string; + json: string; // Template string with placeholders like ${version} and ${build} +} + +/** + * Represents the structure of your metadata, including + * game versions and loader builds. Adapt as needed. + */ +interface MetaData { + game: Array<{ + version: string; + stable: boolean; + }>; + loader: Array<{ + version: string; + stable: boolean; + }>; +} + +/** + * Structure of a library entry in the Fabric JSON manifest. + * Extend this interface if you have additional fields like "rules", etc. + */ +interface FabricLibrary { + name: string; + url: string; + rules?: Array; +} + +/** + * The JSON object returned by Fabric metadata endpoints. + */ +interface FabricJSON { + libraries: FabricLibrary[]; + [key: string]: any; +} + +/** + * This class handles downloading Fabric loader JSON metadata, + * resolving the correct build, and downloading the required libraries. + */ export default class FabricMC extends EventEmitter { - options: any; - - constructor(options = {}) { - super(); - this.options = options; - } - - async downloadJson(Loader) { - let build - let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); - - let version = metaData.game.find(version => version.version === this.options.loader.version); - let AvailableBuilds = metaData.loader.map(build => build.version); - if (!version) return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; - - if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { - build = metaData.loader[0]; - } else { - build = metaData.loader.find(loader => loader.version === this.options.loader.build); - } - - if (!build) return { error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - - let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); - let json = await nodeFetch(url).then(res => res.json()).catch(err => err); - return json - } - - async downloadLibraries(json) { - let { libraries } = json; - let downloader = new download(); - let files: any = []; - let check = 0; - let size = 0; - - for (let lib of libraries) { - if (lib.rules) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - let file = {} - let libInfo = getPathLibraries(lib.name); - let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); - let pathLibFile = path.resolve(pathLib, libInfo.name); - - if (!fs.existsSync(pathLibFile)) { - let url = `${lib.url}${libInfo.path}/${libInfo.name}` - let sizeFile = 0 - - let res: any = await downloader.checkURL(url); - if (res.status === 200) { - sizeFile = res.size; - size += res.size; - } - - file = { - url: url, - folder: pathLib, - path: `${pathLib}/${libInfo.name}`, - name: libInfo.name, - size: sizeFile - } - files.push(file); - } - this.emit('check', check++, libraries.length, 'libraries'); - } - - if (files.length > 0) { - downloader.on("progress", (DL, totDL) => { - this.emit("progress", DL, totDL, 'libraries'); - }); - - await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); - } - return libraries - } -} \ No newline at end of file + private readonly options: FabricOptions; + + constructor(options: FabricOptions) { + super(); + this.options = options; + } + + /** + * Fetches the Fabric loader metadata to find the correct build for the given + * Minecraft version. If the specified build is "latest" or "recommended", + * it uses the first (most recent) entry. Otherwise, it looks up a specific build. + * + * @param Loader A LoaderObject describing metadata and json URL templates. + * @returns A JSON object representing the Fabric loader profile, or an error object. + */ + public async downloadJson(Loader: LoaderObject): Promise { + let buildInfo: { version: string; stable: boolean } | undefined; + + // Fetch the metadata + let response = await nodeFetch(Loader.metaData); + let metaData: MetaData = await response.json(); + + // Check if the Minecraft version is supported + const version = metaData.game.find(v => v.version === this.options.loader.version); + if (!version) { + return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; + } + + // Determine the loader build + const availableBuilds = metaData.loader.map(b => b.version); + if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { + buildInfo = metaData.loader[0]; // The first entry is presumably the latest + } else { + buildInfo = metaData.loader.find(l => l.version === this.options.loader.build); + } + + if (!buildInfo) { + return { + error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${availableBuilds.join(', ')}` + }; + } + + // Build the URL for the Fabric JSON using placeholders + const url = Loader.json + .replace('${build}', buildInfo.version) + .replace('${version}', this.options.loader.version); + + // Fetch the Fabric loader JSON + try { + const result = await nodeFetch(url); + const fabricJson: FabricJSON = await result.json(); + return fabricJson; + } catch (err: any) { + return { error: err.message || 'An error occurred while fetching Fabric JSON' }; + } + } + + /** + * Downloads any missing libraries defined in the Fabric JSON manifest, + * skipping those that already exist locally (or that have rules preventing download). + * + * @param fabricJson The Fabric JSON object with a `libraries` array. + * @returns The same `libraries` array after downloading as needed. + */ + public async downloadLibraries(fabricJson: FabricJSON): Promise { + const { libraries } = fabricJson; + const downloader = new Downloader(); + const downloadQueue: Array<{ + url: string; + folder: string; + path: string; + name: string; + size: number; + }> = []; + + let checkedLibraries = 0; + let totalSize = 0; + + // Identify which libraries need downloading + for (const lib of libraries) { + // Skip if there are any rules that prevent downloading + if (lib.rules) { + this.emit('check', checkedLibraries++, libraries.length, 'libraries'); + continue; + } + + // Parse out the library path + const libInfo = getPathLibraries(lib.name); + const libFolderPath = path.resolve(this.options.path, 'libraries', libInfo.path); + const libFilePath = path.resolve(libFolderPath, libInfo.name); + + // If the file doesn't exist locally, we prepare a download item + if (!fs.existsSync(libFilePath)) { + const libUrl = `${lib.url}${libInfo.path}/${libInfo.name}`; + + let sizeFile = 0; + // Check if the file is accessible and retrieve its size + const res = await downloader.checkURL(libUrl); + if (res && typeof res === 'object' && 'status' in res && res.status === 200) { + sizeFile = res.size; + totalSize += res.size; + } + + downloadQueue.push({ + url: libUrl, + folder: libFolderPath, + path: libFilePath, + name: libInfo.name, + size: sizeFile + }); + } + + // Emit a "check" event for progress tracking + this.emit('check', checkedLibraries++, libraries.length, 'libraries'); + } + + // If there are files to download, do so now + if (downloadQueue.length > 0) { + downloader.on('progress', (downloaded: number, total: number) => { + this.emit('progress', downloaded, total, 'libraries'); + }); + + await downloader.downloadFileMultiple(downloadQueue, totalSize, this.options.downloadFileMultiple); + } + + return libraries; + } +} From bb79a1e59c74a4eab68bace241f5dd5da5e96b89 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 101/185] refactor: restructure Forge installation logic and patch process --- src/Minecraft-Loader/loader/forge/forge.ts | 763 +++++++++++++-------- 1 file changed, 482 insertions(+), 281 deletions(-) diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 906b956b..e207cedc 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -1,289 +1,490 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ -import { getPathLibraries, getFileHash, mirrors, getFileFromArchive, createZIP } from '../../../utils/Index.js'; -import download from '../../../utils/Downloader.js'; -import forgePatcher from '../../patcher.js' -import nodeFetch from 'node-fetch' -import fs from 'fs' -import path from 'path' +import fs from 'fs'; +import path from 'path'; import { EventEmitter } from 'events'; -import { skipLibrary } from '../../../utils/Index.js'; +import nodeFetch from 'node-fetch'; -let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; +import { + getPathLibraries, + getFileHash, + mirrors, + getFileFromArchive, + createZIP, + skipLibrary +} from '../../../utils/Index.js'; +import Downloader from '../../../utils/Downloader.js'; +import ForgePatcher, { Profile } from '../../patcher.js'; + +/** + * Maps Node.js process.platform values to Mojang library naming conventions. + * Used for choosing the right native library. + */ +const Lib: Record = { + win32: 'windows', + darwin: 'osx', + linux: 'linux' +}; + +/** + * Represents the loader configuration. You may need to expand or adjust + * this interface if your real code has more properties. + */ +interface LoaderConfig { + version: string; // Minecraft version for Forge (e.g., "1.19.2") + build: string; // Forge build (e.g., "latest", "recommended", or a numeric version) + config: { + javaPath: string; // Path to Java for patching + minecraftJar: string; // Path to the vanilla Minecraft JAR + minecraftJson: string; // Path to the corresponding .json version file + }; +} + +/** + * Options passed to ForgeMC. Adjust as needed. + */ +interface ForgeOptions { + path: string; // Base path where files will be placed or read from + loader: { + version: string; // Minecraft version (e.g. "1.19.2") + build: string; // Build type ("latest", "recommended", or a numeric version) + config: { + javaPath: string; // Path to the Java executable for patching + minecraftJar: string; // Path to the vanilla Minecraft .jar + minecraftJson: string; // Path to the corresponding .json version file + }; + type: string; // Type of loader + }; + downloadFileMultiple?: number; // Number of concurrent downloads + [key: string]: any; // Allow extra fields as necessary +} + +/** + * Represents information about the Forge installer file after download: + * - If successful, contains filePath, metaData, ext, and an id (e.g. "forge-") + * - If an error occurs, returns an object with `error` describing the issue. + */ +type DownloadInstallerResult = + | { + filePath: string; + metaData: string; + ext: string; + id: string; + } + | { + error: string; + }; + +/** + * Describes the structure of an install_profile.json (Forge Installer) after extraction. + */ +interface ForgeProfile extends Profile { + install?: { + libraries?: any[]; + [key: string]: any; + }; + version?: { + libraries?: any[]; + [key: string]: any; + }; + filePath?: string; + path?: string; + [key: string]: any; +} + +/** + * 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 + */ export default class ForgeMC extends EventEmitter { - options: any; - - constructor(options = {}) { - super(); - this.options = options; - } - - async downloadInstaller(Loader: any) { - let metaData = (await nodeFetch(Loader.metaData).then(res => res.json()))[this.options.loader.version]; - let AvailableBuilds = metaData; - let forgeURL: String; - let ext: String; - let hashFileOrigin: String; - if (!metaData) return { error: `Forge ${this.options.loader.version} not supported` }; - - let build - if (this.options.loader.build === 'latest') { - let promotions = await nodeFetch(Loader.promotions).then(res => res.json()); - promotions = promotions.promos[`${this.options.loader.version}-latest`]; - build = metaData.find(build => build.includes(promotions)) - } else if (this.options.loader.build === 'recommended') { - let promotion = await nodeFetch(Loader.promotions).then(res => res.json()); - let promotions = promotion.promos[`${this.options.loader.version}-recommended`]; - if (!promotions) promotions = promotion.promos[`${this.options.loader.version}-latest`]; - build = metaData.find(build => build.includes(promotions)) - } else { - build = this.options.loader.build; - } - - metaData = metaData.filter(b => b === build)[0]; - if (!metaData) return { error: `Build ${build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - - - let meta = await nodeFetch(Loader.meta.replace(/\${build}/g, metaData)).then(res => res.json()); - let installerType = Object.keys(meta.classifiers).find((key: String) => key == 'installer'); - let clientType = Object.keys(meta.classifiers).find((key: String) => key == 'client'); - let universalType = Object.keys(meta.classifiers).find((key: String) => key == 'universal'); - - if (installerType) { - forgeURL = forgeURL = Loader.install.replace(/\${version}/g, metaData); - ext = Object.keys(meta.classifiers.installer)[0]; - hashFileOrigin = meta.classifiers.installer[`${ext}`]; - } else if (clientType) { - forgeURL = Loader.client.replace(/\${version}/g, metaData); - ext = Object.keys(meta.classifiers.client)[0]; - hashFileOrigin = meta.classifiers.client[`${ext}`]; - } else if (universalType) { - forgeURL = Loader.universal.replace(/\${version}/g, metaData); - ext = Object.keys(meta.classifiers.universal)[0]; - hashFileOrigin = meta.classifiers.universal[`${ext}`]; - } else { - return { error: 'Invalid forge installer' }; - } - - let pathFolder = path.resolve(this.options.path, 'forge'); - let filePath = path.resolve(pathFolder, (`${forgeURL}.${ext}`).split('/').pop()); - - if (!fs.existsSync(filePath)) { - if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); - let downloadForge = new download(); - - downloadForge.on('progress', (downloaded, size) => { - this.emit('progress', downloaded, size, (`${forgeURL}.${ext}`).split('/').pop()); - }); - - await downloadForge.downloadFile(`${forgeURL}.${ext}`, pathFolder, (`${forgeURL}.${ext}`).split('/').pop()); - } - - let hashFileDownload = await getFileHash(filePath, 'md5'); - - if (hashFileDownload !== hashFileOrigin) { - fs.rmSync(filePath); - return { error: 'Invalid hash' }; - } - return { filePath, metaData, ext, id: `forge-${build}` }; - } - - async extractProfile(pathInstaller: any) { - let forgeJSON: any = {} - - let file: any = await getFileFromArchive(pathInstaller, 'install_profile.json') - let forgeJsonOrigin = JSON.parse(file); - - if (!forgeJsonOrigin) return { error: { message: 'Invalid forge installer' } }; - if (forgeJsonOrigin.install) { - forgeJSON.install = forgeJsonOrigin.install; - forgeJSON.version = forgeJsonOrigin.versionInfo; - } else { - forgeJSON.install = forgeJsonOrigin; - let file: any = await getFileFromArchive(pathInstaller, path.basename(forgeJSON.install.json)) - forgeJSON.version = JSON.parse(file); - } - - return forgeJSON; - } - - async extractUniversalJar(profile: any, pathInstaller: any) { - let skipForgeFilter = true - - if (profile.filePath) { - let fileInfo = getPathLibraries(profile.path) - this.emit('extract', `Extracting ${fileInfo.name}...`); - - let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) - if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - - let file: any = await getFileFromArchive(pathInstaller, profile.filePath) - fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) - } else if (profile.path) { - let fileInfo = getPathLibraries(profile.path) - let listFile: any = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`) - - await Promise.all( - listFile.map(async (files: any) => { - let fileName = files.split('/') - this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); - let file: any = await getFileFromArchive(pathInstaller, files) - let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) - if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) - }) - ); - } else { - skipForgeFilter = false - } - - if (profile.processors?.length) { - let universalPath = profile.libraries.find(v => { - return (v.name || '').startsWith('net.minecraftforge:forge') - }) - - let client: any = await getFileFromArchive(pathInstaller, 'data/client.lzma'); - let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') - let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) - - if (!fs.existsSync(pathFile)) fs.mkdirSync(pathFile, { recursive: true }); - fs.writeFileSync(`${pathFile}/${fileInfo.name}`, client, { mode: 0o777 }) - this.emit('extract', `Extracting ${fileInfo.name}...`); - } - - return skipForgeFilter - } - - async downloadLibraries(profile: any, skipForgeFilter: any) { - let { libraries } = profile.version; - let downloader = new download(); - let check = 0; - let files: any = []; - let size = 0; - - if (profile.install.libraries) libraries = libraries.concat(profile.install.libraries); - - libraries = libraries.filter((library, index, self) => index === self.findIndex(t => t.name === library.name)) - - let skipForge = [ - 'net.minecraftforge:forge:', - 'net.minecraftforge:minecraftforge:' - ] - - for (let lib of libraries) { - let natives = null; - - if (skipForgeFilter && skipForge.find(libs => lib.name.includes(libs))) { - if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - } - - if (skipLibrary(lib)) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - - if (lib.natives) { - natives = lib.natives[Lib[process.platform]]; - } - - let file = {} - let libInfo = getPathLibraries(lib.name, natives ? `-${natives}` : ''); - let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); - let pathLibFile = path.resolve(pathLib, libInfo.name); - - if (!fs.existsSync(pathLibFile)) { - let url - let sizeFile = 0; - let baseURL = natives ? `${libInfo.path}/` : `${libInfo.path}/${libInfo.name}`; - let response: any = await downloader.checkMirror(baseURL, mirrors) - - if (response?.status === 200) { - size += response.size; - sizeFile = response.size; - url = response.url; - } else if (lib.downloads?.artifact) { - url = lib.downloads.artifact.url - size += lib.downloads.artifact.size; - sizeFile = lib.downloads.artifact.size; - } else { - url = null - } - - if (url == null || !url) { - return { error: `Impossible to download ${libInfo.name}` }; - } - - file = { - url: url, - folder: pathLib, - path: `${pathLib}/${libInfo.name}`, - name: libInfo.name, - size: sizeFile - } - files.push(file); - } - this.emit('check', check++, libraries.length, 'libraries'); - } - - if (files.length > 0) { - downloader.on("progress", (DL, totDL) => { - this.emit("progress", DL, totDL, 'libraries'); - }); - - await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); - } - return libraries - } - - async patchForge(profile: any) { - if (profile?.processors?.length) { - let patcher: any = new forgePatcher(this.options); - let config: any = {} - - patcher.on('patch', data => { - this.emit('patch', data); - }); - - patcher.on('error', data => { - this.emit('error', data); - }); - - if (!patcher.check(profile)) { - 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 - } - - async createProfile(id: any, pathInstaller: any) { - let forgeFiles: any = await getFileFromArchive(pathInstaller) - let minecraftJar: any = await getFileFromArchive(this.options.loader.config.minecraftJar) - let data: any = await createZIP([...minecraftJar, ...forgeFiles], 'META-INF'); - - let destination = path.resolve(this.options.path, 'versions', id); - - let profile = JSON.parse(fs.readFileSync(this.options.loader.config.minecraftJson, 'utf-8')) - profile.libraries = []; - profile.id = id; - profile.isOldForge = true; - profile.jarPath = path.resolve(destination, `${id}.jar`); - - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${id}.jar`), data, { mode: 0o777 }); - return profile - } -} \ No newline at end of file + private readonly options: ForgeOptions; + + constructor(options: ForgeOptions) { + 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. + */ + public async downloadInstaller(Loader: any): Promise { + // Fetch metadata for the given Forge version + let metaDataList: string[] = await nodeFetch(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: string | undefined; + + // Handle "latest" or "recommended" builds by checking promotions + if (this.options.loader.build === 'latest') { + let promotions = await nodeFetch(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 nodeFetch(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 nodeFetch(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: string = ''; + let ext: string = ''; + let hashFileOrigin: string = ''; + + 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.resolve(this.options.path, 'forge'); + const fileName = `${forgeURL}.${ext}`.split('/').pop()!; + const installerPath = path.resolve(forgeFolder, fileName); + + // Download if not already present + if (!fs.existsSync(installerPath)) { + if (!fs.existsSync(forgeFolder)) { + fs.mkdirSync(forgeFolder, { recursive: true }); + } + const dl = new Downloader(); + dl.on('progress', (downloaded: number, size: number) => { + this.emit('progress', downloaded, size, fileName); + }); + + await dl.downloadFile(`${forgeURL}.${ext}`, forgeFolder, fileName); + } + + // Verify the MD5 hash + const hashFileDownload = await getFileHash(installerPath, 'md5'); + if (hashFileDownload !== hashFileOrigin) { + fs.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. + */ + public async extractProfile(pathInstaller: string): Promise<{ error?: any; install?: any; version?: any }> { + const fileContent = await 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: any = {}; + + // 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 getFileFromArchive(pathInstaller, path.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 + */ + public async extractUniversalJar(profile: ForgeProfile, pathInstaller: string): Promise { + let skipForgeFilter = true; + + // If there's a direct file path, extract just that file + if (profile.filePath) { + const fileInfo = getPathLibraries(profile.path); + this.emit('extract', `Extracting ${fileInfo.name}...`); + + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + + const archiveContent = await getFileFromArchive(pathInstaller, profile.filePath); + if (archiveContent) { + fs.writeFileSync(path.join(destFolder, fileInfo.name), archiveContent, { mode: 0o777 }); + } + } + // Otherwise, if there's a path referencing "maven/" + else if (profile.path) { + const fileInfo = getPathLibraries(profile.path); + const filesInArchive: string[] = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`); + for (const file of filesInArchive) { + const fileName = path.basename(file); + this.emit('extract', `Extracting ${fileName}...`); + const fileContent = await getFileFromArchive(pathInstaller, file); + if (!fileContent) { + continue; + } + + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + + fs.writeFileSync(path.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: any) => (v.name || '').startsWith('net.minecraftforge:forge')); + const clientData = await getFileFromArchive(pathInstaller, 'data/client.lzma'); + if (clientData) { + const fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma'); + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + fs.writeFileSync(path.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). + */ + public async downloadLibraries(profile: ForgeProfile, skipForgeFilter: boolean): Promise { + let libraries = profile.version?.libraries || []; + const dl = new Downloader(); + let checkCount = 0; + const downloadList: Array<{ + url: string; + folder: string; + path: string; + name: string; + size: number; + }> = []; + 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: any, index: number, self: any[]) => 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 (skipLibrary(lib)) { + this.emit('check', checkCount++, libraries.length, 'libraries'); + continue; + } + + // Check if the library includes "natives" for the current OS + let nativesSuffix: string | undefined; + if (lib.natives) { + nativesSuffix = lib.natives[Lib[process.platform]]; + } + + const libInfo = getPathLibraries(lib.name, nativesSuffix ? `-${nativesSuffix}` : ''); + const libFolder = path.resolve(this.options.path, 'libraries', libInfo.path); + const libFilePath = path.resolve(libFolder, libInfo.name); + + // If not present locally, schedule it for download + if (!fs.existsSync(libFilePath)) { + let url: string | null = null; + let fileSize = 0; + + // First, try checking a mirror + const baseURL = nativesSuffix ? `${libInfo.path}/` : `${libInfo.path}/${libInfo.name}`; + const mirrorResp: any = await dl.checkMirror(baseURL, 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: number, totDL: number) => { + 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 + */ + public async patchForge(profile: ForgeProfile): Promise { + if (profile?.processors?.length) { + const patcher = new ForgePatcher(this.options); + + // Forward patcher events + patcher.on('patch', (data: string) => this.emit('patch', data)); + patcher.on('error', (data: string) => 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 + */ + public async createProfile(id: string, pathInstaller: string): Promise { + // Gather all entries from the Forge installer and the vanilla JAR + const forgeFiles = await getFileFromArchive(pathInstaller); + const vanillaJar = await getFileFromArchive(this.options.loader.config.minecraftJar); + + // Combine them, excluding "META-INF" from the final jar + const mergedZip = await createZIP([...vanillaJar, ...forgeFiles], 'META-INF'); + + // Write the new combined jar to versions//.jar + const destination = path.resolve(this.options.path, 'versions', id); + if (!fs.existsSync(destination)) { + fs.mkdirSync(destination, { recursive: true }); + } + fs.writeFileSync(path.resolve(destination, `${id}.jar`), mergedZip, { mode: 0o777 }); + + // Update the version JSON + const profileData = JSON.parse(fs.readFileSync(this.options.loader.config.minecraftJson).toString()); + profileData.libraries = []; + profileData.id = id; + profileData.isOldForge = true; + profileData.jarPath = path.resolve(destination, `${id}.jar`); + + return profileData; + } +} From 24254d0d0d07a0048e0ec3e13798a5edcf2c1232 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 102/185] refactor: unify code style and doc usage --- .../loader/legacyfabric/legacyFabric.ts | 277 ++++++++++++------ 1 file changed, 193 insertions(+), 84 deletions(-) diff --git a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts index 3615df8e..6530d1ab 100644 --- a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts +++ b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts @@ -1,91 +1,200 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ +import fs from 'fs'; +import path from 'path'; +import { EventEmitter } from 'events'; +import nodeFetch from 'node-fetch'; + import { getPathLibraries } from '../../../utils/Index.js'; -import download from '../../../utils/Downloader.js'; +import Downloader from '../../../utils/Downloader.js'; -import nodeFetch from 'node-fetch' -import fs from 'fs' -import path from 'path' -import { EventEmitter } from 'events'; +/** + * Represents the "loader" part of the user's options, containing version info for Minecraft and Fabric. + */ +interface FabricLoaderConfig { + version: string; // e.g., "1.19.2" + build: string; // e.g., "latest", "recommended" or a specific build like "0.14.8" +} +/** + * Overall options passed to FabricMC. + * Adjust or extend according to your project needs. + */ +interface FabricOptions { + path: string; // Base path where libraries and files should be placed + loader: FabricLoaderConfig; // Configuration for the Fabric loader + downloadFileMultiple?: number; // Number of concurrent downloads (if your Downloader supports it) + [key: string]: any; // Allow extra fields as needed +} + +/** + * This object typically references the metadata and JSON URLs for the Fabric API, + * for example: + * { + * metaData: 'https://meta.fabricmc.net/v2/versions', + * json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' + * } + */ +interface LoaderObject { + metaData: string; // URL to fetch general Fabric metadata + json: string; // Template URL to fetch the final Fabric loader JSON +} + +/** + * Represents one library entry in the Fabric loader JSON. + */ +interface FabricLibrary { + name: string; + url: string; + rules?: Array; +} + +/** + * Represents the final JSON object fetched for the Fabric loader, + * containing an array of libraries. + */ +interface FabricJSON { + libraries: FabricLibrary[]; + [key: string]: any; // Extend or adapt based on actual structure +} + +/** + * A class that handles downloading the Fabric loader JSON metadata + * and the libraries needed to launch Fabric. + */ export default class FabricMC extends EventEmitter { - options: any; - - constructor(options = {}) { - super(); - this.options = options; - } - - async downloadJson(Loader) { - let build - let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); - - let version = metaData.game.find(version => version.version === this.options.loader.version); - let AvailableBuilds = metaData.loader.map(build => build.version); - if (!version) return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; - - if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { - build = metaData.loader[0]; - } else { - build = metaData.loader.find(loader => loader.version === this.options.loader.build); - } - - if (!build) return { error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - - let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); - let json = await nodeFetch(url).then(res => res.json()).catch(err => err); - return json - } - - async downloadLibraries(json) { - let { libraries } = json; - let downloader = new download(); - let files: any = []; - let check = 0; - let size = 0; - - for (let lib of libraries) { - if (lib.rules) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - let file = {} - let libInfo = getPathLibraries(lib.name); - let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); - let pathLibFile = path.resolve(pathLib, libInfo.name); - - if (!fs.existsSync(pathLibFile)) { - let url = `${lib.url}${libInfo.path}/${libInfo.name}` - let sizeFile = 0 - - let res: any = await downloader.checkURL(url); - if (res.status === 200) { - sizeFile = res.size; - size += res.size; - } - - file = { - url: url, - folder: pathLib, - path: `${pathLib}/${libInfo.name}`, - name: libInfo.name, - size: sizeFile - } - files.push(file); - } - this.emit('check', check++, libraries.length, 'libraries'); - } - - if (files.length > 0) { - downloader.on("progress", (DL, totDL) => { - this.emit("progress", DL, totDL, 'libraries'); - }); - - await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); - } - return libraries - } -} \ No newline at end of file + private readonly options: FabricOptions; + + constructor(options: FabricOptions = { path: '', loader: { version: '', build: '' } }) { + super(); + this.options = options; + } + + /** + * Fetches metadata from the Fabric API to identify the correct build for the given version. + * If the build is "latest" or "recommended", it picks the first entry from the loader array. + * Otherwise, it tries to match the specific build requested by the user. + * + * @param Loader A LoaderObject with metaData and json URLs for Fabric. + * @returns A FabricJSON object on success, or an error object. + */ + public async downloadJson(Loader: LoaderObject): Promise { + let selectedBuild: { version: string } | undefined; + + // Fetch overall metadata + const metaResponse = await nodeFetch(Loader.metaData); + const metaData = await metaResponse.json(); + + // Check if the requested Minecraft version is supported + const versionExists = metaData.game.find((ver: any) => ver.version === this.options.loader.version); + if (!versionExists) { + return { error: `FabricMC doesn't support Minecraft ${this.options.loader.version}` }; + } + + // Extract all possible loader builds + const availableBuilds = metaData.loader.map((b: any) => b.version); + + // If user wants the "latest" or "recommended" build, use the first in the array + if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { + selectedBuild = metaData.loader[0]; + } else { + // Otherwise, search for a matching build + selectedBuild = metaData.loader.find((loaderBuild: any) => loaderBuild.version === this.options.loader.build); + } + + if (!selectedBuild) { + return { + error: `Fabric Loader ${this.options.loader.build} not found, Available builds: ${availableBuilds.join(', ')}` + }; + } + + // Construct the final URL for fetching the Fabric JSON + const url = Loader.json + .replace('${build}', selectedBuild.version) + .replace('${version}', this.options.loader.version); + + // Fetch and parse the JSON + try { + const response = await nodeFetch(url); + const fabricJson: FabricJSON = await response.json(); + return fabricJson; + } catch (err: any) { + return { error: err.message || 'Failed to fetch or parse Fabric loader JSON' }; + } + } + + /** + * Iterates over the libraries in the Fabric JSON, checks if they exist locally, + * and if not, downloads them. Skips libraries that have "rules" (usually platform-specific). + * + * @param json The Fabric loader JSON object with a "libraries" array. + * @returns The same libraries array after downloads, or an error object if something fails. + */ + public async downloadLibraries(json: FabricJSON): Promise { + const { libraries } = json; + const downloader = new Downloader(); + let pendingDownloads: Array<{ + url: string; + folder: string; + path: string; + name: string; + size: number; + }> = []; + + let checkedCount = 0; + let totalSize = 0; + + // Evaluate each library for possible download + for (const lib of libraries) { + // Skip if library has rules that might disqualify it for this platform + if (lib.rules) { + this.emit('check', checkedCount++, libraries.length, 'libraries'); + continue; + } + + // Build the local file path + const libInfo = getPathLibraries(lib.name); + const libFolder = path.resolve(this.options.path, 'libraries', libInfo.path); + const libFilePath = path.resolve(libFolder, libInfo.name); + + // If it doesn't exist, prepare to download + if (!fs.existsSync(libFilePath)) { + const libUrl = `${lib.url}${libInfo.path}/${libInfo.name}`; + + let fileSize = 0; + // Check if the file is available and get its size + const checkRes = await downloader.checkURL(libUrl); + if (checkRes && typeof checkRes === 'object' && 'status' in checkRes && checkRes.status === 200) { + fileSize = checkRes.size; + totalSize += fileSize; + } + + pendingDownloads.push({ + url: libUrl, + folder: libFolder, + path: libFilePath, + name: libInfo.name, + size: fileSize + }); + } + + this.emit('check', checkedCount++, libraries.length, 'libraries'); + } + + // Download all missing libraries in bulk + if (pendingDownloads.length > 0) { + downloader.on('progress', (downloaded: number, total: number) => { + this.emit('progress', downloaded, total, 'libraries'); + }); + + await downloader.downloadFileMultiple(pendingDownloads, totalSize, this.options.downloadFileMultiple); + } + + return libraries; + } +} From 4e628d2f0fade61b6b66708adfca7fbb3b4d0feb Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 103/185] refactor: enhance NeoForge installation and patch flow --- .../loader/neoForge/neoForge.ts | 628 +++++++++++------- 1 file changed, 401 insertions(+), 227 deletions(-) diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 5758190b..d3708e38 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -1,234 +1,408 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ +import fs from 'fs'; +import path from 'path'; +import nodeFetch from 'node-fetch'; +import { EventEmitter } from 'events'; + import { getPathLibraries, mirrors, getFileFromArchive } from '../../../utils/Index.js'; -import download from '../../../utils/Downloader.js'; -import neoForgePatcher from '../../patcher.js' +import Downloader from '../../../utils/Downloader.js'; +import NeoForgePatcher, { Profile } from '../../patcher.js'; -import nodeFetch from 'node-fetch' -import fs from 'fs' -import path from 'path' -import { EventEmitter } from 'events'; +/** + * Options passed to NeoForgeMC, including paths, loader configs, etc. + * Adjust according to your application's specifics. + */ +interface NeoForgeOptions { + path: string; // Base path where files will be placed or read from + loader: { + version: string; // Minecraft version (e.g. "1.19.2") + build: string; // Build type ("latest", "recommended", or a numeric version) + config: { + javaPath: string; // Path to the Java executable for patching + minecraftJar: string; // Path to the vanilla Minecraft .jar + minecraftJson: string; // Path to the corresponding .json version file + }; + type: string; // Type of loader + }; + downloadFileMultiple?: number; // Number of concurrent downloads + [key: string]: any; // Allow extra fields as necessary +} + +/** + * A structure to describe the loader object with metadata, legacy vs. new API, etc. + * For example: + * { + * legacyMetaData: 'https://.../legacyMetadata.json', + * metaData: 'https://.../metadata.json', + * legacyInstall: 'https://.../NeoForge-${version}.jar', + * install: 'https://.../NeoForge-${version}.jar' + * } + */ +interface LoaderObject { + legacyMetaData: string; + metaData: string; + legacyInstall: string; + install: string; +} +/** + * Represents the result of downloading the NeoForge installer, or an error. + */ +interface DownloadInstallerResult { + filePath?: string; // Path to the downloaded jar + oldAPI?: boolean; // Indicates whether the legacy API was used + error?: string; // Error message if something went wrong +} + +/** + * Represents the structure of a NeoForge install_profile.json + * after extraction from the installer jar. + */ +interface NeoForgeProfile extends Profile { + install?: { + libraries?: any[]; + [key: string]: any; + }; + version?: { + libraries?: any[]; + [key: string]: any; + }; + filePath?: string; + path?: string; + [key: string]: any; +} + +/** + * This class handles downloading and installing NeoForge (formerly Forge) for Minecraft, + * including picking the correct build, extracting libraries, and running patchers if needed. + */ export default class NeoForgeMC extends EventEmitter { - options: any; - - constructor(options = {}) { - super(); - this.options = options; - } - - async downloadInstaller(Loader: any) { - let build: string; - let neoForgeURL: string; - let oldAPI: boolean = true; - let legacyMetaData = await nodeFetch(Loader.legacyMetaData).then(res => res.json()); - let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); - - let versions = legacyMetaData.versions.filter(version => version.includes(`${this.options.loader.version}-`)); - - if (!versions.length) { - let minecraftVersion = `${this.options.loader.version.split('.')[1]}.${this.options.loader.version.split('.')[2] || 0}`; - versions = metaData.versions.filter(version => version.startsWith(minecraftVersion)); - oldAPI = false; - } - - if (!versions.length) return { error: `NeoForge doesn't support Minecraft ${this.options.loader.version}` }; - - if (this.options.loader.build === 'latest' || this.options.loader.build === 'recommended') { - build = versions[versions.length - 1]; - } else build = versions.find(loader => loader === this.options.loader.build); - - if (!build) return { error: `NeoForge Loader ${this.options.loader.build} not found, Available builds: ${versions.join(', ')}` }; - - if (oldAPI) neoForgeURL = Loader.legacyInstall.replaceAll(/\${version}/g, build); - else neoForgeURL = Loader.install.replaceAll(/\${version}/g, build); - - let pathFolder = path.resolve(this.options.path, 'neoForge'); - let filePath = path.resolve(pathFolder, `neoForge-${build}-installer.jar`); - - if (!fs.existsSync(filePath)) { - if (!fs.existsSync(pathFolder)) fs.mkdirSync(pathFolder, { recursive: true }); - let downloadForge = new download(); - - downloadForge.on('progress', (downloaded, size) => { - this.emit('progress', downloaded, size, `neoForge-${build}-installer.jar`); - }); - - await downloadForge.downloadFile(neoForgeURL, pathFolder, `neoForge-${build}-installer.jar`); - } - - return { filePath, oldAPI }; - } - - async extractProfile(pathInstaller: any) { - let neoForgeJSON: any = {} - - let file: any = await getFileFromArchive(pathInstaller, 'install_profile.json') - let neoForgeJsonOrigin = JSON.parse(file); - - if (!neoForgeJsonOrigin) return { error: { message: 'Invalid neoForge installer' } }; - if (neoForgeJsonOrigin.install) { - neoForgeJSON.install = neoForgeJsonOrigin.install; - neoForgeJSON.version = neoForgeJsonOrigin.versionInfo; - } else { - neoForgeJSON.install = neoForgeJsonOrigin; - let file: any = await getFileFromArchive(pathInstaller, path.basename(neoForgeJSON.install.json)) - neoForgeJSON.version = JSON.parse(file); - } - - return neoForgeJSON; - } - - async extractUniversalJar(profile: any, pathInstaller: any, oldAPI: any) { - let skipneoForgeFilter = true - - if (profile.filePath) { - let fileInfo = getPathLibraries(profile.path) - this.emit('extract', `Extracting ${fileInfo.name}...`); - - let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) - if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - - let file: any = await getFileFromArchive(pathInstaller, profile.filePath) - fs.writeFileSync(`${pathFileDest}/${fileInfo.name}`, file, { mode: 0o777 }) - } else if (profile.path) { - let fileInfo = getPathLibraries(profile.path) - let listFile: any = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`) - - await Promise.all( - listFile.map(async (files: any) => { - let fileName = files.split('/') - this.emit('extract', `Extracting ${fileName[fileName.length - 1]}...`); - let file: any = await getFileFromArchive(pathInstaller, files) - let pathFileDest = path.resolve(this.options.path, 'libraries', fileInfo.path) - if (!fs.existsSync(pathFileDest)) fs.mkdirSync(pathFileDest, { recursive: true }); - fs.writeFileSync(`${pathFileDest}/${fileName[fileName.length - 1]}`, file, { mode: 0o777 }) - }) - ); - } else { - skipneoForgeFilter = false - } - - if (profile.processors?.length) { - let universalPath = profile.libraries.find(v => { - return (v.name || '').startsWith(oldAPI ? 'net.neoforged:forge' : 'net.neoforged:neoforge') - }) - - let client: any = await getFileFromArchive(pathInstaller, 'data/client.lzma'); - let fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma') - let pathFile = path.resolve(this.options.path, 'libraries', fileInfo.path) - - if (!fs.existsSync(pathFile)) fs.mkdirSync(pathFile, { recursive: true }); - fs.writeFileSync(`${pathFile}/${fileInfo.name}`, client, { mode: 0o777 }) - this.emit('extract', `Extracting ${fileInfo.name}...`); - } - - return skipneoForgeFilter - } - - async downloadLibraries(profile: any, skipneoForgeFilter: any) { - let { libraries } = profile.version; - let downloader = new download(); - let check = 0; - let files: any = []; - let size = 0; - - if (profile.install.libraries) libraries = libraries.concat(profile.install.libraries); - - libraries = libraries.filter((library, index, self) => index === self.findIndex(t => t.name === library.name)) - - let skipneoForge = [ - 'net.minecraftforge:neoforged:', - 'net.minecraftforge:minecraftforge:' - ] - - for (let lib of libraries) { - if (skipneoForgeFilter && skipneoForge.find(libs => lib.name.includes(libs))) { - if (lib.downloads?.artifact?.url == "" || !lib.downloads?.artifact?.url) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - } - if (lib.rules) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - let file = {} - let libInfo = getPathLibraries(lib.name); - let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); - let pathLibFile = path.resolve(pathLib, libInfo.name); - - if (!fs.existsSync(pathLibFile)) { - let url - let sizeFile = 0 - - let baseURL = `${libInfo.path}/${libInfo.name}`; - let response: any = await downloader.checkMirror(baseURL, mirrors) - - if (response?.status === 200) { - size += response.size; - sizeFile = response.size; - url = response.url; - } else if (lib.downloads?.artifact) { - url = lib.downloads.artifact.url - size += lib.downloads.artifact.size; - sizeFile = lib.downloads.artifact.size; - } else { - url = null - } - - if (url == null || !url) { - return { error: `Impossible to download ${libInfo.name}` }; - } - - file = { - url: url, - folder: pathLib, - path: `${pathLib}/${libInfo.name}`, - name: libInfo.name, - size: sizeFile - } - files.push(file); - } - this.emit('check', check++, libraries.length, 'libraries'); - } - - if (files.length > 0) { - downloader.on("progress", (DL, totDL) => { - this.emit("progress", DL, totDL, 'libraries'); - }); - - await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); - } - return libraries - } - - async patchneoForge(profile: any, oldAPI: any) { - if (profile?.processors?.length) { - let patcher: any = new neoForgePatcher(this.options); - let config: any = {} - - patcher.on('patch', data => { - this.emit('patch', data); - }); - - patcher.on('error', data => { - this.emit('error', data); - }); - - if (!patcher.check(profile)) { - 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 - } -} \ No newline at end of file + private readonly options: NeoForgeOptions; + + constructor(options: NeoForgeOptions) { + 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. + */ + public async downloadInstaller(Loader: LoaderObject): Promise { + let build: string | undefined; + let neoForgeURL: string; + let oldAPI = true; + + // Fetch versions from the legacy API + const legacyMetaData = await nodeFetch(Loader.legacyMetaData).then(res => res.json()); + const metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + + // Filter versions for the specified Minecraft version + let versions: string[] = legacyMetaData.versions.filter((v: string) => + 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: string) => 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.resolve(this.options.path, 'neoForge'); + const installerFilePath = path.resolve(neoForgeFolder, `neoForge-${build}-installer.jar`); + + if (!fs.existsSync(installerFilePath)) { + if (!fs.existsSync(neoForgeFolder)) { + fs.mkdirSync(neoForgeFolder, { recursive: true }); + } + const downloader = new Downloader(); + downloader.on('progress', (downloaded: number, size: number) => { + 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. + */ + public async extractProfile(pathInstaller: string): Promise { + const fileContent = await 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: NeoForgeProfile = { data: {} }; + if (neoForgeJsonOrigin.install) { + result.install = neoForgeJsonOrigin.install; + result.version = neoForgeJsonOrigin.versionInfo; + } else { + result.install = neoForgeJsonOrigin; + const extraFile = await getFileFromArchive(pathInstaller, path.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 + */ + public async extractUniversalJar(profile: NeoForgeProfile, pathInstaller: string, oldAPI: boolean): Promise { + let skipNeoForgeFilter = true; + + if (profile.filePath) { + const fileInfo = getPathLibraries(profile.path); + this.emit('extract', `Extracting ${fileInfo.name}...`); + + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + + const archiveContent = await getFileFromArchive(pathInstaller, profile.filePath); + if (archiveContent) { + fs.writeFileSync(path.join(destFolder, fileInfo.name), archiveContent, { mode: 0o777 }); + } + } else if (profile.path) { + const fileInfo = getPathLibraries(profile.path); + const filesInArchive = await getFileFromArchive(pathInstaller, null, `maven/${fileInfo.path}`); + if (filesInArchive && Array.isArray(filesInArchive)) { + for (const file of filesInArchive) { + const fileName = path.basename(file); + this.emit('extract', `Extracting ${fileName}...`); + + const content = await getFileFromArchive(pathInstaller, file); + if (!content) continue; + + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + fs.writeFileSync(path.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 getFileFromArchive(pathInstaller, 'data/client.lzma'); + if (clientData) { + const fileInfo = getPathLibraries(profile.path || universalPath.name, '-clientdata', '.lzma'); + const destFolder = path.resolve(this.options.path, 'libraries', fileInfo.path); + + if (!fs.existsSync(destFolder)) { + fs.mkdirSync(destFolder, { recursive: true }); + } + fs.writeFileSync(path.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 + */ + public async downloadLibraries(profile: NeoForgeProfile, skipNeoForgeFilter: boolean): Promise { + let libraries = profile.version?.libraries || []; + const dl = new Downloader(); + let checkCount = 0; + const pendingFiles: Array<{ + url: string; + folder: string; + path: string; + name: string; + size: number; + }> = []; + 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 = getPathLibraries(lib.name); + const libFolder = path.resolve(this.options.path, 'libraries', libInfo.path); + const libFilePath = path.resolve(libFolder, libInfo.name); + + // If it doesn't exist locally, schedule for download + if (!fs.existsSync(libFilePath)) { + let finalURL: string | null = null; + let fileSize = 0; + + // Attempt to resolve via mirror first + const baseURL = `${libInfo.path}/${libInfo.name}`; + const mirrorCheck = await dl.checkMirror(baseURL, 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: number, totDL: number) => { + 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. + */ + public async patchneoForge(profile: NeoForgeProfile, oldAPI: boolean): Promise { + if (profile?.processors?.length) { + const patcher = new NeoForgePatcher(this.options); + + // Relay events + patcher.on('patch', (data: string) => { + this.emit('patch', data); + }); + patcher.on('error', (error: string) => { + 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; + } +} From e7a7496cb6d9733c34c3f28a5bd34f2b71b7bf79 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 104/185] refactor: unify code style and doc usage --- src/Minecraft-Loader/loader/quilt/quilt.ts | 290 ++++++++++++++------- 1 file changed, 203 insertions(+), 87 deletions(-) diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts index 10b9a581..be519ce8 100644 --- a/src/Minecraft-Loader/loader/quilt/quilt.ts +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -1,94 +1,210 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ +import fs from 'fs'; +import path from 'path'; +import { EventEmitter } from 'events'; +import nodeFetch from 'node-fetch'; + import { getPathLibraries } from '../../../utils/Index.js'; -import download from '../../../utils/Downloader.js'; +import Downloader from '../../../utils/Downloader.js'; -import nodeFetch from 'node-fetch' -import fs from 'fs' -import path from 'path' -import { EventEmitter } from 'events'; +/** + * Represents the Quilt loader configuration within the user's options. + */ +interface QuiltLoaderConfig { + version: string; // e.g., "1.19.2" + build: string; // e.g., "latest", "recommended", or a specific build ID +} +/** + * The main options object passed to the Quilt class. + * You can extend this as needed by your application. + */ +interface QuiltOptions { + path: string; // Base path for storing downloaded libraries, etc. + loader: QuiltLoaderConfig; // Loader configuration for Quilt + downloadFileMultiple?: number; // Number of concurrent downloads + [key: string]: any; // Allow additional fields as needed +} + +/** + * Describes the data needed for fetching Quilt metadata and loader JSON. + * For example: + * { + * metaData: "https://meta.quiltmc.org/v3/versions", + * json: "https://meta.quiltmc.org/v3/versions/loader/${version}/${build}/profile/json" + * } + */ +interface LoaderObject { + metaData: string; + json: string; // URL pattern with placeholders like ${version} and ${build} +} + +/** + * A structure for one library entry in the Quilt loader JSON. + */ +interface QuiltLibrary { + name: string; + url: string; + rules?: Array; +} + +/** + * The JSON object typically returned by the Quilt API, + * containing an array of libraries and possibly other fields. + */ +interface QuiltJSON { + libraries: QuiltLibrary[]; + [key: string]: any; +} + +/** + * This class handles fetching the Quilt loader metadata, + * identifying the appropriate build for a given Minecraft version, + * and downloading required libraries. + */ export default class Quilt extends EventEmitter { - options: any; - versionMinecraft: any; - - constructor(options = {}) { - super(); - this.options = options; - } - - async downloadJson(Loader: any) { - let build: any - let metaData = await nodeFetch(Loader.metaData).then(res => res.json()); - - let version = metaData.game.find(version => version.version === this.options.loader.version); - let AvailableBuilds = metaData.loader.map(build => build.version); - if (!version) return { error: `QuiltMC doesn't support Minecraft ${this.options.loader.version}` }; - - if (this.options.loader.build === 'latest') { - build = metaData.loader[0]; - } else if (this.options.loader.build === 'recommended') { - build = metaData.loader.find(build => !build.version.includes('beta')); - } else { - build = metaData.loader.find(loader => loader.version === this.options.loader.build); - } - - if (!build) return { error: `QuiltMC Loader ${this.options.loader.build} not found, Available builds: ${AvailableBuilds.join(', ')}` }; - - let url = Loader.json.replace('${build}', build.version).replace('${version}', this.options.loader.version); - let json = await nodeFetch(url).then(res => res.json()).catch(err => err); - return json - } - - async downloadLibraries(json) { - let { libraries } = json; - let downloader = new download(); - let files: any = []; - let check = 0; - let size = 0; - - for (let lib of libraries) { - if (lib.rules) { - this.emit('check', check++, libraries.length, 'libraries'); - continue; - } - let file = {} - let libInfo = getPathLibraries(lib.name); - let pathLib = path.resolve(this.options.path, 'libraries', libInfo.path); - let pathLibFile = path.resolve(pathLib, libInfo.name); - - if (!fs.existsSync(pathLibFile)) { - let url = `${lib.url}${libInfo.path}/${libInfo.name}` - let sizeFile = 0 - - let res: any = await downloader.checkURL(url); - if (res.status === 200) { - sizeFile = res.size; - size += res.size; - } - - file = { - url: url, - folder: pathLib, - path: `${pathLib}/${libInfo.name}`, - name: libInfo.name, - size: sizeFile - } - files.push(file); - } - this.emit('check', check++, libraries.length, 'libraries'); - } - - if (files.length > 0) { - downloader.on("progress", (DL: any, totDL: any) => { - this.emit("progress", DL, totDL, 'libraries'); - }); - - await downloader.downloadFileMultiple(files, size, this.options.downloadFileMultiple); - } - return libraries - } -} \ No newline at end of file + private readonly options: QuiltOptions; + private versionMinecraft: string | undefined; + + constructor(options: QuiltOptions = { path: '', loader: { version: '', build: '' } }) { + super(); + this.options = options; + } + + /** + * Fetches the Quilt loader metadata to identify the correct build for the specified + * Minecraft version. If "latest" or "recommended" is requested, picks the most + * recent or stable build accordingly. + * + * @param Loader An object describing where to fetch Quilt metadata and JSON. + * @returns A QuiltJSON object on success, or an error object if something fails. + */ + public async downloadJson(Loader: LoaderObject): Promise { + let selectedBuild: any; + + // Fetch the metadata + const metaResponse = await nodeFetch(Loader.metaData); + const metaData = await metaResponse.json(); + + // Check if the requested Minecraft version is supported + const mcVersionExists = metaData.game.find((ver: any) => ver.version === this.options.loader.version); + if (!mcVersionExists) { + return { error: `QuiltMC doesn't support Minecraft ${this.options.loader.version}` }; + } + + // Gather all available builds for this version + const availableBuilds = metaData.loader.map((b: any) => b.version); + + // Determine which build to use + if (this.options.loader.build === 'latest') { + selectedBuild = metaData.loader[0]; + } else if (this.options.loader.build === 'recommended') { + // Attempt to find a build that isn't labeled "beta" + selectedBuild = metaData.loader.find((b: any) => !b.version.includes('beta')); + } else { + // Otherwise, match a specific build + selectedBuild = metaData.loader.find( + (loaderItem: any) => loaderItem.version === this.options.loader.build + ); + } + + if (!selectedBuild) { + return { + error: `QuiltMC Loader ${this.options.loader.build} not found, Available builds: ${availableBuilds.join(', ')}` + }; + } + + // Build the URL for the Quilt loader profile JSON + const url = Loader.json + .replace('${build}', selectedBuild.version) + .replace('${version}', this.options.loader.version); + + // Fetch the JSON profile + try { + const response = await nodeFetch(url); + const quiltJson: QuiltJSON = await response.json(); + return quiltJson; + } catch (err: any) { + return { error: err.message || 'Failed to fetch or parse Quilt loader JSON' }; + } + } + + /** + * Parses the Quilt JSON to determine which libraries need downloading, skipping + * any that already exist or that are disqualified by "rules". Downloads them + * in bulk using the Downloader utility. + * + * @param quiltJson A QuiltJSON object containing a list of libraries. + * @returns The final list of libraries, or an error if something fails. + */ + public async downloadLibraries(quiltJson: QuiltJSON): Promise { + const { libraries } = quiltJson; + const downloader = new Downloader(); + + let filesToDownload: Array<{ + url: string; + folder: string; + path: string; + name: string; + size: number; + }> = []; + + let checkedLibraries = 0; + let totalSize = 0; + + for (const lib of libraries) { + // If rules exist, skip it (likely platform-specific logic) + if (lib.rules) { + this.emit('check', checkedLibraries++, libraries.length, 'libraries'); + continue; + } + + // Construct the local path where this library should reside + const libInfo = getPathLibraries(lib.name); + const libFolder = path.resolve(this.options.path, 'libraries', libInfo.path); + const libFilePath = path.resolve(libFolder, libInfo.name); + + // If the library doesn't exist locally, prepare to download + if (!fs.existsSync(libFilePath)) { + const libUrl = `${lib.url}${libInfo.path}/${libInfo.name}`; + + let fileSize = 0; + const checkResult = await downloader.checkURL(libUrl); + + if (checkResult && checkResult.status === 200) { + fileSize = checkResult.size; + totalSize += fileSize; + } + + filesToDownload.push({ + url: libUrl, + folder: libFolder, + path: libFilePath, + name: libInfo.name, + size: fileSize + }); + } + + + // Emit a "check" event for each library + this.emit('check', checkedLibraries++, libraries.length, 'libraries'); + } + + // If there are libraries to download, proceed with the bulk download + if (filesToDownload.length > 0) { + downloader.on('progress', (downloaded: number, total: number) => { + this.emit('progress', downloaded, total, 'libraries'); + }); + + await downloader.downloadFileMultiple(filesToDownload, totalSize, this.options.downloadFileMultiple); + } + + return libraries; + } +} From cad5f99deaae6b72722a60ba42f826b500add73d Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 105/185] refactor: reorganize patch logic and add TypeScript-style documentation --- src/Minecraft-Loader/patcher.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index cae284c3..8196106f 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -1,3 +1,10 @@ +/** + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis + */ + import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; @@ -7,7 +14,7 @@ import { getPathLibraries, getFileFromArchive } from '../utils/Index.js'; interface ForgePatcherOptions { path: string; loader: { - type: 'forge' | 'neoforge'; + type: string; }; } @@ -29,10 +36,10 @@ interface Processor { sides?: string[]; } -interface Profile { +export interface Profile { data: Record; - libraries: Array<{ name: string }>; - processors: Record; + processors?: any[]; + libraries?: Array<{ name?: string }>; // The universal jar/libraries reference path?: string; } From 46088897c62068421a9d428747780982b115c138 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 106/185] refactor: Minecraft: add typed definitions and improve doc strings --- src/Minecraft/Minecraft-Arguments.ts | 502 ++++++++++++++++++--------- 1 file changed, 332 insertions(+), 170 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 3c108710..c375452f 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -1,179 +1,341 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import fs from 'fs'; -import os from 'os' - +import os from 'os'; import { getPathLibraries, isold } from '../utils/Index.js'; -let MojangLib = { win32: "windows", darwin: "osx", linux: "linux" }; +/** + * Maps the Node.js process.platform values to Mojang's library folders. + */ +const MOJANG_LIBRARY_MAP: Record = { + win32: 'windows', + darwin: 'osx', + linux: 'linux' +}; + +/** + * Represents options for memory usage, screen size, extra args, etc. + * Adapt or expand as needed for your use case. + */ +export interface LaunchOptions { + path: string; // Base path to Minecraft data folder + instance?: string; // Instance name (if using multi-instance approach) + authenticator: any; // Auth object containing tokens, user info, etc. + memory: { + min?: string; // Minimum memory (e.g. "512M", "1G") + max?: string; // Maximum memory (e.g. "4G", "8G") + }; + screen?: { + width?: number; + height?: number; + }; + GAME_ARGS: Array; // Additional arguments passed to the game + JVM_ARGS: Array; // Additional arguments passed to the JVM + mcp?: string; // MCP config path (for modded usage) +} + +/** + * Represents the data structure of a Minecraft version JSON file (simplified). + * Adapt this interface if your JSON includes more properties. + */ +export interface VersionJSON { + id: string; + type: string; + assetIndex: { + id: string; + }; + assets?: string; // Name of the assets index + mainClass?: string; + minecraftArguments?: string; // Legacy format for older MC versions + arguments?: { + game?: Array; + jvm?: Array; + }; + libraries?: Array; // List of library dependencies + nativesList?: Array; +} + +/** + * Represents a loader JSON structure (e.g. Forge or Fabric). + * Again, adapt as your loader's actual structure requires. + */ +export interface LoaderJSON { + id?: string; + mainClass?: string; + libraries?: Array; + minecraftArguments?: string; + isOldForge?: boolean; + jarPath?: string; +} + +/** + * Data structure returned by the class, detailing arguments + * for launching Minecraft (game args, JVM args, classpath, etc.). + */ +export interface LaunchArguments { + game: Array; + jvm: Array; + classpath: Array; + mainClass?: string; +} +/** + * Builds and organizes JVM and game arguments required to launch Minecraft, + * including optional loader (e.g., Forge) arguments. + */ export default class MinecraftArguments { - options: any; - authenticator: any; - constructor(options: any) { - this.options = options; - this.authenticator = options.authenticator; - } - - async GetArguments(json: any, loaderJson: any) { - let game = await this.GetGameArguments(json, loaderJson); - let jvm = await this.GetJVMArguments(json); - let classpath = await this.GetClassPath(json, loaderJson); - - return { - game: game, - jvm: jvm, - classpath: classpath.classpath, - mainClass: classpath.mainClass - } - } - - async GetGameArguments(json: any, loaderJson: any) { - let game = json.minecraftArguments ? json.minecraftArguments.split(' ') : json.arguments.game; - let userType: String - - if (loaderJson) { - let gameLoader = loaderJson.minecraftArguments ? loaderJson.minecraftArguments.split(' ') : []; - game = game.concat(gameLoader); - game = game.filter((item: String, index: Number, self: any) => index === self.findIndex((res: String) => res == item)) - } - - if (json.id.startsWith('1.16')) userType = 'Xbox' - else userType = this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type - - let table = { - '${auth_access_token}': this.authenticator.access_token, - '${auth_session}': this.authenticator.access_token, - '${auth_player_name}': this.authenticator.name, - '${auth_uuid}': this.authenticator.uuid, - '${auth_xuid}': this.authenticator?.xboxAccount?.xuid || this.authenticator.access_token, - '${user_properties}': this.authenticator.user_properties, - '${user_type}': userType, - '${version_name}': loaderJson ? loaderJson.id : json.id, - '${assets_index_name}': json.assetIndex.id, - '${game_directory}': this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path, - '${assets_root}': isold(json) ? `${this.options.path}/resources` : `${this.options.path}/assets`, - '${game_assets}': isold(json) ? `${this.options.path}/resources` : `${this.options.path}/assets`, - '${version_type}': json.type, - '${clientid}': this.authenticator.clientId || (this.authenticator.client_token || this.authenticator.access_token) - } - - for (let i in game) { - if (typeof game[i] == 'object') game.splice(i, 2) - if (Object.keys(table).includes(game[i])) game[i] = table[game[i]] - } - - if (this.options.screen) { - if (this.options.screen.width !== null && this.options.screen.height !== null) { - game.push('--width') - game.push(this.options.screen.width) - game.push('--height') - game.push(this.options.screen.height) - } - } - - game.push(...this.options.GAME_ARGS) - - return game.filter((item: any) => typeof item !== 'object') - } - - async GetJVMArguments(json: any) { - let opts = { - win32: '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump', - darwin: '-XstartOnFirstThread', - linux: '-Xss1M' - } - let jvm = [ - `-Xms${this.options.memory.min}`, - `-Xmx${this.options.memory.max}`, - '-XX:+UnlockExperimentalVMOptions', - '-XX:G1NewSizePercent=20', - '-XX:G1ReservePercent=20', - '-XX:MaxGCPauseMillis=50', - '-XX:G1HeapRegionSize=32M', - '-Dfml.ignoreInvalidMinecraftCertificates=true', - `-Djna.tmpdir=${this.options.path}/versions/${json.id}/natives`, - `-Dorg.lwjgl.system.SharedLibraryExtractPath=${this.options.path}/versions/${json.id}/natives`, - `-Dio.netty.native.workdir=${this.options.path}/versions/${json.id}/natives` - ] - - if (!json.minecraftArguments) { - jvm.push(opts[process.platform]) - } - - if (json.nativesList) { - jvm.push(`-Djava.library.path=${this.options.path}/versions/${json.id}/natives`) - } - - if (os.platform() == "darwin") { - let pathAssets = `${this.options.path}/assets/indexes/${json.assets}.json`; - let assets = JSON.parse(fs.readFileSync(pathAssets, 'utf-8')); - let icon = assets.objects['icons/minecraft.icns'].hash - - jvm.push(`-Xdock:name=Minecraft`) - jvm.push(`-Xdock:icon=${this.options.path}/assets/objects/${icon.substring(0, 2)}/${icon}`) - } - jvm.push(...this.options.JVM_ARGS) - - return jvm; - } - - async GetClassPath(json: any, loaderJson: any) { - let librariesList: string[] = [] - let classPath: string[] = [] - let libraries: any = json.libraries; - - if (loaderJson?.libraries) libraries = loaderJson.libraries.concat(libraries); - libraries = libraries.filter((library: any, index: any, self: any) => index === self.findIndex((res: any) => res.name === library.name)); - - for (let lib of libraries) { - if (lib.loader && lib.name.startsWith('org.apache.logging.log4j:log4j-slf4j2-impl')) continue; - - if (lib.natives) { - let native = lib.natives[MojangLib[process.platform]]; - if (!native) native = lib.natives[process.platform]; - if (!native) continue; - } else { - if (lib.rules && lib.rules[0].os) { - if (lib.rules[0].os.name !== MojangLib[process.platform]) continue; - } - } - - let path = getPathLibraries(lib.name); - if (lib.loader) { - librariesList.push(`${lib.loader}/libraries/${path.path}/${path.name}`); - } else { - librariesList.push(`${this.options.path}/libraries/${path.path}/${path.name}`); - } - } - - if (loaderJson?.isOldForge) { - librariesList.push(loaderJson?.jarPath); - } else if (this.options.mcp) { - librariesList.push(this.options.mcp); - } else { - librariesList.push(`${this.options.path}/versions/${json.id}/${json.id}.jar`); - } - - - classPath = librariesList.filter((path: string) => { - let lib = path.split('/').pop(); - if (lib && !classPath.includes(lib)) { - classPath.push(lib); - return true; - } - return false; - }); - - return { - classpath: [ - `-cp`, - classPath.join(process.platform === 'win32' ? ';' : ':'), - ], - mainClass: loaderJson ? loaderJson.mainClass : json.mainClass - } - } + private options: LaunchOptions; + private authenticator: any; + + constructor(options: LaunchOptions) { + this.options = options; + this.authenticator = options.authenticator; + } + + /** + * Gathers all arguments (game, JVM, classpath) and returns them for launching. + * @param versionJson The Minecraft version JSON. + * @param loaderJson An optional loader JSON (Forge, Fabric, etc.). + */ + public async GetArguments(versionJson: VersionJSON, loaderJson?: LoaderJSON): Promise { + const gameArguments = await this.GetGameArguments(versionJson, loaderJson); + const jvmArguments = await this.GetJVMArguments(versionJson); + const classpathData = await this.GetClassPath(versionJson, loaderJson); + + return { + game: gameArguments, + jvm: jvmArguments, + classpath: classpathData.classpath, + mainClass: classpathData.mainClass + }; + } + + /** + * Builds the Minecraft game arguments, injecting authentication tokens, + * user info, and any loader arguments if present. + * @param versionJson The Minecraft version JSON. + * @param loaderJson The loader JSON (e.g., Forge) if applicable. + */ + public async GetGameArguments(versionJson: VersionJSON, loaderJson?: LoaderJSON): Promise> { + // For older MC versions, arguments may be in `minecraftArguments` instead of `arguments.game` + let gameArgs = versionJson.minecraftArguments + ? versionJson.minecraftArguments.split(' ') + : versionJson.arguments?.game ?? []; + + // Merge loader's Minecraft arguments if provided + if (loaderJson) { + const loaderGameArgs = loaderJson.minecraftArguments ? loaderJson.minecraftArguments.split(' ') : []; + gameArgs = gameArgs.concat(loaderGameArgs); + // Remove duplicate arguments + gameArgs = gameArgs.filter( + (item, index, self) => index === self.findIndex(arg => arg === item) + ); + } + + // Determine user type (e.g. 'msa' or 'Xbox') depending on version and authenticator + let userType = 'msa'; + if (versionJson.id.startsWith('1.16')) { + userType = 'Xbox'; + } else { + userType = this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type; + } + + // Map of placeholders to actual values + const placeholderMap: Record = { + '${auth_access_token}': this.authenticator.access_token, + '${auth_session}': this.authenticator.access_token, + '${auth_player_name}': this.authenticator.name, + '${auth_uuid}': this.authenticator.uuid, + '${auth_xuid}': this.authenticator?.xboxAccount?.xuid || this.authenticator.access_token, + '${user_properties}': this.authenticator.user_properties, + '${user_type}': userType, + '${version_name}': loaderJson ? loaderJson.id || versionJson.id : versionJson.id, + '${assets_index_name}': versionJson.assetIndex.id, + '${game_directory}': this.options.instance + ? `${this.options.path}/instances/${this.options.instance}` + : this.options.path, + '${assets_root}': isold(versionJson) + ? `${this.options.path}/resources` + : `${this.options.path}/assets`, + '${game_assets}': isold(versionJson) + ? `${this.options.path}/resources` + : `${this.options.path}/assets`, + '${version_type}': versionJson.type, + '${clientid}': this.authenticator.clientId + || this.authenticator.client_token + || this.authenticator.access_token + }; + + // Replace placeholders in the game arguments + for (let i = 0; i < gameArgs.length; i++) { + if (typeof gameArgs[i] === 'object') { + // If it's an unexpected object, remove it + gameArgs.splice(i, 1); + i--; + continue; + } + if (placeholderMap[gameArgs[i]]) { + gameArgs[i] = placeholderMap[gameArgs[i]]; + } + } + + // If screen options are provided, add them + if (this.options.screen) { + const { width, height } = this.options.screen; + if (width && height) { + gameArgs.push('--width', String(width), '--height', String(height)); + } + } + + // Add any extra game arguments from user config + gameArgs.push(...this.options.GAME_ARGS); + + // Filter out any remaining unexpected objects + return gameArgs.filter(item => typeof item === 'string'); + } + + /** + * Builds the JVM arguments needed by Minecraft. This includes memory settings, + * OS-specific options, and any additional arguments supplied by the user. + * @param versionJson The Minecraft version JSON. + */ + public async GetJVMArguments(versionJson: VersionJSON): Promise> { + // Some OS-specific defaults + const osSpecificOpts: Record = { + win32: '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump', + darwin: '-XstartOnFirstThread', + linux: '-Xss1M' + }; + + // Core JVM arguments + const jvmArgs: Array = [ + `-Xms${this.options.memory.min}`, + `-Xmx${this.options.memory.max}`, + '-XX:+UnlockExperimentalVMOptions', + '-XX:G1NewSizePercent=20', + '-XX:G1ReservePercent=20', + '-XX:MaxGCPauseMillis=50', + '-XX:G1HeapRegionSize=32M', + '-Dfml.ignoreInvalidMinecraftCertificates=true', + `-Djna.tmpdir=${this.options.path}/versions/${versionJson.id}/natives`, + `-Dorg.lwjgl.system.SharedLibraryExtractPath=${this.options.path}/versions/${versionJson.id}/natives`, + `-Dio.netty.native.workdir=${this.options.path}/versions/${versionJson.id}/natives` + ]; + + // For newer MC versions that use "arguments.game" instead of "minecraftArguments", + // we add OS-specific arguments (e.g., Mac uses -XstartOnFirstThread). + if (!versionJson.minecraftArguments) { + const opt = osSpecificOpts[process.platform]; + if (opt) { + jvmArgs.push(opt); + } + } + + // If natives are specified, add the native library path + if (versionJson.nativesList) { + jvmArgs.push(`-Djava.library.path=${this.options.path}/versions/${versionJson.id}/natives`); + } + + // Special handling for macOS (setting dock icon) + if (os.platform() === 'darwin') { + const assetsPath = `${this.options.path}/assets/indexes/${versionJson.assets}.json`; + const assetsContent = fs.readFileSync(assetsPath, 'utf-8'); + const assetsJson = JSON.parse(assetsContent); + + // Retrieve the hash of the minecraft.icns file + const iconHash = assetsJson.objects['icons/minecraft.icns']?.hash; + if (iconHash) { + jvmArgs.push('-Xdock:name=Minecraft'); + jvmArgs.push(`-Xdock:icon=${this.options.path}/assets/objects/${iconHash.substring(0, 2)}/${iconHash}`); + } + } + + // Append any user-supplied JVM arguments + jvmArgs.push(...this.options.JVM_ARGS); + + return jvmArgs; + } + + /** + * Constructs the classpath (including libraries) that Minecraft requires + * to launch, and identifies the main class. Optionally merges loader libraries. + * @param versionJson The Minecraft version JSON. + * @param loaderJson The loader JSON (e.g., Forge, Fabric) if applicable. + */ + public async GetClassPath(versionJson: VersionJSON, loaderJson?: LoaderJSON): Promise<{ + classpath: Array; + mainClass: string | undefined; + }> { + let combinedLibraries = versionJson.libraries ?? []; + + // If a loader JSON is provided, merge its libraries with the base MC version + if (loaderJson?.libraries) { + combinedLibraries = loaderJson.libraries.concat(combinedLibraries); + } + + // Remove duplicates by `library.name` + combinedLibraries = combinedLibraries.filter((lib, index, self) => + index === self.findIndex(other => other.name === lib.name) + ); + + // Prepare to accumulate all library paths + const librariesList: string[] = []; + + for (const lib of combinedLibraries) { + // Skip certain logging libraries if flagged (e.g., in Forge's "loader" property) + if (lib.loader && lib.name.startsWith('org.apache.logging.log4j:log4j-slf4j2-impl')) continue; + + + // Check if the library has native bindings + if (lib.natives) { + const nativeName = lib.natives[MOJANG_LIBRARY_MAP[process.platform]] || lib.natives[process.platform]; + if (!nativeName) continue; + } else if (lib.rules && lib.rules[0].os) { + // Some libraries only apply to specific OS platforms + if (lib.rules[0].os.name !== MOJANG_LIBRARY_MAP[process.platform]) continue; + } + + // Build the path for this library + const libPath = getPathLibraries(lib.name); + if (lib.loader) { + // If the loader uses a specific library path + librariesList.push(`${lib.loader}/libraries/${libPath.path}/${libPath.name}`); + } else { + librariesList.push(`${this.options.path}/libraries/${libPath.path}/${libPath.name}`); + } + } + + // Add the main Minecraft JAR (or special jar if using old Forge or MCP) + if (loaderJson?.isOldForge && loaderJson.jarPath) { + librariesList.push(loaderJson.jarPath); + } else if (this.options.mcp) { + librariesList.push(this.options.mcp); + } else { + librariesList.push(`${this.options.path}/versions/${versionJson.id}/${versionJson.id}.jar`); + } + + // Filter out duplicates in the final library paths + const uniquePaths: string[] = []; + for (const libPath of librariesList) { + // We only check if we've already used the exact file name + const fileName = libPath.split('/').pop(); + if (fileName && !uniquePaths.includes(fileName)) { + uniquePaths.push(libPath); + } + } + + // The final classpath argument is OS-dependent (':' on Unix, ';' on Windows) + const cpSeparator = process.platform === 'win32' ? ';' : ':'; + const cpArgument = uniquePaths.length > 0 ? uniquePaths.join(cpSeparator) : ''; + + return { + classpath: ['-cp', cpArgument], + mainClass: loaderJson ? loaderJson.mainClass : versionJson.mainClass + }; + } } From dd4859db0b168dd2eb9311d4a6e1bd911ad540ce Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:15 +0100 Subject: [PATCH 107/185] refactor: unify code style and enhance documentation --- src/Minecraft/Minecraft-Assets.ts | 208 +++++++++++++++++++++--------- 1 file changed, 149 insertions(+), 59 deletions(-) diff --git a/src/Minecraft/Minecraft-Assets.ts b/src/Minecraft/Minecraft-Assets.ts index 9bf38caa..acd41cad 100755 --- a/src/Minecraft/Minecraft-Assets.ts +++ b/src/Minecraft/Minecraft-Assets.ts @@ -1,66 +1,156 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import nodeFetch from 'node-fetch'; import fs from 'fs'; +/** + * Represents the general structure of the options passed to MinecraftAssets. + * You can expand or modify these fields as necessary for your specific use case. + */ +export interface MinecraftAssetsOptions { + path: string; // Base path to the Minecraft data folder + instance?: string; // Instance name (if using multi-instance setup) +} + +/** + * Represents a simplified version of the Minecraft version JSON structure. + */ +export interface VersionJSON { + assetIndex?: { + id: string; // e.g. "1.19" + url: string; // URL where the asset index JSON can be fetched + }; + assets?: string; // e.g. "1.19" +} + +/** + * Represents a single asset object in the final array returned by getAssets(). + */ +export interface AssetItem { + type: 'CFILE' | 'Assets'; + path: string; + content?: string; // Used if type = "CFILE" + sha1?: string; // Used if type = "Assets" + size?: number; // Used if type = "Assets" + url?: string; // Used if type = "Assets" +} + +/** + * Class responsible for handling Minecraft asset index fetching + * and optionally copying legacy assets to the correct directory. + */ export default class MinecraftAssets { - assetIndex: any; - options: any; - constructor(options: any) { - this.options = options; - } - - async GetAssets(json: any) { - this.assetIndex = json.assetIndex; - - let assets = []; - let data = await nodeFetch(this.assetIndex.url).then(res => res.json()); - - assets.push({ - type: "CFILE", - path: `assets/indexes/${this.assetIndex.id}.json`, - content: JSON.stringify(data) - }); - - data = Object.values(data.objects); - - for (let asset of data) { - assets.push({ - sha1: asset.hash, - size: asset.size, - type: "Assets", - path: `assets/objects/${asset.hash.substring(0, 2)}/${asset.hash}`, - url: `https://resources.download.minecraft.net/${asset.hash.substring(0, 2)}/${asset.hash}` - }); - } - return assets - } - - copyAssets(json: any) { - let legacyDirectory: string = `${this.options.path}/resources`; - if (this.options.instance) legacyDirectory = `${this.options.path}/instances/${this.options.instance}/resources`; - let pathAssets = `${this.options.path}/assets/indexes/${json.assets}.json`; - if (!fs.existsSync(pathAssets)) return; - let assets = JSON.parse(fs.readFileSync(pathAssets, 'utf-8')); - assets = Object.entries(assets.objects); - - for (let [file, hash] of assets) { - let Hash = hash.hash; - let Subhash = Hash.substring(0, 2) - let SubAsset = `${this.options.path}/assets/objects/${Subhash}` - let legacyAsset = file.split('/') - legacyAsset.pop() - - if (!fs.existsSync(`${legacyDirectory}/${legacyAsset.join('/')}`)) { - fs.mkdirSync(`${legacyDirectory}/${legacyAsset.join('/')}`, { recursive: true }) - } - - if (!fs.existsSync(`${legacyDirectory}/${file}`)) { - fs.copyFileSync(`${SubAsset}/${Hash}`, `${legacyDirectory}/${file}`) - } - } - } -} \ No newline at end of file + private assetIndex: { id: string; url: string } | undefined; + private readonly options: MinecraftAssetsOptions; + + constructor(options: MinecraftAssetsOptions) { + this.options = options; + } + + /** + * Fetches the asset index from the provided JSON object, then constructs + * and returns an array of asset download objects. These can be processed + * by a downloader to ensure all assets are present locally. + * + * @param versionJson A JSON object containing an "assetIndex" field. + * @returns An array of AssetItem objects with download info. + */ + public async getAssets(versionJson: VersionJSON): Promise { + this.assetIndex = versionJson.assetIndex; + if (!this.assetIndex) { + // If there's no assetIndex, there's nothing to download. + return []; + } + + // Fetch the asset index JSON from the remote URL + let data; + try { + const response = await nodeFetch(this.assetIndex.url); + data = await response.json(); + } catch (err: any) { + throw new Error(`Failed to fetch asset index: ${err.message}`); + } + + // First item is the index file itself, which we'll store locally + const assetsArray: AssetItem[] = [ + { + type: 'CFILE', + path: `assets/indexes/${this.assetIndex.id}.json`, + content: JSON.stringify(data) + } + ]; + + // Convert the "objects" property into a list of individual assets + const objects = Object.values(data.objects || {}); + for (const obj of objects as Array<{ hash: string; size: number }>) { + assetsArray.push({ + type: 'Assets', + sha1: obj.hash, + size: obj.size, + path: `assets/objects/${obj.hash.substring(0, 2)}/${obj.hash}`, + url: `https://resources.download.minecraft.net/${obj.hash.substring(0, 2)}/${obj.hash}` + }); + } + + return assetsArray; + } + + /** + * Copies legacy assets (when using older versions of Minecraft) from + * the main "objects" folder to a "resources" folder, preserving the + * directory structure. + * + * @param versionJson A JSON object that has an "assets" property for the index name. + */ + public copyAssets(versionJson: VersionJSON): void { + // Determine the legacy directory where resources should go + let legacyDirectory = `${this.options.path}/resources`; + if (this.options.instance) { + legacyDirectory = `${this.options.path}/instances/${this.options.instance}/resources`; + } + + // The path to the local asset index JSON + const pathAssets = `${this.options.path}/assets/indexes/${versionJson.assets}.json`; + if (!fs.existsSync(pathAssets)) { + return; // Nothing to copy if the file doesn't exist + } + + // Parse the asset index JSON + let assetsData; + try { + assetsData = JSON.parse(fs.readFileSync(pathAssets, 'utf-8')); + } catch (err: any) { + throw new Error(`Failed to read assets index file: ${err.message}`); + } + + // Each entry is [filePath, { hash, size }] + const assetsEntries = Object.entries(assetsData.objects || {}); + for (const [filePath, hashData] of assetsEntries) { + const hashObj = hashData as { hash: string; size: number }; + const fullHash = hashObj.hash; + const subHash = fullHash.substring(0, 2); + + // Directory where the hashed file is stored + const subAssetDir = `${this.options.path}/assets/objects/${subHash}`; + + // If needed, create the corresponding directories in the legacy folder + const pathSegments = filePath.split('/'); + pathSegments.pop(); // Remove the last segment (the filename itself) + if (!fs.existsSync(`${legacyDirectory}/${pathSegments.join('/')}`)) { + fs.mkdirSync(`${legacyDirectory}/${pathSegments.join('/')}`, { recursive: true }); + } + + // Copy the file if it doesn't already exist in the legacy location + const sourceFile = `${subAssetDir}/${fullHash}`; + const targetFile = `${legacyDirectory}/${filePath}`; + if (!fs.existsSync(targetFile)) { + fs.copyFileSync(sourceFile, targetFile); + } + } + } +} From 4b7d685edd6d5526d06b9f1d25577e9f7210fb88 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 108/185] refactor: restructure bundle checks and file cleanup logic --- src/Minecraft/Minecraft-Bundle.ts | 317 ++++++++++++++++++++---------- 1 file changed, 216 insertions(+), 101 deletions(-) diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index 6fa5d99b..94222c24 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -1,108 +1,223 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import fs from 'fs'; import path from 'path'; -import { getFileHash } from '../utils/Index.js' +import { getFileHash } from '../utils/Index.js'; +/** + * Represents a single file or object that may need to be downloaded or checked. + */ +export interface BundleItem { + type?: 'CFILE' | 'Assets' | string; // e.g., "CFILE" for direct content files + path: string; // Local path where file is or should be stored + folder?: string; // Directory path (derived from 'path') + content?: string; // File content if type === "CFILE" + sha1?: string; // Expected SHA-1 hash for the file + size?: number; // Size in bytes if relevant + url?: string; // Download URL if relevant +} + +/** + * Options for the MinecraftBundle class, indicating paths and ignored files. + */ +export interface MinecraftBundleOptions { + path: string; // The main Minecraft directory or root path + instance?: string; // Instance name, if working with multiple instances + ignored: string[]; // Files or directories to ignore when cleaning +} + +/** + * This class manages checking, downloading, and cleaning up Minecraft files. + * It compares local files with a provided bundle, identifies missing or + * outdated files, and can remove extraneous files. + */ export default class MinecraftBundle { - options: any; - constructor(options: any) { - this.options = options; - } - - async checkBundle(bundle: any) { - let todownload = []; - - for (let file of bundle) { - if (!file.path) continue - file.path = path.resolve(this.options.path, file.path).replace(/\\/g, "/"); - file.folder = file.path.split("/").slice(0, -1).join("/"); - - if (file.type == "CFILE") { - if (!fs.existsSync(file.folder)) fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); - fs.writeFileSync(file.path, file.content, { encoding: "utf8", mode: 0o755 }); - continue; - } - - if (fs.existsSync(file.path)) { - let replaceName = `${this.options.path}/` - if (this.options.instance) replaceName = `${this.options.path}/instances/${this.options.instance}/` - if (this.options.ignored.find(ignored => ignored == file.path.replaceAll(replaceName, ""))) continue - - if (file.sha1) { - if (await getFileHash(file.path) != file.sha1) todownload.push(file); - } - - } else todownload.push(file); - } - return todownload; - } - - async getTotalSize(bundle: any) { - let todownload = 0; - for (let file of bundle) { - todownload += file.size; - } - return todownload; - } - - async checkFiles(bundle: any) { - let instancePath = '' - if (this.options.instance) { - if (!fs.existsSync(`${this.options.path}/instances`)) fs.mkdirSync(`${this.options.path}/instances`, { recursive: true }); - instancePath = `/instances/${this.options.instance}` - } - let files = this.options.instance ? this.getFiles(`${this.options.path}/instances/${this.options.instance}`) : this.getFiles(this.options.path); - let ignoredfiles = [...this.getFiles(`${this.options.path}/loader`), ...this.getFiles(`${this.options.path}/runtime`)] - - for (let file of this.options.ignored) { - file = (`${this.options.path}${instancePath}/${file}`) - if (fs.existsSync(file)) { - if (fs.statSync(file).isDirectory()) { - ignoredfiles.push(...this.getFiles(file)); - } else if (fs.statSync(file).isFile()) { - ignoredfiles.push(file); - } - } - } - - ignoredfiles.forEach(file => this.options.ignored.push((file))); - bundle.forEach(file => ignoredfiles.push((file.path))); - files = files.filter(file => ignoredfiles.indexOf(file) < 0); - - for (let file of files) { - try { - if (fs.statSync(file).isDirectory()) { - fs.rmSync(file, { recursive: true }); - } else { - fs.unlinkSync(file); - let folder = file.split("/").slice(0, -1).join("/"); - while (true) { - if (folder == this.options.path) break; - let content = fs.readdirSync(folder); - if (content.length == 0) fs.rmSync(folder); - folder = folder.split("/").slice(0, -1).join("/"); - } - } - } catch (e) { - continue; - } - } - } - - getFiles(path: any, file = []) { - if (fs.existsSync(path)) { - let files = fs.readdirSync(path); - if (files.length == 0) file.push(path); - for (let i in files) { - let name = `${path}/${files[i]}`; - if (fs.statSync(name).isDirectory()) this.getFiles(name, file); - else file.push(name); - } - } - return file; - } -} \ No newline at end of file + private options: MinecraftBundleOptions; + + constructor(options: MinecraftBundleOptions) { + this.options = options; + } + + /** + * Checks each item in the provided bundle to see if it needs to be + * downloaded or updated (e.g., if hashes don't match). + * + * @param bundle Array of file items describing what needs to be on disk. + * @returns Array of BundleItem objects that require downloading. + */ + public async checkBundle(bundle: BundleItem[]): Promise { + const toDownload: BundleItem[] = []; + + for (const file of bundle) { + if (!file.path) continue; + + // Convert path to absolute, consistent format + file.path = path.resolve(this.options.path, file.path).replace(/\\/g, '/'); + file.folder = file.path.split('/').slice(0, -1).join('/'); + + // If it's a direct content file (CFILE), we create/write the content immediately + if (file.type === 'CFILE') { + if (!fs.existsSync(file.folder)) { + fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); + } + fs.writeFileSync(file.path, file.content ?? '', { encoding: 'utf8', mode: 0o755 }); + continue; + } + + // If the file is supposed to have a certain hash, check it. + if (fs.existsSync(file.path)) { + // Build the instance path prefix for ignoring checks + let replaceName = `${this.options.path}/`; + if (this.options.instance) { + replaceName = `${this.options.path}/instances/${this.options.instance}/`; + } + + // If file is in "ignored" list, skip checks + const relativePath = file.path.replace(replaceName, ''); + if (this.options.ignored.includes(relativePath)) { + continue; + } + + // If the file has a hash and doesn't match, mark it for download + if (file.sha1) { + const localHash = await getFileHash(file.path); + if (localHash !== file.sha1) { + toDownload.push(file); + } + } + } else { + // The file doesn't exist at all, mark it for download + toDownload.push(file); + } + } + + return toDownload; + } + + /** + * Calculates the total download size of all files in the bundle. + * + * @param bundle Array of items in the bundle (with a 'size' field). + * @returns Sum of all file sizes in bytes. + */ + public async getTotalSize(bundle: BundleItem[]): Promise { + let totalSize = 0; + for (const file of bundle) { + if (file.size) { + totalSize += file.size; + } + } + return totalSize; + } + + /** + * Removes files or directories that should not be present, i.e., those + * not listed in the bundle and not in the "ignored" list. + * If the file is a directory, it's removed recursively. + * + * @param bundle Array of BundleItems representing valid files. + */ + public async checkFiles(bundle: BundleItem[]): Promise { + // If using instances, ensure the 'instances' directory exists + let instancePath = ''; + if (this.options.instance) { + if (!fs.existsSync(`${this.options.path}/instances`)) { + fs.mkdirSync(`${this.options.path}/instances`, { recursive: true }); + } + instancePath = `/instances/${this.options.instance}`; + } + + // Gather all existing files in the relevant directory + const allFiles = this.options.instance + ? this.getFiles(`${this.options.path}${instancePath}`) + : this.getFiles(this.options.path); + + // Also gather files from "loader" and "runtime" directories to ignore + const ignoredFiles = [ + ...this.getFiles(`${this.options.path}/loader`), + ...this.getFiles(`${this.options.path}/runtime`) + ]; + + // Convert custom ignored paths to actual file paths + for (let ignoredPath of this.options.ignored) { + ignoredPath = `${this.options.path}${instancePath}/${ignoredPath}`; + if (fs.existsSync(ignoredPath)) { + if (fs.statSync(ignoredPath).isDirectory()) { + // If it's a directory, add all files within it + ignoredFiles.push(...this.getFiles(ignoredPath)); + } else { + // If it's a single file, just add that file + ignoredFiles.push(ignoredPath); + } + } + } + + // Mark bundle paths as ignored (so we don't delete them) + bundle.forEach(file => { + ignoredFiles.push(file.path); + }); + + // Filter out all ignored files from the main file list + const filesToDelete = allFiles.filter(file => !ignoredFiles.includes(file)); + + // Remove each file or directory + for (const filePath of filesToDelete) { + try { + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + fs.rmSync(filePath, { recursive: true }); + } else { + fs.unlinkSync(filePath); + + // Clean up empty folders going upward until we hit the main path + let currentDir = path.dirname(filePath); + while (true) { + if (currentDir === this.options.path) break; + const dirContents = fs.readdirSync(currentDir); + if (dirContents.length === 0) { + fs.rmSync(currentDir); + } + currentDir = path.dirname(currentDir); + } + } + } catch { + // If an error occurs (e.g. file locked or non-existent), skip it + continue; + } + } + } + + /** + * Recursively gathers all files in a given directory path. + * If a directory is empty, it is also added to the returned array. + * + * @param dirPath The starting directory path to walk. + * @param collectedFiles Used internally to store file paths. + * @returns The array of all file paths (and empty directories) under dirPath. + */ + private getFiles(dirPath: string, collectedFiles: string[] = []): string[] { + if (fs.existsSync(dirPath)) { + const entries = fs.readdirSync(dirPath); + // If the directory is empty, store it as a "file" so it can be processed + if (entries.length === 0) { + collectedFiles.push(dirPath); + } + // Explore each child entry + for (const entry of entries) { + const fullPath = `${dirPath}/${entry}`; + const stats = fs.statSync(fullPath); + if (stats.isDirectory()) { + this.getFiles(fullPath, collectedFiles); + } else { + collectedFiles.push(fullPath); + } + } + } + return collectedFiles; + } +} From bf8f57d3b4c1c00ea0a65a291f428d7bc5de13d5 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 109/185] refactor: reorganize Java download and extraction workflow --- src/Minecraft/Minecraft-Java.ts | 517 +++++++++++++++++++++----------- 1 file changed, 347 insertions(+), 170 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 49ab1c3d..a01ca6d7 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import os from 'os'; @@ -9,175 +11,350 @@ import path from 'path'; import fs from 'fs'; import EventEmitter from 'events'; import Seven from 'node-7z'; -import sevenBin from '7zip-bin' +import sevenBin from '7zip-bin'; import { getFileHash } from '../utils/Index.js'; -import downloader from '../utils/Downloader.js'; +import Downloader from '../utils/Downloader.js'; +/** + * Represents the Java-specific options a user might pass to the downloader. + */ +export interface JavaDownloaderOptions { + path: string; // Base path to store the downloaded Java runtime + java: { + version?: string; // Force a specific Java version (e.g., "17") + type: string; // Image type for Adoptium (e.g., "jdk" or "jre") + }; + intelEnabledMac?: boolean; // If `true`, allows using Intel-based Java on Apple Silicon +} + +/** + * A generic JSON structure for the Minecraft version, which may include + * a javaVersion property. Adjust as needed to fit your actual data. + */ +export interface MinecraftVersionJSON { + javaVersion?: { + component?: string; // e.g., "jre-legacy" or "java-runtime-alpha" + majorVersion?: number; // e.g., 8, 17, 19 + }; +} + +/** + * Structure returned by getJavaFiles() and getJavaOther(). + */ +export interface JavaDownloadResult { + files: JavaFileItem[]; + path: string; // Local path to the java executable + error?: boolean; // Indicate an error if any + message?: string; // Error message if error is true +} + +/** + * Represents a single Java file entry that might need downloading. + */ +export interface JavaFileItem { + path: string; // Relative path to store the file under the runtime directory + executable?: boolean; + sha1?: string; + size?: number; + url?: string; + type?: string; // "Java" or other type +} + +/** + * Manages the download and extraction of the correct Java runtime for Minecraft. + * It supports both Mojang's curated list of Java runtimes and the Adoptium fallback. + */ export default class JavaDownloader extends EventEmitter { - options: any; - constructor(options: any) { - super(); - this.options = options; - } - - async getJavaFiles(jsonversion: any) { - if (this.options.java.version) return await this.getJavaOther(jsonversion, this.options.java.version); - const archMapping = { - win32: { x64: 'windows-x64', ia32: 'windows-x86', arm64: 'windows-arm64' }, - darwin: { x64: 'mac-os', arm64: this.options.intelEnabledMac ? "mac-os" : "mac-os-arm64" }, - linux: { x64: 'linux', ia32: 'linux-i386' } - }; - - const osPlatform = os.platform(); - const arch = os.arch(); - const osArchMapping = archMapping[osPlatform]; - const javaVersion = jsonversion.javaVersion?.component || 'jre-legacy'; - let files = []; - - if (!osArchMapping) return await this.getJavaOther(jsonversion); - - const archOs: any = osArchMapping[arch]; - const javaVersionsJson = await nodeFetch(`https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json`).then(res => res.json()); - - const versionName = javaVersionsJson[archOs]?.[javaVersion]?.[0]?.version?.name; - - if (!versionName) return await this.getJavaOther(jsonversion); - - const manifestUrl = javaVersionsJson[archOs][javaVersion][0]?.manifest?.url; - const manifest = await nodeFetch(manifestUrl).then(res => res.json()); - const javaFiles: any = Object.entries(manifest.files); - - const java = javaFiles.find(([path]) => path.endsWith(process.platform === 'win32' ? 'bin/javaw.exe' : 'bin/java'))[0]; - const toDelete = java.replace(process.platform === 'win32' ? 'bin/javaw.exe' : 'bin/java', ''); - - for (let [path, info] of javaFiles) { - if (info.type == "directory") continue; - if (!info.downloads) continue; - let file: any = {}; - file.path = `runtime/jre-${versionName}-${archOs}/${path.replace(toDelete, "")}`; - file.executable = info.executable; - file.sha1 = info.downloads.raw.sha1; - file.size = info.downloads.raw.size; - file.url = info.downloads.raw.url; - file.type = "Java"; - files.push(file); - } - - return { - files, - path: path.resolve(this.options.path, `runtime/jre-${versionName}-${archOs}/bin/java`), - }; - } - - - async getJavaOther(jsonversion: any, versionDownload?: any) { - const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; - const { platform, arch } = this.getPlatformArch(); - const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot?` + new URLSearchParams({ - image_type: this.options.java.type, - architecture: arch, - os: platform - }).toString(); - const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); - - const java = javaVersions[0]; - - if (!java) return { error: true, message: "No Java found" }; - - const { checksum, link: url, name: fileName } = java.binary.package; - const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); - const filePath = path.join(pathFolder, fileName); - - let javaPath = path.join(pathFolder, 'bin', 'java'); - if (platform === 'mac') javaPath = path.join(pathFolder, 'Contents', 'Home', 'bin', 'java'); - - if (!fs.existsSync(javaPath)) { - await this.verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }); - await this.extract(filePath, pathFolder); - fs.unlinkSync(filePath); - - if (filePath.endsWith('.tar.gz')) { - const tarFilePath = filePath.replace('.gz', ''); - await this.extract(tarFilePath, pathFolder); - if (fs.existsSync(tarFilePath)) fs.unlinkSync(tarFilePath); - } - - const extractedItems = fs.readdirSync(pathFolder); - if (extractedItems.length === 1) { - const extractedFolder = path.join(pathFolder, extractedItems[0]); - const stat = fs.statSync(extractedFolder); - if (stat.isDirectory()) { - const subItems = fs.readdirSync(extractedFolder); - for (const item of subItems) { - const srcPath = path.join(extractedFolder, item); - const destPath = path.join(pathFolder, item); - fs.renameSync(srcPath, destPath); - } - fs.rmdirSync(extractedFolder); - } - } - - if (platform !== 'windows') fs.chmodSync(javaPath, 0o755); - } - - return { files: [], path: javaPath }; - } - - getPlatformArch() { - const platformMap = { win32: 'windows', darwin: 'mac', linux: 'linux' }; - const archMap = { x64: 'x64', ia32: 'x32', arm64: 'aarch64', arm: 'arm' }; - const platform = platformMap[os.platform()] || os.platform(); - let arch = archMap[os.arch()] || os.arch(); - - if (os.platform() === 'darwin' && os.arch() === 'arm64' && this.options.intelEnabledMac) { - arch = 'x64'; - } - - return { platform, arch }; - } - - async verifyAndDownloadFile({ filePath, pathFolder, fileName, url, checksum }) { - if (fs.existsSync(filePath)) { - const existingChecksum = await getFileHash(filePath, 'sha256'); - if (existingChecksum !== checksum) { - fs.unlinkSync(filePath); - fs.rmSync(pathFolder, { recursive: true, force: true }); - } - } - - if (!fs.existsSync(filePath)) { - fs.mkdirSync(pathFolder, { recursive: true }); - const download = new downloader(); - - download.on('progress', (downloaded, size) => { - this.emit('progress', downloaded, size, fileName); - }); - - await download.downloadFile(url, pathFolder, fileName); - } - - const downloadedChecksum = await getFileHash(filePath, 'sha256'); - if (downloadedChecksum !== checksum) { - throw new Error("Java checksum failed"); - } - } - - async extract(filePath: string, destPath: string) { - if (os.platform() !== 'win32') fs.chmodSync(sevenBin.path7za, 0o755); - - await new Promise((resolve, reject) => { - const extract = Seven.extractFull(filePath, destPath, { - $bin: sevenBin.path7za, - recursive: true, - $progress: true, - }); - - extract.on('end', () => resolve()); - extract.on('error', (err) => reject(err)); - extract.on('progress', (progress) => { - if (progress.percent > 0) this.emit('extract', progress.percent); - }); - }); - } -} \ No newline at end of file + private options: JavaDownloaderOptions; + + constructor(options: JavaDownloaderOptions) { + super(); + this.options = options; + } + + /** + * Retrieves Java files from Mojang's runtime metadata if possible, + * otherwise falls back to getJavaOther(). + * + * @param jsonversion A JSON object describing the Minecraft version (with optional javaVersion). + * @returns An object containing a list of JavaFileItems and the final path to "java". + */ + public async getJavaFiles(jsonversion: MinecraftVersionJSON): Promise { + // If a specific version is forced, delegate to getJavaOther() immediately + if (this.options.java.version) { + return this.getJavaOther(jsonversion, this.options.java.version); + } + + // OS-to-architecture mapping for Mojang's curated Java. + const archMapping: Record> = { + win32: { x64: 'windows-x64', ia32: 'windows-x86', arm64: 'windows-arm64' }, + darwin: { x64: 'mac-os', arm64: this.options.intelEnabledMac ? 'mac-os' : 'mac-os-arm64' }, + linux: { x64: 'linux', ia32: 'linux-i386' } + }; + + const osPlatform = os.platform(); // "win32", "darwin", "linux", ... + const arch = os.arch(); // "x64", "arm64", "ia32", ... + + const javaVersionName = jsonversion.javaVersion?.component || 'jre-legacy'; + const osArchMapping = archMapping[osPlatform]; + const files: JavaFileItem[] = []; + + // If we don't have a valid mapping for the current OS, fallback to Adoptium + if (!osArchMapping) { + return this.getJavaOther(jsonversion); + } + + // Determine the OS-specific identifier + const archOs = osArchMapping[arch]; + if (!archOs) { + // If we can't match the arch in the sub-object, fallback + return this.getJavaOther(jsonversion); + } + + // Fetch Mojang's Java runtime metadata + const javaVersionsJson = await nodeFetch( + 'https://launchermeta.mojang.com/v1/products/java-runtime/' + + '2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json' + ).then(res => res.json()); + + const versionName = javaVersionsJson[archOs]?.[javaVersionName]?.[0]?.version?.name; + if (!versionName) { + return this.getJavaOther(jsonversion); + } + + // Fetch the runtime manifest which lists individual files + const manifestUrl = javaVersionsJson[archOs][javaVersionName][0]?.manifest?.url; + const manifest = await nodeFetch(manifestUrl).then(res => res.json()); + const manifestEntries: Array<[string, any]> = Object.entries(manifest.files); + + // Identify the Java executable in the manifest + const javaExeKey = process.platform === 'win32' ? 'bin/javaw.exe' : 'bin/java'; + const javaEntry = manifestEntries.find(([relPath]) => relPath.endsWith(javaExeKey)); + if (!javaEntry) { + // If we can't find the executable, fallback + return this.getJavaOther(jsonversion); + } + + const toDelete = javaEntry[0].replace(javaExeKey, ''); + for (const [relPath, info] of manifestEntries) { + if (info.type === 'directory') continue; + if (!info.downloads) continue; + + files.push({ + path: `runtime/jre-${versionName}-${archOs}/${relPath.replace(toDelete, '')}`, + executable: info.executable, + sha1: info.downloads.raw.sha1, + size: info.downloads.raw.size, + url: info.downloads.raw.url, + type: 'Java' + }); + } + + return { + files, + path: path.resolve( + this.options.path, + `runtime/jre-${versionName}-${archOs}`, + 'bin', + process.platform === 'win32' ? 'javaw.exe' : 'java' + ) + }; + } + + /** + * Fallback method to download Java from Adoptium if Mojang's metadata is unavailable + * or doesn't have the appropriate runtime for the user's platform/arch. + * + * @param jsonversion A Minecraft version JSON (with optional javaVersion). + * @param versionDownload A forced Java version (string) if provided by the user. + */ + public async getJavaOther(jsonversion: MinecraftVersionJSON, versionDownload?: string): Promise { + // Determine which major version of Java we need + const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; + const { platform, arch } = this.getPlatformArch(); + + // Build the Adoptium API URL + const queryParams = new URLSearchParams({ + image_type: this.options.java.type, // e.g. "jdk" or "jre" + architecture: arch, + os: platform + }); + const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot?${queryParams.toString()}`; + const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); + + // If no valid version is found, return an error + const java = javaVersions[0]; + if (!java) { + return { files: [], path: '', error: true, message: 'No Java found' }; + } + + const { checksum, link: url, name: fileName } = java.binary.package; + const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); + const filePath = path.join(pathFolder, fileName); + + // Determine the final path to the java executable after extraction + let javaExePath = path.join(pathFolder, 'bin', 'java'); + if (platform === 'mac') { + javaExePath = path.join(pathFolder, 'Contents', 'Home', 'bin', 'java'); + } + + // Download and extract if needed + if (!fs.existsSync(javaExePath)) { + await this.verifyAndDownloadFile({ + filePath, + pathFolder, + fileName, + url, + checksum + }); + + // Extract the downloaded archive + await this.extract(filePath, pathFolder); + fs.unlinkSync(filePath); + + // For .tar.gz files, we may need a second extraction step + if (filePath.endsWith('.tar.gz')) { + const tarFilePath = filePath.replace('.gz', ''); + await this.extract(tarFilePath, pathFolder); + if (fs.existsSync(tarFilePath)) { + fs.unlinkSync(tarFilePath); + } + } + + // If there's only one folder extracted, move its contents up + const extractedItems = fs.readdirSync(pathFolder); + if (extractedItems.length === 1) { + const singleFolder = path.join(pathFolder, extractedItems[0]); + const stat = fs.statSync(singleFolder); + if (stat.isDirectory()) { + const subItems = fs.readdirSync(singleFolder); + for (const item of subItems) { + const srcPath = path.join(singleFolder, item); + const destPath = path.join(pathFolder, item); + fs.renameSync(srcPath, destPath); + } + fs.rmdirSync(singleFolder); + } + } + + // Ensure the Java executable is marked as executable on non-Windows systems + if (platform !== 'windows') { + fs.chmodSync(javaExePath, 0o755); + } + } + + return { files: [], path: javaExePath }; + } + + /** + * Maps the Node `os.platform()` and `os.arch()` to Adoptium's expected format. + * Apple Silicon can optionally download x64 if `intelEnabledMac` is true. + */ + private getPlatformArch(): { platform: string; arch: string } { + const platformMap: Record = { + win32: 'windows', + darwin: 'mac', + linux: 'linux' + }; + const archMap: Record = { + x64: 'x64', + ia32: 'x32', + arm64: 'aarch64', + arm: 'arm' + }; + + const mappedPlatform = platformMap[os.platform()] || os.platform(); + let mappedArch = archMap[os.arch()] || os.arch(); + + // Force x64 if Apple Silicon but user wants to use Intel-based Java + if (os.platform() === 'darwin' && os.arch() === 'arm64' && this.options.intelEnabledMac) { + mappedArch = 'x64'; + } + + return { platform: mappedPlatform, arch: mappedArch }; + } + + /** + * Verifies if the Java archive already exists and matches the expected checksum. + * If it doesn't exist or fails the hash check, it downloads from the given URL. + * + * @param params.filePath The local file path + * @param params.pathFolder The folder to place the file in + * @param params.fileName The name of the file + * @param params.url The remote download URL + * @param params.checksum Expected SHA-256 hash + */ + private async verifyAndDownloadFile({ + filePath, + pathFolder, + fileName, + url, + checksum + }: { + filePath: string; + pathFolder: string; + fileName: string; + url: string; + checksum: string; + }): Promise { + // If the file already exists, check its integrity + if (fs.existsSync(filePath)) { + const existingChecksum = await getFileHash(filePath, 'sha256'); + if (existingChecksum !== checksum) { + fs.unlinkSync(filePath); + fs.rmSync(pathFolder, { recursive: true, force: true }); + } + } + + // If not found or failed checksum, download anew + if (!fs.existsSync(filePath)) { + fs.mkdirSync(pathFolder, { recursive: true }); + const download = new Downloader(); + + // Relay progress events + download.on('progress', (downloaded: number, size: number) => { + this.emit('progress', downloaded, size, fileName); + }); + + // Start download + await download.downloadFile(url, pathFolder, fileName); + } + + // Final verification of the downloaded file + const downloadedChecksum = await getFileHash(filePath, 'sha256'); + if (downloadedChecksum !== checksum) { + throw new Error('Java checksum failed'); + } + } + + /** + * Extracts the given archive (ZIP or 7Z), using the `node-7z` library and the system's 7z binary. + * Emits an "extract" event with the extraction progress (percent). + * + * @param filePath Path to the archive file + * @param destPath Destination folder to extract into + */ + private async extract(filePath: string, destPath: string): Promise { + // Ensure the 7z binary is executable on Unix-like OSes + if (os.platform() !== 'win32') { + fs.chmodSync(sevenBin.path7za, 0o755); + } + + return new Promise((resolve, reject) => { + const extractor = Seven.extractFull(filePath, destPath, { + $bin: sevenBin.path7za, + recursive: true, + $progress: true + }); + + extractor.on('end', () => resolve()); + extractor.on('error', (err) => reject(err)); + extractor.on('progress', (progress) => { + if (progress.percent > 0) { + this.emit('extract', progress.percent); + } + }); + }); + } +} From ed011c4a784db8c5c8d52916d322a130f34453c2 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 110/185] refactor: unify code style and improve version fetch logic --- src/Minecraft/Minecraft-Json.ts | 150 +++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 39 deletions(-) diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index 25db0da9..7a89275b 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -1,46 +1,118 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ +import os from 'os'; +import nodeFetch from 'node-fetch'; import MinecraftNativeLinuxARM from './Minecraft-Lwjgl-Native.js'; -import nodeFetch from 'node-fetch'; -import os from 'os'; +/** + * Basic structure for options passed to the Json class. + * Modify or expand based on your actual usage. + */ +export interface JsonOptions { + version: string; // The targeted Minecraft version (e.g. "1.19", "latest_release", etc.) + [key: string]: any; // Include any additional fields needed by your code +} + +/** + * Represents a single version entry from Mojang's version manifest. + */ +export interface VersionEntry { + id: string; + type: string; + url: string; + time: string; + releaseTime: string; +} + +/** + * Structure of the Mojang version manifest (simplified). + */ +export interface MojangVersionManifest { + latest: { + release: string; + snapshot: string; + }; + versions: VersionEntry[]; +} + +/** + * Structure returned by the getInfoVersion method on success. + */ +export interface GetInfoVersionResult { + InfoVersion: VersionEntry; + json: any; // The specific version JSON fetched from Mojang + version: string; // The final resolved version (e.g., "1.19" if "latest_release" was given) +} + +/** + * Structure returned by getInfoVersion if an error occurs (version not found). + */ +export interface GetInfoVersionError { + error: true; + message: string; +} +/** + * This class retrieves Minecraft version information from Mojang's + * version manifest, and optionally processes the JSON for ARM-based Linux. + */ export default class Json { - options: any; - - constructor(options: any) { - this.options = options; - } - - async GetInfoVersion() { - let version: string = this.options.version; - let data: any = await nodeFetch(`https://launchermeta.mojang.com/mc/game/version_manifest_v2.json?_t=${new Date().toISOString()}`); - data = await data.json(); - - if (version == 'latest_release' || version == 'r' || version == 'lr') { - version = data.latest.release; - } - else if (version == 'latest_snapshot' || version == 's' || version == 'ls') { - version = data.latest.snapshot; - } - - data = data.versions.find(v => v.id === version); - - if (!data) return { - error: true, - message: `Minecraft ${version} is not found.` - }; - - let json: any = await nodeFetch(data.url).then(res => res.json()); - if (os.platform() == 'linux' && os.arch().startsWith('arm')) json = await new MinecraftNativeLinuxARM(this.options).ProcessJson(json); - - return { - InfoVersion: data, - json: json, - version: version - }; - } -} \ No newline at end of file + private readonly options: JsonOptions; + + constructor(options: JsonOptions) { + this.options = options; + } + + /** + * Fetches the Mojang version manifest, resolves the intended version (release, snapshot, etc.), + * and returns the associated JSON object for that version. + * If the system is Linux ARM, it will run additional processing on the JSON. + * + * @returns An object containing { InfoVersion, json, version }, or an error object. + */ + public async GetInfoVersion(): Promise { + let { version } = this.options; + + // Fetch the version manifest + const response = await nodeFetch( + `https://launchermeta.mojang.com/mc/game/version_manifest_v2.json?_t=${new Date().toISOString()}` + ); + const manifest: MojangVersionManifest = await response.json(); + + // Resolve "latest_release"/"latest_snapshot" shorthands + if (version === 'latest_release' || version === 'r' || version === 'lr') { + version = manifest.latest.release; + } else if (version === 'latest_snapshot' || version === 's' || version === 'ls') { + version = manifest.latest.snapshot; + } + + // Find the matching version info from the manifest + const matchedVersion = manifest.versions.find((v) => v.id === version); + if (!matchedVersion) { + return { + error: true, + message: `Minecraft ${version} is not found.` + }; + } + + // Fetch the detailed version JSON from Mojang + const jsonResponse = await nodeFetch(matchedVersion.url); + let versionJson = await jsonResponse.json(); + + // If on Linux ARM, run additional processing + if (os.platform() === 'linux' && os.arch().startsWith('arm')) { + versionJson = await new MinecraftNativeLinuxARM(this.options).ProcessJson(versionJson); + } + + return { + InfoVersion: matchedVersion, + json: versionJson, + version + }; + } +} From 41e6c83011d33e47a9e33a985f491c348ae8b382 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 111/185] refactor: enhance library download and natives extraction --- src/Minecraft/Minecraft-Libraries.ts | 363 ++++++++++++++++++++------- 1 file changed, 266 insertions(+), 97 deletions(-) diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 5f8e2881..5876ea3c 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import os from 'os'; @@ -8,100 +10,267 @@ import fs from 'fs'; import AdmZip from 'adm-zip'; import nodeFetch from 'node-fetch'; -let MojangLib = { win32: "windows", darwin: "osx", linux: "linux" }; -let Arch = { x32: "32", x64: "64", arm: "32", arm64: "64" }; +/** + * Maps Node.js platforms to Mojang's naming scheme for OS in library natives. + */ +const MojangLib: Record = { + win32: 'windows', + darwin: 'osx', + linux: 'linux' +}; + +/** + * Maps Node.js architecture strings to Mojang's arch replacements (e.g., "${arch}" => 64). + */ +const Arch: Record = { + x32: '32', + x64: '64', + arm: '32', + arm64: '64' +}; + +/** + * Represents a single library entry in the version JSON. + * Adjust or extend this interface based on your actual JSON structure. + */ +interface MinecraftLibrary { + name?: string; + rules?: Array<{ + os?: { name: string }; + action?: string; + }>; + natives?: Record; + downloads: { + artifact?: { + sha1: string; + size: number; + path: string; + url: string; + }; + classifiers?: Record< + string, + { + sha1: string; + size: number; + path: string; + url: string; + } + >; + }; +} + +/** + * Represents a Minecraft version JSON structure. + * Extend this interface to reflect any additional fields you use. + */ +interface MinecraftVersionJSON { + id: string; + libraries: MinecraftLibrary[]; + downloads: { + client: { + sha1: string; + size: number; + url: string; + }; + }; + [key: string]: any; +} +/** + * Represents an item in the optional "asset" array fetched from a custom URL. + */ +interface CustomAssetItem { + path: string; + hash: string; + size: number; + url: string; +} + +/** + * Represents the user-provided options for the Libraries class. + * Adjust as needed for your codebase. + */ +interface LibrariesOptions { + path: string; // Base path to the Minecraft folder + instance?: string; // Instance name if using multi-instances + [key: string]: any; // Other fields your code might need +} + +/** + * Represents a file or library entry that needs to be downloaded and stored. + */ +interface LibraryDownload { + sha1?: string; + size?: number; + path: string; + type: string; + url?: string; + content?: string; // For CFILE entries (JSON content) +} + +/** + * This class is responsible for: + * - Gathering library download info from the version JSON + * - Handling custom asset entries if provided + * - Extracting native libraries for the current OS into the appropriate folder + */ export default class Libraries { - json: any; - options: any; - constructor(options: any) { - this.options = options; - } - - async Getlibraries(json: any) { - this.json = json; - let libraries = []; - - for (let lib of this.json.libraries) { - let artifact: any; - let type = "Libraries"; - - if (lib.natives) { - let classifiers = lib.downloads.classifiers; - let native = lib.natives[MojangLib[process.platform]]; - if (!native) native = lib.natives[process.platform]; - type = "Native"; - if (native) artifact = classifiers[native.replace("${arch}", Arch[os.arch()])]; - else continue; - } else { - if (lib.rules && lib.rules[0].os) { - if (lib.rules[0].os.name !== MojangLib[process.platform]) continue; - } - artifact = lib.downloads.artifact; - } - if (!artifact) continue; - libraries.push({ - sha1: artifact.sha1, - size: artifact.size, - path: `libraries/${artifact.path}`, - type: type, - url: artifact.url - }); - } - - libraries.push({ - sha1: this.json.downloads.client.sha1, - size: this.json.downloads.client.size, - path: `versions/${this.json.id}/${this.json.id}.jar`, - type: "Libraries", - url: this.json.downloads.client.url - }); - - libraries.push({ - path: `versions/${this.json.id}/${this.json.id}.json`, - type: "CFILE", - content: JSON.stringify(this.json) - }); - return libraries; - } - - async GetAssetsOthers(url: any) { - if (!url) return []; - let data = await nodeFetch(url).then(res => res.json()); - - let assets = []; - for (let asset of data) { - if (!asset.path) continue - let path = asset.path; - assets.push({ - sha1: asset.hash, - size: asset.size, - type: path.split("/")[0], - path: this.options.instance ? `instances/${this.options.instance}/${path}` : path, - url: asset.url - }); - } - return assets - } - - async natives(bundle: any) { - let natives = bundle.filter(mod => mod.type === "Native").map(mod => `${mod.path}`); - if (natives.length === 0) return natives; - let nativeFolder = (`${this.options.path}/versions/${this.json.id}/natives`).replace(/\\/g, "/"); - if (!fs.existsSync(nativeFolder)) fs.mkdirSync(nativeFolder, { recursive: true, mode: 0o777 }); - - for (let native of natives) { - let zip = new AdmZip(native); - let entries = zip.getEntries(); - for (let entry of entries) { - if (entry.entryName.startsWith("META-INF")) continue; - if (entry.isDirectory) { - fs.mkdirSync(`${nativeFolder}/${entry.entryName}`, { recursive: true, mode: 0o777 }); - continue - } - fs.writeFile(`${nativeFolder}/${entry.entryName}`, zip.readFile(entry), { encoding: "utf8", mode: 0o777 }, () => { }); - } - } - return natives; - } -} \ No newline at end of file + private json!: MinecraftVersionJSON; + private readonly options: LibrariesOptions; + + constructor(options: LibrariesOptions) { + this.options = options; + } + + /** + * Processes the provided Minecraft version JSON to build a list of libraries + * that need to be downloaded (including the main client jar and the version JSON itself). + * + * @param json A MinecraftVersionJSON object (containing libraries, downloads, etc.) + * @returns An array of LibraryDownload items describing each file. + */ + public async Getlibraries(json: MinecraftVersionJSON): Promise { + this.json = json; + const libraries: LibraryDownload[] = []; + + for (const lib of this.json.libraries) { + let artifact: { sha1: string; size: number; path: string; url: string } | undefined; + let type = 'Libraries'; + + if (lib.natives) { + // If this library has OS natives, pick the correct classifier + const classifiers = lib.downloads.classifiers; + let native = lib.natives[MojangLib[os.platform()]] || lib.natives[os.platform()]; + type = 'Native'; + if (native) { + // Replace "${arch}" if present, e.g. "natives-windows-${arch}" + const archReplaced = native.replace('${arch}', Arch[os.arch()] || ''); + artifact = classifiers ? classifiers[archReplaced] : undefined; + } else { + // No valid native for the current platform + continue; + } + } else { + // If there are rules restricting OS, skip if not matching + if (lib.rules && lib.rules[0]?.os?.name) { + if (lib.rules[0].os.name !== MojangLib[os.platform()]) { + continue; + } + } + artifact = lib.downloads.artifact; + } + + if (!artifact) continue; + + libraries.push({ + sha1: artifact.sha1, + size: artifact.size, + path: `libraries/${artifact.path}`, + type: type, + url: artifact.url + }); + } + + // Add the main Minecraft client JAR to the list + libraries.push({ + sha1: this.json.downloads.client.sha1, + size: this.json.downloads.client.size, + path: `versions/${this.json.id}/${this.json.id}.jar`, + type: 'Libraries', + url: this.json.downloads.client.url + }); + + // Add the JSON file for this version as a "CFILE" + libraries.push({ + path: `versions/${this.json.id}/${this.json.id}.json`, + type: 'CFILE', + content: JSON.stringify(this.json) + }); + + return libraries; + } + + /** + * Fetches custom assets or libraries from a remote URL if provided. + * This method expects the response to be an array of objects with + * "path", "hash", "size", and "url". + * + * @param url The remote URL that returns a JSON array of CustomAssetItem + * @returns An array of LibraryDownload entries describing each item + */ + public async GetAssetsOthers(url: string | null): Promise { + if (!url) return []; + + const response = await nodeFetch(url); + const data: CustomAssetItem[] = await response.json(); + + const assets: LibraryDownload[] = []; + for (const asset of data) { + if (!asset.path) continue; + + // The 'type' is deduced from the first part of the path + const fileType = asset.path.split('/')[0]; + assets.push({ + sha1: asset.hash, + size: asset.size, + type: fileType, + path: this.options.instance + ? `instances/${this.options.instance}/${asset.path}` + : asset.path, + url: asset.url + }); + } + return assets; + } + + /** + * Extracts native libraries from the downloaded jars (those marked type="Native") + * and places them into the "natives" folder under "versions//natives". + * + * @param bundle An array of library entries (some of which may be natives) + * @returns The paths of the native files that were extracted + */ + public async natives(bundle: LibraryDownload[]): Promise { + // Gather only the native library files + const natives = bundle + .filter((item) => item.type === 'Native') + .map((item) => `${item.path}`); + + if (natives.length === 0) { + return []; + } + + // Create the natives folder if it doesn't already exist + const nativesFolder = `${this.options.path}/versions/${this.json.id}/natives`.replace(/\\/g, '/'); + if (!fs.existsSync(nativesFolder)) { + fs.mkdirSync(nativesFolder, { recursive: true, mode: 0o777 }); + } + + // For each native jar, extract its contents (excluding META-INF) + for (const native of natives) { + // Load it as a zip + const zip = new AdmZip(native); + const entries = zip.getEntries(); + + for (const entry of entries) { + if (entry.entryName.startsWith('META-INF')) { + continue; + } + + // Create subdirectories if needed + if (entry.isDirectory) { + fs.mkdirSync(`${nativesFolder}/${entry.entryName}`, { recursive: true, mode: 0o777 }); + continue; + } + + // Write the file to the natives folder + fs.writeFileSync( + `${nativesFolder}/${entry.entryName}`, + zip.readFile(entry), + { mode: 0o777 } + ); + } + } + return natives; + } +} From b9b50575792c93fe71821eeb730d8de7b36b6338 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 112/185] refactor: unify loader usage and improve argument handling --- src/Minecraft/Minecraft-Loader.ts | 270 ++++++++++++++++++++---------- 1 file changed, 177 insertions(+), 93 deletions(-) diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 775803e0..082d5ee5 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -1,97 +1,181 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import { EventEmitter } from 'events'; -import loaderDownloader from '../Minecraft-Loader/index.js' -import path from 'path' - -export default class MinecraftLoader { - options: any; - on: any; - emit: any; - loaderPath: string; - constructor(options: any) { - this.options = options; - this.on = EventEmitter.prototype.on; - this.emit = EventEmitter.prototype.emit; - this.loaderPath = path.join(this.options.path, this.options.loader.path); - } - - async GetLoader(version: any, javaPath: any) { - let loader = new loaderDownloader({ - path: this.loaderPath, - downloadFileMultiple: this.options.downloadFileMultiple, - loader: { - type: this.options.loader.type, - version: version, - build: this.options.loader.build, - config: { - javaPath: javaPath, - minecraftJar: `${this.options.path}/versions/${version}/${version}.jar`, - minecraftJson: `${this.options.path}/versions/${version}/${version}.json` - } - } - }); - - return await new Promise((resolve, reject) => { - loader.install(); - - loader.on('json', (json: any) => { - let loaderJson = json; - loaderJson.libraries = loaderJson.libraries.map((lib: any) => { - lib.loader = this.loaderPath; - return lib; - }); - resolve(loaderJson); - }); - - loader.on('extract', (extract: any) => { - this.emit('extract', extract); - }); - - loader.on('progress', (progress: any, size: any, element: any) => { - this.emit('progress', progress, size, element); - }); - - loader.on('check', (progress: any, size: any, element: any) => { - this.emit('check', progress, size, element); - }); - - loader.on('patch', (patch: any) => { - this.emit('patch', patch); - }); - - loader.on('error', (err: any) => { - reject(err); - }); - }) - } - - async GetArguments(json: any, version: any) { - if (json === null) { - return { - game: [], - jvm: [] - } - } - - let moddeArguments = json.arguments; - if (!moddeArguments) return { game: [], jvm: [] }; - let Arguments: any = {} - if (moddeArguments.game) Arguments.game = moddeArguments.game; - if (moddeArguments.jvm) Arguments.jvm = moddeArguments.jvm.map(jvm => { - return jvm - .replace(/\${version_name}/g, version) - .replace(/\${library_directory}/g, `${this.loaderPath}/libraries`) - .replace(/\${classpath_separator}/g, process.platform === 'win32' ? ';' : ':'); - }) - - return { - game: Arguments.game || [], - jvm: Arguments.jvm || [], - mainClass: json.mainClass - }; - } -} \ No newline at end of file +import path from 'path'; +// Note: Adjust the import path according to your actual TypeScript setup. +import LoaderDownloader, { LoaderType } from '../Minecraft-Loader/index.js'; + +/** + * Describes the loader options, including a path and other configurations. + * You can expand this interface if your real code requires more fields. + */ +interface LoaderOptions { + path: string; // Base path for the Minecraft data or installation + loader: { + path?: string; // Path to store loader files (e.g. Forge, Fabric) + type?: string; // Type of loader (forge, fabric, etc.) + build?: string; // Build number if applicable (e.g., for Forge) + }; + downloadFileMultiple?: number; // If your downloader can handle multiple files +} + +/** + * Represents the MinecraftLoader class options, merging LoaderOptions + * with any additional fields your code might require. + */ +interface MinecraftLoaderOptions extends LoaderOptions { + // Additional fields that might be needed by your application + [key: string]: any; +} + +/** + * A simple interface describing the JSON structure returned by loader installation. + * Adjust to reflect the actual fields from your loader JSON. + */ +interface LoaderJSON { + libraries: Array<{ + loader?: string; + name?: string; // Or any other required fields + }>; + arguments?: { + game?: string[]; + jvm?: string[]; + }; + mainClass?: string; + [key: string]: any; +} + +/** + * This class manages the installation and argument-building for a Minecraft + * mod loader (e.g. Forge, Fabric). It wraps a `loaderDownloader` and emits + * the same events for progress, extraction, patching, etc. + */ +export default class MinecraftLoader extends EventEmitter { + private options: MinecraftLoaderOptions; + private loaderPath: string; + + constructor(options: MinecraftLoaderOptions) { + super(); + this.options = options; + this.loaderPath = path.join(this.options.path, this.options.loader.path); + } + + /** + * Installs the loader for a given Minecraft version using a LoaderDownloader, + * returning the loader's JSON on completion. This function emits several events + * for progress reporting and patch notifications. + * + * @param version The Minecraft version (e.g. "1.19.2") + * @param javaPath Path to the Java executable used by the loader for patching + * @returns A Promise that resolves to the loader's JSON configuration + */ + public async GetLoader(version: string, javaPath: string): Promise { + const loader = new LoaderDownloader({ + path: this.loaderPath, + downloadFileMultiple: this.options.downloadFileMultiple, + loader: { + type: this.options.loader.type as LoaderType, + version: version, + build: this.options.loader.build, + config: { + javaPath, + minecraftJar: `${this.options.path}/versions/${version}/${version}.jar`, + minecraftJson: `${this.options.path}/versions/${version}/${version}.json` + } + } + }); + + return new Promise((resolve, reject) => { + loader.install(); + + loader.on('json', (json: LoaderJSON) => { + // Inject the loader path into each library if needed + const modifiedJson = json; + modifiedJson.libraries = modifiedJson.libraries.map(lib => { + lib.loader = this.loaderPath; + return lib; + }); + resolve(modifiedJson); + }); + + loader.on('extract', (extract: any) => { + // Forward the "extract" event + this.emit('extract', extract); + }); + + loader.on('progress', (progress: any, size: any, element: any) => { + // Forward the "progress" event + this.emit('progress', progress, size, element); + }); + + loader.on('check', (progress: any, size: any, element: any) => { + // Forward the "check" event + this.emit('check', progress, size, element); + }); + + loader.on('patch', (patch: any) => { + // Forward the "patch" event + this.emit('patch', patch); + }); + + loader.on('error', (err: any) => { + reject(err); + }); + }); + } + + /** + * Builds the game and JVM arguments based on the loader's JSON data. + * This may involve placeholder replacements for the main class, library directories, etc. + * + * @param json The loader JSON previously returned by GetLoader (or null) + * @param version The targeted Minecraft version (used for placeholder substitution) + * @returns An object with `game`, `jvm`, and an optional `mainClass` property + */ + public async GetArguments(json: LoaderJSON | null, version: string): Promise<{ + game: string[]; + jvm: string[]; + mainClass?: string; + }> { + // If no loader JSON is provided, return empty arrays + if (json === null) { + return { game: [], jvm: [] }; + } + + const moddedArgs = json.arguments; + // If no arguments field is present, return empty arrays + if (!moddedArgs) return { game: [], jvm: [] }; + + const args: { + game?: string[]; + jvm?: string[]; + mainClass?: string; + } = {}; + + if (moddedArgs.game) { + args.game = moddedArgs.game; + } + + if (moddedArgs.jvm) { + // Replace placeholders in the JVM arguments + args.jvm = moddedArgs.jvm.map((jvmArg) => + jvmArg + .replace(/\${version_name}/g, version) + .replace(/\${library_directory}/g, `${this.loaderPath}/libraries`) + .replace(/\${classpath_separator}/g, process.platform === 'win32' ? ';' : ':') + ); + } + + args.mainClass = json.mainClass; + return { + game: args.game || [], + jvm: args.jvm || [], + mainClass: args.mainClass + }; + } +} From 9fc4ba554b6f9ac91dad275faeff97e0980234fd Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 113/185] refactor: unify code style and ARM logic --- src/Minecraft/Minecraft-Lwjgl-Native.ts | 157 +++++++++++++++--------- 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts index e83f6bea..0cf9d8c7 100644 --- a/src/Minecraft/Minecraft-Lwjgl-Native.ts +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -1,64 +1,111 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import path from 'path'; import fs from 'fs'; import os from 'os'; +/** + * Minimal interface describing a single library entry in the version JSON. + * Adjust as needed to reflect your actual library data structure. + */ +interface MinecraftLibrary { + name: string; + // You may include other fields like 'downloads', 'natives', etc. if needed +} + +/** + * Represents the structure of a Minecraft version JSON. + * You can expand this interface based on your actual usage. + */ +interface MinecraftVersion { + libraries: MinecraftLibrary[]; + // Additional fields like 'id', 'mainClass', etc. can go here +} + +/** + * Options for constructing the MinecraftLoader, if needed. + * Extend or remove fields to match your actual requirements. + */ +interface LoaderOptions { + // Add any relevant configuration fields if needed + [key: string]: unknown; +} +/** + * This class modifies the version JSON for ARM-based Linux systems, + * specifically handling LWJGL library replacements for versions 2.9.x or custom LWJGL versions. + */ export default class MinecraftLoader { - options: any; - constructor(options: any) { - this.options = options; - } - - async ProcessJson(version: any) { - let archMapping: any = { arm64: "aarch64", arm: 'aarch' }[os.arch()] - let pathLWJGL = path.join(__dirname, `../../assets/LWJGL/${archMapping}`); - - let versionJinput = version.libraries.find((lib: any) => { - if (lib.name.startsWith("net.java.jinput:jinput-platform:")) { - return true; - } else if (lib.name.startsWith("net.java.jinput:jinput:")) { - return true; - } - })?.name.split(":").pop(); - - let versionLWJGL = version.libraries.find((lib: any) => { - if (lib.name.startsWith("org.lwjgl:lwjgl:")) { - return true; - } else if (lib.name.startsWith("org.lwjgl.lwjgl:lwjgl:")) { - return true; - } - })?.name.split(":").pop(); - - - if (versionJinput) { - version.libraries = version.libraries.filter((lib: any) => { - if (lib.name.includes("jinput")) return false - return true; - }); - } - - if (versionLWJGL) { - version.libraries = version.libraries.filter((lib: any) => { - if (lib.name.includes("lwjgl")) return false; - return true; - }); - - if (versionLWJGL.includes('2.9')) { - let versionLWJGLNatives = JSON.parse(fs.readFileSync(path.join(pathLWJGL, '2.9.4.json'), 'utf-8')); - version.libraries.push(...versionLWJGLNatives.libraries); - } else { - let versionLWJGLNatives = JSON.parse(fs.readFileSync(path.join(pathLWJGL, `${versionLWJGL}.json`), 'utf-8')); - version.libraries.push(...versionLWJGLNatives.libraries); - } - - - } - - return version; - } -} \ No newline at end of file + private options: LoaderOptions; + + constructor(options: LoaderOptions) { + this.options = options; + } + + /** + * Processes a Minecraft version JSON, removing default JInput and LWJGL entries + * if needed, then injecting ARM-compatible LWJGL libraries from local JSON files. + * + * @param version A MinecraftVersion object containing a list of libraries + * @returns The same version object, but with updated libraries for ARM-based Linux + */ + public async ProcessJson(version: MinecraftVersion): Promise { + // Maps Node's arm architecture to the expected LWJGL naming + const archMapping: Record = { + arm64: 'aarch64', + arm: 'aarch' + }; + const currentArch = os.arch(); + const mappedArch = archMapping[currentArch]; + + // If running on a non-ARM environment, or if the mapping doesn't exist, no changes are needed + if (!mappedArch) { + return version; + } + + // Path to the directory containing LWJGL JSON files for ARM + const pathLWJGL = path.join(__dirname, '../../assets/LWJGL', mappedArch); + + // Identify the version strings for JInput and LWJGL from the existing libraries + const versionJinput = version.libraries.find(lib => + lib.name.startsWith('net.java.jinput:jinput-platform:') || + lib.name.startsWith('net.java.jinput:jinput:') + )?.name.split(':').pop(); + + const versionLWJGL = version.libraries.find(lib => + lib.name.startsWith('org.lwjgl:lwjgl:') || + lib.name.startsWith('org.lwjgl.lwjgl:lwjgl:') + )?.name.split(':').pop(); + + // Remove all JInput-related libraries if a JInput version is found + if (versionJinput) { + version.libraries = version.libraries.filter(lib => !lib.name.includes('jinput')); + } + + // Remove all LWJGL-related libraries if an LWJGL version is found + if (versionLWJGL) { + version.libraries = version.libraries.filter(lib => !lib.name.includes('lwjgl')); + + // Inject ARM-compatible LWJGL libraries + let lwjglJsonFile = versionLWJGL.includes('2.9') + ? '2.9.4.json' + : `${versionLWJGL}.json`; + + const lwjglPath = path.join(pathLWJGL, lwjglJsonFile); + + // Read the appropriate LWJGL JSON (e.g., "2.9.4.json" or ".json") + const lwjglNativesContent = fs.readFileSync(lwjglPath, 'utf-8'); + const lwjglNatives = JSON.parse(lwjglNativesContent) as MinecraftVersion; + + // Append the ARM-compatible libraries + version.libraries.push(...lwjglNatives.libraries); + } + + return version; + } +} From 0758edf771b6024c4abc7754697733238d0f1147 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 114/185] docs: add license header and doc comment --- src/StatusServer/buffer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/StatusServer/buffer.ts b/src/StatusServer/buffer.ts index c99aaa21..365915ab 100644 --- a/src/StatusServer/buffer.ts +++ b/src/StatusServer/buffer.ts @@ -1,3 +1,10 @@ +/** + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis + */ + function CustomBuffer(existingBuffer: any = Buffer.alloc(48)) { let buffer = existingBuffer; let offset = 0; From e742c33bfa1dd29caacb33a04cb98b0fc836bcf1 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 115/185] docs(: add license header and doc comment --- src/StatusServer/status.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/StatusServer/status.ts b/src/StatusServer/status.ts index d7dadf3b..a16f7fdd 100755 --- a/src/StatusServer/status.ts +++ b/src/StatusServer/status.ts @@ -1,6 +1,8 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import net from 'net' From a61fef5762cb7be51c1b9d2d6b3ea4e772471e47 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 116/185] refactor: unify code style and enhance concurrency logic --- src/utils/Downloader.ts | 363 +++++++++++++++++++++++++--------------- 1 file changed, 228 insertions(+), 135 deletions(-) diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index 2b535827..e12eb849 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -1,144 +1,237 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import fs from 'fs'; import nodeFetch from 'node-fetch'; import { EventEmitter } from 'events'; -interface downloadOptions { - url: string, - path: string, - length: number, - folder: string +/** + * Describes a single file to be downloaded by the Downloader class. + */ +export interface DownloadOptions { + /** The URL to download from */ + url: string; + /** Local path (including filename) where the file will be saved */ + path: string; + /** The total length of the file (in bytes), if known */ + length?: number; + /** Local folder in which the file's path resides */ + folder: string; + /** Optional type descriptor, used when emitting 'progress' events */ + type?: string; } -export default class download extends EventEmitter { - async downloadFile(url: string, path: string, fileName: string) { - if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true }); - const writer = fs.createWriteStream(path + '/' + fileName); - const response = await nodeFetch(url); - let size = response.headers.get('content-length'); - let downloaded = 0; - - return new Promise((resolve: any, reject: any) => { - response.body.on('data', (chunk) => { - downloaded += chunk.length; - this.emit('progress', downloaded, size); - writer.write(chunk); - }); - - response.body.on('end', () => { - writer.end(); - resolve(); - }); - - response.body.on('error', (err) => { - this.emit('error', err); - reject(err); - }); - }) - } - - async downloadFileMultiple(files: downloadOptions, size: number, limit: number = 1, timeout: number = 10000) { - if (limit > files.length) limit = files.length; - let completed = 0; - let downloaded = 0; - let queued = 0; - - let start = new Date().getTime(); - let before = 0; - let speeds = []; - - let estimated = setInterval(() => { - let duration = (new Date().getTime() - start) / 1000; - let loaded = (downloaded - before) * 8; - if (speeds.length >= 5) speeds = speeds.slice(1); - speeds.push((loaded / duration) / 8); - let speed = 0; - for (let s of speeds) speed += s; - speed /= speeds.length; - this.emit("speed", speed); - let time = (size - downloaded) / (speed); - this.emit("estimated", time); - start = new Date().getTime(); - before = downloaded; - }, 500); - - const downloadNext = async () => { - if (queued < files.length) { - let file = files[queued]; - queued++; - - if (!fs.existsSync(file.foler)) fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); - const writer: any = fs.createWriteStream(file.path, { flags: 'w', mode: 0o777 }); - - try { - const response = await nodeFetch(file.url, { timeout: timeout }); - - response.body.on('data', (chunk: any) => { - downloaded += chunk.length; - this.emit('progress', downloaded, size, file.type); - writer.write(chunk); - }); - - response.body.on('end', () => { - writer.end(); - completed++; - downloadNext(); - }); - - } catch (e) { - writer.end(); - completed++; - downloadNext(); - this.emit('error', e); - } - } - }; - - while (queued < limit) downloadNext(); - - return new Promise((resolve: any) => { - const interval = setInterval(() => { - if (completed === files.length) { - clearInterval(estimated); - clearInterval(interval); - resolve(); - } - }, 100); - }); - } - - async checkURL(url: string, timeout = 10000) { - return await new Promise(async (resolve, reject) => { - await nodeFetch(url, { method: 'HEAD', timeout: timeout }).then(res => { - if (res.status === 200) { - resolve({ - size: parseInt(res.headers.get('content-length')), - status: res.status - }) - } - }) - reject(false); - }); - } - - async checkMirror(baseURL: string, mirrors: any) { - for (let mirror of mirrors) { - let url = `${mirror}/${baseURL}`; - let res: any = await this.checkURL(url).then(res => res).catch(err => false); - - if (res?.status == 200) { - return { - url: url, - size: res.size, - status: res.status - } - break; - } continue; - } - return false; - } -} \ No newline at end of file +/** + * A class responsible for downloading single or multiple files, + * emitting events for progress, speed, estimated time, and errors. + */ +export default class Downloader extends EventEmitter { + /** + * Downloads a single file from the given URL to the specified local path. + * Emits "progress" events with the number of bytes downloaded and total size. + * + * @param url - The remote URL to download from + * @param dirPath - Local folder path where the file is saved + * @param fileName - Name of the file (e.g., "mod.jar") + */ + public async downloadFile(url: string, dirPath: string, fileName: string): Promise { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + const writer = fs.createWriteStream(`${dirPath}/${fileName}`); + const response = await nodeFetch(url); + const contentLength = response.headers.get('content-length'); + const totalSize = contentLength ? parseInt(contentLength, 10) : 0; + + let downloaded = 0; + + return new Promise((resolve, reject) => { + response.body.on('data', (chunk: Buffer) => { + downloaded += chunk.length; + // Emit progress with the current number of bytes vs. total size + this.emit('progress', downloaded, totalSize); + writer.write(chunk); + }); + + response.body.on('end', () => { + writer.end(); + resolve(); + }); + + response.body.on('error', (err: Error) => { + this.emit('error', err); + reject(err); + }); + }); + } + + /** + * Downloads multiple files concurrently (up to the specified limit). + * Emits "progress" events with cumulative bytes downloaded vs. total size, + * as well as "speed" and "estimated" events for speed and ETA calculations. + * + * @param files - An array of DownloadOptions describing each file + * @param size - The total size (in bytes) of all files to be downloaded + * @param limit - The maximum number of simultaneous downloads + * @param timeout - A timeout in milliseconds for each fetch request + */ + public async downloadFileMultiple( + files: DownloadOptions[], + size: number, + limit: number = 1, + timeout: number = 10000 + ): Promise { + if (limit > files.length) { + limit = files.length; + } + + let completed = 0; // Number of downloads completed + let downloaded = 0; // Cumulative bytes downloaded + let queued = 0; // Index of the next file to download + + let start = Date.now(); + let before = 0; + let speeds: number[] = []; + + // A repeating interval to calculate speed and ETA + const estimated = setInterval(() => { + const duration = (Date.now() - start) / 1000; // seconds + const chunkDownloaded = downloaded - before; // new bytes in this interval + if (speeds.length >= 5) { + speeds.shift(); // keep last 4 measurements + } + speeds.push(chunkDownloaded / duration); + + // Average of speeds + const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length; + this.emit('speed', avgSpeed); + + const timeRemaining = (size - downloaded) / avgSpeed; + this.emit('estimated', timeRemaining); + + start = Date.now(); + before = downloaded; + }, 500); + + // Recursive function that downloads the next file in the queue + const downloadNext = async (): Promise => { + if (queued < files.length) { + const file = files[queued]; + queued++; + + if (!fs.existsSync(file.folder)) { + fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); + } + + // Create a write stream for the file + const writer = fs.createWriteStream(file.path, { flags: 'w', mode: 0o777 }); + + try { + const response = await nodeFetch(file.url, { timeout }); + // On data reception, increase the global downloaded counter + response.body.on('data', (chunk: Buffer) => { + downloaded += chunk.length; + // Emit progress with the current total downloaded vs. full size + this.emit('progress', downloaded, size, file.type); + writer.write(chunk); + }); + + response.body.on('end', () => { + writer.end(); + completed++; + downloadNext(); + }); + + response.body.on('error', (err: Error) => { + writer.end(); + completed++; + downloadNext(); + this.emit('error', err); + }); + } catch (e) { + writer.end(); + completed++; + downloadNext(); + this.emit('error', e); + } + } + }; + + // Start "limit" concurrent downloads + for (let i = 0; i < limit; i++) { + downloadNext(); + } + + // Wait until all downloads complete + return new Promise((resolve) => { + const interval = setInterval(() => { + if (completed === files.length) { + clearInterval(estimated); + clearInterval(interval); + resolve(); + } + }, 100); + }); + } + + /** + * Performs a HEAD request on the given URL to check if it is valid (status=200) + * and retrieves the "content-length" if available. + * + * @param url The URL to check + * @param timeout Time in ms before the request times out + * @returns An object containing { size, status } or rejects with false + */ + public async checkURL( + url: string, + timeout: number = 10000 + ): Promise<{ size: number; status: number } | false> { + + return new Promise(async (resolve) => { + try { + const res = await nodeFetch(url, { method: 'HEAD', timeout }); + if (res.status === 200) { + const contentLength = res.headers.get('content-length'); + const size = contentLength ? parseInt(contentLength, 10) : 0; + resolve({ size, status: 200 }); + } else resolve(false); + } catch (e) { + resolve(false); + } + }); + } + + + /** + * Tries each mirror in turn, constructing an URL (mirror + baseURL). If a valid + * response is found (status=200), it returns the final URL and size. Otherwise, returns false. + * + * @param baseURL The relative path (e.g. "group/id/artifact.jar") + * @param mirrors An array of possible mirror base URLs + * @returns An object { url, size, status } if found, or false if all mirrors fail + */ + public async checkMirror( + baseURL: string, + mirrors: string[] + ): Promise<{ url: string; size: number; status: number } | false> { + + for (const mirror of mirrors) { + const testURL = `${mirror}/${baseURL}`; + const res = await this.checkURL(testURL); + + if (res !== false && res.status === 200) { + return { + url: testURL, + size: res.size, + status: 200 + }; + } + } + return false; + } +} From b82dc777fb2a16295359c30ff034415f65830216 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:09:16 +0100 Subject: [PATCH 117/185] refactor: unify code style and doc usage --- src/utils/Index.ts | 385 +++++++++++++++++++++++++++++++-------------- 1 file changed, 264 insertions(+), 121 deletions(-) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 020b6062..ffe760a4 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -1,146 +1,289 @@ /** - * @author Luuxis - * @license CC-BY-NC 4.0 - https://creativecommons.org/licenses/by-nc/4.0/ + * This code is distributed under the CC-BY-NC 4.0 license: + * https://creativecommons.org/licenses/by-nc/4.0/ + * + * Original author: Luuxis */ import crypto from 'crypto'; import fs from 'fs'; -import admZip from 'adm-zip'; - -function getPathLibraries(main: any, nativeString?: any, forceExt?: any) { - let libSplit = main.split(':') - let fileName = libSplit[3] ? `${libSplit[2]}-${libSplit[3]}` : libSplit[2]; - let finalFileName = fileName.includes('@') ? fileName.replace('@', '.') : `${fileName}${nativeString || ''}${forceExt || '.jar'}`; - let pathLib = `${libSplit[0].replace(/\./g, '/')}/${libSplit[1]}/${libSplit[2].split('@')[0]}` - return { - path: pathLib, - name: `${libSplit[1]}-${finalFileName}` - }; +import AdmZip from 'adm-zip'; +import path from 'path'; + +// This interface defines the structure of a Minecraft library rule. +interface LibraryRule { + action: 'allow' | 'disallow'; + os?: { + name?: string; + }; + features?: any; // Adjust or remove if not used in your code +} + +/** + * Represents a Library object, possibly containing rules or additional fields. + * Adjust according to your actual library structure. + */ +interface MinecraftLibrary { + name: string; + rules?: LibraryRule[]; + downloads?: { + artifact?: { + url?: string; + size?: number; + }; + }; + natives?: Record; + [key: string]: any; // Extend if needed +} + +/** + * Represents a minimal version JSON structure to check if it's considered "old" (pre-1.6 or legacy). + */ +interface MinecraftVersionJSON { + assets?: string; // "legacy" or "pre-1.6" indicates older assets + [key: string]: any; +} + +/** + * Parses a Gradle/Maven identifier string (like "net.minecraftforge:forge:1.19-41.0.63") + * into a local file path (group/artifact/version) and final filename (artifact-version.jar). + * Optionally allows specifying a native string suffix or forcing an extension. + * + * @param main A Gradle-style coordinate (group:artifact:version[:classifier]) + * @param nativeString A suffix for native libraries (e.g., "-natives-linux") + * @param forceExt A forced file extension (default is ".jar") + * @returns An object with `path` and `name`, where `path` is the directory path and `name` is the filename + */ +function getPathLibraries(main: string, nativeString?: string, forceExt?: string) { + // Example "net.minecraftforge:forge:1.19-41.0.63" + const libSplit = main.split(':'); + + // If there's a fourth element, it's typically a classifier appended to version + const fileName = libSplit[3] ? `${libSplit[2]}-${libSplit[3]}` : libSplit[2]; + + // Replace '@' in versions if present (e.g., "1.0@beta" => "1.0.beta") + let finalFileName = fileName.includes('@') + ? fileName.replace('@', '.') + : `${fileName}${nativeString || ''}${forceExt || '.jar'}`; + + // Construct the path: "net.minecraftforge" => "net/minecraftforge" + // artifact => "forge" + // version => "1.19-41.0.63" + const pathLib = `${libSplit[0].replace(/\./g, '/')}/${libSplit[1]}/${libSplit[2].split('@')[0]}`; + + return { + path: pathLib, + name: `${libSplit[1]}-${finalFileName}` + }; } -async function getFileHash(filePath: string, algorithm: string = 'sha1') { - let shasum = crypto.createHash(algorithm); +/** + * Computes a hash (default SHA-1) of the given file by streaming its contents. + * + * @param filePath Full path to the file on disk + * @param algorithm Hashing algorithm (default: "sha1") + * @returns A Promise resolving to the hex string of the file's hash + */ +async function getFileHash(filePath: string, algorithm: string = 'sha1'): Promise { + const shasum = crypto.createHash(algorithm); + const fileStream = fs.createReadStream(filePath); - let file = fs.createReadStream(filePath); - file.on('data', data => { - shasum.update(data); - }); + return new Promise((resolve) => { + fileStream.on('data', (data) => { + shasum.update(data); + }); - let hash = await new Promise(resolve => { - file.on('end', () => { - resolve(shasum.digest('hex')); - }); - }); - return hash; + fileStream.on('end', () => { + resolve(shasum.digest('hex')); + }); + }); } -function isold(json: any) { - return json.assets === 'legacy' || json.assets === 'pre-1.6' +/** + * Determines if a given Minecraft version JSON is considered "old" + * by checking its assets field (e.g., "legacy" or "pre-1.6"). + * + * @param json The Minecraft version JSON + * @returns true if it's an older version, false otherwise + */ +function isold(json: MinecraftVersionJSON): boolean { + return json.assets === 'legacy' || json.assets === 'pre-1.6'; } +/** + * Returns metadata necessary to download specific loaders (Forge, Fabric, etc.) + * based on a loader type string (e.g., "forge", "fabric"). + * If the loader type is unrecognized, returns undefined. + * + * @param type A string representing the loader type + */ function loader(type: string) { - if (type === 'forge') { - return { - metaData: 'https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json', - meta: 'https://files.minecraftforge.net/net/minecraftforge/forge/${build}/meta.json', - promotions: 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json', - install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer', - universal: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-universal', - client: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-client', - } - } else if (type === 'neoforge') { - return { - legacyMetaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', - metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge', - legacyInstall: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar', - install: 'https://maven.neoforged.net/net/neoforged/neoforge/${version}/neoforge-${version}-installer.jar' - } - } else if (type === 'fabric') { - return { - metaData: 'https://meta.fabricmc.net/v2/versions', - json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' - } - } else if (type === 'legacyfabric') { - return { - metaData: 'https://meta.legacyfabric.net/v2/versions', - json: 'https://meta.legacyfabric.net/v2/versions/loader/${version}/${build}/profile/json' - } - } else if (type === 'quilt') { - return { - metaData: 'https://meta.quiltmc.org/v3/versions', - json: 'https://meta.quiltmc.org/v3/versions/loader/${version}/${build}/profile/json' - } - } + if (type === 'forge') { + return { + metaData: 'https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json', + meta: 'https://files.minecraftforge.net/net/minecraftforge/forge/${build}/meta.json', + promotions: 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json', + install: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-installer', + universal: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-universal', + client: 'https://maven.minecraftforge.net/net/minecraftforge/forge/${version}/forge-${version}-client' + }; + } else if (type === 'neoforge') { + return { + legacyMetaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', + metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge', + legacyInstall: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar', + install: 'https://maven.neoforged.net/net/neoforged/neoforge/${version}/neoforge-${version}-installer.jar' + }; + } else if (type === 'fabric') { + return { + metaData: 'https://meta.fabricmc.net/v2/versions', + json: 'https://meta.fabricmc.net/v2/versions/loader/${version}/${build}/profile/json' + }; + } else if (type === 'legacyfabric') { + return { + metaData: 'https://meta.legacyfabric.net/v2/versions', + json: 'https://meta.legacyfabric.net/v2/versions/loader/${version}/${build}/profile/json' + }; + } else if (type === 'quilt') { + return { + metaData: 'https://meta.quiltmc.org/v3/versions', + json: 'https://meta.quiltmc.org/v3/versions/loader/${version}/${build}/profile/json' + }; + } + // If none match, return undefined } +/** + * A list of potential Maven mirrors for downloading libraries. + */ +const mirrors = [ + 'https://maven.minecraftforge.net', + 'https://maven.neoforged.net/releases', + 'https://maven.creeperhost.net', + 'https://libraries.minecraft.net', + 'https://repo1.maven.org/maven2' +]; + +/** + * Reads a .jar or .zip file, returning specific entries or listing file entries in the archive. + * Uses adm-zip under the hood. + * + * @param jar Full path to the jar/zip file + * @param file The file entry to extract data from (e.g., "install_profile.json"). If null, returns all entries or partial lists. + * @param prefix A path prefix filter (e.g., "maven/org/lwjgl/") if you want a list of matching files instead of direct extraction + * @returns A buffer or an array of { name, data }, or a list of filenames if prefix is given + */ +async function getFileFromArchive(jar: string, file: string | null = null, prefix: string | null = null): Promise { + const result: any[] = []; + const zip = new AdmZip(jar); + const entries = zip.getEntries(); + + return new Promise((resolve) => { + for (const entry of entries) { + if (!entry.isDirectory && !prefix) { + // If no prefix is given, either return a specific file if 'file' is set, + // or accumulate all entries if 'file' is null + if (entry.entryName === file) { + return resolve(entry.getData()); + } else if (!file) { + result.push({ name: entry.entryName, data: entry.getData() }); + } + } + + // If a prefix is given, collect all entry names under that prefix + if (!entry.isDirectory && prefix && entry.entryName.includes(prefix)) { + result.push(entry.entryName); + } + } -let mirrors = [ - "https://maven.minecraftforge.net", - "https://maven.neoforged.net/releases", - "https://maven.creeperhost.net", - "https://libraries.minecraft.net", - "https://repo1.maven.org/maven2" -] - -async function getFileFromArchive(jar: string, file: string = null, path: string = null) { - let fileReturn: any = [] - let zip = new admZip(jar); - let entries = zip.getEntries(); - - return await new Promise(resolve => { - for (let entry of entries) { - if (!entry.isDirectory && !path) { - if (entry.entryName == file) fileReturn = entry.getData(); - if (!file) fileReturn.push({ name: entry.entryName, data: entry.getData() }); - } - - if (!entry.isDirectory && entry.entryName.includes(path) && path) { - fileReturn.push(entry.entryName); - } - } - resolve(fileReturn); - }); + if (file && !prefix) { + // If a specific file was requested but not found, return undefined or empty + return resolve(undefined); + } + + // Otherwise, resolve the array of results + resolve(result); + }); } -async function createZIP(files: any, ignored: any = null) { - let zip = new admZip(); +/** + * Creates a new ZIP buffer by combining multiple file entries (name, data), + * optionally ignoring entries containing a certain string (e.g. "META-INF"). + * + * @param files An array of { name, data } objects to include in the new zip + * @param ignored A substring to skip any matching files + * @returns A buffer containing the newly created ZIP + */ +async function createZIP(files: { name: string; data: Buffer }[], ignored: string | null = null): Promise { + const zip = new AdmZip(); - return await new Promise(resolve => { - for (let entry of files) { - if (ignored && entry.name.includes(ignored)) continue; - zip.addFile(entry.name, entry.data); - } - resolve(zip.toBuffer()); - }); + return new Promise((resolve) => { + for (const entry of files) { + if (ignored && entry.name.includes(ignored)) { + continue; + } + zip.addFile(entry.name, entry.data); + } + resolve(zip.toBuffer()); + }); } -function skipLibrary(lib) { - let Lib = { win32: "windows", darwin: "osx", linux: "linux" }; - - let skip = false; - if (lib.rules) { - skip = true; - lib.rules.forEach(({ action, os, features }) => { - if (features) return true; - if (action === 'allow' && ((os && os.name === Lib[process.platform]) || !os)) { - skip = false; - } - - if (action === 'disallow' && ((os && os.name === Lib[process.platform]) || !os)) { - skip = true; - } - }); - } - return skip; +/** + * Determines if a library should be skipped based on its 'rules' property. + * For example, it might skip libraries if action='disallow' for the current OS, + * or if there are specific conditions not met. + * + * @param lib A library object (with optional 'rules' array) + * @returns true if the library should be skipped, false otherwise + */ +function skipLibrary(lib: MinecraftLibrary): boolean { + // Map Node.js platform strings to Mojang's naming + const LibMap: Record = { + win32: 'windows', + darwin: 'osx', + linux: 'linux' + }; + + // If no rules, it's not skipped + if (!lib.rules) { + return false; + } + + let shouldSkip = true; + + for (const rule of lib.rules) { + // If features exist, your logic can handle them here + if (rule.features) { + // Implementation is up to your usage + continue; + } + + // "allow" means it can be used if OS matches (or no OS specified) + // "disallow" means skip if OS matches (or no OS specified) + if ( + rule.action === 'allow' && + ((rule.os && rule.os.name === LibMap[process.platform]) || !rule.os) + ) { + shouldSkip = false; + } else if ( + rule.action === 'disallow' && + ((rule.os && rule.os.name === LibMap[process.platform]) || !rule.os) + ) { + shouldSkip = true; + } + } + + return shouldSkip; } +// Export all utility functions and constants export { - getPathLibraries, - isold, - getFileHash, - mirrors, - loader, - getFileFromArchive, - createZIP, - skipLibrary -}; \ No newline at end of file + getPathLibraries, + getFileHash, + isold, + loader, + mirrors, + getFileFromArchive, + createZIP, + skipLibrary +}; From 0b1a613442c35cccf0b3ff7cab5246c9fd4e74e7 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:18:18 +0100 Subject: [PATCH 118/185] publish 4.0.0 alpha 1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 564348e0..d673d344 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "3.11.4", + "version": "4.0.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "3.11.4", + "version": "4.0.0-alpha.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index de8394f9..9d97aadb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "3.11.4", + "version": "4.0.0-alpha.1", "types": "./build/Index.d.ts", "exports": { ".": { From f3a9e737791ecf901ad3a5264ce2c3d98a75b6f4 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:31:18 +0200 Subject: [PATCH 119/185] improve library merging logic to keep latest version per group --- src/Minecraft/Minecraft-Arguments.ts | 31 +++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index c375452f..e6a2d3ec 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -60,6 +60,13 @@ export interface VersionJSON { nativesList?: Array; } +export interface Library { + name: string; + loader?: string; + natives?: Record; + rules?: { os?: { name?: string } }[]; +} + /** * Represents a loader JSON structure (e.g. Forge or Fabric). * Again, adapt as your loader's actual structure requires. @@ -278,15 +285,29 @@ export default class MinecraftArguments { combinedLibraries = loaderJson.libraries.concat(combinedLibraries); } - // Remove duplicates by `library.name` - combinedLibraries = combinedLibraries.filter((lib, index, self) => - index === self.findIndex(other => other.name === lib.name) + const map = new Map(); + + for (const dep of combinedLibraries) { + const parts = dep.name.split(":"); + const key = parts.slice(0, 2).join(":"); + const classifier = parts[3] ? parts[3] : ""; + const versionKey = `${key}:${classifier}`; + + const current = map.get(versionKey); + const version = parts[2]; + + if (!current || version < current.name.split(":")[2]) { + map.set(versionKey, dep); + } + } + + const latest: Record = Object.fromEntries( + Array.from(map.entries()).map(([key, value]) => [key, value as Library]) ); // Prepare to accumulate all library paths const librariesList: string[] = []; - - for (const lib of combinedLibraries) { + for (const lib of Object.values(latest)) { // Skip certain logging libraries if flagged (e.g., in Forge's "loader" property) if (lib.loader && lib.name.startsWith('org.apache.logging.log4j:log4j-slf4j2-impl')) continue; From 7045472ac23d0f4f016ffe2eb4e1eadef95e0144 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:31:18 +0200 Subject: [PATCH 120/185] update auth endpoint to new domain --- test/AZauth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/AZauth.js b/test/AZauth.js index dbb261e8..4f3207b9 100755 --- a/test/AZauth.js +++ b/test/AZauth.js @@ -1,7 +1,7 @@ const prompt = require('prompt') const { AZauth, Launch } = require('../build/Index'); const launch = new Launch(); -const auth = new AZauth('https://poke-universe.fr'); +const auth = new AZauth('https://nincraft.fr'); const fs = require('fs'); let mc @@ -78,7 +78,7 @@ async function main() { } } - // await launch.Launch(opt); + await launch.Launch(opt); launch.on('extract', extract => { console.log(extract); From f1b7e2bd071c8436f5cac267ef14688d3cb3bb1e Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:31:19 +0200 Subject: [PATCH 121/185] update test configuration with Fabric loader and new version options --- test/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/index.js b/test/index.js index 0bcfac8a..da096d46 100755 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,4 @@ +const path = require('path'); const { Microsoft, Launch } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -22,10 +23,8 @@ let mc } let opt = { - // url: 'http://www.pokefree.fr/launcher_pokefree/files?instance=PokeFree', authenticator: mc, timeout: 10000, - instance: 'PokeFree', path: './Minecraft', version: '1.16.5', detached: false, @@ -33,8 +32,8 @@ let mc downloadFileMultiple: 30, loader: { - type: 'forge', - build: '1.16.5-36.2.35', + type: 'fabric', + build: 'latest', enable: true, path: './', }, From 8b389a56d40e5bf105230e2fe561d8b858796b3f Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:31:36 +0200 Subject: [PATCH 122/185] publish 4.0.0 stable --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d673d344..c454c6b4 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.0.0-alpha.1", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.0.0-alpha.1", + "version": "4.0.0", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 9d97aadb..3168d8c6 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.0.0-alpha.1", + "version": "4.0.0", "types": "./build/Index.d.ts", "exports": { ".": { From e304ca1ec265e192c40de6b684f08b2daf7d1547 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:44:23 +0200 Subject: [PATCH 123/185] Update README.md --- README.md | 335 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 205 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index b0aff7e3..b22f397b 100755 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -# minecraft-java-core -NodeJS Module for Minecraft launcher -
-[![Number](https://img.shields.io/npm/v/minecraft-java-core?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -[![Install](https://img.shields.io/npm/dm/minecraft-java-core.svg?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -[![size](https://img.shields.io/github/languages/code-size/luuxis/minecraft-java-core?style=social&logo=appveyor)](https://npmjs.com/minecraft-java-core) -[![sizeinstall](https://badgen.net/packagephobia/install/minecraft-java-core)](https://npmjs.com/minecraft-java-core) +![logo](https://raw.githubusercontent.com/Luuxis/minecraft-java-core/main/logo.png) + +##### v4.0.0 • **minecraft‑java‑core** +[![License: CC‑BY‑NC 4.0](https://img.shields.io/badge/License-CC--BY--NC%204.0-yellow.svg)](https://creativecommons.org/licenses/by-nc/4.0/) +![stable version](https://img.shields.io/badge/stable_version-4.0.0-blue) + +**minecraft‑java‑core** is a **NodeJS/TypeScript** solution for launching both vanilla *and* modded Minecraft Java Edition without juggling JSON manifests, assets, libraries or Java runtimes yourself. Think of it as the *core* of an Electron/NW.js/CLI launcher. + +--- + +### Getting support +Need help or just want to chat? Join the community Discord!

@@ -13,133 +18,203 @@ NodeJS Module for Minecraft launcher

--- -## Avantages :dizzy: -- Auto check & downloading compatible java version -- Support 100% custom minecraft version -- Work with ftp without any zip file, juste drop folder in your ftp -- Auto check & delete file with bad hash & size - -# Install Client - -## Quick Start :zap: -```npm -git clone https://github.com/luuxis/Selvania-Launcher.git -cd Selvania-Launcher -npm install -npm start -``` -## Installation :package: -```npm +### Installing + +```bash npm i minecraft-java-core +# or +yarn add minecraft-java-core ``` -## Usage :triangular_flag_on_post: -Require library -```javascript -const { Launch, Mojang } = require('minecraft-java-core'); +*Requirements:* Node ≥ 18, TypeScript (only if you import *.ts*), 7‑Zip embedded binary. + +--- + +### Standard Example (ESM) +```ts +import { Launch, Microsoft } from 'minecraft-java-core'; + +// ⚠️ In production, perform auth **before** initialising the launcher +// so you can handle refresh / error flows cleanly. +const auth = await Microsoft.auth({ + client_id: '00000000-0000-0000-0000-000000000000', + type: 'terminal' // 'electron' | 'nwjs' +}); + +const launcher = new Launch(); + +launcher.on('progress', p => console.log(`[DL] ${p}%`)) + .on('data', line => process.stdout.write(line)) + .on('close', () => console.log('Game exited.')); + +await launcher.launch({ + root: './minecraft', + authenticator: auth, + version: { + number: '1.20.4', + type: 'release' + }, + loader: { type: 'fabric', version: '0.15.9' }, + memory: { min: '2G', max: '4G' } +}); ``` -## Launch :rocket: -### Options -```javascript -const { Mojang, Launch } = require('minecraft-java-core'); -const launch = new Launch(); - -async function main() { - let opt = { - url: 'https://launcher.luuxis.fr/files/?instance=PokeMoonX', - authenticator: await Mojang.login('Luuxis'), - timeout: 10000, - path: './Minecraft', - instance: 'PokeMoonX', - version: '1.20.4', - detached: false, - intelEnabledMac: true, - downloadFileMultiple: 30, - - loader: { - path: '', - type: 'forge', - build: 'latest', - enable: true - }, - - verify: true, - ignored: [ - 'config', - 'essential', - 'logs', - 'resourcepacks', - 'saves', - 'screenshots', - 'shaderpacks', - 'W-OVERFLOW', - 'options.txt', - 'optionsof.txt' - ], - - JVM_ARGS: [], - GAME_ARGS: [], - - java: { - path: null, - version: null, - type: 'jre', - }, - - screen: { - width: 1500, - height: 900 - }, - - memory: { - min: '4G', - max: '6G' - } - } - - await launch.Launch(opt); - - launch.on('extract', extract => { - console.log(extract); - }); - - launch.on('progress', (progress, size, element) => { - console.log(`Downloading ${element} ${Math.round((progress / size) * 100)}%`); - }); - - launch.on('check', (progress, size, element) => { - console.log(`Checking ${element} ${Math.round((progress / size) * 100)}%`); - }); - - launch.on('estimated', (time) => { - let hours = Math.floor(time / 3600); - let minutes = Math.floor((time - hours * 3600) / 60); - let seconds = Math.floor(time - hours * 3600 - minutes * 60); - console.log(`${hours}h ${minutes}m ${seconds}s`); - }) - - launch.on('speed', (speed) => { - console.log(`${(speed / 1067008).toFixed(2)} Mb/s`) - }) - - launch.on('patch', patch => { - console.log(patch); - }); - - launch.on('data', (e) => { - console.log(e); - }) - - launch.on('close', code => { - console.log(code); - }); - - launch.on('error', err => { - console.log(err); - }); -} - -main() +--- + +## Documentation + +### Launch class + +| Function | Type | Description | +|----------|---------|------------------------------------------------------------------------| +| `launch` | Promise | Launches Minecraft with the given **`LaunchOptions`** (see below). | + +#### LaunchOptions + +| Parameter | Type | Description | Required | +|-----------|------|-------------|----------| +| `path` | String | Working directory where game files are stored (usually `.minecraft`). | ✔︎ | +| `url` | String \| null | Custom version manifest base URL (only for mirror setups). | — | +| `authenticator` | Object | Microsoft / Mojang / AZauth profile returned by the authenticator. | ✔︎ | +| `timeout` | Integer | Network timeout in **milliseconds** for downloads. | — | +| `version` | String | `'latest_release'`, `'latest_snapshot'`, `'1.21.1'`. | — | +| `instance` | String \| null | Name of the instance if you manage multiple profiles. | — | +| `detached` | Boolean | Detach the Java process from the launcher. | — | +| `intelEnabledMac` | Boolean | Force Rosetta when running on Apple Silicon. | — | +| `downloadFileMultiple` | Integer | Max parallel downloads. | — | +| `loader.enable` | Boolean | Whether to install a mod‑loader (Forge/Fabric/…). | — | +| `loader.type` | String \| null | `forge`, `neoforge`, `fabric`, `legacyfabric`, `quilt`. | — | +| `loader.build` | String | Loader build tag (e.g. `latest`, `0.15.9`). | — | +| `loader.path` | String | Destination folder for loader files. Defaults to `./loader`. | — | +| `mcp` | String \| null | Path to MCP configuration for legacy mods. | — | +| `verify` | Boolean | Verify SHA‑1 of downloaded files. | — | +| `ignored` | Array | List of files to skip during verification. | — | +| `JVM_ARGS` | Array | Extra JVM arguments. | — | +| `GAME_ARGS` | Array | Extra Minecraft arguments. | — | +| `java.path` | String \| null | Absolute path to Java runtime. | — | +| `java.version` | String \| null | Force a specific Java version (e.g. `17`). | — | +| `java.type` | String | `jre` or `jdk`. | — | +| `screen.width` | Number \| null | Width of game window. | — | +| `screen.height` | Number \| null | Height of game window. | — | +| `screen.fullscreen` | Boolean | Start the game in fullscreen mode. | — | +| `memory.min` | String | Minimum RAM (e.g. `1G`). | ✔︎ | +| `memory.max` | String | Maximum RAM (e.g. `2G`). | ✔︎ | + +> **Recommendation:** Start with the minimal set (`authenticator`, `path`, `version`, `memory`) and gradually add overrides only when you need them. + +#### Default configuration + +Below is the complete **default** `LaunchOptions` object returned by +`minecraft‑java‑core` when you don’t override any field. Use it as a quick +reference for every available parameter and its default value. +(Parameters marked *nullable* can be left `null`/`undefined` and the library +will figure out sane values.) + +```ts +const defaultOptions = { + url: null, // Optional custom manifest URL + authenticator: null, // Microsoft/Mojang/AZauth profile + timeout: 10000, // Network timeout in ms + path: '.Minecraft', // Root directory (alias: root) + version: 'latest_release', // Minecraft version (string or 'latest_…') + instance: null, // Multi‑instance name (optional) + detached: false, // Detach Java process from parent + intelEnabledMac: false, // Rosetta toggle for Apple Silicon + downloadFileMultiple: 5, // Parallel downloads + + loader: { + path: './loader', // Where to install loaders + type: null, // forge | neoforge | fabric | … + build: 'latest', // Build number / tag + enable: false, // Whether to install the loader + }, + + mcp: null, // Path to MCP config (legacy mods) + + verify: false, // SHA‑1 check after download + ignored: [], // Files to skip verification + JVM_ARGS: [], // Extra JVM arguments + GAME_ARGS: [], // Extra game arguments + + java: { + path: null, // Custom JVM path + version: null, // Explicit Java version + type: 'jre', // jre | jdk + }, + + screen: { + width: null, + height: null, + fullscreen: false, + }, + + memory: { + min: '1G', + max: '2G', + }, +} as const; +``` + +> **Note** : Any field you provide when calling `Launch.launch()` will be +> merged on top of these defaults; you rarely need to specify more than +> `authenticator`, `path`, `version` and `memory`. + +--- + +#### Events + +| Event Name | Payload | Description | +|-------------|---------|--------------------------------------------------------------| +| `data` | String | Raw output from the Java process. | +| `progress` | Number | Global download progress percentage. | +| `speed` | Number | Current download speed (kB/s). | +| `estimated` | Number | Estimated time remaining (s). | +| `extract` | String | Name of the file currently being extracted. | +| `patch` | String | Loader patch currently applied. | +| `close` | void | Emitted when the Java process exits. | +| `error` | Error | Something went wrong. | + +--- + +### Authentication *(built‑in)* + +* **Microsoft** — OAuth 2 Device Code flow via Xbox Live → XSTS → Minecraft. +* **Mojang** *(legacy)* — classic Yggdrasil endpoint. +* **AZauth** — community Yggdrasil‑compatible server. + +> The authenticator returns a profile object that you pass directly to `Launch.launch()`. + +--- + +### Utilities + +* **Downloader** — resilient downloader with resume, integrity check & `progress`/`speed` events. +* **Status** — simple TCP ping that returns MOTD, player count & latency. + +--- + +### File structure (simplified) +``` +src/ + Authenticator/ Microsoft, Mojang, AZauth flows + Minecraft/ Version JSON, assets, libraries, args builder + Minecraft-Loader/ Forge, NeoForge, Fabric, Quilt, … installers + StatusServer/ Server ping implementation + utils/ Downloader & helpers + Launch.ts Main entry point +assets/ LWJGL native indexes ``` + +--- + +### Contributors +See the commit history for a full list. Special thanks to: + +* **Luuxis** — original author. +* Community testers & issue reporters. + +--- + +### License +Released under **Creative Commons Attribution‑NonCommercial 4.0 International**. \ No newline at end of file From 53b2917c6aa11ed313868fb584c1afac5f4673a2 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:48 +0200 Subject: [PATCH 124/185] update project to remove node-fetch dependency and require node 18+ --- package-lock.json | 50 +++++------------------------------------------ package.json | 6 ++++-- 2 files changed, 9 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index c454c6b4..5f93e8a2 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "minecraft-java-core", - "version": "4.0.0", + "version": "4.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.0.0", + "version": "4.0.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", "adm-zip": "^0.5.16", "node-7z": "^3.0.0", - "node-fetch": "^2.7.0", "prompt": "^1.3.0", "tslib": "^2.8.1" }, @@ -23,6 +22,9 @@ "@types/node-fetch": "^2.6.12", "rimraf": "^6.0.1", "typescript": "^5.7.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@colors/colors": { @@ -498,26 +500,6 @@ "node": ">=10" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -767,12 +749,6 @@ "node": ">=8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -800,22 +776,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 3168d8c6..ffd8ccb8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.0.0", + "version": "4.0.1", "types": "./build/Index.d.ts", "exports": { ".": { @@ -14,6 +14,9 @@ "build": "rimraf ./build && tsc", "prepublishOnly": "npm i && npm run build" }, + "engines": { + "node": ">=18.0.0" + }, "files": [ "assets/**", "build/**", @@ -35,7 +38,6 @@ "7zip-bin": "^5.2.0", "adm-zip": "^0.5.16", "node-7z": "^3.0.0", - "node-fetch": "^2.7.0", "prompt": "^1.3.0", "tslib": "^2.8.1" }, From 37a37b76edc8e8b56b1a06119b0a3ac04fd46dfc Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 125/185] refactor authenticators to use fetch instead of node-fetch --- src/Authenticator/AZauth.ts | 13 +++++++------ src/Authenticator/Microsoft.ts | 15 ++++++++------- src/Authenticator/Mojang.ts | 9 ++++----- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 017eb3aa..0c0232ef 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -5,7 +5,7 @@ * Original author: Luuxis */ -import nodeFetch from 'node-fetch'; +import { Buffer } from 'node:buffer'; // This interface defines the structure of the user object // returned by the AZauth service. You can adapt it to your needs. @@ -63,7 +63,7 @@ export default class AZauth { * @returns A Promise that resolves to an AZauthUser object */ public async login(username: string, password: string, A2F: string | null = null): Promise { - const response = await nodeFetch(`${this.url}/authenticate`, { + const response = await fetch(`${this.url}/authenticate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -119,7 +119,7 @@ export default class AZauth { * @returns A Promise that resolves to an updated AZauthUser object or an error object */ public async verify(user: AZauthUser): Promise { - const response = await nodeFetch(`${this.url}/verify`, { + const response = await fetch(`${this.url}/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -168,7 +168,7 @@ export default class AZauth { * @returns A Promise that resolves to true if logout is successful, otherwise false */ public async signout(user: AZauthUser): Promise { - const response = await nodeFetch(`${this.url}/logout`, { + const response = await fetch(`${this.url}/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -190,7 +190,7 @@ export default class AZauth { * @returns A Promise resolving to an object with the skin URL (and optional base64 data) */ private async skin(uuid: string): Promise<{ url: string; base64?: string }> { - let response = await nodeFetch(`${this.skinAPI}/${uuid}`, { + let response = await fetch(`${this.skinAPI}/${uuid}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); @@ -203,7 +203,8 @@ export default class AZauth { } // Otherwise, convert the skin image to a base64-encoded string - const buffer = await response.buffer(); + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); return { url: `${this.skinAPI}/${uuid}`, base64: `data:image/png;base64,${buffer.toString('base64')}` diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 7e36fb67..9d08d37b 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -5,7 +5,7 @@ * Original author: Luuxis */ -import nodeFetch from 'node-fetch'; +import { Buffer } from 'node:buffer'; import crypto from 'crypto'; // Possible client types (Electron, NW.js, or terminal usage) @@ -61,8 +61,9 @@ export interface AuthResponse { // Utility function to fetch and convert an image to base64 async function getBase64(url: string): Promise { - const response = await nodeFetch(url); - const buffer = await response.buffer(); + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); return buffer.toString('base64'); } @@ -134,7 +135,7 @@ export default class Microsoft { */ private async exchangeCodeForToken(code: string): Promise { try { - const response = await nodeFetch('https://login.live.com/oauth20_token.srf', { + const response = await fetch('https://login.live.com/oauth20_token.srf', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=https://login.live.com/oauth20_desktop.srf` @@ -176,7 +177,7 @@ export default class Microsoft { // Otherwise, refresh the token try { - const response = await nodeFetch('https://login.live.com/oauth20_token.srf', { + const response = await fetch('https://login.live.com/oauth20_token.srf', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `grant_type=refresh_token&client_id=${this.client_id}&refresh_token=${acc.refresh_token}` @@ -330,7 +331,7 @@ export default class Microsoft { */ public async getProfile(mcLogin: { access_token: string }): Promise { try { - const response = await nodeFetch('https://api.minecraftservices.com/minecraft/profile', { + const response = await fetch('https://api.minecraftservices.com/minecraft/profile', { method: 'GET', headers: { Authorization: `Bearer ${mcLogin.access_token}` @@ -377,7 +378,7 @@ export default class Microsoft { */ private async fetchJSON(url: string, options: Record): Promise { try { - const response = await nodeFetch(url, options); + const response = await fetch(url, options); return response.json(); } catch (err: any) { return { error: err.message }; diff --git a/src/Authenticator/Mojang.ts b/src/Authenticator/Mojang.ts index 45605134..1dce376a 100755 --- a/src/Authenticator/Mojang.ts +++ b/src/Authenticator/Mojang.ts @@ -6,7 +6,6 @@ */ import crypto from 'crypto'; -import nodeFetch from 'node-fetch'; let api_url = 'https://authserver.mojang.com'; @@ -27,7 +26,7 @@ async function login(username: string, password?: string) { } } - let message = await nodeFetch(`${api_url}/authenticate`, { + let message = await fetch(`${api_url}/authenticate`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -62,7 +61,7 @@ async function login(username: string, password?: string) { } async function refresh(acc: any) { - let message = await nodeFetch(`${api_url}/refresh`, { + let message = await fetch(`${api_url}/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -93,7 +92,7 @@ async function refresh(acc: any) { } async function validate(acc: any) { - let message = await nodeFetch(`${api_url}/validate`, { + let message = await fetch(`${api_url}/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -112,7 +111,7 @@ async function validate(acc: any) { } async function signout(acc: any) { - let message = await nodeFetch(`${api_url}/invalidate`, { + let message = await fetch(`${api_url}/invalidate`, { method: 'POST', headers: { 'Content-Type': 'application/json' From 587d8b9bfc9374828ad37162139125cf37324573 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 126/185] export Downloader module from index --- src/Index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Index.ts b/src/Index.ts index fa5a5ff8..80e49928 100755 --- a/src/Index.ts +++ b/src/Index.ts @@ -10,11 +10,13 @@ import Launch from './Launch.js'; import Microsoft from './Authenticator/Microsoft.js'; import * as Mojang from './Authenticator/Mojang.js'; import Status from './StatusServer/status.js'; +import Downloader from './utils/Downloader.js'; export { AZauth as AZauth, Launch as Launch, Microsoft as Microsoft, Mojang as Mojang, - Status as Status + Status as Status, + Downloader as Downloader }; \ No newline at end of file From 4b83c99bed8759417290dc88c8ec007d94b78a54 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 127/185] refactor minecraft loaders to use fetch instead of node-fetch --- src/Minecraft-Loader/loader/fabric/fabric.ts | 5 ++--- src/Minecraft-Loader/loader/forge/forge.ts | 9 ++++----- src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts | 5 ++--- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 5 ++--- src/Minecraft-Loader/loader/quilt/quilt.ts | 5 ++--- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts index b5eb36fb..4a4a1a16 100644 --- a/src/Minecraft-Loader/loader/fabric/fabric.ts +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -8,7 +8,6 @@ import { EventEmitter } from 'events'; import fs from 'fs'; import path from 'path'; -import nodeFetch from 'node-fetch'; import { getPathLibraries } from '../../../utils/Index.js'; import Downloader from '../../../utils/Downloader.js'; @@ -96,7 +95,7 @@ export default class FabricMC extends EventEmitter { let buildInfo: { version: string; stable: boolean } | undefined; // Fetch the metadata - let response = await nodeFetch(Loader.metaData); + let response = await fetch(Loader.metaData); let metaData: MetaData = await response.json(); // Check if the Minecraft version is supported @@ -126,7 +125,7 @@ export default class FabricMC extends EventEmitter { // Fetch the Fabric loader JSON try { - const result = await nodeFetch(url); + const result = await fetch(url); const fabricJson: FabricJSON = await result.json(); return fabricJson; } catch (err: any) { diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index e207cedc..c5754a26 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -9,7 +9,6 @@ import fs from 'fs'; import path from 'path'; import { EventEmitter } from 'events'; -import nodeFetch from 'node-fetch'; import { getPathLibraries, @@ -122,7 +121,7 @@ export default class ForgeMC extends EventEmitter { */ public async downloadInstaller(Loader: any): Promise { // Fetch metadata for the given Forge version - let metaDataList: string[] = await nodeFetch(Loader.metaData) + let metaDataList: string[] = await fetch(Loader.metaData) .then(res => res.json()) .then(json => json[this.options.loader.version]); @@ -135,12 +134,12 @@ export default class ForgeMC extends EventEmitter { // Handle "latest" or "recommended" builds by checking promotions if (this.options.loader.build === 'latest') { - let promotions = await nodeFetch(Loader.promotions).then(res => res.json()); + 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 nodeFetch(Loader.promotions).then(res => res.json()); + 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)); @@ -157,7 +156,7 @@ export default class ForgeMC extends EventEmitter { } // Fetch info about the chosen build from the meta URL - const meta = await nodeFetch(Loader.meta.replace(/\${build}/g, chosenBuild)).then(res => res.json()); + 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; diff --git a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts index 6530d1ab..9761741d 100644 --- a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts +++ b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts @@ -8,7 +8,6 @@ import fs from 'fs'; import path from 'path'; import { EventEmitter } from 'events'; -import nodeFetch from 'node-fetch'; import { getPathLibraries } from '../../../utils/Index.js'; import Downloader from '../../../utils/Downloader.js'; @@ -87,7 +86,7 @@ export default class FabricMC extends EventEmitter { let selectedBuild: { version: string } | undefined; // Fetch overall metadata - const metaResponse = await nodeFetch(Loader.metaData); + const metaResponse = await fetch(Loader.metaData); const metaData = await metaResponse.json(); // Check if the requested Minecraft version is supported @@ -120,7 +119,7 @@ export default class FabricMC extends EventEmitter { // Fetch and parse the JSON try { - const response = await nodeFetch(url); + const response = await fetch(url); const fabricJson: FabricJSON = await response.json(); return fabricJson; } catch (err: any) { diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index d3708e38..9a5f3c1c 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -7,7 +7,6 @@ import fs from 'fs'; import path from 'path'; -import nodeFetch from 'node-fetch'; import { EventEmitter } from 'events'; import { getPathLibraries, mirrors, getFileFromArchive } from '../../../utils/Index.js'; @@ -104,8 +103,8 @@ export default class NeoForgeMC extends EventEmitter { let oldAPI = true; // Fetch versions from the legacy API - const legacyMetaData = await nodeFetch(Loader.legacyMetaData).then(res => res.json()); - const metaData = await nodeFetch(Loader.metaData).then(res => res.json()); + 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: string[] = legacyMetaData.versions.filter((v: string) => diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts index be519ce8..467b79af 100644 --- a/src/Minecraft-Loader/loader/quilt/quilt.ts +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -8,7 +8,6 @@ import fs from 'fs'; import path from 'path'; import { EventEmitter } from 'events'; -import nodeFetch from 'node-fetch'; import { getPathLibraries } from '../../../utils/Index.js'; import Downloader from '../../../utils/Downloader.js'; @@ -89,7 +88,7 @@ export default class Quilt extends EventEmitter { let selectedBuild: any; // Fetch the metadata - const metaResponse = await nodeFetch(Loader.metaData); + const metaResponse = await fetch(Loader.metaData); const metaData = await metaResponse.json(); // Check if the requested Minecraft version is supported @@ -127,7 +126,7 @@ export default class Quilt extends EventEmitter { // Fetch the JSON profile try { - const response = await nodeFetch(url); + const response = await fetch(url); const quiltJson: QuiltJSON = await response.json(); return quiltJson; } catch (err: any) { From 92160f17baf32c0c7dec30f08bb5b80b6ca52176 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 128/185] replace node-fetch usage in minecraft core modules with native fetch --- src/Minecraft/Minecraft-Arguments.ts | 2 +- src/Minecraft/Minecraft-Assets.ts | 4 +--- src/Minecraft/Minecraft-Java.ts | 11 ++++------- src/Minecraft/Minecraft-Json.ts | 5 ++--- src/Minecraft/Minecraft-Libraries.ts | 3 +-- 5 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index e6a2d3ec..c463aaff 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -296,7 +296,7 @@ export default class MinecraftArguments { const current = map.get(versionKey); const version = parts[2]; - if (!current || version < current.name.split(":")[2]) { + if (!current || version > current.name.split(":")[2]) { map.set(versionKey, dep); } } diff --git a/src/Minecraft/Minecraft-Assets.ts b/src/Minecraft/Minecraft-Assets.ts index acd41cad..aa836ddc 100755 --- a/src/Minecraft/Minecraft-Assets.ts +++ b/src/Minecraft/Minecraft-Assets.ts @@ -4,8 +4,6 @@ * * Original author: Luuxis */ - -import nodeFetch from 'node-fetch'; import fs from 'fs'; /** @@ -70,7 +68,7 @@ export default class MinecraftAssets { // Fetch the asset index JSON from the remote URL let data; try { - const response = await nodeFetch(this.assetIndex.url); + const response = await fetch(this.assetIndex.url); data = await response.json(); } catch (err: any) { throw new Error(`Failed to fetch asset index: ${err.message}`); diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index a01ca6d7..f0af4fdf 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -6,7 +6,6 @@ */ import os from 'os'; -import nodeFetch from 'node-fetch'; import path from 'path'; import fs from 'fs'; import EventEmitter from 'events'; @@ -113,10 +112,8 @@ export default class JavaDownloader extends EventEmitter { } // Fetch Mojang's Java runtime metadata - const javaVersionsJson = await nodeFetch( - 'https://launchermeta.mojang.com/v1/products/java-runtime/' + - '2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json' - ).then(res => res.json()); + const url = 'https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json'; + const javaVersionsJson = await fetch(url).then(res => res.json()); const versionName = javaVersionsJson[archOs]?.[javaVersionName]?.[0]?.version?.name; if (!versionName) { @@ -125,7 +122,7 @@ export default class JavaDownloader extends EventEmitter { // Fetch the runtime manifest which lists individual files const manifestUrl = javaVersionsJson[archOs][javaVersionName][0]?.manifest?.url; - const manifest = await nodeFetch(manifestUrl).then(res => res.json()); + const manifest = await fetch(manifestUrl).then(res => res.json()); const manifestEntries: Array<[string, any]> = Object.entries(manifest.files); // Identify the Java executable in the manifest @@ -181,7 +178,7 @@ export default class JavaDownloader extends EventEmitter { os: platform }); const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot?${queryParams.toString()}`; - const javaVersions = await nodeFetch(javaVersionURL).then(res => res.json()); + const javaVersions = await fetch(javaVersionURL).then(res => res.json()); // If no valid version is found, return an error const java = javaVersions[0]; diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index 7a89275b..b2f561f2 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -6,7 +6,6 @@ */ import os from 'os'; -import nodeFetch from 'node-fetch'; import MinecraftNativeLinuxARM from './Minecraft-Lwjgl-Native.js'; /** @@ -79,7 +78,7 @@ export default class Json { let { version } = this.options; // Fetch the version manifest - const response = await nodeFetch( + const response = await fetch( `https://launchermeta.mojang.com/mc/game/version_manifest_v2.json?_t=${new Date().toISOString()}` ); const manifest: MojangVersionManifest = await response.json(); @@ -101,7 +100,7 @@ export default class Json { } // Fetch the detailed version JSON from Mojang - const jsonResponse = await nodeFetch(matchedVersion.url); + const jsonResponse = await fetch(matchedVersion.url); let versionJson = await jsonResponse.json(); // If on Linux ARM, run additional processing diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 5876ea3c..7f67dc55 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -8,7 +8,6 @@ import os from 'os'; import fs from 'fs'; import AdmZip from 'adm-zip'; -import nodeFetch from 'node-fetch'; /** * Maps Node.js platforms to Mojang's naming scheme for OS in library natives. @@ -201,7 +200,7 @@ export default class Libraries { public async GetAssetsOthers(url: string | null): Promise { if (!url) return []; - const response = await nodeFetch(url); + const response = await fetch(url); const data: CustomAssetItem[] = await response.json(); const assets: LibraryDownload[] = []; From eb9e706922ac0b271025cbefbe985138c9c319ec Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 129/185] improve downloader class to use fetch and stream APIs --- src/utils/Downloader.ts | 133 +++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index e12eb849..cefb4a01 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -6,8 +6,8 @@ */ import fs from 'fs'; -import nodeFetch from 'node-fetch'; import { EventEmitter } from 'events'; +import { Readable } from 'stream'; /** * Describes a single file to be downloaded by the Downloader class. @@ -44,26 +44,30 @@ export default class Downloader extends EventEmitter { } const writer = fs.createWriteStream(`${dirPath}/${fileName}`); - const response = await nodeFetch(url); + const response = await fetch(url); + const contentLength = response.headers.get('content-length'); const totalSize = contentLength ? parseInt(contentLength, 10) : 0; let downloaded = 0; return new Promise((resolve, reject) => { - response.body.on('data', (chunk: Buffer) => { + const body = Readable.fromWeb(response.body as any); + + body.on('data', (chunk: Buffer) => { downloaded += chunk.length; // Emit progress with the current number of bytes vs. total size this.emit('progress', downloaded, totalSize); writer.write(chunk); }); - response.body.on('end', () => { + body.on('end', () => { writer.end(); resolve(); }); - response.body.on('error', (err: Error) => { + body.on('error', (err: Error) => { + writer.destroy(); this.emit('error', err); reject(err); }); @@ -86,9 +90,7 @@ export default class Downloader extends EventEmitter { limit: number = 1, timeout: number = 10000 ): Promise { - if (limit > files.length) { - limit = files.length; - } + if (limit > files.length) limit = files.length; let completed = 0; // Number of downloads completed let downloaded = 0; // Cumulative bytes downloaded @@ -96,18 +98,14 @@ export default class Downloader extends EventEmitter { let start = Date.now(); let before = 0; - let speeds: number[] = []; + const speeds: number[] = []; - // A repeating interval to calculate speed and ETA const estimated = setInterval(() => { - const duration = (Date.now() - start) / 1000; // seconds - const chunkDownloaded = downloaded - before; // new bytes in this interval - if (speeds.length >= 5) { - speeds.shift(); // keep last 4 measurements - } + const duration = (Date.now() - start) / 1000; + const chunkDownloaded = downloaded - before; + if (speeds.length >= 5) speeds.shift(); speeds.push(chunkDownloaded / duration); - // Average of speeds const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length; this.emit('speed', avgSpeed); @@ -118,47 +116,49 @@ export default class Downloader extends EventEmitter { before = downloaded; }, 500); - // Recursive function that downloads the next file in the queue const downloadNext = async (): Promise => { - if (queued < files.length) { - const file = files[queued]; - queued++; + if (queued >= files.length) return; - if (!fs.existsSync(file.folder)) { - fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); - } + const file = files[queued++]; + if (!fs.existsSync(file.folder)) { + fs.mkdirSync(file.folder, { recursive: true, mode: 0o777 }); + } + + const writer = fs.createWriteStream(file.path, { flags: 'w', mode: 0o777 }); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); - // Create a write stream for the file - const writer = fs.createWriteStream(file.path, { flags: 'w', mode: 0o777 }); - - try { - const response = await nodeFetch(file.url, { timeout }); - // On data reception, increase the global downloaded counter - response.body.on('data', (chunk: Buffer) => { - downloaded += chunk.length; - // Emit progress with the current total downloaded vs. full size - this.emit('progress', downloaded, size, file.type); - writer.write(chunk); - }); - - response.body.on('end', () => { - writer.end(); - completed++; - downloadNext(); - }); - - response.body.on('error', (err: Error) => { - writer.end(); - completed++; - downloadNext(); - this.emit('error', err); - }); - } catch (e) { + try { + const response = await fetch(file.url, { signal: controller.signal }); + + clearTimeout(timeoutId); + + const stream = Readable.fromWeb(response.body as any); + + stream.on('data', (chunk: Buffer) => { + downloaded += chunk.length; + this.emit('progress', downloaded, size, file.type); + writer.write(chunk); + }); + + stream.on('end', () => { writer.end(); completed++; downloadNext(); - this.emit('error', e); - } + }); + + stream.on('error', (err) => { + writer.destroy(); + this.emit('error', err); + completed++; + downloadNext(); + }); + } catch (e) { + clearTimeout(timeoutId); + writer.destroy(); + this.emit('error', e); + completed++; + downloadNext(); } }; @@ -191,22 +191,31 @@ export default class Downloader extends EventEmitter { url: string, timeout: number = 10000 ): Promise<{ size: number; status: number } | false> { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); - return new Promise(async (resolve) => { - try { - const res = await nodeFetch(url, { method: 'HEAD', timeout }); - if (res.status === 200) { - const contentLength = res.headers.get('content-length'); - const size = contentLength ? parseInt(contentLength, 10) : 0; - resolve({ size, status: 200 }); - } else resolve(false); - } catch (e) { - resolve(false); + try { + const res = await fetch(url, { + method: 'HEAD', + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (res.status === 200) { + const contentLength = res.headers.get('content-length'); + const size = contentLength ? parseInt(contentLength, 10) : 0; + return { size, status: 200 }; } - }); + return false; + } catch (e: any) { + clearTimeout(timeoutId); + return false; + } } + /** * Tries each mirror in turn, constructing an URL (mirror + baseURL). If a valid * response is found (status=200), it returns the final URL and size. Otherwise, returns false. From 591d6040eff13948455ff8456eed2eed56fb5793 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 130/185] update test launcher configuration --- test/index.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/index.js b/test/index.js index da096d46..dd8baabe 100755 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,3 @@ -const path = require('path'); const { Microsoft, Launch } = require('../build/Index'); const launch = new Launch(); const fs = require('fs'); @@ -24,18 +23,18 @@ let mc let opt = { authenticator: mc, - timeout: 10000, + timeout: 1000, path: './Minecraft', - version: '1.16.5', + instance: 'test', + version: '1.1', detached: false, intelEnabledMac: true, - downloadFileMultiple: 30, + downloadFileMultiple: 5, loader: { - type: 'fabric', + type: 'forge', build: 'latest', - enable: true, - path: './', + enable: true }, verify: false, From 2c7274eec4ac32a7cb43b2dd0bb7ffc664d0860c Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:13:49 +0200 Subject: [PATCH 131/185] add DOM lib to tsconfig for fetch compatibility --- README.md | 11 ++--- src/utils/Index.ts | 1 - test/index.js | 102 +++++++-------------------------------------- tsconfig.json | 5 ++- 4 files changed, 22 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index b22f397b..67e68ae6 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -![logo](https://raw.githubusercontent.com/Luuxis/minecraft-java-core/main/logo.png) - -##### v4.0.0 • **minecraft‑java‑core** +##### v4 • **minecraft‑java‑core** [![License: CC‑BY‑NC 4.0](https://img.shields.io/badge/License-CC--BY--NC%204.0-yellow.svg)](https://creativecommons.org/licenses/by-nc/4.0/) ![stable version](https://img.shields.io/badge/stable_version-4.0.0-blue) @@ -51,11 +49,8 @@ launcher.on('progress', p => console.log(`[DL] ${p}%`)) await launcher.launch({ root: './minecraft', authenticator: auth, - version: { - number: '1.20.4', - type: 'release' - }, - loader: { type: 'fabric', version: '0.15.9' }, + version: '1.20.4', + loader: { type: 'fabric', build: '0.15.9' }, memory: { min: '2G', max: '4G' } }); ``` diff --git a/src/utils/Index.ts b/src/utils/Index.ts index ffe760a4..01bc1eda 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -8,7 +8,6 @@ import crypto from 'crypto'; import fs from 'fs'; import AdmZip from 'adm-zip'; -import path from 'path'; // This interface defines the structure of a Minecraft library rule. interface LibraryRule { diff --git a/test/index.js b/test/index.js index dd8baabe..dfafa021 100755 --- a/test/index.js +++ b/test/index.js @@ -1,113 +1,43 @@ -const { Microsoft, Launch } = require('../build/Index'); -const launch = new Launch(); -const fs = require('fs'); +const { Launch, Microsoft } = require('minecraft-java-core'); +const launcher = new Launch(); -let client_id = '13f589e1-e2fc-443e-a68a-63b0092b8eeb' +const fs = require('fs'); let mc (async () => { if (!fs.existsSync('./account.json')) { - mc = await new Microsoft(client_id).getAuth(); + mc = await new Microsoft().getAuth(); fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); } else { mc = JSON.parse(fs.readFileSync('./account.json')); if (!mc.refresh_token) { - mc = await new Microsoft(client_id).getAuth(); + mc = await new Microsoft().getAuth(); fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); } else { - mc = await new Microsoft(client_id).refresh(mc); - if (mc.error) mc = await new Microsoft(client_id).getAuth(); + mc = await new Microsoft().refresh(mc); + if (mc.error) mc = await new Microsoft().getAuth(); fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); } } - let opt = { + await launcher.Launch({ + path: './minecraft', authenticator: mc, - timeout: 1000, - path: './Minecraft', - instance: 'test', - version: '1.1', - detached: false, + version: '1.8.9', intelEnabledMac: true, - downloadFileMultiple: 5, - loader: { type: 'forge', build: 'latest', enable: true }, - - verify: false, - ignored: [ - 'config', - 'essential', - 'logs', - 'resourcepacks', - 'saves', - 'screenshots', - 'shaderpacks', - 'W-OVERFLOW', - 'options.txt', - 'optionsof.txt' - ], - JVM_ARGS: [], - GAME_ARGS: [], - - java: { - path: null, - version: null, - type: 'jre', - }, - - // screen: { - // width: 1500, - // height: 900 - // }, - memory: { - min: '4G', - max: '6G' + min: '2G', + max: '4G' } - } - - await launch.Launch(opt); - - launch.on('extract', extract => { - console.log(extract); }); - launch.on('progress', (progress, size, element) => { - console.log(`Downloading ${element} ${Math.round((progress / size) * 100)}%`); - }); - - launch.on('check', (progress, size, element) => { - console.log(`Checking ${element} ${Math.round((progress / size) * 100)}%`); - }); - - launch.on('estimated', (time) => { - let hours = Math.floor(time / 3600); - let minutes = Math.floor((time - hours * 3600) / 60); - let seconds = Math.floor(time - hours * 3600 - minutes * 60); - console.log(`${hours}h ${minutes}m ${seconds}s`); - }) - - launch.on('speed', (speed) => { - console.log(`${(speed / 1067008).toFixed(2)} Mb/s`) - }) - - launch.on('patch', patch => { - console.log(patch); - }); - - launch.on('data', (e) => { - console.log(e); - }) - - launch.on('close', code => { - console.log(code); - }); - - launch.on('error', err => { - console.log(err); - }); + launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) + .on('patch', pacth => process.stdout.write(pacth)) + .on('data', line => process.stdout.write(line)) + .on('close', () => console.log('Game exited.')); })(); diff --git a/tsconfig.json b/tsconfig.json index a473eadc..9b3e3ebd 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,12 @@ "src/**/*" ], "compilerOptions": { - "sourceMap": false, + "sourceMap": true, "target": "ES2020", "module": "CommonJS", "lib": [ - "ES2021" + "ES2021", + "DOM" ], "declaration": true, "outDir": "build", From fd78ce979439a1e309532a3d1e64924023dc144a Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 15 May 2025 18:00:11 +0200 Subject: [PATCH 132/185] fix minecraft start game 1.18.2 --- src/Minecraft/Minecraft-Arguments.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index c463aaff..81002ced 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -7,6 +7,7 @@ import fs from 'fs'; import os from 'os'; +import semver from 'semver'; import { getPathLibraries, isold } from '../utils/Index.js'; /** @@ -26,6 +27,7 @@ export interface LaunchOptions { path: string; // Base path to Minecraft data folder instance?: string; // Instance name (if using multi-instance approach) authenticator: any; // Auth object containing tokens, user info, etc. + version?: string; // Minecraft version memory: { min?: string; // Minimum memory (e.g. "512M", "1G") max?: string; // Maximum memory (e.g. "4G", "8G") @@ -296,7 +298,7 @@ export default class MinecraftArguments { const current = map.get(versionKey); const version = parts[2]; - if (!current || version > current.name.split(":")[2]) { + if (!current || version < current.name.split(":")[2] && semver.lt('1.21.5', this.options.version)) { map.set(versionKey, dep); } } From 26ccd8cb830c5ee7d3db8e9c18c4c7ae20185883 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 15 May 2025 18:00:29 +0200 Subject: [PATCH 133/185] publish 4.0.2 --- package-lock.json | 25 +++++++++++++++++++++++-- package.json | 4 +++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f93e8a2..25b908d7 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "minecraft-java-core", - "version": "4.0.1", + "version": "4.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.0.1", + "version": "4.0.2", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", "adm-zip": "^0.5.16", "node-7z": "^3.0.0", "prompt": "^1.3.0", + "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { @@ -20,6 +21,7 @@ "@types/node": "^22.10.2", "@types/node-7z": "^2.1.10", "@types/node-fetch": "^2.6.12", + "@types/semver": "^7.7.0", "rimraf": "^6.0.1", "typescript": "^5.7.2" }, @@ -95,6 +97,13 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, "node_modules/7zip-bin": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", @@ -600,6 +609,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index ffd8ccb8..a4885d88 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.0.1", + "version": "4.0.2", "types": "./build/Index.d.ts", "exports": { ".": { @@ -39,6 +39,7 @@ "adm-zip": "^0.5.16", "node-7z": "^3.0.0", "prompt": "^1.3.0", + "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { @@ -46,6 +47,7 @@ "@types/node": "^22.10.2", "@types/node-7z": "^2.1.10", "@types/node-fetch": "^2.6.12", + "@types/semver": "^7.7.0", "rimraf": "^6.0.1", "typescript": "^5.7.2" }, From a882f04e8bf5d95b32a1323f2c7196df3d1c6636 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 18 May 2025 20:29:30 +0200 Subject: [PATCH 134/185] Extract fromAnyReadable utility to handle stream compatibility --- src/utils/Downloader.ts | 6 +++--- src/utils/Index.ts | 27 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index cefb4a01..8959f224 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import { EventEmitter } from 'events'; -import { Readable } from 'stream'; +import { fromAnyReadable } from './Index.js'; /** * Describes a single file to be downloaded by the Downloader class. @@ -52,7 +52,7 @@ export default class Downloader extends EventEmitter { let downloaded = 0; return new Promise((resolve, reject) => { - const body = Readable.fromWeb(response.body as any); + const body = fromAnyReadable(response.body as any); body.on('data', (chunk: Buffer) => { downloaded += chunk.length; @@ -133,7 +133,7 @@ export default class Downloader extends EventEmitter { clearTimeout(timeoutId); - const stream = Readable.fromWeb(response.body as any); + const stream = fromAnyReadable(response.body as any); stream.on('data', (chunk: Buffer) => { downloaded += chunk.length; diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 01bc1eda..663a3a28 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -8,6 +8,7 @@ import crypto from 'crypto'; import fs from 'fs'; import AdmZip from 'adm-zip'; +import { Readable } from 'node:stream'; // This interface defines the structure of a Minecraft library rule. interface LibraryRule { @@ -275,6 +276,29 @@ function skipLibrary(lib: MinecraftLibrary): boolean { return shouldSkip; } +function fromAnyReadable(webStream: ReadableStream): import('node:stream').Readable { + let NodeReadableStreamCtor: typeof ReadableStream | undefined; + if (!NodeReadableStreamCtor && typeof globalThis?.navigator === 'undefined') { + import('node:stream/web').then((mod) => { NodeReadableStreamCtor = mod.ReadableStream; }); + } + if (NodeReadableStreamCtor && webStream instanceof NodeReadableStreamCtor && typeof (Readable as any).fromWeb === 'function') { + return Readable.fromWeb(webStream as any); + } + + const nodeStream = new Readable({ read() { } }); + const reader = webStream.getReader(); + + (function pump() { + reader.read().then(({ done, value }) => { + if (done) return nodeStream.push(null); + nodeStream.push(Buffer.from(value)); + pump(); + }).catch(err => nodeStream.destroy(err)); + })(); + + return nodeStream; +} + // Export all utility functions and constants export { getPathLibraries, @@ -284,5 +308,6 @@ export { mirrors, getFileFromArchive, createZIP, - skipLibrary + skipLibrary, + fromAnyReadable }; From 5926f0cd2aa7fe6dcabb6575ef959f836fe1f9e1 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 18 May 2025 20:29:53 +0200 Subject: [PATCH 135/185] publish 4.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25b908d7..b1d845a1 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.0.2", + "version": "4.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.0.2", + "version": "4.1.0", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index a4885d88..88780584 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.0.2", + "version": "4.1.0", "types": "./build/Index.d.ts", "exports": { ".": { From c701edb29125b555ea8fd9723816a25db3c9ac16 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 19 May 2025 23:26:47 +0200 Subject: [PATCH 136/185] fix error minecraft start windows --- src/Minecraft/Minecraft-Arguments.ts | 18 ++++++++++-------- src/utils/Index.ts | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 81002ced..5e73547a 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -290,16 +290,18 @@ export default class MinecraftArguments { const map = new Map(); for (const dep of combinedLibraries) { - const parts = dep.name.split(":"); - const key = parts.slice(0, 2).join(":"); - const classifier = parts[3] ? parts[3] : ""; - const versionKey = `${key}:${classifier}`; + const parts = getPathLibraries(dep.name); + const version = semver.coerce(parts.version); + if (!version) continue; - const current = map.get(versionKey); - const version = parts[2]; + const pathParts = parts.path.split('/'); + const basePath = pathParts.slice(0, -1).join('/'); - if (!current || version < current.name.split(":")[2] && semver.lt('1.21.5', this.options.version)) { - map.set(versionKey, dep); + const key = `${basePath}/${parts.name.replace(`-${parts.version}`, '')}`; + const current = map.get(key); + + if (!current || semver.gt(version, current.version)) { + map.set(key, { ...dep, version }); } } diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 663a3a28..026a4391 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -73,7 +73,8 @@ function getPathLibraries(main: string, nativeString?: string, forceExt?: string return { path: pathLib, - name: `${libSplit[1]}-${finalFileName}` + name: `${libSplit[1]}-${finalFileName}`, + version: libSplit[2], }; } From 74907331ccf763eab0153eac5abdccdac801d87a Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 20 May 2025 16:01:26 +0200 Subject: [PATCH 137/185] publish 4.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1d845a1..029cca39 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.0", + "version": "4.1.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 88780584..1b1f3285 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.0", + "version": "4.1.1", "types": "./build/Index.d.ts", "exports": { ".": { From 0bf1ccb8ae828724c86e83eef490782e7ab8d0d8 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:38:49 +0200 Subject: [PATCH 138/185] Implement bypass offline mode feature --- src/Launch.ts | 9 ++++++++- src/Minecraft/Minecraft-Arguments.ts | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Launch.ts b/src/Launch.ts index 40b41771..c43d52e4 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -152,6 +152,12 @@ export type LaunchOPTS = { * How many concurrent downloads can be in progress at once. */ downloadFileMultiple?: number, + /** + * Should the launcher bypass offline mode? + * + * If `true`, the launcher will not check if the user is online. + */ + bypassOffline?: boolean, intelEnabledMac?: boolean, /** * Loader config @@ -208,6 +214,7 @@ export default class Launch extends EventEmitter { detached: false, intelEnabledMac: false, downloadFileMultiple: 5, + bypassOffline: false, loader: { path: './loader', @@ -308,7 +315,7 @@ export default class Launch extends EventEmitter { return this.emit('error', InfoVersion); } let { json, version } = InfoVersion; - + let libraries = new librariesMinecraft(this.options) let bundle = new bundleMinecraft(this.options) let java = new javaMinecraft(this.options) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 5e73547a..131c91a1 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -28,6 +28,7 @@ export interface LaunchOptions { instance?: string; // Instance name (if using multi-instance approach) authenticator: any; // Auth object containing tokens, user info, etc. version?: string; // Minecraft version + bypassOffline?: boolean; // Bypass offline mode for multiplayer memory: { min?: string; // Minimum memory (e.g. "512M", "1G") max?: string; // Maximum memory (e.g. "4G", "8G") @@ -245,6 +246,14 @@ export default class MinecraftArguments { } } + // bypass offline mode multiplayer + if (this.options?.bypassOffline) { + jvmArgs.push('-Dminecraft.api.auth.host=https://nope.invalid/'); + jvmArgs.push('-Dminecraft.api.account.host=https://nope.invalid/'); + jvmArgs.push('-Dminecraft.api.session.host=https://nope.invalid/'); + jvmArgs.push('-Dminecraft.api.services.host=https://nope.invalid/'); + } + // If natives are specified, add the native library path if (versionJson.nativesList) { jvmArgs.push(`-Djava.library.path=${this.options.path}/versions/${versionJson.id}/natives`); From e8b87d3fafd8381467fdc638b177ef807945bcb2 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:39:56 +0200 Subject: [PATCH 139/185] Fix library resolution and error handling --- src/Minecraft-Loader/loader/forge/forge.ts | 4 +++- src/Minecraft/Minecraft-Arguments.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index c5754a26..fce95daf 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -400,7 +400,9 @@ export default class ForgeMC extends EventEmitter { } if (!url) { - return { error: `Impossible to download ${libInfo.name}` }; + this.emit('check', checkCount++, libraries.length, 'libraries'); + this.emit('error', `Library ${libInfo.name} not found`); + continue; } downloadList.push({ diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 131c91a1..1f88fd17 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -300,7 +300,7 @@ export default class MinecraftArguments { for (const dep of combinedLibraries) { const parts = getPathLibraries(dep.name); - const version = semver.coerce(parts.version); + const version = semver.valid(semver.coerce(parts.version)); if (!version) continue; const pathParts = parts.path.split('/'); @@ -309,7 +309,7 @@ export default class MinecraftArguments { const key = `${basePath}/${parts.name.replace(`-${parts.version}`, '')}`; const current = map.get(key); - if (!current || semver.gt(version, current.version)) { + if (!current || semver.gt(version, current.version) && !semver.satisfies(semver.valid(semver.coerce(this.options.version)), '1.14.4 - 1.18.2')) { map.set(key, { ...dep, version }); } } From e49eb9b3020ddc4947317544878f6e8b46e5e217 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:39:56 +0200 Subject: [PATCH 140/185] Bump version to 4.1.2-beta.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 029cca39..e08dc884 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.1", + "version": "4.1.2-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.1", + "version": "4.1.2-beta.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 1b1f3285..4f1c5816 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.1", + "version": "4.1.2-beta.1", "types": "./build/Index.d.ts", "exports": { ".": { From a1ada8700ca24db8a12759502bef871b63e0c877 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:52:59 +0200 Subject: [PATCH 141/185] add Offline test --- test/offline.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 test/offline.js diff --git a/test/offline.js b/test/offline.js new file mode 100755 index 00000000..5f6cac69 --- /dev/null +++ b/test/offline.js @@ -0,0 +1,26 @@ +const { Launch, Mojang } = require('minecraft-java-core'); +const launcher = new Launch(); +(async () => { + await launcher.Launch({ + path: './minecraft', + authenticator: await Mojang.login('Luuxis'), + version: '1.16.5', + intelEnabledMac: true, + bypassOffline: true, + loader: { + path: './', + type: 'fabric', + build: 'latest', + enable: true + }, + memory: { + min: '2G', + max: '4G' + } + }); + + launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) + .on('patch', pacth => process.stdout.write(pacth)) + .on('data', line => process.stdout.write(line)) + .on('close', () => console.log('Game exited.')); +})(); From 0fc763fdd646914d93c111f9cc567d9b07fa06b7 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:54:05 +0200 Subject: [PATCH 142/185] change path installer forge and neo forge --- src/Minecraft-Loader/loader/forge/forge.ts | 2 +- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index fce95daf..72225c56 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -183,7 +183,7 @@ export default class ForgeMC extends EventEmitter { return { error: 'Invalid forge installer' }; } - const forgeFolder = path.resolve(this.options.path, 'forge'); + const forgeFolder = path.resolve(this.options.path, 'libraries/net/minecraftforge/installer'); const fileName = `${forgeURL}.${ext}`.split('/').pop()!; const installerPath = path.resolve(forgeFolder, fileName); diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 9a5f3c1c..167cc7f1 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -145,7 +145,7 @@ export default class NeoForgeMC extends EventEmitter { } // Create a local folder for "neoForge" if it doesn't exist - const neoForgeFolder = path.resolve(this.options.path, 'neoForge'); + const neoForgeFolder = path.resolve(this.options.path, 'libraries/net/neoforged/installer'); const installerFilePath = path.resolve(neoForgeFolder, `neoForge-${build}-installer.jar`); if (!fs.existsSync(installerFilePath)) { From 68525c632ec1ad0b52648d6e8d24d777674c573f Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:54:58 +0200 Subject: [PATCH 143/185] fix launcher game 1.14.4 > 1.18.2 on windows --- src/Minecraft/Minecraft-Arguments.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 1f88fd17..8eb741c0 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -309,7 +309,10 @@ export default class MinecraftArguments { const key = `${basePath}/${parts.name.replace(`-${parts.version}`, '')}`; const current = map.get(key); - if (!current || semver.gt(version, current.version) && !semver.satisfies(semver.valid(semver.coerce(this.options.version)), '1.14.4 - 1.18.2')) { + const isSupportedVersion = semver.satisfies(semver.valid(semver.coerce(this.options.version)), '1.14.4 - 1.18.2'); + const isWindows = process.platform === 'win32'; + + if (!current || semver.gt(version, current.version) && (isSupportedVersion && isWindows)) { map.set(key, { ...dep, version }); } } From 413d1edef9004fb7a014a4916b841f7f6ccbea61 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:55:22 +0200 Subject: [PATCH 144/185] publish 4.1.2 --- package-lock.json | 4 ++-- package.json | 2 +- test/index.js | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e08dc884..c4e8c08b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.2-beta.1", + "version": "4.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.2-beta.1", + "version": "4.1.2", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 4f1c5816..2f17d9a0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.2-beta.1", + "version": "4.1.2", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/test/index.js b/test/index.js index dfafa021..5391a562 100755 --- a/test/index.js +++ b/test/index.js @@ -23,8 +23,9 @@ let mc await launcher.Launch({ path: './minecraft', authenticator: mc, - version: '1.8.9', + version: '1.16.5', intelEnabledMac: true, + instance: "test-instance", loader: { type: 'forge', build: 'latest', @@ -33,7 +34,12 @@ let mc memory: { min: '2G', max: '4G' - } + }, + java: { + path: null, + version: null, + type: 'jre', + }, }); launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) From a9099c20d93272c3eca0eaa66c259b2a476e5bd3 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:23:13 +0200 Subject: [PATCH 145/185] Fix NeoForged installer URLs to include 'releases' path Updated the NeoForged legacyInstall and install URLs to include the '/releases/' segment, ensuring correct download links for installer jars. --- src/utils/Index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 026a4391..6727aaae 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -132,8 +132,8 @@ function loader(type: string) { return { legacyMetaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge', metaData: 'https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge', - legacyInstall: 'https://maven.neoforged.net/net/neoforged/forge/${version}/forge-${version}-installer.jar', - install: 'https://maven.neoforged.net/net/neoforged/neoforge/${version}/neoforge-${version}-installer.jar' + legacyInstall: 'https://maven.neoforged.net/releases/net/neoforged/forge/${version}/forge-${version}-installer.jar', + install: 'https://maven.neoforged.net/releases/net/neoforged/neoforge/${version}/neoforge-${version}-installer.jar' }; } else if (type === 'fabric') { return { From fcde98fc853efa4a10412f0ff8b82a959acefc9e Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:24:05 +0200 Subject: [PATCH 146/185] Bump version to 4.1.3 Update package.json and package-lock.json to reflect new version 4.1.3. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4e8c08b..b39d2cb8 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.2", + "version": "4.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.2", + "version": "4.1.3", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index 2f17d9a0..7c0b5fa4 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.2", + "version": "4.1.3", "types": "./build/Index.d.ts", "exports": { ".": { From cf2cdb1d03fbe0ef04a8bbefaa3b357f48e06400 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:35:47 +0200 Subject: [PATCH 147/185] Add custom unzipper utility and integrate with Index.ts Introduces a new Unzipper class in src/utils/unzipper.ts to manually parse ZIP files, replacing AdmZip usage in Index.ts. This change allows for more controlled ZIP file handling and reduces external dependencies. --- src/utils/Index.ts | 3 +- src/utils/unzipper.ts | 129 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/utils/unzipper.ts diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 6727aaae..3ea9c7a0 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -9,6 +9,7 @@ import crypto from 'crypto'; import fs from 'fs'; import AdmZip from 'adm-zip'; import { Readable } from 'node:stream'; +import Unzipper from './unzipper.js'; // This interface defines the structure of a Minecraft library rule. interface LibraryRule { @@ -176,7 +177,7 @@ const mirrors = [ */ async function getFileFromArchive(jar: string, file: string | null = null, prefix: string | null = null): Promise { const result: any[] = []; - const zip = new AdmZip(jar); + const zip = new Unzipper(jar); const entries = zip.getEntries(); return new Promise((resolve) => { diff --git a/src/utils/unzipper.ts b/src/utils/unzipper.ts new file mode 100644 index 00000000..c824e86d --- /dev/null +++ b/src/utils/unzipper.ts @@ -0,0 +1,129 @@ +import fs from 'fs'; +import zlib from 'zlib'; + +interface ZipEntry { + entryName: string; + isDirectory: boolean; + getData: () => Buffer; +} + +export default class Unzipper { + private entries: ZipEntry[] = []; + + constructor(zipFilePath: string) { + const fileBuffer = fs.readFileSync(zipFilePath); + + const eocdSig = Buffer.from([0x50, 0x4B, 0x05, 0x06]); + const maxCommentLength = 0xFFFF; + const scanStart = Math.max(0, fileBuffer.length - (maxCommentLength + 22)); + + let eocdPos = -1; + for (let i = fileBuffer.length - 22; i >= scanStart; i--) { + if ( + fileBuffer[i] === 0x50 && + fileBuffer[i + 1] === 0x4B && + fileBuffer[i + 2] === 0x05 && + fileBuffer[i + 3] === 0x06 + ) { + eocdPos = i; + break; + } + } + + if (eocdPos !== -1) { + const cdOffset = fileBuffer.readUInt32LE(eocdPos + 16); + const cdSize = fileBuffer.readUInt32LE(eocdPos + 12); + const cdEnd = cdOffset + cdSize; + + let cdCursor = cdOffset; + + while (cdCursor < cdEnd) { + if (fileBuffer.readUInt32LE(cdCursor) !== 0x02014b50) break; + + const compressionMethod = fileBuffer.readUInt16LE(cdCursor + 10); + const compressedSize = fileBuffer.readUInt32LE(cdCursor + 20); + const uncompressedSize = fileBuffer.readUInt32LE(cdCursor + 24); + const fileNameLength = fileBuffer.readUInt16LE(cdCursor + 28); + const extraFieldLength = fileBuffer.readUInt16LE(cdCursor + 30); + const fileCommentLength = fileBuffer.readUInt16LE(cdCursor + 32); + const relativeOffset = fileBuffer.readUInt32LE(cdCursor + 42); + + const fileName = fileBuffer.toString( + 'utf-8', + cdCursor + 46, + cdCursor + 46 + fileNameLength + ); + + const headerOffset = relativeOffset; + if (fileBuffer.readUInt32LE(headerOffset) !== 0x04034b50) continue; + + const lfFileNameLength = fileBuffer.readUInt16LE(headerOffset + 26); + const lfExtraFieldLength = fileBuffer.readUInt16LE(headerOffset + 28); + const dataStart = headerOffset + 30 + lfFileNameLength + lfExtraFieldLength; + const compressedData = fileBuffer.slice(dataStart, dataStart + compressedSize); + + this.entries.push({ + entryName: fileName, + isDirectory: fileName.endsWith('/'), + getData: () => { + if (compressionMethod === 8) { + return zlib.inflateRawSync(compressedData); + } else if (compressionMethod === 0) { + return compressedData; + } else { + throw new Error(`Méthode de compression non supportée: ${compressionMethod}`); + } + } + }); + + cdCursor += 46 + fileNameLength + extraFieldLength + fileCommentLength; + } + } else { + let currentOffset = 0; + while (currentOffset < fileBuffer.length - 4) { + const signaturePos = fileBuffer.indexOf( + Buffer.from([0x50, 0x4B, 0x03, 0x04]), + currentOffset + ); + if (signaturePos === -1) break; + + const headerOffset = signaturePos; + const compressionMethod = fileBuffer.readUInt16LE(headerOffset + 8); + const compressedSize = fileBuffer.readUInt32LE(headerOffset + 18); + const uncompressedSize = fileBuffer.readUInt32LE(headerOffset + 22); + const fileNameLength = fileBuffer.readUInt16LE(headerOffset + 26); + const extraFieldLength = fileBuffer.readUInt16LE(headerOffset + 28); + + const fileNameStart = headerOffset + 30; + const fileName = fileBuffer.toString( + 'utf-8', + fileNameStart, + fileNameStart + fileNameLength + ); + + const dataStart = fileNameStart + fileNameLength + extraFieldLength; + const compressedData = fileBuffer.slice(dataStart, dataStart + compressedSize); + + this.entries.push({ + entryName: fileName, + isDirectory: fileName.endsWith('/'), + getData: () => { + if (compressionMethod === 8) { + return zlib.inflateRawSync(compressedData); + } else if (compressionMethod === 0) { + return compressedData; + } else { + throw new Error(`Méthode de compression non supportée: ${compressionMethod}`); + } + } + }); + + currentOffset = dataStart + compressedSize; + } + } + } + + getEntries(): ZipEntry[] { + return this.entries; + } +} From ab20294ee43678ca215d49123db929342ec949e6 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:40:00 +0200 Subject: [PATCH 148/185] Remove legacy Forge profile creation logic Eliminated support for creating merged jars for older Forge versions by removing the createProfile method and related utility functions. The loader now uses a unified installation process for Forge, simplifying the codebase and reducing maintenance overhead. --- src/Minecraft-Loader/index.ts | 45 ++++++++-------------- src/Minecraft-Loader/loader/forge/forge.ts | 34 ---------------- src/utils/Index.ts | 24 ------------ 3 files changed, 16 insertions(+), 87 deletions(-) diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 5ff4ae92..69949f4c 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -156,40 +156,27 @@ export default class Loader extends EventEmitter { const installer: any = await forge.downloadInstaller(LoaderData); if (installer.error) return installer; // e.g., { error: "..." } - // 2. If the installer extension is ".jar", we do the standard "install_profile.json" approach - if ("ext" in installer && installer.ext === 'jar') { - const profile: any = await forge.extractProfile(installer.filePath); - if (profile.error) return profile; - - // Write the version JSON to disk - const destination = path.resolve(this.options.path, 'versions', profile.version.id); - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); - - // 3. Extract universal jar if needed - const universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); - if (universal.error) return universal; + const profile: any = await forge.extractProfile(installer.filePath); + if (profile.error) return profile; - // 4. Download libraries - const libraries: any = await forge.downloadLibraries(profile, universal); - if (libraries.error) return libraries; + // Write the version JSON to disk + const destination = path.resolve(this.options.path, 'versions', profile.version.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); - // 5. Patch Forge if necessary - const patch: any = await forge.patchForge(profile.install); - if (patch.error) return patch; + // 3. Extract universal jar if needed + const universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); + if (universal.error) return universal; - return profile.version; - } else { - // For older Forge, create a merged jar - const profile = await forge.createProfile(installer.id, installer.filePath); - if (profile.error) return profile; + // 4. Download libraries + const libraries: any = await forge.downloadLibraries(profile, universal); + if (libraries.error) return libraries; - const destination = path.resolve(this.options.path, 'versions', profile.id); - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.id}.json`), JSON.stringify(profile, null, 4)); + // 5. Patch Forge if necessary + const patch: any = await forge.patchForge(profile.install); + if (patch.error) return patch; - return profile; - } + return profile.version; } /** diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index 72225c56..f36e47a1 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -15,7 +15,6 @@ import { getFileHash, mirrors, getFileFromArchive, - createZIP, skipLibrary } from '../../../utils/Index.js'; @@ -455,37 +454,4 @@ export default class ForgeMC extends EventEmitter { } 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 - */ - public async createProfile(id: string, pathInstaller: string): Promise { - // Gather all entries from the Forge installer and the vanilla JAR - const forgeFiles = await getFileFromArchive(pathInstaller); - const vanillaJar = await getFileFromArchive(this.options.loader.config.minecraftJar); - - // Combine them, excluding "META-INF" from the final jar - const mergedZip = await createZIP([...vanillaJar, ...forgeFiles], 'META-INF'); - - // Write the new combined jar to versions//.jar - const destination = path.resolve(this.options.path, 'versions', id); - if (!fs.existsSync(destination)) { - fs.mkdirSync(destination, { recursive: true }); - } - fs.writeFileSync(path.resolve(destination, `${id}.jar`), mergedZip, { mode: 0o777 }); - - // Update the version JSON - const profileData = JSON.parse(fs.readFileSync(this.options.loader.config.minecraftJson).toString()); - profileData.libraries = []; - profileData.id = id; - profileData.isOldForge = true; - profileData.jarPath = path.resolve(destination, `${id}.jar`); - - return profileData; - } } diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 3ea9c7a0..603321ff 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -7,7 +7,6 @@ import crypto from 'crypto'; import fs from 'fs'; -import AdmZip from 'adm-zip'; import { Readable } from 'node:stream'; import Unzipper from './unzipper.js'; @@ -208,28 +207,6 @@ async function getFileFromArchive(jar: string, file: string | null = null, prefi }); } -/** - * Creates a new ZIP buffer by combining multiple file entries (name, data), - * optionally ignoring entries containing a certain string (e.g. "META-INF"). - * - * @param files An array of { name, data } objects to include in the new zip - * @param ignored A substring to skip any matching files - * @returns A buffer containing the newly created ZIP - */ -async function createZIP(files: { name: string; data: Buffer }[], ignored: string | null = null): Promise { - const zip = new AdmZip(); - - return new Promise((resolve) => { - for (const entry of files) { - if (ignored && entry.name.includes(ignored)) { - continue; - } - zip.addFile(entry.name, entry.data); - } - resolve(zip.toBuffer()); - }); -} - /** * Determines if a library should be skipped based on its 'rules' property. * For example, it might skip libraries if action='disallow' for the current OS, @@ -309,7 +286,6 @@ export { loader, mirrors, getFileFromArchive, - createZIP, skipLibrary, fromAnyReadable }; From 93e73f736239a6f4691499db7b16dbe941439f29 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:40:16 +0200 Subject: [PATCH 149/185] Remove adm-zip and its type definitions Deleted adm-zip from dependencies and @types/adm-zip from devDependencies in package.json, likely because they are no longer needed in the project. --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 7c0b5fa4..11d114d9 100755 --- a/package.json +++ b/package.json @@ -36,14 +36,12 @@ "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", - "adm-zip": "^0.5.16", "node-7z": "^3.0.0", "prompt": "^1.3.0", "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { - "@types/adm-zip": "^0.5.7", "@types/node": "^22.10.2", "@types/node-7z": "^2.1.10", "@types/node-fetch": "^2.6.12", From 2d5863e59969cbeae2d9407fd0b8295979173446 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:46:36 +0200 Subject: [PATCH 150/185] Add includeDirs option to getFileFromArchive Introduces an includeDirs parameter to getFileFromArchive, allowing directory entries to be included in results. Also updates result objects to include an isDirectory property. --- src/utils/Index.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 603321ff..ea7b4c7a 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -167,32 +167,31 @@ const mirrors = [ /** * Reads a .jar or .zip file, returning specific entries or listing file entries in the archive. - * Uses adm-zip under the hood. * * @param jar Full path to the jar/zip file * @param file The file entry to extract data from (e.g., "install_profile.json"). If null, returns all entries or partial lists. * @param prefix A path prefix filter (e.g., "maven/org/lwjgl/") if you want a list of matching files instead of direct extraction * @returns A buffer or an array of { name, data }, or a list of filenames if prefix is given */ -async function getFileFromArchive(jar: string, file: string | null = null, prefix: string | null = null): Promise { +async function getFileFromArchive(jar: string, file: string | null = null, prefix: string | null = null, includeDirs: boolean = false): Promise { const result: any[] = []; const zip = new Unzipper(jar); const entries = zip.getEntries(); return new Promise((resolve) => { for (const entry of entries) { - if (!entry.isDirectory && !prefix) { + if (includeDirs ? !prefix : (!entry.isDirectory && !prefix)) { // If no prefix is given, either return a specific file if 'file' is set, // or accumulate all entries if 'file' is null if (entry.entryName === file) { return resolve(entry.getData()); } else if (!file) { - result.push({ name: entry.entryName, data: entry.getData() }); + result.push({ name: entry.entryName, data: entry.getData(), isDirectory: entry.isDirectory }); } } // If a prefix is given, collect all entry names under that prefix - if (!entry.isDirectory && prefix && entry.entryName.includes(prefix)) { + if (includeDirs ? !prefix : (!entry.isDirectory && !prefix) && entry.entryName.includes(prefix)) { result.push(entry.entryName); } } From 90ea8620466ad8e0e2cbe6d0178c559501f94498 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:47:22 +0200 Subject: [PATCH 151/185] Refactor native extraction to use getFileFromArchive Replaces direct usage of adm-zip with the getFileFromArchive utility for extracting native libraries. Simplifies the extraction logic and improves modularity by delegating archive handling to a shared utility. --- src/Minecraft/Minecraft-Libraries.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 7f67dc55..1884e84e 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -7,7 +7,7 @@ import os from 'os'; import fs from 'fs'; -import AdmZip from 'adm-zip'; +import { getFileFromArchive } from '../utils/Index.js'; /** * Maps Node.js platforms to Mojang's naming scheme for OS in library natives. @@ -247,27 +247,19 @@ export default class Libraries { // For each native jar, extract its contents (excluding META-INF) for (const native of natives) { - // Load it as a zip - const zip = new AdmZip(native); - const entries = zip.getEntries(); + const entries = await getFileFromArchive(native, null, null, true); + for (const entry of entries) { - if (entry.entryName.startsWith('META-INF')) { - continue; - } + if (entry.name.startsWith('META-INF')) continue; - // Create subdirectories if needed if (entry.isDirectory) { - fs.mkdirSync(`${nativesFolder}/${entry.entryName}`, { recursive: true, mode: 0o777 }); + fs.mkdirSync(`${nativesFolder}/${entry.name}`, { recursive: true, mode: 0o777 }); continue; } // Write the file to the natives folder - fs.writeFileSync( - `${nativesFolder}/${entry.entryName}`, - zip.readFile(entry), - { mode: 0o777 } - ); + fs.writeFileSync(`${nativesFolder}/${entry.name}`, entry.data, { mode: 0o777 }); } } return natives; From 0e092eb70823e510802c9fc297613d782d553e83 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:47:48 +0200 Subject: [PATCH 152/185] Bump version to 4.1.4-beta.1 Updated the package version to 4.1.4-beta.1 --- package-lock.json | 25 ++----------------------- package.json | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index b39d2cb8..656c6551 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,21 @@ { "name": "minecraft-java-core", - "version": "4.1.3", + "version": "4.1.4-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.3", + "version": "4.1.4-beta.1", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", - "adm-zip": "^0.5.16", "node-7z": "^3.0.0", "prompt": "^1.3.0", "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { - "@types/adm-zip": "^0.5.7", "@types/node": "^22.10.2", "@types/node-7z": "^2.1.10", "@types/node-fetch": "^2.6.12", @@ -56,16 +54,6 @@ "node": ">=12" } }, - "node_modules/@types/adm-zip": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", - "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "22.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", @@ -110,15 +98,6 @@ "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", "license": "MIT" }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", - "license": "MIT", - "engines": { - "node": ">=12.0" - } - }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", diff --git a/package.json b/package.json index 11d114d9..ed37378f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.3", + "version": "4.1.4-beta.1", "types": "./build/Index.d.ts", "exports": { ".": { From 95ea251d4d0de2d0efb7905b76afe6994f1c8ee0 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:57:50 +0200 Subject: [PATCH 153/185] Fix prefix filtering logic in getFileFromArchive Simplifies the condition to only include non-directory entries whose names include the specified prefix, ensuring correct file filtering from the archive. --- src/utils/Index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Index.ts b/src/utils/Index.ts index ea7b4c7a..89a9e01a 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -191,7 +191,7 @@ async function getFileFromArchive(jar: string, file: string | null = null, prefi } // If a prefix is given, collect all entry names under that prefix - if (includeDirs ? !prefix : (!entry.isDirectory && !prefix) && entry.entryName.includes(prefix)) { + if (!entry.isDirectory && entry.entryName.includes(prefix)) { result.push(entry.entryName); } } From b2261709bce77d9dbd2edae2fea1e7a05287b55d Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:59:38 +0200 Subject: [PATCH 154/185] Bump version to 4.1.4-beta.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 656c6551..5dc4ef51 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.1", + "version": "4.1.4-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.4-beta.1", + "version": "4.1.4-beta.2", "license": "CCANC", "dependencies": { "7zip-bin": "^5.2.0", diff --git a/package.json b/package.json index ed37378f..f9349156 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.1", + "version": "4.1.4-beta.2", "types": "./build/Index.d.ts", "exports": { ".": { From 111055aa5f5d91976abab853521be705eedebd46 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:09:38 +0200 Subject: [PATCH 155/185] Refactor Java downloader to use Azul API and custom extraction Replaces Adoptium API with Azul API for Java downloads and removes dependency on node-7z for archive extraction. Implements custom extraction logic using getFileFromArchive and updates platform/arch mapping. Simplifies file verification and extraction process. --- src/Minecraft/Minecraft-Java.ts | 142 ++++++++------------------------ 1 file changed, 34 insertions(+), 108 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index f0af4fdf..90551c1c 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -9,10 +9,8 @@ import os from 'os'; import path from 'path'; import fs from 'fs'; import EventEmitter from 'events'; -import Seven from 'node-7z'; -import sevenBin from '7zip-bin'; -import { getFileHash } from '../utils/Index.js'; +import { getFileFromArchive } from '../utils/Index.js'; import Downloader from '../utils/Downloader.js'; /** @@ -167,78 +165,52 @@ export default class JavaDownloader extends EventEmitter { * @param versionDownload A forced Java version (string) if provided by the user. */ public async getJavaOther(jsonversion: MinecraftVersionJSON, versionDownload?: string): Promise { - // Determine which major version of Java we need - const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; const { platform, arch } = this.getPlatformArch(); + const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; + const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); + let javaExePath = path.join(pathFolder, 'bin', 'java'); - // Build the Adoptium API URL + // Build the API query to fetch the Java version const queryParams = new URLSearchParams({ - image_type: this.options.java.type, // e.g. "jdk" or "jre" - architecture: arch, - os: platform + java_version: majorVersion.toString(), + os: platform, + arch: arch, + archive_type: 'zip', + java_package_type: this.options.java.type }); - const javaVersionURL = `https://api.adoptium.net/v3/assets/latest/${majorVersion}/hotspot?${queryParams.toString()}`; - const javaVersions = await fetch(javaVersionURL).then(res => res.json()); - - // If no valid version is found, return an error - const java = javaVersions[0]; - if (!java) { - return { files: [], path: '', error: true, message: 'No Java found' }; + const javaVersionURL = `https://api.azul.com/metadata/v1/zulu/packages/?${queryParams.toString()}`; + let javaVersions = await fetch(javaVersionURL).then(res => res.json()); + if (!javaVersions || !javaVersions.length) { + return { files: [], path: javaExePath, error: true, message: 'No Java versions found for the specified criteria.' }; } - const { checksum, link: url, name: fileName } = java.binary.package; - const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); - const filePath = path.join(pathFolder, fileName); + javaVersions = javaVersions[0] + console - // Determine the final path to the java executable after extraction - let javaExePath = path.join(pathFolder, 'bin', 'java'); - if (platform === 'mac') { - javaExePath = path.join(pathFolder, 'Contents', 'Home', 'bin', 'java'); - } - - // Download and extract if needed if (!fs.existsSync(javaExePath)) { await this.verifyAndDownloadFile({ - filePath, - pathFolder, - fileName, - url, - checksum - }); + filePath: path.join(pathFolder, javaVersions.name), + pathFolder: pathFolder, + fileName: javaVersions.name, + url: javaVersions.download_url + }) - // Extract the downloaded archive - await this.extract(filePath, pathFolder); - fs.unlinkSync(filePath); + const entries = await getFileFromArchive(path.join(pathFolder, javaVersions.name), null, null, true); - // For .tar.gz files, we may need a second extraction step - if (filePath.endsWith('.tar.gz')) { - const tarFilePath = filePath.replace('.gz', ''); - await this.extract(tarFilePath, pathFolder); - if (fs.existsSync(tarFilePath)) { - fs.unlinkSync(tarFilePath); - } - } + for (const entry of entries) { + if (entry.name.startsWith('META-INF')) continue; - // If there's only one folder extracted, move its contents up - const extractedItems = fs.readdirSync(pathFolder); - if (extractedItems.length === 1) { - const singleFolder = path.join(pathFolder, extractedItems[0]); - const stat = fs.statSync(singleFolder); - if (stat.isDirectory()) { - const subItems = fs.readdirSync(singleFolder); - for (const item of subItems) { - const srcPath = path.join(singleFolder, item); - const destPath = path.join(pathFolder, item); - fs.renameSync(srcPath, destPath); - } - fs.rmdirSync(singleFolder); + if (entry.isDirectory) { + fs.mkdirSync(`${pathFolder}/${entry.name}`, { recursive: true, mode: 0o777 }); + continue; } + fs.writeFileSync(`${pathFolder}/${entry.name}`, entry.data, { mode: 0o777 }); } + } - // Ensure the Java executable is marked as executable on non-Windows systems - if (platform !== 'windows') { - fs.chmodSync(javaExePath, 0o755); - } + if (platform === 'macos') { + const a = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); + javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), a, 'java'); } return { files: [], path: javaExePath }; @@ -251,7 +223,7 @@ export default class JavaDownloader extends EventEmitter { private getPlatformArch(): { platform: string; arch: string } { const platformMap: Record = { win32: 'windows', - darwin: 'mac', + darwin: 'macos', linux: 'linux' }; const archMap: Record = { @@ -286,23 +258,13 @@ export default class JavaDownloader extends EventEmitter { filePath, pathFolder, fileName, - url, - checksum + url }: { filePath: string; pathFolder: string; fileName: string; url: string; - checksum: string; }): Promise { - // If the file already exists, check its integrity - if (fs.existsSync(filePath)) { - const existingChecksum = await getFileHash(filePath, 'sha256'); - if (existingChecksum !== checksum) { - fs.unlinkSync(filePath); - fs.rmSync(pathFolder, { recursive: true, force: true }); - } - } // If not found or failed checksum, download anew if (!fs.existsSync(filePath)) { @@ -317,41 +279,5 @@ export default class JavaDownloader extends EventEmitter { // Start download await download.downloadFile(url, pathFolder, fileName); } - - // Final verification of the downloaded file - const downloadedChecksum = await getFileHash(filePath, 'sha256'); - if (downloadedChecksum !== checksum) { - throw new Error('Java checksum failed'); - } - } - - /** - * Extracts the given archive (ZIP or 7Z), using the `node-7z` library and the system's 7z binary. - * Emits an "extract" event with the extraction progress (percent). - * - * @param filePath Path to the archive file - * @param destPath Destination folder to extract into - */ - private async extract(filePath: string, destPath: string): Promise { - // Ensure the 7z binary is executable on Unix-like OSes - if (os.platform() !== 'win32') { - fs.chmodSync(sevenBin.path7za, 0o755); - } - - return new Promise((resolve, reject) => { - const extractor = Seven.extractFull(filePath, destPath, { - $bin: sevenBin.path7za, - recursive: true, - $progress: true - }); - - extractor.on('end', () => resolve()); - extractor.on('error', (err) => reject(err)); - extractor.on('progress', (progress) => { - if (progress.percent > 0) { - this.emit('extract', progress.percent); - } - }); - }); } } From f151d3f48e2ea278a2f536d9e27e84c74e0bd865 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:09:47 +0200 Subject: [PATCH 156/185] Remove 7zip and node-7z dependencies Eliminated '7zip-bin', 'node-7z', and related type and utility packages from dependencies and devDependencies in package.json and package-lock.json. This reduces package size and removes unused or unnecessary modules. --- package-lock.json | 179 ---------------------------------------------- package.json | 4 -- 2 files changed, 183 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5dc4ef51..04209eba 100755 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,12 @@ "version": "4.1.4-beta.2", "license": "CCANC", "dependencies": { - "7zip-bin": "^5.2.0", - "node-7z": "^3.0.0", "prompt": "^1.3.0", "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { "@types/node": "^22.10.2", - "@types/node-7z": "^2.1.10", - "@types/node-fetch": "^2.6.12", "@types/semver": "^7.7.0", "rimraf": "^6.0.1", "typescript": "^5.7.2" @@ -64,27 +60,6 @@ "undici-types": "~6.20.0" } }, - "node_modules/@types/node-7z": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@types/node-7z/-/node-7z-2.1.10.tgz", - "integrity": "sha512-LdfuQcGAKsLafyM96+F8VekToCuGQnFy9DMM0UdS6f5pEnaP5kAixp3TQPc1NJU4C6IwLwnednktYBjp/z2LRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -92,12 +67,6 @@ "dev": true, "license": "MIT" }, - "node_modules/7zip-bin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", - "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -130,13 +99,6 @@ "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -183,19 +145,6 @@ "node": ">=0.1.90" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -219,33 +168,6 @@ "node": ">=0.4.0" } }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -285,21 +207,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/glob": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", @@ -369,36 +276,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash.defaultsdeep": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", - "license": "MIT" - }, - "node_modules/lodash.defaultto": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/lodash.defaultto/-/lodash.defaultto-4.14.0.tgz", - "integrity": "sha512-G6tizqH6rg4P5j32Wy4Z3ZIip7OfG8YWWlPFzUFGcYStH1Ld0l1tWs6NevEQNEDnO1M3NZYjuHuraaFSN5WqeQ==", - "license": "MIT" - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "license": "MIT" - }, - "node_modules/lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", - "license": "MIT" - }, - "node_modules/lodash.negate": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.negate/-/lodash.negate-3.0.2.tgz", - "integrity": "sha512-JGJYYVslKYC0tRMm/7igfdHulCjoXjoganRNWM8AgS+RXfOvFnPkOveDhPI65F9aAypCX9QEEQoBqWf7Q6uAeA==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -409,29 +286,6 @@ "node": "20 || >=22" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -458,45 +312,12 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "license": "ISC" }, - "node_modules/node-7z": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-7z/-/node-7z-3.0.0.tgz", - "integrity": "sha512-KIznWSxIkOYO/vOgKQfJEaXd7rgoFYKZbaurainCEdMhYc7V7mRHX+qdf2HgbpQFcdJL/Q6/XOPrDLoBeTfuZA==", - "license": "ISC", - "dependencies": { - "debug": "^4.3.2", - "lodash.defaultsdeep": "^4.6.1", - "lodash.defaultto": "^4.14.0", - "lodash.flattendeep": "^4.4.0", - "lodash.isempty": "^4.4.0", - "lodash.negate": "^3.0.2", - "normalize-path": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", diff --git a/package.json b/package.json index f9349156..9674e979 100755 --- a/package.json +++ b/package.json @@ -35,16 +35,12 @@ "author": "Luuxis", "license": "CCANC", "dependencies": { - "7zip-bin": "^5.2.0", - "node-7z": "^3.0.0", "prompt": "^1.3.0", "semver": "^7.7.2", "tslib": "^2.8.1" }, "devDependencies": { "@types/node": "^22.10.2", - "@types/node-7z": "^2.1.10", - "@types/node-fetch": "^2.6.12", "@types/semver": "^7.7.0", "rimraf": "^6.0.1", "typescript": "^5.7.2" From 92ae2cc6722f823bc8b38f3582117bca0574885f Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:16:25 +0200 Subject: [PATCH 157/185] Update stable version badge in README Replaced the static stable version badge with a dynamic npm version badge for minecraft-java-core. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67e68ae6..91a2e2d8 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ##### v4 • **minecraft‑java‑core** [![License: CC‑BY‑NC 4.0](https://img.shields.io/badge/License-CC--BY--NC%204.0-yellow.svg)](https://creativecommons.org/licenses/by-nc/4.0/) -![stable version](https://img.shields.io/badge/stable_version-4.0.0-blue) +![stable version](https://img.shields.io/npm/v/minecraft-java-core?logo=nodedotjs) **minecraft‑java‑core** is a **NodeJS/TypeScript** solution for launching both vanilla *and* modded Minecraft Java Edition without juggling JSON manifests, assets, libraries or Java runtimes yourself. Think of it as the *core* of an Electron/NW.js/CLI launcher. From 4b631db7d0767c6b389d8811c013ed1ab6f52996 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:16:33 +0200 Subject: [PATCH 158/185] Bump version to 4.1.4-beta.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04209eba..cdff44fd 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.2", + "version": "4.1.4-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.4-beta.2", + "version": "4.1.4-beta.3", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 9674e979..510b0f3b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.2", + "version": "4.1.4-beta.3", "types": "./build/Index.d.ts", "exports": { ".": { From 10513fe857b5b2d17a754c19d8ed63ee949c4caf Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:33:13 +0200 Subject: [PATCH 159/185] Fix Java executable path resolution for macOS Moved javaExePath initialization to after javaVersions is available and improved path construction for macOS by reading the correct bin directory. This ensures the correct Java binary is located on different platforms. --- src/Minecraft/Minecraft-Java.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 90551c1c..745cd94f 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -168,7 +168,6 @@ export default class JavaDownloader extends EventEmitter { const { platform, arch } = this.getPlatformArch(); const majorVersion = versionDownload || jsonversion.javaVersion?.majorVersion || 8; const pathFolder = path.resolve(this.options.path, `runtime/jre-${majorVersion}`); - let javaExePath = path.join(pathFolder, 'bin', 'java'); // Build the API query to fetch the Java version const queryParams = new URLSearchParams({ @@ -208,9 +207,10 @@ export default class JavaDownloader extends EventEmitter { } } + let javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), 'bin', 'java'); if (platform === 'macos') { - const a = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); - javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), a, 'java'); + const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); + javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); } return { files: [], path: javaExePath }; From ec372b7f3c9e801c12c05a3aa249f7ddae9096ed Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:43:15 +0200 Subject: [PATCH 160/185] Fix Java path resolution and error handling in downloader Improves error handling when no Java versions are found by returning an empty path and a clearer message. Refactors Java executable path resolution, especially for macOS, and removes redundant code. --- src/Minecraft/Minecraft-Java.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 745cd94f..9219ddb4 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -179,12 +179,16 @@ export default class JavaDownloader extends EventEmitter { }); const javaVersionURL = `https://api.azul.com/metadata/v1/zulu/packages/?${queryParams.toString()}`; let javaVersions = await fetch(javaVersionURL).then(res => res.json()); - if (!javaVersions || !javaVersions.length) { - return { files: [], path: javaExePath, error: true, message: 'No Java versions found for the specified criteria.' }; + if (!Array.isArray(javaVersions) || javaVersions.length === 0) { + return { files: [], path: '', error: true, message: 'No Java versions found for the specified parameters.' }; } + javaVersions = javaVersions[0]; - javaVersions = javaVersions[0] - console + let javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), 'bin', 'java'); + if (platform === 'macos') { + const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); + javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); + } if (!fs.existsSync(javaExePath)) { await this.verifyAndDownloadFile({ @@ -207,11 +211,7 @@ export default class JavaDownloader extends EventEmitter { } } - let javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), 'bin', 'java'); - if (platform === 'macos') { - const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); - javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); - } + return { files: [], path: javaExePath }; } From de9ad88be4e8fb95fab5396d4ba2f4e604bf54f8 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:55:24 +0200 Subject: [PATCH 161/185] Handle missing bin file for macOS Java path resolution Wrapped reading of the 'bin' file in try-catch blocks to prevent errors if the file does not exist when resolving the Java executable path on macOS. This improves robustness during Java extraction and path setup. --- src/Minecraft/Minecraft-Java.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 9219ddb4..f9db6a5b 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -186,8 +186,11 @@ export default class JavaDownloader extends EventEmitter { let javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), 'bin', 'java'); if (platform === 'macos') { - const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); - javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); + try { + const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); + javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); + } catch (_) { + } } if (!fs.existsSync(javaExePath)) { @@ -209,6 +212,14 @@ export default class JavaDownloader extends EventEmitter { } fs.writeFileSync(`${pathFolder}/${entry.name}`, entry.data, { mode: 0o777 }); } + + if (platform === 'macos') { + try { + const pathBin = fs.readFileSync(path.join(pathFolder, javaVersions.name.replace('.zip', ''), "bin"), 'utf8').toString(); + javaExePath = path.join(pathFolder, javaVersions.name.replace('.zip', ''), pathBin, 'java'); + } catch (_) { + } + } } From e46ccaecfd58a197e638e98f9849e647906bf9fe Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:22:37 +0200 Subject: [PATCH 162/185] Bump version to 4.1.4 Update package.json and package-lock.json to release version 4.1.4, moving out of beta. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cdff44fd..d2f0c972 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.3", + "version": "4.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.4-beta.3", + "version": "4.1.4", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 510b0f3b..43468fcb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.4-beta.3", + "version": "4.1.4", "types": "./build/Index.d.ts", "exports": { ".": { From 873721b6fa97b4e7e2472fd602fad45578b644bb Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:14:44 +0200 Subject: [PATCH 163/185] Copy Minecraft jar to version directory after install Adds logic to copy the Minecraft jar file to the corresponding version directory after writing the version JSON. This ensures the jar is available alongside the version metadata for Forge, NeoForge, Fabric, Legacy Fabric, and Quilt installations. --- src/Minecraft-Loader/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index 69949f4c..f854e498 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -160,9 +160,12 @@ export default class Loader extends EventEmitter { if (profile.error) return profile; // Write the version JSON to disk - const destination = path.resolve(this.options.path, 'versions', profile.version.id); - if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); - fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + if ("version" in profile && "id" in profile.version) { + const destination = path.resolve(this.options.path, 'versions', profile.version.id); + if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); + fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + fs.cpSync(path.resolve(this.options.loader.config.minecraftJar), path.resolve(destination, `${profile.version.id}.jar`)); + } // 3. Extract universal jar if needed const universal: any = await forge.extractUniversalJar(profile.install, installer.filePath); @@ -216,6 +219,7 @@ export default class Loader extends EventEmitter { const destination = path.resolve(this.options.path, 'versions', profile.version.id); if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); fs.writeFileSync(path.resolve(destination, `${profile.version.id}.json`), JSON.stringify(profile.version, null, 4)); + fs.cpSync(path.resolve(this.options.loader.config.minecraftJar), path.resolve(destination, `${profile.version.id}.jar`)); } // Extract universal jar const universal: any = await neoForge.extractUniversalJar(profile.install, installer.filePath, installer.oldAPI); @@ -256,6 +260,7 @@ export default class Loader extends EventEmitter { const destination = path.resolve(this.options.path, 'versions', json.id); if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + fs.cpSync(path.resolve(this.options.loader.config.minecraftJar), path.resolve(destination, `${json.id}.jar`)); } if ("libraries" in json) { @@ -289,6 +294,7 @@ export default class Loader extends EventEmitter { const destination = path.resolve(this.options.path, 'versions', json.id); if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + fs.cpSync(path.resolve(this.options.loader.config.minecraftJar), path.resolve(destination, `${json.id}.jar`)); } if ("libraries" in json) { await legacyFabric.downloadLibraries(json); @@ -320,6 +326,7 @@ export default class Loader extends EventEmitter { const destination = path.resolve(this.options.path, 'versions', json.id); if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); fs.writeFileSync(path.resolve(destination, `${json.id}.json`), JSON.stringify(json, null, 4)); + fs.cpSync(path.resolve(this.options.loader.config.minecraftJar), path.resolve(destination, `${json.id}.jar`)); } if ("libraries" in json) { await quilt.downloadLibraries(json); From ac3e7cf7cd5618b9dfc1514dc44805f691598e88 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:37:04 +0200 Subject: [PATCH 164/185] Add buffer overrun checks in unzipper utility Introduces additional boundary checks to prevent buffer overruns when parsing ZIP file structures. Also standardizes error messages to English for unsupported compression methods. --- src/utils/unzipper.ts | 46 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/utils/unzipper.ts b/src/utils/unzipper.ts index c824e86d..bba3ab3f 100644 --- a/src/utils/unzipper.ts +++ b/src/utils/unzipper.ts @@ -38,6 +38,8 @@ export default class Unzipper { let cdCursor = cdOffset; while (cdCursor < cdEnd) { + if (cdCursor + 46 > fileBuffer.length) break; // sécurité + if (fileBuffer.readUInt32LE(cdCursor) !== 0x02014b50) break; const compressionMethod = fileBuffer.readUInt16LE(cdCursor + 10); @@ -55,12 +57,28 @@ export default class Unzipper { ); const headerOffset = relativeOffset; - if (fileBuffer.readUInt32LE(headerOffset) !== 0x04034b50) continue; + // Sécurité sur l'offset du header + if (headerOffset + 30 > fileBuffer.length) { + cdCursor += 46 + fileNameLength + extraFieldLength + fileCommentLength; + continue; + } + if (fileBuffer.readUInt32LE(headerOffset) !== 0x04034b50) { + cdCursor += 46 + fileNameLength + extraFieldLength + fileCommentLength; + continue; + } const lfFileNameLength = fileBuffer.readUInt16LE(headerOffset + 26); const lfExtraFieldLength = fileBuffer.readUInt16LE(headerOffset + 28); const dataStart = headerOffset + 30 + lfFileNameLength + lfExtraFieldLength; - const compressedData = fileBuffer.slice(dataStart, dataStart + compressedSize); + const dataEnd = dataStart + compressedSize; + + // Sécurité: ne pas dépasser la taille du buffer + if (dataEnd > fileBuffer.length) { + cdCursor += 46 + fileNameLength + extraFieldLength + fileCommentLength; + continue; + } + + const compressedData = fileBuffer.slice(dataStart, dataEnd); this.entries.push({ entryName: fileName, @@ -71,7 +89,7 @@ export default class Unzipper { } else if (compressionMethod === 0) { return compressedData; } else { - throw new Error(`Méthode de compression non supportée: ${compressionMethod}`); + throw new Error(`Unsupported compression method: ${compressionMethod}`); } } }); @@ -88,6 +106,8 @@ export default class Unzipper { if (signaturePos === -1) break; const headerOffset = signaturePos; + if (headerOffset + 30 > fileBuffer.length) break; + const compressionMethod = fileBuffer.readUInt16LE(headerOffset + 8); const compressedSize = fileBuffer.readUInt32LE(headerOffset + 18); const uncompressedSize = fileBuffer.readUInt32LE(headerOffset + 22); @@ -95,14 +115,24 @@ export default class Unzipper { const extraFieldLength = fileBuffer.readUInt16LE(headerOffset + 28); const fileNameStart = headerOffset + 30; + const fileNameEnd = fileNameStart + fileNameLength; + if (fileNameEnd > fileBuffer.length) break; + const fileName = fileBuffer.toString( 'utf-8', fileNameStart, - fileNameStart + fileNameLength + fileNameEnd ); - const dataStart = fileNameStart + fileNameLength + extraFieldLength; - const compressedData = fileBuffer.slice(dataStart, dataStart + compressedSize); + const dataStart = fileNameEnd + extraFieldLength; + const dataEnd = dataStart + compressedSize; + + if (dataEnd > fileBuffer.length) { + currentOffset = dataEnd; + continue; + } + + const compressedData = fileBuffer.slice(dataStart, dataEnd); this.entries.push({ entryName: fileName, @@ -113,12 +143,12 @@ export default class Unzipper { } else if (compressionMethod === 0) { return compressedData; } else { - throw new Error(`Méthode de compression non supportée: ${compressionMethod}`); + throw new Error(`Unsupported compression method: ${compressionMethod}`); } } }); - currentOffset = dataStart + compressedSize; + currentOffset = dataEnd; } } } From 2b307cfd6b38abaa882101f15f5a9c739c08d997 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:08:26 +0200 Subject: [PATCH 165/185] Update license to Luuxis License v1.0 Replaced the previous CC-BY-NC 4.0 license with a custom Luuxis License v1.0 in LICENSE.md and updated license headers in all source files to reference the new license and author attribution. This change clarifies usage, redistribution, and commercial restrictions as defined by the new license. --- LICENSE.md | 187 +++++------------- src/Authenticator/AZauth.ts | 6 +- src/Authenticator/GUI/Electron.ts | 6 +- src/Authenticator/GUI/NW.ts | 6 +- src/Authenticator/GUI/Terminal.ts | 6 +- src/Authenticator/Microsoft.ts | 6 +- src/Authenticator/Mojang.ts | 6 +- src/Index.ts | 6 +- src/Launch.ts | 6 +- src/Minecraft-Loader/index.ts | 6 +- src/Minecraft-Loader/loader/fabric/fabric.ts | 6 +- src/Minecraft-Loader/loader/forge/forge.ts | 6 +- .../loader/legacyfabric/legacyFabric.ts | 6 +- .../loader/neoForge/neoForge.ts | 6 +- src/Minecraft-Loader/loader/quilt/quilt.ts | 6 +- src/Minecraft-Loader/patcher.ts | 6 +- src/Minecraft/Minecraft-Arguments.ts | 6 +- src/Minecraft/Minecraft-Assets.ts | 6 +- src/Minecraft/Minecraft-Bundle.ts | 6 +- src/Minecraft/Minecraft-Java.ts | 6 +- src/Minecraft/Minecraft-Json.ts | 6 +- src/Minecraft/Minecraft-Libraries.ts | 6 +- src/Minecraft/Minecraft-Loader.ts | 6 +- src/Minecraft/Minecraft-Lwjgl-Native.ts | 6 +- src/StatusServer/buffer.ts | 6 +- src/StatusServer/status.ts | 6 +- src/utils/Downloader.ts | 6 +- src/utils/Index.ts | 6 +- 28 files changed, 106 insertions(+), 243 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index c94c4001..82901b88 100755 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,160 +1,77 @@ -# Creative Commons Attribution-NonCommercial 4.0 International +LUUXIS LICENSE v1.0 – LICENCE PERSONNALISÉE / CUSTOM LICENSE -Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. +Copyright © 2025 Luuxis -### Using Creative Commons Public Licenses +FRANÇAIS 🇫🇷 +──────────────────────────────────────────────────────────── -Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. +Ce logiciel et son code source sont la propriété exclusive de l’auteur (ci-après « le Titulaire »). +L’utilisation, la modification et la redistribution sont autorisées sous réserve du respect strict des conditions suivantes : -* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). +1. 🛑 Interdiction d’usage commercial par des tiers + - Il est strictement interdit de vendre, louer ou redistribuer ce code (ou ses dérivés) à des fins commerciales. + - Toute utilisation commerciale directe du code est interdite, sauf autorisation écrite explicite du Titulaire. -* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). +2. 💰 Microtransactions autorisées + - La monétisation par microtransactions en jeu est autorisée, tant que : + - le code n’est pas vendu ni monétisé directement, + - le code source reste public et accessible. -## Creative Commons Attribution-NonCommercial 4.0 International Public License +3. 📂 Code source obligatoire et public + - Toute version redistribuée ou modifiée doit publier son code source complet, librement et gratuitement accessible, sans restriction. -By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. +4. 🧾 Attribution + - Le nom de l’auteur original (« Luuxis ») doit être clairement mentionné dans toute redistribution ou version modifiée. -### Section 1 – Definitions. +5. 🔐 Droit exclusif de revente par le créateur + - Seul le Titulaire (Luuxis) est autorisé à vendre ou concéder une licence commerciale du code source. -a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. +6. 🚫 Interdiction de modifier cette licence + - Il est interdit de modifier, supprimer ou remplacer cette licence. Toute redistribution doit inclure cette licence sans altération. -b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. +7. ⚠️ Absence de garantie + - Le logiciel est fourni "tel quel", sans garantie. L’auteur décline toute responsabilité pour tout dommage lié à son usage. -c. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. +8. ⚖️ Violation et poursuites + - Tout non-respect entraîne la résiliation immédiate de cette licence. L’usage non autorisé peut entraîner des poursuites conformément aux articles L.122-1 et L.335-3 du Code de la propriété intellectuelle (France). -d. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. +Par l’utilisation de ce logiciel, vous acceptez toutes les conditions de cette licence. -e. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. +──────────────────────────────────────────────────────────── -f. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. +ENGLISH 🇬🇧 +──────────────────────────────────────────────────────────── -g. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. +This software and its source code are the exclusive property of the author (hereinafter “the Licensor”). +Use, modification, and redistribution are permitted under the following strict conditions: -h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. +1. 🛑 Prohibition of commercial use by third parties + - It is strictly forbidden to sell, rent or redistribute this code (or any derivative) for commercial purposes. + - Any direct commercial use of the code is forbidden unless explicitly authorized in writing by the Licensor. -i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. +2. 💰 Microtransactions allowed + - In-game microtransaction monetization is allowed, as long as: + - the code itself is not sold or directly monetized, + - the source code remains public and freely accessible. -j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. +3. 📂 Mandatory public source code + - Any redistributed or modified version must make its complete source code freely and publicly accessible, without restrictions. -k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. +4. 🧾 Attribution + - The original author’s name (“Luuxis”) must be clearly credited in any redistribution or modified version. -l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. __Your__ has a corresponding meaning. +5. 🔐 Exclusive resale rights reserved by the creator + - Only the Licensor (Luuxis) is allowed to sell or license this software or its source code for commercial purposes. -### Section 2 – Scope. +6. 🚫 License modification is forbidden + - This license must not be altered, replaced, or removed. Any redistribution must include this license as-is. -a. ___License grant.___ +7. ⚠️ No warranty + - This software is provided “as is”, without any warranties. The author accepts no liability for any damage resulting from its use. - 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: +8. ⚖️ Violation and legal consequences + - Any violation results in immediate termination of this license. Unauthorized use may lead to legal action under applicable copyright law. - A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and - - B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. - - 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. - - 3. __Term.__ The term of this Public License is specified in Section 6(a). - - 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. - - 5. __Downstream recipients.__ - - A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. - - B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. - - 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). - -b. ___Other rights.___ - - 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this Public License. - - 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. - -### Section 3 – License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the following conditions. - -a. ___Attribution.___ - - 1. If You Share the Licensed Material (including in modified form), You must: - - A. retain the following if it is supplied by the Licensor with the Licensed Material: - - i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of warranties; - - v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; - - B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and - - C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. - - 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. - -### Section 4 – Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: - -a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; - -b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and - -c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. - -### Section 5 – Disclaimer of Warranties and Limitation of Liability. - -a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ - -b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ - -c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -### Section 6 – Term and Termination. - -a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. - -b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. - -c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. - -d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. - -### Section 7 – Other Terms and Conditions. - -a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. - -b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. - -### Section 8 – Interpretation. - -a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. - -b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. - -c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. - -d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. - -> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. -> -> Creative Commons may be contacted at creativecommons.org +By using this software, you fully and irrevocably agree to the terms of this license. +──────────────────────────────────────────────────────────── diff --git a/src/Authenticator/AZauth.ts b/src/Authenticator/AZauth.ts index 0c0232ef..d52e7731 100755 --- a/src/Authenticator/AZauth.ts +++ b/src/Authenticator/AZauth.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { Buffer } from 'node:buffer'; diff --git a/src/Authenticator/GUI/Electron.ts b/src/Authenticator/GUI/Electron.ts index f3aaa84b..a0f6f628 100755 --- a/src/Authenticator/GUI/Electron.ts +++ b/src/Authenticator/GUI/Electron.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ const path = require('path') diff --git a/src/Authenticator/GUI/NW.ts b/src/Authenticator/GUI/NW.ts index 0175e9d0..aac345c1 100755 --- a/src/Authenticator/GUI/NW.ts +++ b/src/Authenticator/GUI/NW.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import path from 'path'; diff --git a/src/Authenticator/GUI/Terminal.ts b/src/Authenticator/GUI/Terminal.ts index b794775e..9eddb839 100755 --- a/src/Authenticator/GUI/Terminal.ts +++ b/src/Authenticator/GUI/Terminal.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import prompt from 'prompt'; diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 9d08d37b..99a9fcb4 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { Buffer } from 'node:buffer'; diff --git a/src/Authenticator/Mojang.ts b/src/Authenticator/Mojang.ts index 1dce376a..f244f807 100755 --- a/src/Authenticator/Mojang.ts +++ b/src/Authenticator/Mojang.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import crypto from 'crypto'; diff --git a/src/Index.ts b/src/Index.ts index 80e49928..60babe3f 100755 --- a/src/Index.ts +++ b/src/Index.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import AZauth from './Authenticator/AZauth.js'; diff --git a/src/Launch.ts b/src/Launch.ts index c43d52e4..496e33ff 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { EventEmitter } from 'events'; diff --git a/src/Minecraft-Loader/index.ts b/src/Minecraft-Loader/index.ts index f854e498..f2ad73fd 100644 --- a/src/Minecraft-Loader/index.ts +++ b/src/Minecraft-Loader/index.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { EventEmitter } from 'events'; diff --git a/src/Minecraft-Loader/loader/fabric/fabric.ts b/src/Minecraft-Loader/loader/fabric/fabric.ts index 4a4a1a16..e092d49a 100644 --- a/src/Minecraft-Loader/loader/fabric/fabric.ts +++ b/src/Minecraft-Loader/loader/fabric/fabric.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { EventEmitter } from 'events'; diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index f36e47a1..acdc2bc5 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ diff --git a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts index 9761741d..dbca8cf1 100644 --- a/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts +++ b/src/Minecraft-Loader/loader/legacyfabric/legacyFabric.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 167cc7f1..4265c184 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft-Loader/loader/quilt/quilt.ts b/src/Minecraft-Loader/loader/quilt/quilt.ts index 467b79af..1ca069cb 100644 --- a/src/Minecraft-Loader/loader/quilt/quilt.ts +++ b/src/Minecraft-Loader/loader/quilt/quilt.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft-Loader/patcher.ts b/src/Minecraft-Loader/patcher.ts index 8196106f..249d6cbf 100644 --- a/src/Minecraft-Loader/patcher.ts +++ b/src/Minecraft-Loader/patcher.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { spawn } from 'child_process'; diff --git a/src/Minecraft/Minecraft-Arguments.ts b/src/Minecraft/Minecraft-Arguments.ts index 8eb741c0..1fdce0c2 100755 --- a/src/Minecraft/Minecraft-Arguments.ts +++ b/src/Minecraft/Minecraft-Arguments.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft/Minecraft-Assets.ts b/src/Minecraft/Minecraft-Assets.ts index aa836ddc..382a8327 100755 --- a/src/Minecraft/Minecraft-Assets.ts +++ b/src/Minecraft/Minecraft-Assets.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft/Minecraft-Bundle.ts b/src/Minecraft/Minecraft-Bundle.ts index 94222c24..08aa00e1 100755 --- a/src/Minecraft/Minecraft-Bundle.ts +++ b/src/Minecraft/Minecraft-Bundle.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index f9db6a5b..611158bd 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import os from 'os'; diff --git a/src/Minecraft/Minecraft-Json.ts b/src/Minecraft/Minecraft-Json.ts index b2f561f2..340f01d8 100755 --- a/src/Minecraft/Minecraft-Json.ts +++ b/src/Minecraft/Minecraft-Json.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import os from 'os'; diff --git a/src/Minecraft/Minecraft-Libraries.ts b/src/Minecraft/Minecraft-Libraries.ts index 1884e84e..df21c512 100755 --- a/src/Minecraft/Minecraft-Libraries.ts +++ b/src/Minecraft/Minecraft-Libraries.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import os from 'os'; diff --git a/src/Minecraft/Minecraft-Loader.ts b/src/Minecraft/Minecraft-Loader.ts index 082d5ee5..84e4a3a8 100755 --- a/src/Minecraft/Minecraft-Loader.ts +++ b/src/Minecraft/Minecraft-Loader.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import { EventEmitter } from 'events'; diff --git a/src/Minecraft/Minecraft-Lwjgl-Native.ts b/src/Minecraft/Minecraft-Lwjgl-Native.ts index 0cf9d8c7..1aeb5f16 100644 --- a/src/Minecraft/Minecraft-Lwjgl-Native.ts +++ b/src/Minecraft/Minecraft-Lwjgl-Native.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import path from 'path'; diff --git a/src/StatusServer/buffer.ts b/src/StatusServer/buffer.ts index 365915ab..f7d250f5 100644 --- a/src/StatusServer/buffer.ts +++ b/src/StatusServer/buffer.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ function CustomBuffer(existingBuffer: any = Buffer.alloc(48)) { diff --git a/src/StatusServer/status.ts b/src/StatusServer/status.ts index a16f7fdd..a0080975 100755 --- a/src/StatusServer/status.ts +++ b/src/StatusServer/status.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import net from 'net' diff --git a/src/utils/Downloader.ts b/src/utils/Downloader.ts index 8959f224..57a784e4 100755 --- a/src/utils/Downloader.ts +++ b/src/utils/Downloader.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import fs from 'fs'; diff --git a/src/utils/Index.ts b/src/utils/Index.ts index 89a9e01a..f92d3589 100755 --- a/src/utils/Index.ts +++ b/src/utils/Index.ts @@ -1,8 +1,6 @@ /** - * This code is distributed under the CC-BY-NC 4.0 license: - * https://creativecommons.org/licenses/by-nc/4.0/ - * - * Original author: Luuxis + * @author Luuxis + * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ import crypto from 'crypto'; From dcfba870ff7e8eccb81c473a3aa3f0da19d64a15 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:08:51 +0200 Subject: [PATCH 166/185] Bump version to 4.1.5 Update package.json and package-lock.json to version 4.1.5 for minecraft-java-core. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2f0c972..d15a3d6a 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.4", + "version": "4.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.4", + "version": "4.1.5", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 43468fcb..5daaf7e4 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.4", + "version": "4.1.5", "types": "./build/Index.d.ts", "exports": { ".": { From 738833a8f01d86c7a2982a4ba0c0c51b2aad294f Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Fri, 15 Aug 2025 03:11:55 +0200 Subject: [PATCH 167/185] Update test config and add error handling Changed test launcher configuration to use new instance, version, and loader settings. Added error event handler for improved debugging. Minor cleanup in Minecraft-Java.ts. --- src/Minecraft/Minecraft-Java.ts | 2 -- test/index.js | 16 ++++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Minecraft/Minecraft-Java.ts b/src/Minecraft/Minecraft-Java.ts index 611158bd..6957c452 100755 --- a/src/Minecraft/Minecraft-Java.ts +++ b/src/Minecraft/Minecraft-Java.ts @@ -220,8 +220,6 @@ export default class JavaDownloader extends EventEmitter { } } - - return { files: [], path: javaExePath }; } diff --git a/test/index.js b/test/index.js index 5391a562..c3c8030f 100755 --- a/test/index.js +++ b/test/index.js @@ -21,14 +21,16 @@ let mc } await launcher.Launch({ + url: "http://launcher.luuxis.fr/files?instance=Lost World - The Broken Script", path: './minecraft', authenticator: mc, - version: '1.16.5', + version: '1.21.1', intelEnabledMac: true, - instance: "test-instance", + instance: "Lost World - The Broken Script", + loader: { - type: 'forge', - build: 'latest', + type: 'neoforge', + build: '21.1.193', enable: true }, memory: { @@ -37,13 +39,15 @@ let mc }, java: { path: null, - version: null, + version: '24', type: 'jre', }, }); - launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) + launcher + .on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) .on('patch', pacth => process.stdout.write(pacth)) .on('data', line => process.stdout.write(line)) + .on('error', err => console.error(err)) .on('close', () => console.log('Game exited.')); })(); From 8d98ea18118b74de00585f1eb91366bebe46a2a6 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Mon, 18 Aug 2025 16:58:34 +0200 Subject: [PATCH 168/185] Update launcher config for Hypixel instance Changed the launcher configuration to target the Hypixel instance with Minecraft version 1.8.9 and Forge loader. Updated memory allocation and added ignored files and folders to the configuration. --- test/index.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/index.js b/test/index.js index c3c8030f..080146f3 100755 --- a/test/index.js +++ b/test/index.js @@ -21,26 +21,29 @@ let mc } await launcher.Launch({ - url: "http://launcher.luuxis.fr/files?instance=Lost World - The Broken Script", + url: "http://launcher.luuxis.fr/files?instance=Hypixel", path: './minecraft', authenticator: mc, - version: '1.21.1', + version: '1.8.9', intelEnabledMac: true, - instance: "Lost World - The Broken Script", + instance: "Hypixel", + + ignored: [ + "config", + "logs", + "resourcepacks", + "options.txt", + "optionsof.txt" + ], loader: { - type: 'neoforge', - build: '21.1.193', + type: 'forge', + build: 'latest', enable: true }, memory: { - min: '2G', - max: '4G' - }, - java: { - path: null, - version: '24', - type: 'jre', + min: '14G', + max: '16G' }, }); From 8710bf6072802f153423cc2a7f5acc041e7cc973 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:58:03 +0200 Subject: [PATCH 169/185] Add Forge maven metadata and update loader logic Added a comprehensive Forge maven-metadata.json file to assets. Updated forge loader logic in forge.ts to utilize this metadata. Adjusted package.json and test/index.js to support these changes. --- assets/forge/maven-metadata.json | 4969 ++++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- src/Minecraft-Loader/loader/forge/forge.ts | 13 +- test/index.js | 2 +- 5 files changed, 4983 insertions(+), 7 deletions(-) create mode 100644 assets/forge/maven-metadata.json diff --git a/assets/forge/maven-metadata.json b/assets/forge/maven-metadata.json new file mode 100644 index 00000000..a8182810 --- /dev/null +++ b/assets/forge/maven-metadata.json @@ -0,0 +1,4969 @@ +{ + "1.1": [ + "1.1-1.3.2.1", + "1.1-1.3.2.2", + "1.1-1.3.2.3", + "1.1-1.3.2.4", + "1.1-1.3.2.5", + "1.1-1.3.2.6", + "1.1-1.3.2.7", + "1.1-1.3.2.8", + "1.1-1.3.2.9", + "1.1-1.3.2.10", + "1.1-1.3.3.12", + "1.1-1.3.3.13", + "1.1-1.3.3.14", + "1.1-1.3.3.15", + "1.1-1.3.3.16", + "1.1-1.3.3.18", + "1.1-1.3.3.19", + "1.1-1.3.3.20", + "1.1-1.3.3.21", + "1.1-1.3.3.22", + "1.1-1.3.3.23", + "1.1-1.3.3.24", + "1.1-1.3.3.26", + "1.1-1.3.3.27", + "1.1-1.3.3.28", + "1.1-1.3.4.29" + ], + "1.2.3": [ + "1.2.3-1.3.4.30", + "1.2.3-1.3.4.31", + "1.2.3-1.3.4.32", + "1.2.3-1.3.4.33", + "1.2.3-1.3.4.34", + "1.2.3-1.3.4.35", + "1.2.3-1.3.4.36", + "1.2.3-1.3.4.37", + "1.2.3-1.3.4.38", + "1.2.3-1.3.4.39", + "1.2.3-1.3.4.41", + "1.2.3-1.4.0.44", + "1.2.3-1.4.0.45", + "1.2.3-1.4.0.46", + "1.2.3-1.4.0.47", + "1.2.3-1.4.0.48", + "1.2.3-1.4.0.50", + "1.2.3-1.4.0.51", + "1.2.3-1.4.0.52", + "1.2.3-1.4.0.55", + "1.2.3-1.4.0.56", + "1.2.3-1.4.0.57", + "1.2.3-1.4.1.58", + "1.2.3-1.4.1.59", + "1.2.3-1.4.1.60", + "1.2.3-1.4.1.61", + "1.2.3-1.4.1.62", + "1.2.3-1.4.1.63", + "1.2.3-1.4.1.64" + ], + "1.2.4": [ + "1.2.4-2.0.0.65", + "1.2.4-2.0.0.66", + "1.2.4-2.0.0.67", + "1.2.4-2.0.0.68" + ], + "1.2.5": [ + "1.2.5-3.0.0.69", + "1.2.5-3.0.0.70", + "1.2.5-3.0.0.71", + "1.2.5-3.0.0.72", + "1.2.5-3.0.1.73", + "1.2.5-3.0.1.74", + "1.2.5-3.0.1.75", + "1.2.5-3.0.1.77", + "1.2.5-3.0.1.78", + "1.2.5-3.0.1.79", + "1.2.5-3.0.1.80", + "1.2.5-3.0.1.81", + "1.2.5-3.0.1.82", + "1.2.5-3.0.1.83", + "1.2.5-3.0.1.84", + "1.2.5-3.0.1.85", + "1.2.5-3.0.1.86", + "1.2.5-3.0.1.87", + "1.2.5-3.0.1.88", + "1.2.5-3.0.1.89", + "1.2.5-3.1.2.90", + "1.2.5-3.1.2.91", + "1.2.5-3.1.2.92", + "1.2.5-3.1.2.93", + "1.2.5-3.1.2.94", + "1.2.5-3.1.2.95", + "1.2.5-3.1.2.96", + "1.2.5-3.1.2.97", + "1.2.5-3.1.2.98", + "1.2.5-3.1.3.99", + "1.2.5-3.1.3.100", + "1.2.5-3.1.3.101", + "1.2.5-3.1.3.102", + "1.2.5-3.1.3.103", + "1.2.5-3.1.3.104", + "1.2.5-3.1.3.105", + "1.2.5-3.1.3.106", + "1.2.5-3.1.3.107", + "1.2.5-3.2.3.108", + "1.2.5-3.2.4.110", + "1.2.5-3.2.4.111", + "1.2.5-3.2.4.114", + "1.2.5-3.2.4.115", + "1.2.5-3.2.4.116", + "1.2.5-3.2.5.117", + "1.2.5-3.2.5.118", + "1.2.5-3.2.5.119", + "1.2.5-3.2.5.120", + "1.2.5-3.2.5.121", + "1.2.5-3.2.5.122", + "1.2.5-3.2.5.123", + "1.2.5-3.2.5.124", + "1.2.5-3.2.5.125", + "1.2.5-3.2.5.126", + "1.2.5-3.2.5.127", + "1.2.5-3.2.5.128", + "1.2.5-3.2.6.129", + "1.2.5-3.2.6.130", + "1.2.5-3.2.6.131", + "1.2.5-3.2.6.132", + "1.2.5-3.3.7.133", + "1.2.5-3.3.7.134", + "1.2.5-3.3.7.135", + "1.2.5-3.3.7.136", + "1.2.5-3.3.7.137", + "1.2.5-3.3.7.138", + "1.2.5-3.3.7.139", + "1.2.5-3.3.7.140", + "1.2.5-3.3.8.141", + "1.2.5-3.3.8.142", + "1.2.5-3.3.8.143", + "1.2.5-3.3.8.144", + "1.2.5-3.3.8.145", + "1.2.5-3.3.8.146", + "1.2.5-3.3.8.147", + "1.2.5-3.3.8.148", + "1.2.5-3.3.8.150", + "1.2.5-3.3.8.151", + "1.2.5-3.3.8.152", + "1.2.5-3.3.8.153", + "1.2.5-3.3.8.154", + "1.2.5-3.3.8.155", + "1.2.5-3.3.8.156", + "1.2.5-3.3.8.157", + "1.2.5-3.3.8.158", + "1.2.5-3.3.8.159", + "1.2.5-3.3.8.160", + "1.2.5-3.3.8.161", + "1.2.5-3.3.8.162", + "1.2.5-3.3.8.163", + "1.2.5-3.3.8.164", + "1.2.5-3.3.8.168", + "1.2.5-3.3.8.170", + "1.2.5-3.4.9.171" + ], + "1.3.2": [ + "1.3.2-4.0.0.172", + "1.3.2-4.0.0.173", + "1.3.2-4.0.0.176", + "1.3.2-4.0.0.177", + "1.3.2-4.0.0.178", + "1.3.2-4.0.0.179", + "1.3.2-4.0.0.180", + "1.3.2-4.0.0.181", + "1.3.2-4.0.0.182", + "1.3.2-4.0.0.183", + "1.3.2-4.0.0.184", + "1.3.2-4.0.0.185", + "1.3.2-4.0.0.186", + "1.3.2-4.0.0.187", + "1.3.2-4.0.0.188", + "1.3.2-4.0.0.189", + "1.3.2-4.0.0.190", + "1.3.2-4.0.0.191", + "1.3.2-4.0.0.192", + "1.3.2-4.0.0.193", + "1.3.2-4.0.0.194", + "1.3.2-4.0.0.195", + "1.3.2-4.0.0.196", + "1.3.2-4.0.0.197", + "1.3.2-4.0.0.198", + "1.3.2-4.0.0.199", + "1.3.2-4.0.0.200", + "1.3.2-4.0.0.204", + "1.3.2-4.0.0.205", + "1.3.2-4.0.0.206", + "1.3.2-4.0.0.207", + "1.3.2-4.0.0.208", + "1.3.2-4.0.0.209", + "1.3.2-4.0.0.210", + "1.3.2-4.0.0.211", + "1.3.2-4.0.0.212", + "1.3.2-4.0.0.213", + "1.3.2-4.0.0.214", + "1.3.2-4.0.0.215", + "1.3.2-4.0.0.216", + "1.3.2-4.0.0.217", + "1.3.2-4.0.0.220", + "1.3.2-4.0.0.221", + "1.3.2-4.0.0.222", + "1.3.2-4.0.0.223", + "1.3.2-4.0.0.224", + "1.3.2-4.0.0.225", + "1.3.2-4.0.0.226", + "1.3.2-4.0.0.227", + "1.3.2-4.0.0.228", + "1.3.2-4.0.0.229", + "1.3.2-4.0.0.230", + "1.3.2-4.0.0.231", + "1.3.2-4.0.0.232", + "1.3.2-4.0.0.233", + "1.3.2-4.0.0.234", + "1.3.2-4.0.0.235", + "1.3.2-4.0.0.236", + "1.3.2-4.0.0.237", + "1.3.2-4.0.0.238", + "1.3.2-4.0.0.239", + "1.3.2-4.0.0.240", + "1.3.2-4.0.0.241", + "1.3.2-4.0.0.242", + "1.3.2-4.0.0.243", + "1.3.2-4.0.0.245", + "1.3.2-4.0.0.246", + "1.3.2-4.0.0.247", + "1.3.2-4.0.0.248", + "1.3.2-4.0.0.249", + "1.3.2-4.0.0.250", + "1.3.2-4.1.1.251", + "1.3.2-4.1.1.252", + "1.3.2-4.1.1.253", + "1.3.2-4.1.1.254", + "1.3.2-4.1.1.255", + "1.3.2-4.1.1.256", + "1.3.2-4.1.1.257", + "1.3.2-4.1.1.258", + "1.3.2-4.1.2.259", + "1.3.2-4.1.2.260", + "1.3.2-4.1.2.261", + "1.3.2-4.1.2.262", + "1.3.2-4.1.2.263", + "1.3.2-4.1.2.264", + "1.3.2-4.1.2.265", + "1.3.2-4.1.2.266", + "1.3.2-4.1.2.267", + "1.3.2-4.1.2.268", + "1.3.2-4.1.2.269", + "1.3.2-4.1.3.270", + "1.3.2-4.1.4.271", + "1.3.2-4.1.4.272", + "1.3.2-4.1.4.274", + "1.3.2-4.1.4.275", + "1.3.2-4.1.4.276", + "1.3.2-4.1.4.277", + "1.3.2-4.1.4.278", + "1.3.2-4.1.4.279", + "1.3.2-4.1.4.280", + "1.3.2-4.1.4.281", + "1.3.2-4.1.4.282", + "1.3.2-4.1.4.284", + "1.3.2-4.1.4.285", + "1.3.2-4.1.4.286", + "1.3.2-4.1.4.287", + "1.3.2-4.1.4.288", + "1.3.2-4.1.4.289", + "1.3.2-4.1.4.290", + "1.3.2-4.1.4.291", + "1.3.2-4.1.4.292", + "1.3.2-4.1.4.294", + "1.3.2-4.1.4.295", + "1.3.2-4.1.4.296", + "1.3.2-4.1.4.297", + "1.3.2-4.1.4.298", + "1.3.2-4.2.5.299", + "1.3.2-4.2.5.300", + "1.3.2-4.2.5.301", + "1.3.2-4.2.5.302", + "1.3.2-4.2.5.303", + "1.3.2-4.2.5.305", + "1.3.2-4.2.5.306", + "1.3.2-4.2.5.307", + "1.3.2-4.2.5.310", + "1.3.2-4.2.5.311", + "1.3.2-4.2.5.312", + "1.3.2-4.2.5.313", + "1.3.2-4.2.5.314", + "1.3.2-4.2.5.315", + "1.3.2-4.2.5.316", + "1.3.2-4.2.5.317", + "1.3.2-4.3.5.318" + ], + "1.4.0": [ + "1.4.0-5.0.0.320", + "1.4.0-5.0.0.321", + "1.4.0-5.0.0.322", + "1.4.0-5.0.0.323", + "1.4.0-5.0.0.324", + "1.4.0-5.0.0.325", + "1.4.0-5.0.0.326" + ], + "1.4.1": [ + "1.4.1-6.0.0.327", + "1.4.1-6.0.0.328", + "1.4.1-6.0.0.329" + ], + "1.4.2": [ + "1.4.2-6.0.1.330", + "1.4.2-6.0.1.331", + "1.4.2-6.0.1.332", + "1.4.2-6.0.1.336", + "1.4.2-6.0.1.337", + "1.4.2-6.0.1.338", + "1.4.2-6.0.1.339", + "1.4.2-6.0.1.341", + "1.4.2-6.0.1.342", + "1.4.2-6.0.1.343", + "1.4.2-6.0.1.345", + "1.4.2-6.0.1.347", + "1.4.2-6.0.1.348", + "1.4.2-6.0.1.349", + "1.4.2-6.0.1.350", + "1.4.2-6.0.1.351", + "1.4.2-6.0.1.353", + "1.4.2-6.0.1.354", + "1.4.2-6.0.1.355" + ], + "1.4.3": [ + "1.4.3-6.2.1.356", + "1.4.3-6.2.1.357", + "1.4.3-6.2.1.358" + ], + "1.4.4": [ + "1.4.4-6.3.0.360", + "1.4.4-6.3.0.361", + "1.4.4-6.3.0.362", + "1.4.4-6.3.0.363", + "1.4.4-6.3.0.364", + "1.4.4-6.3.0.365", + "1.4.4-6.3.0.366", + "1.4.4-6.3.0.367", + "1.4.4-6.3.0.368", + "1.4.4-6.3.0.369", + "1.4.4-6.3.0.370", + "1.4.4-6.3.0.371", + "1.4.4-6.3.0.372", + "1.4.4-6.3.0.373", + "1.4.4-6.3.0.374", + "1.4.4-6.3.0.375", + "1.4.4-6.3.0.376", + "1.4.4-6.3.0.377", + "1.4.4-6.3.0.378" + ], + "1.4.5": [ + "1.4.5-6.4.0.379", + "1.4.5-6.4.0.380", + "1.4.5-6.4.0.381", + "1.4.5-6.4.0.382", + "1.4.5-6.4.0.383", + "1.4.5-6.4.0.384", + "1.4.5-6.4.0.385", + "1.4.5-6.4.0.386", + "1.4.5-6.4.0.387", + "1.4.5-6.4.0.388", + "1.4.5-6.4.0.390", + "1.4.5-6.4.0.393", + "1.4.5-6.4.0.394", + "1.4.5-6.4.0.395", + "1.4.5-6.4.0.396", + "1.4.5-6.4.0.397", + "1.4.5-6.4.0.398", + "1.4.5-6.4.0.399", + "1.4.5-6.4.1.400", + "1.4.5-6.4.1.401", + "1.4.5-6.4.1.402", + "1.4.5-6.4.1.403", + "1.4.5-6.4.1.404", + "1.4.5-6.4.1.405", + "1.4.5-6.4.1.406", + "1.4.5-6.4.1.407", + "1.4.5-6.4.1.408", + "1.4.5-6.4.1.409", + "1.4.5-6.4.1.410", + "1.4.5-6.4.1.411", + "1.4.5-6.4.1.413", + "1.4.5-6.4.1.414", + "1.4.5-6.4.1.416", + "1.4.5-6.4.1.424", + "1.4.5-6.4.1.425", + "1.4.5-6.4.1.426", + "1.4.5-6.4.1.428", + "1.4.5-6.4.1.430", + "1.4.5-6.4.1.432", + "1.4.5-6.4.1.433", + "1.4.5-6.4.1.434", + "1.4.5-6.4.1.435", + "1.4.5-6.4.1.436", + "1.4.5-6.4.1.437", + "1.4.5-6.4.1.438", + "1.4.5-6.4.1.439", + "1.4.5-6.4.1.441", + "1.4.5-6.4.1.442", + "1.4.5-6.4.2.443", + "1.4.5-6.4.2.444", + "1.4.5-6.4.2.445", + "1.4.5-6.4.2.446", + "1.4.5-6.4.2.447", + "1.4.5-6.4.2.448" + ], + "1.4.6": [ + "1.4.6-6.5.0.451", + "1.4.6-6.5.0.452", + "1.4.6-6.5.0.453", + "1.4.6-6.5.0.454", + "1.4.6-6.5.0.455", + "1.4.6-6.5.0.456", + "1.4.6-6.5.0.457", + "1.4.6-6.5.0.458", + "1.4.6-6.5.0.459", + "1.4.6-6.5.0.460", + "1.4.6-6.5.0.461", + "1.4.6-6.5.0.462", + "1.4.6-6.5.0.463", + "1.4.6-6.5.0.464", + "1.4.6-6.5.0.465", + "1.4.6-6.5.0.466", + "1.4.6-6.5.0.467", + "1.4.6-6.5.0.468", + "1.4.6-6.5.0.469", + "1.4.6-6.5.0.470", + "1.4.6-6.5.0.471", + "1.4.6-6.5.0.472", + "1.4.6-6.5.0.473", + "1.4.6-6.5.0.474", + "1.4.6-6.5.0.475", + "1.4.6-6.5.0.476", + "1.4.6-6.5.0.477", + "1.4.6-6.5.0.478", + "1.4.6-6.5.0.479", + "1.4.6-6.5.0.480", + "1.4.6-6.5.0.481", + "1.4.6-6.5.0.482", + "1.4.6-6.5.0.483", + "1.4.6-6.5.0.484", + "1.4.6-6.5.0.486", + "1.4.6-6.5.0.487", + "1.4.6-6.5.0.488", + "1.4.6-6.5.0.489" + ], + "1.4.7": [ + "1.4.7-6.6.0.490", + "1.4.7-6.6.0.491", + "1.4.7-6.6.0.492", + "1.4.7-6.6.0.493", + "1.4.7-6.6.0.494", + "1.4.7-6.6.0.495", + "1.4.7-6.6.0.496", + "1.4.7-6.6.0.497", + "1.4.7-6.6.0.499", + "1.4.7-6.6.0.501", + "1.4.7-6.6.0.502", + "1.4.7-6.6.0.503", + "1.4.7-6.6.0.504", + "1.4.7-6.6.0.505", + "1.4.7-6.6.0.506", + "1.4.7-6.6.0.507", + "1.4.7-6.6.0.509", + "1.4.7-6.6.0.510", + "1.4.7-6.6.0.511", + "1.4.7-6.6.0.515", + "1.4.7-6.6.0.516", + "1.4.7-6.6.0.517", + "1.4.7-6.6.0.518", + "1.4.7-6.6.1.521", + "1.4.7-6.6.1.522", + "1.4.7-6.6.1.523", + "1.4.7-6.6.1.524", + "1.4.7-6.6.1.527", + "1.4.7-6.6.1.528", + "1.4.7-6.6.1.529", + "1.4.7-6.6.1.530", + "1.4.7-6.6.1.531", + "1.4.7-6.6.1.532", + "1.4.7-6.6.2.533", + "1.4.7-6.6.2.534" + ], + "1.5": [ + "1.5-7.7.0.559", + "1.5-7.7.0.560", + "1.5-7.7.0.561", + "1.5-7.7.0.562", + "1.5-7.7.0.563", + "1.5-7.7.0.565", + "1.5-7.7.0.566", + "1.5-7.7.0.567", + "1.5-7.7.0.568", + "1.5-7.7.0.569", + "1.5-7.7.0.571", + "1.5-7.7.0.572", + "1.5-7.7.0.573", + "1.5-7.7.0.574", + "1.5-7.7.0.575", + "1.5-7.7.0.576", + "1.5-7.7.0.577", + "1.5-7.7.0.578", + "1.5-7.7.0.579", + "1.5-7.7.0.580", + "1.5-7.7.0.581", + "1.5-7.7.0.582", + "1.5-7.7.0.583", + "1.5-7.7.0.584", + "1.5-7.7.0.585", + "1.5-7.7.0.586", + "1.5-7.7.0.587", + "1.5-7.7.0.588", + "1.5-7.7.0.589", + "1.5-7.7.0.590", + "1.5-7.7.0.591", + "1.5-7.7.0.592", + "1.5-7.7.0.593", + "1.5-7.7.0.594", + "1.5-7.7.0.595", + "1.5-7.7.0.598" + ], + "1.5.1": [ + "1.5.1-7.7.0.600", + "1.5.1-7.7.0.601", + "1.5.1-7.7.0.602", + "1.5.1-7.7.0.603", + "1.5.1-7.7.0.604", + "1.5.1-7.7.0.605", + "1.5.1-7.7.0.608", + "1.5.1-7.7.0.609", + "1.5.1-7.7.0.610", + "1.5.1-7.7.1.611", + "1.5.1-7.7.1.614", + "1.5.1-7.7.1.615", + "1.5.1-7.7.1.616", + "1.5.1-7.7.1.617", + "1.5.1-7.7.1.618", + "1.5.1-7.7.1.620", + "1.5.1-7.7.1.621", + "1.5.1-7.7.1.622", + "1.5.1-7.7.1.623", + "1.5.1-7.7.1.624", + "1.5.1-7.7.1.625", + "1.5.1-7.7.1.627", + "1.5.1-7.7.1.628", + "1.5.1-7.7.1.629", + "1.5.1-7.7.1.630", + "1.5.1-7.7.1.631", + "1.5.1-7.7.1.632", + "1.5.1-7.7.1.633", + "1.5.1-7.7.1.634", + "1.5.1-7.7.1.635", + "1.5.1-7.7.1.636", + "1.5.1-7.7.1.637", + "1.5.1-7.7.1.638", + "1.5.1-7.7.1.639", + "1.5.1-7.7.1.640", + "1.5.1-7.7.1.642", + "1.5.1-7.7.1.643", + "1.5.1-7.7.1.644", + "1.5.1-7.7.1.645", + "1.5.1-7.7.1.646", + "1.5.1-7.7.1.647", + "1.5.1-7.7.1.648", + "1.5.1-7.7.1.649", + "1.5.1-7.7.1.650", + "1.5.1-7.7.1.651", + "1.5.1-7.7.1.652", + "1.5.1-7.7.1.653", + "1.5.1-7.7.1.654", + "1.5.1-7.7.1.655", + "1.5.1-7.7.1.656", + "1.5.1-7.7.1.657", + "1.5.1-7.7.1.659", + "1.5.1-7.7.1.660", + "1.5.1-7.7.1.661", + "1.5.1-7.7.1.662", + "1.5.1-7.7.1.663", + "1.5.1-7.7.1.664", + "1.5.1-7.7.1.665", + "1.5.1-7.7.1.666", + "1.5.1-7.7.1.667", + "1.5.1-7.7.1.672", + "1.5.1-7.7.1.673", + "1.5.1-7.7.1.674", + "1.5.1-7.7.1.675", + "1.5.1-7.7.1.676", + "1.5.1-7.7.2.678", + "1.5.1-7.7.2.679", + "1.5.1-7.7.2.682" + ], + "1.5.2": [ + "1.5.2-7.8.0.684", + "1.5.2-7.8.0.685", + "1.5.2-7.8.0.686", + "1.5.2-7.8.0.687", + "1.5.2-7.8.0.688", + "1.5.2-7.8.0.689", + "1.5.2-7.8.0.690", + "1.5.2-7.8.0.691", + "1.5.2-7.8.0.692", + "1.5.2-7.8.0.693", + "1.5.2-7.8.0.694", + "1.5.2-7.8.0.695", + "1.5.2-7.8.0.696", + "1.5.2-7.8.0.697", + "1.5.2-7.8.0.698", + "1.5.2-7.8.0.699", + "1.5.2-7.8.0.700", + "1.5.2-7.8.0.701", + "1.5.2-7.8.0.702", + "1.5.2-7.8.0.703", + "1.5.2-7.8.0.704", + "1.5.2-7.8.0.705", + "1.5.2-7.8.0.706", + "1.5.2-7.8.0.707", + "1.5.2-7.8.0.708", + "1.5.2-7.8.0.710", + "1.5.2-7.8.0.711", + "1.5.2-7.8.0.712", + "1.5.2-7.8.0.713", + "1.5.2-7.8.0.715", + "1.5.2-7.8.0.716", + "1.5.2-7.8.0.719", + "1.5.2-7.8.0.720", + "1.5.2-7.8.0.721", + "1.5.2-7.8.0.722", + "1.5.2-7.8.0.723", + "1.5.2-7.8.0.725", + "1.5.2-7.8.0.726", + "1.5.2-7.8.0.727", + "1.5.2-7.8.0.728", + "1.5.2-7.8.0.729", + "1.5.2-7.8.0.730", + "1.5.2-7.8.0.731", + "1.5.2-7.8.0.732", + "1.5.2-7.8.0.733", + "1.5.2-7.8.0.734", + "1.5.2-7.8.0.735", + "1.5.2-7.8.0.736", + "1.5.2-7.8.1.737", + "1.5.2-7.8.1.738" + ], + "1.6.1": [ + "1.6.1-8.9.0.749", + "1.6.1-8.9.0.751", + "1.6.1-8.9.0.753", + "1.6.1-8.9.0.755", + "1.6.1-8.9.0.756", + "1.6.1-8.9.0.757", + "1.6.1-8.9.0.758", + "1.6.1-8.9.0.759", + "1.6.1-8.9.0.760", + "1.6.1-8.9.0.761", + "1.6.1-8.9.0.762", + "1.6.1-8.9.0.763", + "1.6.1-8.9.0.764", + "1.6.1-8.9.0.765", + "1.6.1-8.9.0.766", + "1.6.1-8.9.0.767", + "1.6.1-8.9.0.768", + "1.6.1-8.9.0.771", + "1.6.1-8.9.0.772", + "1.6.1-8.9.0.773", + "1.6.1-8.9.0.774", + "1.6.1-8.9.0.775" + ], + "1.6.2": [ + "1.6.2-9.10.0.776", + "1.6.2-9.10.0.777", + "1.6.2-9.10.0.778", + "1.6.2-9.10.0.779", + "1.6.2-9.10.0.780", + "1.6.2-9.10.0.781", + "1.6.2-9.10.0.784", + "1.6.2-9.10.0.785", + "1.6.2-9.10.0.786", + "1.6.2-9.10.0.787", + "1.6.2-9.10.0.789", + "1.6.2-9.10.0.790", + "1.6.2-9.10.0.791", + "1.6.2-9.10.0.792", + "1.6.2-9.10.0.793", + "1.6.2-9.10.0.794", + "1.6.2-9.10.0.795", + "1.6.2-9.10.0.796", + "1.6.2-9.10.0.797", + "1.6.2-9.10.0.798", + "1.6.2-9.10.0.799", + "1.6.2-9.10.0.800", + "1.6.2-9.10.0.801", + "1.6.2-9.10.0.802", + "1.6.2-9.10.0.803", + "1.6.2-9.10.0.804", + "1.6.2-9.10.0.816", + "1.6.2-9.10.0.817", + "1.6.2-9.10.0.818", + "1.6.2-9.10.0.819", + "1.6.2-9.10.0.820", + "1.6.2-9.10.0.821", + "1.6.2-9.10.0.822", + "1.6.2-9.10.0.823", + "1.6.2-9.10.0.824", + "1.6.2-9.10.0.825", + "1.6.2-9.10.0.826", + "1.6.2-9.10.0.827", + "1.6.2-9.10.0.828", + "1.6.2-9.10.0.829", + "1.6.2-9.10.0.830", + "1.6.2-9.10.0.831", + "1.6.2-9.10.0.832", + "1.6.2-9.10.0.833", + "1.6.2-9.10.0.834", + "1.6.2-9.10.0.835", + "1.6.2-9.10.0.836", + "1.6.2-9.10.0.837", + "1.6.2-9.10.0.838", + "1.6.2-9.10.0.839", + "1.6.2-9.10.0.840", + "1.6.2-9.10.0.841", + "1.6.2-9.10.0.842", + "1.6.2-9.10.0.843", + "1.6.2-9.10.0.844", + "1.6.2-9.10.0.845", + "1.6.2-9.10.0.846", + "1.6.2-9.10.0.847", + "1.6.2-9.10.0.848", + "1.6.2-9.10.1.849", + "1.6.2-9.10.1.850", + "1.6.2-9.10.1.851", + "1.6.2-9.10.1.852", + "1.6.2-9.10.1.853", + "1.6.2-9.10.1.854", + "1.6.2-9.10.1.855", + "1.6.2-9.10.1.856", + "1.6.2-9.10.1.857", + "1.6.2-9.10.1.858", + "1.6.2-9.10.1.859", + "1.6.2-9.10.1.860", + "1.6.2-9.10.1.861", + "1.6.2-9.10.1.862", + "1.6.2-9.10.1.863", + "1.6.2-9.10.1.864", + "1.6.2-9.10.1.865", + "1.6.2-9.10.1.866", + "1.6.2-9.10.1.867", + "1.6.2-9.10.1.869", + "1.6.2-9.10.1.870", + "1.6.2-9.10.1.871" + ], + "1.6.3": [ + "1.6.3-9.11.0.873", + "1.6.3-9.11.0.874", + "1.6.3-9.11.0.875", + "1.6.3-9.11.0.876", + "1.6.3-9.11.0.877", + "1.6.3-9.11.0.878" + ], + "1.6.4": [ + "1.6.4-9.11.0.879", + "1.6.4-9.11.0.880", + "1.6.4-9.11.0.881", + "1.6.4-9.11.0.882", + "1.6.4-9.11.0.883", + "1.6.4-9.11.0.884", + "1.6.4-9.11.0.885", + "1.6.4-9.11.0.886", + "1.6.4-9.11.0.891", + "1.6.4-9.11.0.892", + "1.6.4-9.11.0.893", + "1.6.4-9.11.0.894", + "1.6.4-9.11.0.895", + "1.6.4-9.11.0.896", + "1.6.4-9.11.0.897", + "1.6.4-9.11.0.898", + "1.6.4-9.11.0.899", + "1.6.4-9.11.0.900", + "1.6.4-9.11.0.901", + "1.6.4-9.11.0.902", + "1.6.4-9.11.0.903", + "1.6.4-9.11.0.904", + "1.6.4-9.11.0.905", + "1.6.4-9.11.0.906", + "1.6.4-9.11.0.907", + "1.6.4-9.11.0.908", + "1.6.4-9.11.0.909", + "1.6.4-9.11.0.910", + "1.6.4-9.11.0.911", + "1.6.4-9.11.0.912", + "1.6.4-9.11.0.913", + "1.6.4-9.11.1.914", + "1.6.4-9.11.1.915", + "1.6.4-9.11.1.916", + "1.6.4-9.11.1.917", + "1.6.4-9.11.1.918", + "1.6.4-9.11.1.919", + "1.6.4-9.11.1.920", + "1.6.4-9.11.1.921", + "1.6.4-9.11.1.922", + "1.6.4-9.11.1.923", + "1.6.4-9.11.1.924", + "1.6.4-9.11.1.925", + "1.6.4-9.11.1.926", + "1.6.4-9.11.1.928", + "1.6.4-9.11.1.930", + "1.6.4-9.11.1.931", + "1.6.4-9.11.1.933", + "1.6.4-9.11.1.934", + "1.6.4-9.11.1.935", + "1.6.4-9.11.1.937", + "1.6.4-9.11.1.938", + "1.6.4-9.11.1.939", + "1.6.4-9.11.1.940", + "1.6.4-9.11.1.941", + "1.6.4-9.11.1.942", + "1.6.4-9.11.1.943", + "1.6.4-9.11.1.944", + "1.6.4-9.11.1.945", + "1.6.4-9.11.1.946", + "1.6.4-9.11.1.947", + "1.6.4-9.11.1.948", + "1.6.4-9.11.1.949", + "1.6.4-9.11.1.951", + "1.6.4-9.11.1.952", + "1.6.4-9.11.1.960", + "1.6.4-9.11.1.961", + "1.6.4-9.11.1.963", + "1.6.4-9.11.1.964", + "1.6.4-9.11.1.953", + "1.6.4-9.11.1.965", + "1.6.4-9.11.1.1345" + ], + "1.7.2": [ + "1.7.2-10.12.0.967", + "1.7.2-10.12.0.968", + "1.7.2-10.12.0.969", + "1.7.2-10.12.0.970", + "1.7.2-10.12.0.971", + "1.7.2-10.12.0.972", + "1.7.2-10.12.0.973", + "1.7.2-10.12.0.974", + "1.7.2-10.12.0.975", + "1.7.2-10.12.0.976", + "1.7.2-10.12.0.977", + "1.7.2-10.12.0.979", + "1.7.2-10.12.0.980", + "1.7.2-10.12.0.981", + "1.7.2-10.12.0.982", + "1.7.2-10.12.0.984", + "1.7.2-10.12.0.985", + "1.7.2-10.12.0.986", + "1.7.2-10.12.0.987", + "1.7.2-10.12.0.989", + "1.7.2-10.12.0.990", + "1.7.2-10.12.0.991", + "1.7.2-10.12.0.993", + "1.7.2-10.12.0.994", + "1.7.2-10.12.0.995", + "1.7.2-10.12.0.996", + "1.7.2-10.12.0.997", + "1.7.2-10.12.0.998", + "1.7.2-10.12.0.999", + "1.7.2-10.12.0.1000", + "1.7.2-10.12.0.1001", + "1.7.2-10.12.0.1002", + "1.7.2-10.12.0.1003", + "1.7.2-10.12.0.1004", + "1.7.2-10.12.0.1005", + "1.7.2-10.12.0.1006", + "1.7.2-10.12.0.1007", + "1.7.2-10.12.0.1008", + "1.7.2-10.12.0.1009", + "1.7.2-10.12.0.1010", + "1.7.2-10.12.0.1011", + "1.7.2-10.12.0.1012", + "1.7.2-10.12.0.1013", + "1.7.2-10.12.0.1014", + "1.7.2-10.12.0.1015", + "1.7.2-10.12.0.1016", + "1.7.2-10.12.0.1017", + "1.7.2-10.12.0.1018", + "1.7.2-10.12.0.1019", + "1.7.2-10.12.0.1020", + "1.7.2-10.12.0.1021", + "1.7.2-10.12.0.1022", + "1.7.2-10.12.0.1023", + "1.7.2-10.12.0.1024", + "1.7.2-10.12.0.1025", + "1.7.2-10.12.0.1026", + "1.7.2-10.12.0.1027", + "1.7.2-10.12.0.1028", + "1.7.2-10.12.0.1029", + "1.7.2-10.12.0.1030", + "1.7.2-10.12.0.1031", + "1.7.2-10.12.0.1032", + "1.7.2-10.12.0.1033", + "1.7.2-10.12.0.1034", + "1.7.2-10.12.0.1039", + "1.7.2-10.12.0.1040", + "1.7.2-10.12.0.1041", + "1.7.2-10.12.0.1042", + "1.7.2-10.12.0.1043", + "1.7.2-10.12.0.1044", + "1.7.2-10.12.0.1045", + "1.7.2-10.12.0.1046", + "1.7.2-10.12.0.1047", + "1.7.2-10.12.0.1048", + "1.7.2-10.12.0.1049", + "1.7.2-10.12.0.1050", + "1.7.2-10.12.0.1051", + "1.7.2-10.12.0.1052", + "1.7.2-10.12.0.1053", + "1.7.2-10.12.0.1054", + "1.7.2-10.12.0.1055", + "1.7.2-10.12.0.1056", + "1.7.2-10.12.0.1057", + "1.7.2-10.12.0.1058", + "1.7.2-10.12.0.1059", + "1.7.2-10.12.1.1060", + "1.7.2-10.12.1.1061", + "1.7.2-10.12.1.1063", + "1.7.2-10.12.1.1065", + "1.7.2-10.12.1.1066", + "1.7.2-10.12.1.1067", + "1.7.2-10.12.1.1068", + "1.7.2-10.12.1.1069", + "1.7.2-10.12.1.1070", + "1.7.2-10.12.1.1071", + "1.7.2-10.12.1.1072", + "1.7.2-10.12.1.1073", + "1.7.2-10.12.1.1074", + "1.7.2-10.12.1.1075", + "1.7.2-10.12.1.1076", + "1.7.2-10.12.1.1077", + "1.7.2-10.12.1.1078", + "1.7.2-10.12.1.1079", + "1.7.2-10.12.1.1080", + "1.7.2-10.12.1.1081", + "1.7.2-10.12.1.1082", + "1.7.2-10.12.1.1083", + "1.7.2-10.12.1.1084", + "1.7.2-10.12.1.1085", + "1.7.2-10.12.1.1087", + "1.7.2-10.12.1.1088", + "1.7.2-10.12.1.1090", + "1.7.2-10.12.1.1091", + "1.7.2-10.12.1.1092", + "1.7.2-10.12.1.1093", + "1.7.2-10.12.1.1094", + "1.7.2-10.12.1.1095", + "1.7.2-10.12.1.1096", + "1.7.2-10.12.1.1097", + "1.7.2-10.12.1.1098", + "1.7.2-10.12.1.1099", + "1.7.2-10.12.1.1100", + "1.7.2-10.12.1.1101", + "1.7.2-10.12.1.1103", + "1.7.2-10.12.1.1104", + "1.7.2-10.12.1.1105", + "1.7.2-10.12.1.1106", + "1.7.2-10.12.1.1107", + "1.7.2-10.12.1.1108", + "1.7.2-10.12.1.1109", + "1.7.2-10.12.1.1110", + "1.7.2-10.12.1.1111", + "1.7.2-10.12.1.1112", + "1.7.2-10.12.1.1113", + "1.7.2-10.12.1.1114", + "1.7.2-10.12.1.1115", + "1.7.2-10.12.1.1116", + "1.7.2-10.12.1.1117", + "1.7.2-10.12.1.1118", + "1.7.2-10.12.1.1119", + "1.7.2-10.12.1.1120", + "1.7.2-10.12.2.1121", + "1.7.2-10.12.2.1122", + "1.7.2-10.12.2.1123", + "1.7.2-10.12.2.1124", + "1.7.2-10.12.2.1125", + "1.7.2-10.12.2.1126", + "1.7.2-10.12.2.1127", + "1.7.2-10.12.2.1128", + "1.7.2-10.12.2.1129", + "1.7.2-10.12.2.1130", + "1.7.2-10.12.2.1131", + "1.7.2-10.12.2.1132", + "1.7.2-10.12.2.1133", + "1.7.2-10.12.2.1145", + "1.7.2-10.12.2.1147", + "1.7.2-10.12.2.1154-mc172", + "1.7.2-10.12.2.1155-mc172", + "1.7.2-10.12.2.1161-mc172" + ], + "1.7.10_pre4": [ + "1.7.10_pre4-10.12.2.1137-prerelease", + "1.7.10_pre4-10.12.2.1138-prerelease", + "1.7.10_pre4-10.12.2.1139-prerelease", + "1.7.10_pre4-10.12.2.1141-prerelease", + "1.7.10_pre4-10.12.2.1142-prerelease", + "1.7.10_pre4-10.12.2.1143-prerelease", + "1.7.10_pre4-10.12.2.1144-prerelease", + "1.7.10_pre4-10.12.2.1146-prerelease", + "1.7.10_pre4-10.12.2.1148-prerelease", + "1.7.10_pre4-10.12.2.1149-prerelease" + ], + "1.7.10": [ + "1.7.10-10.13.0.1150", + "1.7.10-10.13.0.1151", + "1.7.10-10.13.0.1152", + "1.7.10-10.13.0.1153", + "1.7.10-10.13.0.1156", + "1.7.10-10.13.0.1157", + "1.7.10-10.13.0.1158", + "1.7.10-10.13.0.1159", + "1.7.10-10.13.0.1160", + "1.7.10-10.13.0.1162", + "1.7.10-10.13.0.1166", + "1.7.10-10.13.0.1167", + "1.7.10-10.13.0.1168", + "1.7.10-10.13.0.1169", + "1.7.10-10.13.0.1170", + "1.7.10-10.13.0.1171", + "1.7.10-10.13.0.1172", + "1.7.10-10.13.0.1174", + "1.7.10-10.13.0.1175", + "1.7.10-10.13.0.1176", + "1.7.10-10.13.0.1177", + "1.7.10-10.13.0.1178", + "1.7.10-10.13.0.1179", + "1.7.10-10.13.0.1180", + "1.7.10-10.13.0.1181", + "1.7.10-10.13.0.1182", + "1.7.10-10.13.0.1183", + "1.7.10-10.13.0.1184", + "1.7.10-10.13.0.1185", + "1.7.10-10.13.0.1186", + "1.7.10-10.13.0.1187", + "1.7.10-10.13.0.1188", + "1.7.10-10.13.0.1189", + "1.7.10-10.13.0.1190", + "1.7.10-10.13.0.1191", + "1.7.10-10.13.0.1194", + "1.7.10-10.13.0.1195", + "1.7.10-10.13.0.1197", + "1.7.10-10.13.0.1198", + "1.7.10-10.13.0.1199", + "1.7.10-10.13.0.1200", + "1.7.10-10.13.0.1201", + "1.7.10-10.13.0.1202", + "1.7.10-10.13.0.1203", + "1.7.10-10.13.0.1204", + "1.7.10-10.13.0.1205", + "1.7.10-10.13.0.1206", + "1.7.10-10.13.0.1207", + "1.7.10-10.13.0.1208", + "1.7.10-10.13.1.1210-new", + "1.7.10-10.13.1.1211-new", + "1.7.10-10.13.1.1212-new", + "1.7.10-10.13.1.1213-new", + "1.7.10-10.13.1.1214-new", + "1.7.10-10.13.1.1215-new", + "1.7.10-10.13.1.1216-new", + "1.7.10-10.13.1.1217", + "1.7.10-10.13.1.1219", + "1.7.10-10.13.1.1220", + "1.7.10-10.13.1.1221", + "1.7.10-10.13.1.1222", + "1.7.10-10.13.1.1223", + "1.7.10-10.13.1.1224", + "1.7.10-10.13.1.1225", + "1.7.10-10.13.1.1226", + "1.7.10-10.13.1.1229", + "1.7.10-10.13.2.1230", + "1.7.10-10.13.2.1231", + "1.7.10-10.13.2.1232", + "1.7.10-10.13.2.1233", + "1.7.10-10.13.2.1234", + "1.7.10-10.13.2.1235", + "1.7.10-10.13.2.1236", + "1.7.10-10.13.2.1240", + "1.7.10-10.13.2.1253", + "1.7.10-10.13.2.1254", + "1.7.10-10.13.2.1256", + "1.7.10-10.13.2.1258", + "1.7.10-10.13.2.1263", + "1.7.10-10.13.2.1264", + "1.7.10-10.13.2.1270", + "1.7.10-10.13.2.1272", + "1.7.10-10.13.2.1275", + "1.7.10-10.13.2.1276", + "1.7.10-10.13.2.1277", + "1.7.10-10.13.2.1283", + "1.7.10-10.13.2.1284", + "1.7.10-10.13.2.1286", + "1.7.10-10.13.2.1291", + "1.7.10-10.13.2.1300-1.7.10", + "1.7.10-10.13.2.1307-1.7.10", + "1.7.10-10.13.2.1340-1.7.10", + "1.7.10-10.13.2.1342-1.7.10", + "1.7.10-10.13.2.1343-1.7.10", + "1.7.10-10.13.2.1346-1.7.10", + "1.7.10-10.13.2.1347-1.7.10", + "1.7.10-10.13.2.1351-1.7.10", + "1.7.10-10.13.2.1352-1.7.10", + "1.7.10-10.13.3.1355-1.7.10", + "1.7.10-10.13.3.1356-1.7.10", + "1.7.10-10.13.3.1358-1.7.10", + "1.7.10-10.13.3.1360-1.7.10", + "1.7.10-10.13.3.1362-1.7.10", + "1.7.10-10.13.3.1363-1.7.10", + "1.7.10-10.13.3.1364-1.7.10", + "1.7.10-10.13.3.1365-1.7.10", + "1.7.10-10.13.3.1366-1.7.10", + "1.7.10-10.13.3.1367-1.7.10", + "1.7.10-10.13.3.1368-1.7.10", + "1.7.10-10.13.3.1369-1.7.10", + "1.7.10-10.13.3.1370-1.7.10", + "1.7.10-10.13.3.1372-1.7.10", + "1.7.10-10.13.3.1373-1.7.10", + "1.7.10-10.13.3.1374-1.7.10", + "1.7.10-10.13.3.1376-1.7.10", + "1.7.10-10.13.3.1377-1.7.10", + "1.7.10-10.13.3.1378-1.7.10", + "1.7.10-10.13.3.1379-1.7.10", + "1.7.10-10.13.3.1380-1.7.10", + "1.7.10-10.13.3.1381-1.7.10", + "1.7.10-10.13.3.1382-1.7.10", + "1.7.10-10.13.3.1383-1.7.10", + "1.7.10-10.13.3.1384-1.7.10", + "1.7.10-10.13.3.1385-1.7.10", + "1.7.10-10.13.3.1387-1.7.10", + "1.7.10-10.13.3.1388-1.7.10", + "1.7.10-10.13.3.1389-1710ls", + "1.7.10-10.13.3.1391-1710ls", + "1.7.10-10.13.3.1393-1710ls", + "1.7.10-10.13.3.1394-1710ls", + "1.7.10-10.13.3.1395-1710ls", + "1.7.10-10.13.3.1399-1.7.10", + "1.7.10-10.13.3.1400-1.7.10", + "1.7.10-10.13.3.1401-1710ls", + "1.7.10-10.13.3.1403-1.7.10", + "1.7.10-10.13.3.1406-1.7.10", + "1.7.10-10.13.3.1407-1.7.10", + "1.7.10-10.13.3.1408-1.7.10", + "1.7.10-10.13.3.1420-1.7.10", + "1.7.10-10.13.3.1422-1.7.10", + "1.7.10-10.13.3.1424-1.7.10", + "1.7.10-10.13.3.1428-1.7.10", + "1.7.10-10.13.4.1445-1.7.10", + "1.7.10-10.13.4.1447-1.7.10", + "1.7.10-10.13.4.1448-1.7.10", + "1.7.10-10.13.4.1451-1.7.10", + "1.7.10-10.13.4.1452-1.7.10", + "1.7.10-10.13.4.1456-1.7.10", + "1.7.10-10.13.4.1469-1.7.10", + "1.7.10-10.13.4.1470-1.7.10", + "1.7.10-10.13.4.1472-1.7.10", + "1.7.10-10.13.4.1481-1.7.10", + "1.7.10-10.13.4.1490-1.7.10", + "1.7.10-10.13.4.1492-1.7.10", + "1.7.10-10.13.4.1517-1.7.10", + "1.7.10-10.13.4.1539-1.7.10", + "1.7.10-10.13.4.1540-1.7.10", + "1.7.10-10.13.4.1541-1.7.10", + "1.7.10-10.13.4.1557-1.7.10", + "1.7.10-10.13.4.1558-1.7.10", + "1.7.10-10.13.4.1564-1.7.10", + "1.7.10-10.13.4.1566-1.7.10", + "1.7.10-10.13.4.1614-1.7.10" + ], + "1.8": [ + "1.8-11.14.0.1237-1.8", + "1.8-11.14.0.1238-1.8", + "1.8-11.14.0.1239-1.8", + "1.8-11.14.0.1241-1.8", + "1.8-11.14.0.1242-1.8", + "1.8-11.14.0.1243-1.8", + "1.8-11.14.0.1244-1.8", + "1.8-11.14.0.1245-1.8", + "1.8-11.14.0.1246-1.8", + "1.8-11.14.0.1247-1.8", + "1.8-11.14.0.1248-1.8", + "1.8-11.14.0.1249-1.8", + "1.8-11.14.0.1251-1.8", + "1.8-11.14.0.1252-1.8", + "1.8-11.14.0.1255-1.8", + "1.8-11.14.0.1257-1.8", + "1.8-11.14.0.1259-1.8", + "1.8-11.14.0.1260-1.8", + "1.8-11.14.0.1261-1.8", + "1.8-11.14.0.1262-1.8", + "1.8-11.14.0.1265-1.8", + "1.8-11.14.0.1266-1.8", + "1.8-11.14.0.1267-1.8", + "1.8-11.14.0.1268-1.8", + "1.8-11.14.0.1269-1.8", + "1.8-11.14.0.1271-1.8", + "1.8-11.14.0.1273-1.8", + "1.8-11.14.0.1274-1.8", + "1.8-11.14.0.1278-1.8", + "1.8-11.14.0.1279-1.8", + "1.8-11.14.0.1280-1.8", + "1.8-11.14.0.1281-1.8", + "1.8-11.14.0.1282-1.8", + "1.8-11.14.0.1285-1.8", + "1.8-11.14.0.1287-1.8", + "1.8-11.14.0.1288-1.8", + "1.8-11.14.0.1289-1.8", + "1.8-11.14.0.1290-1.8", + "1.8-11.14.0.1292-1.8", + "1.8-11.14.0.1293-1.8", + "1.8-11.14.0.1294-1.8", + "1.8-11.14.0.1295-1.8", + "1.8-11.14.0.1296", + "1.8-11.14.0.1297", + "1.8-11.14.0.1298", + "1.8-11.14.0.1299", + "1.8-11.14.1.1301", + "1.8-11.14.1.1302", + "1.8-11.14.1.1303", + "1.8-11.14.1.1305", + "1.8-11.14.1.1306", + "1.8-11.14.1.1308", + "1.8-11.14.1.1309", + "1.8-11.14.1.1310", + "1.8-11.14.1.1311", + "1.8-11.14.1.1312", + "1.8-11.14.1.1313", + "1.8-11.14.1.1314", + "1.8-11.14.1.1315", + "1.8-11.14.1.1316", + "1.8-11.14.1.1317", + "1.8-11.14.1.1318", + "1.8-11.14.1.1319", + "1.8-11.14.1.1320", + "1.8-11.14.1.1321", + "1.8-11.14.1.1322", + "1.8-11.14.1.1323", + "1.8-11.14.1.1324", + "1.8-11.14.1.1325", + "1.8-11.14.1.1326", + "1.8-11.14.1.1327", + "1.8-11.14.1.1328", + "1.8-11.14.1.1329", + "1.8-11.14.1.1332", + "1.8-11.14.1.1333", + "1.8-11.14.1.1334", + "1.8-11.14.1.1335", + "1.8-11.14.1.1336", + "1.8-11.14.1.1337", + "1.8-11.14.1.1338", + "1.8-11.14.1.1339", + "1.8-11.14.1.1341", + "1.8-11.14.1.1349", + "1.8-11.14.1.1350", + "1.8-11.14.1.1353", + "1.8-11.14.1.1354", + "1.8-11.14.1.1357", + "1.8-11.14.1.1359", + "1.8-11.14.1.1361", + "1.8-11.14.1.1371", + "1.8-11.14.1.1375", + "1.8-11.14.1.1390", + "1.8-11.14.1.1392", + "1.8-11.14.1.1396", + "1.8-11.14.1.1397", + "1.8-11.14.1.1398", + "1.8-11.14.1.1402", + "1.8-11.14.1.1404", + "1.8-11.14.1.1405", + "1.8-11.14.1.1409", + "1.8-11.14.1.1410", + "1.8-11.14.1.1411", + "1.8-11.14.1.1412", + "1.8-11.14.1.1413", + "1.8-11.14.1.1414", + "1.8-11.14.1.1415", + "1.8-11.14.1.1416", + "1.8-11.14.1.1417", + "1.8-11.14.1.1418", + "1.8-11.14.1.1419", + "1.8-11.14.2.1421", + "1.8-11.14.2.1423", + "1.8-11.14.2.1426", + "1.8-11.14.2.1427", + "1.8-11.14.2.1429", + "1.8-11.14.2.1430", + "1.8-11.14.2.1431", + "1.8-11.14.2.1433", + "1.8-11.14.2.1434", + "1.8-11.14.2.1435", + "1.8-11.14.2.1436", + "1.8-11.14.2.1437", + "1.8-11.14.2.1439", + "1.8-11.14.2.1440", + "1.8-11.14.2.1441", + "1.8-11.14.2.1442", + "1.8-11.14.2.1443", + "1.8-11.14.2.1444", + "1.8-11.14.3.1446", + "1.8-11.14.3.1449", + "1.8-11.14.3.1450", + "1.8-11.14.3.1453", + "1.8-11.14.3.1457", + "1.8-11.14.3.1458", + "1.8-11.14.3.1459", + "1.8-11.14.3.1460", + "1.8-11.14.3.1461", + "1.8-11.14.3.1462", + "1.8-11.14.3.1463", + "1.8-11.14.3.1464", + "1.8-11.14.3.1465", + "1.8-11.14.3.1466", + "1.8-11.14.3.1467", + "1.8-11.14.3.1468", + "1.8-11.14.3.1473", + "1.8-11.14.3.1474", + "1.8-11.14.3.1475", + "1.8-11.14.3.1476", + "1.8-11.14.3.1479", + "1.8-11.14.3.1480", + "1.8-11.14.3.1482", + "1.8-11.14.3.1483", + "1.8-11.14.3.1484", + "1.8-11.14.3.1485", + "1.8-11.14.3.1486", + "1.8-11.14.3.1487", + "1.8-11.14.3.1491", + "1.8-11.14.3.1493", + "1.8-11.14.3.1494", + "1.8-11.14.3.1495", + "1.8-11.14.3.1496", + "1.8-11.14.3.1497", + "1.8-11.14.3.1498", + "1.8-11.14.3.1499", + "1.8-11.14.3.1500", + "1.8-11.14.3.1501", + "1.8-11.14.3.1502", + "1.8-11.14.3.1503", + "1.8-11.14.3.1504", + "1.8-11.14.3.1505", + "1.8-11.14.3.1506", + "1.8-11.14.3.1507", + "1.8-11.14.3.1508", + "1.8-11.14.3.1509", + "1.8-11.14.3.1510", + "1.8-11.14.3.1511", + "1.8-11.14.3.1512", + "1.8-11.14.3.1513", + "1.8-11.14.3.1514", + "1.8-11.14.3.1515", + "1.8-11.14.3.1516", + "1.8-11.14.3.1518", + "1.8-11.14.3.1519", + "1.8-11.14.3.1520", + "1.8-11.14.3.1521", + "1.8-11.14.3.1523", + "1.8-11.14.3.1524", + "1.8-11.14.3.1525", + "1.8-11.14.3.1526", + "1.8-11.14.3.1529", + "1.8-11.14.3.1530", + "1.8-11.14.3.1531", + "1.8-11.14.3.1532", + "1.8-11.14.3.1533", + "1.8-11.14.3.1534", + "1.8-11.14.3.1535", + "1.8-11.14.3.1542", + "1.8-11.14.3.1543", + "1.8-11.14.3.1544", + "1.8-11.14.3.1545", + "1.8-11.14.3.1546", + "1.8-11.14.3.1547", + "1.8-11.14.3.1548", + "1.8-11.14.3.1549", + "1.8-11.14.3.1550", + "1.8-11.14.3.1551", + "1.8-11.14.3.1552", + "1.8-11.14.3.1553", + "1.8-11.14.3.1554", + "1.8-11.14.3.1555", + "1.8-11.14.3.1556", + "1.8-11.14.3.1559", + "1.8-11.14.3.1560", + "1.8-11.14.3.1561", + "1.8-11.14.3.1562", + "1.8-11.14.4.1563", + "1.8-11.14.4.1565", + "1.8-11.14.4.1568", + "1.8-11.14.4.1569", + "1.8-11.14.4.1570", + "1.8-11.14.4.1571", + "1.8-11.14.4.1572", + "1.8-11.14.4.1577" + ], + "1.8.8": [ + "1.8.8-11.14.4.1575-1.8.8", + "1.8.8-11.14.4.1576-1.8.8", + "1.8.8-11.14.4.1579-1.8.8", + "1.8.8-11.14.4.1580-1.8.8", + "1.8.8-11.14.4.1581-1.8.8", + "1.8.8-11.14.4.1582-1.8.8", + "1.8.8-11.14.4.1583-1.8.8", + "1.8.8-11.14.4.1584-1.8.8", + "1.8.8-11.14.4.1585-1.8.8", + "1.8.8-11.14.4.1586-1.8.8", + "1.8.8-11.14.4.1587-1.8.8", + "1.8.8-11.14.4.1588-1.8.8", + "1.8.8-11.14.4.1589-1.8.8", + "1.8.8-11.14.4.1590-1.8.8", + "1.8.8-11.15.0.1591-1.8.8", + "1.8.8-11.15.0.1592-1.8.8", + "1.8.8-11.15.0.1594-1.8.8", + "1.8.8-11.15.0.1595-1.8.8", + "1.8.8-11.15.0.1596-1.8.8", + "1.8.8-11.15.0.1600-1.8.8", + "1.8.8-11.15.0.1601-1.8.8", + "1.8.8-11.15.0.1602-1.8.8", + "1.8.8-11.15.0.1603-1.8.8", + "1.8.8-11.15.0.1604-1.8.8", + "1.8.8-11.15.0.1605-1.8.8", + "1.8.8-11.15.0.1606-1.8.8", + "1.8.8-11.15.0.1607-1.8.8", + "1.8.8-11.15.0.1608-1.8.8", + "1.8.8-11.15.0.1609-1.8.8", + "1.8.8-11.15.0.1610-1.8.8", + "1.8.8-11.15.0.1611-1.8.8", + "1.8.8-11.15.0.1612-1.8.8", + "1.8.8-11.15.0.1613-1.8.8", + "1.8.8-11.15.0.1615-1.8.8", + "1.8.8-11.15.0.1616-1.8.8", + "1.8.8-11.15.0.1617-1.8.8", + "1.8.8-11.15.0.1618-1.8.8", + "1.8.8-11.15.0.1619-1.8.8", + "1.8.8-11.15.0.1620-1.8.8", + "1.8.8-11.15.0.1621-1.8.8", + "1.8.8-11.15.0.1622-1.8.8", + "1.8.8-11.15.0.1623-1.8.8", + "1.8.8-11.15.0.1624-1.8.8", + "1.8.8-11.15.0.1625-1.8.8", + "1.8.8-11.15.0.1626-1.8.8", + "1.8.8-11.15.0.1627-1.8.8", + "1.8.8-11.15.0.1628-1.8.8", + "1.8.8-11.15.0.1630-1.8.8", + "1.8.8-11.15.0.1632-1.8.8", + "1.8.8-11.15.0.1633-1.8.8", + "1.8.8-11.15.0.1634-1.8.8", + "1.8.8-11.15.0.1635-1.8.8", + "1.8.8-11.15.0.1636-1.8.8", + "1.8.8-11.15.0.1637-1.8.8", + "1.8.8-11.15.0.1638-1.8.8", + "1.8.8-11.15.0.1639-1.8.8", + "1.8.8-11.15.0.1640-1.8.8", + "1.8.8-11.15.0.1641-1.8.8", + "1.8.8-11.15.0.1642-1.8.8", + "1.8.8-11.15.0.1643-1.8.8", + "1.8.8-11.15.0.1644-1.8.8", + "1.8.8-11.15.0.1645-1.8.8", + "1.8.8-11.15.0.1646-1.8.8", + "1.8.8-11.15.0.1647-1.8.8", + "1.8.8-11.15.0.1649-1.8.8", + "1.8.8-11.15.0.1650-1.8.8", + "1.8.8-11.15.0.1651-1.8.8", + "1.8.8-11.15.0.1652-1.8.8", + "1.8.8-11.15.0.1653-1.8.8", + "1.8.8-11.15.0.1654-1.8.8", + "1.8.8-11.15.0.1655" + ], + "1.8.9": [ + "1.8.9-11.15.0.1656", + "1.8.9-11.15.0.1657", + "1.8.9-11.15.0.1658", + "1.8.9-11.15.0.1659", + "1.8.9-11.15.0.1661", + "1.8.9-11.15.0.1662", + "1.8.9-11.15.0.1663", + "1.8.9-11.15.0.1664", + "1.8.9-11.15.0.1665", + "1.8.9-11.15.0.1666", + "1.8.9-11.15.0.1668", + "1.8.9-11.15.0.1669", + "1.8.9-11.15.0.1670", + "1.8.9-11.15.0.1671", + "1.8.9-11.15.0.1672", + "1.8.9-11.15.0.1673", + "1.8.9-11.15.0.1674", + "1.8.9-11.15.0.1675", + "1.8.9-11.15.0.1676", + "1.8.9-11.15.0.1677", + "1.8.9-11.15.0.1681", + "1.8.9-11.15.0.1682", + "1.8.9-11.15.0.1683", + "1.8.9-11.15.0.1684", + "1.8.9-11.15.0.1686", + "1.8.9-11.15.0.1687", + "1.8.9-11.15.0.1688", + "1.8.9-11.15.0.1689", + "1.8.9-11.15.0.1690", + "1.8.9-11.15.0.1691", + "1.8.9-11.15.0.1692", + "1.8.9-11.15.0.1693", + "1.8.9-11.15.0.1694", + "1.8.9-11.15.0.1695", + "1.8.9-11.15.0.1696", + "1.8.9-11.15.0.1697", + "1.8.9-11.15.0.1698", + "1.8.9-11.15.0.1699", + "1.8.9-11.15.0.1700", + "1.8.9-11.15.0.1701", + "1.8.9-11.15.0.1702", + "1.8.9-11.15.0.1703", + "1.8.9-11.15.0.1705", + "1.8.9-11.15.0.1706", + "1.8.9-11.15.0.1707", + "1.8.9-11.15.0.1708", + "1.8.9-11.15.0.1709", + "1.8.9-11.15.0.1710", + "1.8.9-11.15.0.1711", + "1.8.9-11.15.0.1712", + "1.8.9-11.15.0.1713", + "1.8.9-11.15.0.1714", + "1.8.9-11.15.0.1715", + "1.8.9-11.15.0.1716", + "1.8.9-11.15.0.1718", + "1.8.9-11.15.0.1719", + "1.8.9-11.15.0.1720", + "1.8.9-11.15.0.1721", + "1.8.9-11.15.1.1722", + "1.8.9-11.15.1.1723", + "1.8.9-11.15.1.1724", + "1.8.9-11.15.1.1725", + "1.8.9-11.15.1.1726", + "1.8.9-11.15.1.1727", + "1.8.9-11.15.1.1729", + "1.8.9-11.15.1.1730", + "1.8.9-11.15.1.1731", + "1.8.9-11.15.1.1732", + "1.8.9-11.15.1.1733", + "1.8.9-11.15.1.1734", + "1.8.9-11.15.1.1735", + "1.8.9-11.15.1.1736", + "1.8.9-11.15.1.1737", + "1.8.9-11.15.1.1738", + "1.8.9-11.15.1.1739", + "1.8.9-11.15.1.1740", + "1.8.9-11.15.1.1741", + "1.8.9-11.15.1.1742", + "1.8.9-11.15.1.1743", + "1.8.9-11.15.1.1744", + "1.8.9-11.15.1.1745", + "1.8.9-11.15.1.1746", + "1.8.9-11.15.1.1747", + "1.8.9-11.15.1.1748", + "1.8.9-11.15.1.1749", + "1.8.9-11.15.1.1750", + "1.8.9-11.15.1.1751", + "1.8.9-11.15.1.1752", + "1.8.9-11.15.1.1754", + "1.8.9-11.15.1.1755", + "1.8.9-11.15.1.1756", + "1.8.9-11.15.1.1757", + "1.8.9-11.15.1.1758", + "1.8.9-11.15.1.1759", + "1.8.9-11.15.1.1760", + "1.8.9-11.15.1.1761", + "1.8.9-11.15.1.1762", + "1.8.9-11.15.1.1763", + "1.8.9-11.15.1.1764", + "1.8.9-11.15.1.1765", + "1.8.9-11.15.1.1777", + "1.8.9-11.15.1.1783", + "1.8.9-11.15.1.1785", + "1.8.9-11.15.1.1791", + "1.8.9-11.15.1.1794", + "1.8.9-11.15.1.1808", + "1.8.9-11.15.1.1847", + "1.8.9-11.15.1.1855", + "1.8.9-11.15.1.1872", + "1.8.9-11.15.1.1873", + "1.8.9-11.15.1.1875", + "1.8.9-11.15.1.1890-1.8.9", + "1.8.9-11.15.1.1902-1.8.9", + "1.8.9-11.15.1.2318-1.8.9" + ], + "1.9": [ + "1.9-12.16.0.1766-1.9", + "1.9-12.16.0.1767-1.9", + "1.9-12.16.0.1768-1.9", + "1.9-12.16.0.1769-1.9", + "1.9-12.16.0.1770-1.9", + "1.9-12.16.0.1771-1.9", + "1.9-12.16.0.1772-1.9", + "1.9-12.16.0.1773-1.9", + "1.9-12.16.0.1774-1.9", + "1.9-12.16.0.1775-1.9", + "1.9-12.16.0.1776-1.9", + "1.9-12.16.0.1778-1.9", + "1.9-12.16.0.1779-1.9", + "1.9-12.16.0.1780-1.9", + "1.9-12.16.0.1781-1.9", + "1.9-12.16.0.1782-1.9", + "1.9-12.16.0.1784-1.9", + "1.9-12.16.0.1786-1.9", + "1.9-12.16.0.1787-1.9", + "1.9-12.16.0.1788-1.9", + "1.9-12.16.0.1789-1.9", + "1.9-12.16.0.1790-1.9", + "1.9-12.16.0.1792-1.9", + "1.9-12.16.0.1793-1.9", + "1.9-12.16.0.1795-1.9", + "1.9-12.16.0.1796-1.9", + "1.9-12.16.0.1797-1.9", + "1.9-12.16.0.1798-1.9", + "1.9-12.16.0.1799-1.9", + "1.9-12.16.0.1800-1.9", + "1.9-12.16.0.1801-1.9", + "1.9-12.16.0.1802-1.9", + "1.9-12.16.0.1803-1.9", + "1.9-12.16.0.1804-1.9", + "1.9-12.16.0.1805-1.9", + "1.9-12.16.0.1806-1.9", + "1.9-12.16.0.1807-1.9", + "1.9-12.16.0.1809-1.9", + "1.9-12.16.0.1810-1.9", + "1.9-12.16.0.1811-1.9", + "1.9-12.16.0.1812-1.9", + "1.9-12.16.0.1813-1.9", + "1.9-12.16.0.1814-1.9", + "1.9-12.16.0.1815-1.9", + "1.9-12.16.0.1816-1.9", + "1.9-12.16.0.1817-1.9", + "1.9-12.16.0.1819-1.9", + "1.9-12.16.0.1820-1.9", + "1.9-12.16.0.1821-1.9", + "1.9-12.16.0.1822-1.9", + "1.9-12.16.0.1823-1.9", + "1.9-12.16.0.1824-1.9", + "1.9-12.16.0.1825-1.9", + "1.9-12.16.0.1826-1.9", + "1.9-12.16.0.1827-1.9", + "1.9-12.16.0.1828-1.9", + "1.9-12.16.0.1829-1.9", + "1.9-12.16.0.1830-1.9", + "1.9-12.16.0.1831-1.9", + "1.9-12.16.0.1832-1.9", + "1.9-12.16.0.1833-1.9", + "1.9-12.16.0.1834-1.9", + "1.9-12.16.0.1835-1.9", + "1.9-12.16.0.1836-1.9", + "1.9-12.16.0.1837-1.9", + "1.9-12.16.0.1838-1.9", + "1.9-12.16.0.1839-1.9", + "1.9-12.16.0.1840-1.9", + "1.9-12.16.0.1841-1.9", + "1.9-12.16.0.1842-1.9", + "1.9-12.16.0.1843-1.9", + "1.9-12.16.0.1844-1.9", + "1.9-12.16.0.1845-1.9", + "1.9-12.16.0.1846-1.9", + "1.9-12.16.0.1848-1.9", + "1.9-12.16.0.1849-1.9", + "1.9-12.16.0.1850-1.9", + "1.9-12.16.0.1851-1.9", + "1.9-12.16.0.1852-1.9", + "1.9-12.16.0.1853-1.9", + "1.9-12.16.0.1854-1.9", + "1.9-12.16.0.1856-1.9", + "1.9-12.16.0.1857-1.9", + "1.9-12.16.0.1858-1.9", + "1.9-12.16.0.1859-1.9", + "1.9-12.16.0.1860-1.9", + "1.9-12.16.0.1861-1.9", + "1.9-12.16.0.1862-1.9", + "1.9-12.16.0.1863-1.9", + "1.9-12.16.0.1864-1.9", + "1.9-12.16.0.1865-1.9", + "1.9-12.16.0.1866-1.9", + "1.9-12.16.0.1867-1.9", + "1.9-12.16.0.1868-1.9", + "1.9-12.16.0.1869-1.9", + "1.9-12.16.0.1870-1.9", + "1.9-12.16.0.1871-1.9", + "1.9-12.16.0.1874-1.9", + "1.9-12.16.0.1877-1.9", + "1.9-12.16.0.1878-1.9", + "1.9-12.16.0.1879-1.9", + "1.9-12.16.0.1880-1.9", + "1.9-12.16.0.1881-1.9", + "1.9-12.16.0.1882-1.9", + "1.9-12.16.0.1883-1.9", + "1.9-12.16.0.1884-1.9", + "1.9-12.16.0.1885-1.9", + "1.9-12.16.0.1886", + "1.9-12.16.1.1887", + "1.9-12.16.1.1888", + "1.9-12.16.1.1889", + "1.9-12.16.1.1891", + "1.9-12.16.1.1892", + "1.9-12.16.1.1893", + "1.9-12.16.1.1894", + "1.9-12.16.1.1895", + "1.9-12.16.1.1896", + "1.9-12.16.1.1897", + "1.9-12.16.1.1898", + "1.9-12.16.1.1899", + "1.9-12.16.1.1900", + "1.9-12.16.1.1901", + "1.9-12.16.1.1904", + "1.9-12.16.1.1905", + "1.9-12.16.1.1906", + "1.9-12.16.1.1907", + "1.9-12.16.1.1923", + "1.9-12.16.1.1934", + "1.9-12.16.1.1938-1.9.0" + ], + "1.9.4": [ + "1.9.4-12.17.0.1908-1.9.4", + "1.9.4-12.17.0.1909-1.9.4", + "1.9.4-12.17.0.1910-1.9.4", + "1.9.4-12.17.0.1912-1.9.4", + "1.9.4-12.17.0.1913-1.9.4", + "1.9.4-12.17.0.1914-1.9.4", + "1.9.4-12.17.0.1915-1.9.4", + "1.9.4-12.17.0.1916-1.9.4", + "1.9.4-12.17.0.1917-1.9.4", + "1.9.4-12.17.0.1918-1.9.4", + "1.9.4-12.17.0.1919-EHUnit", + "1.9.4-12.17.0.1920-1.9.4", + "1.9.4-12.17.0.1921-1.9.4", + "1.9.4-12.17.0.1922-1.9.4", + "1.9.4-12.17.0.1924-1.9.4", + "1.9.4-12.17.0.1925-1.9.4", + "1.9.4-12.17.0.1926-1.9.4", + "1.9.4-12.17.0.1927-1.9.4", + "1.9.4-12.17.0.1928-1.9.4", + "1.9.4-12.17.0.1929-1.9.4", + "1.9.4-12.17.0.1930-1.9.4", + "1.9.4-12.17.0.1931-1.9.4", + "1.9.4-12.17.0.1932-1.9.4", + "1.9.4-12.17.0.1933-1.9.4", + "1.9.4-12.17.0.1935-1.9.4", + "1.9.4-12.17.0.1936-1.9.4", + "1.9.4-12.17.0.1937", + "1.9.4-12.17.0.1939", + "1.9.4-12.17.0.1940", + "1.9.4-12.17.0.1941", + "1.9.4-12.17.0.1943", + "1.9.4-12.17.0.1944", + "1.9.4-12.17.0.1945", + "1.9.4-12.17.0.1946", + "1.9.4-12.17.0.1947", + "1.9.4-12.17.0.1948", + "1.9.4-12.17.0.1949", + "1.9.4-12.17.0.1950", + "1.9.4-12.17.0.1951", + "1.9.4-12.17.0.1952", + "1.9.4-12.17.0.1953", + "1.9.4-12.17.0.1954", + "1.9.4-12.17.0.1955", + "1.9.4-12.17.0.1956", + "1.9.4-12.17.0.1957", + "1.9.4-12.17.0.1958", + "1.9.4-12.17.0.1959", + "1.9.4-12.17.0.1960", + "1.9.4-12.17.0.1961", + "1.9.4-12.17.0.1962", + "1.9.4-12.17.0.1963", + "1.9.4-12.17.0.1964", + "1.9.4-12.17.0.1965", + "1.9.4-12.17.0.1966", + "1.9.4-12.17.0.1967", + "1.9.4-12.17.0.1968", + "1.9.4-12.17.0.1969", + "1.9.4-12.17.0.1970", + "1.9.4-12.17.0.1972", + "1.9.4-12.17.0.1973", + "1.9.4-12.17.0.1974-1.9.4", + "1.9.4-12.17.0.1975", + "1.9.4-12.17.0.1976", + "1.9.4-12.17.0.1987", + "1.9.4-12.17.0.1990", + "1.9.4-12.17.0.2051", + "1.9.4-12.17.0.2317-1.9.4" + ], + "1.10": [ + "1.10-12.18.0.1981-1.10.0", + "1.10-12.18.0.1982-1.10.0", + "1.10-12.18.0.1983-1.10.0", + "1.10-12.18.0.1984-1.10.0", + "1.10-12.18.0.1985-1.10.0", + "1.10-12.18.0.1986-1.10.0", + "1.10-12.18.0.1988-1.10.0", + "1.10-12.18.0.1989-1.10.0", + "1.10-12.18.0.1991-1.10.0", + "1.10-12.18.0.1992-1.10.0", + "1.10-12.18.0.1993-1.10.0", + "1.10-12.18.0.1994-1.10.0", + "1.10-12.18.0.1995-1.10.0", + "1.10-12.18.0.1996-1.10.0", + "1.10-12.18.0.1997-1.10.0", + "1.10-12.18.0.1998-1.10.0", + "1.10-12.18.0.1999-1.10.0", + "1.10-12.18.0.2000-1.10.0" + ], + "1.10.2": [ + "1.10.2-12.18.0.2001-1.10.0", + "1.10.2-12.18.0.2002-1.10.0", + "1.10.2-12.18.0.2003-1.10.0", + "1.10.2-12.18.0.2004-1.10.0", + "1.10.2-12.18.0.2005-1.10.0", + "1.10.2-12.18.0.2006-1.10.0", + "1.10.2-12.18.0.2007-1.10.0", + "1.10.2-12.18.0.2008", + "1.10.2-12.18.0.2009", + "1.10.2-12.18.0.2010", + "1.10.2-12.18.1.2011", + "1.10.2-12.18.1.2012", + "1.10.2-12.18.1.2013", + "1.10.2-12.18.1.2014", + "1.10.2-12.18.1.2015-failtests", + "1.10.2-12.18.1.2016-failtests", + "1.10.2-12.18.1.2017", + "1.10.2-12.18.1.2018", + "1.10.2-12.18.1.2019", + "1.10.2-12.18.1.2020", + "1.10.2-12.18.1.2021", + "1.10.2-12.18.1.2022", + "1.10.2-12.18.1.2023", + "1.10.2-12.18.1.2024", + "1.10.2-12.18.1.2025", + "1.10.2-12.18.1.2026", + "1.10.2-12.18.1.2027", + "1.10.2-12.18.1.2028", + "1.10.2-12.18.1.2029", + "1.10.2-12.18.1.2030", + "1.10.2-12.18.1.2031", + "1.10.2-12.18.1.2032", + "1.10.2-12.18.1.2033", + "1.10.2-12.18.1.2034", + "1.10.2-12.18.1.2035", + "1.10.2-12.18.1.2036", + "1.10.2-12.18.1.2037", + "1.10.2-12.18.1.2038", + "1.10.2-12.18.1.2039", + "1.10.2-12.18.1.2040", + "1.10.2-12.18.1.2041", + "1.10.2-12.18.1.2042", + "1.10.2-12.18.1.2043", + "1.10.2-12.18.1.2044", + "1.10.2-12.18.1.2045", + "1.10.2-12.18.1.2046", + "1.10.2-12.18.1.2047", + "1.10.2-12.18.1.2048", + "1.10.2-12.18.1.2049", + "1.10.2-12.18.1.2050", + "1.10.2-12.18.1.2052", + "1.10.2-12.18.1.2053", + "1.10.2-12.18.1.2054", + "1.10.2-12.18.1.2055", + "1.10.2-12.18.1.2056", + "1.10.2-12.18.1.2057", + "1.10.2-12.18.1.2058", + "1.10.2-12.18.1.2059", + "1.10.2-12.18.1.2060", + "1.10.2-12.18.1.2061", + "1.10.2-12.18.1.2062", + "1.10.2-12.18.1.2063", + "1.10.2-12.18.1.2064", + "1.10.2-12.18.1.2065", + "1.10.2-12.18.1.2066", + "1.10.2-12.18.1.2067", + "1.10.2-12.18.1.2068", + "1.10.2-12.18.1.2069", + "1.10.2-12.18.1.2070", + "1.10.2-12.18.1.2071", + "1.10.2-12.18.1.2072", + "1.10.2-12.18.1.2073", + "1.10.2-12.18.1.2074", + "1.10.2-12.18.1.2075", + "1.10.2-12.18.1.2076", + "1.10.2-12.18.1.2077", + "1.10.2-12.18.1.2078", + "1.10.2-12.18.1.2079", + "1.10.2-12.18.1.2080", + "1.10.2-12.18.1.2081", + "1.10.2-12.18.1.2082", + "1.10.2-12.18.1.2083", + "1.10.2-12.18.1.2084", + "1.10.2-12.18.1.2085", + "1.10.2-12.18.1.2086", + "1.10.2-12.18.1.2087", + "1.10.2-12.18.1.2088", + "1.10.2-12.18.1.2089", + "1.10.2-12.18.1.2090", + "1.10.2-12.18.1.2091", + "1.10.2-12.18.1.2092", + "1.10.2-12.18.1.2093", + "1.10.2-12.18.1.2094", + "1.10.2-12.18.1.2095", + "1.10.2-12.18.1.2096", + "1.10.2-12.18.2.2097", + "1.10.2-12.18.2.2098", + "1.10.2-12.18.2.2099", + "1.10.2-12.18.2.2100", + "1.10.2-12.18.2.2101", + "1.10.2-12.18.2.2102", + "1.10.2-12.18.2.2103", + "1.10.2-12.18.2.2104", + "1.10.2-12.18.2.2105", + "1.10.2-12.18.2.2106", + "1.10.2-12.18.2.2107", + "1.10.2-12.18.2.2108", + "1.10.2-12.18.2.2109", + "1.10.2-12.18.2.2110", + "1.10.2-12.18.2.2111", + "1.10.2-12.18.2.2112", + "1.10.2-12.18.2.2113", + "1.10.2-12.18.2.2114", + "1.10.2-12.18.2.2115", + "1.10.2-12.18.2.2116", + "1.10.2-12.18.2.2117", + "1.10.2-12.18.2.2118", + "1.10.2-12.18.2.2119", + "1.10.2-12.18.2.2120", + "1.10.2-12.18.2.2121", + "1.10.2-12.18.2.2122", + "1.10.2-12.18.2.2123", + "1.10.2-12.18.2.2124", + "1.10.2-12.18.2.2125", + "1.10.2-12.18.2.2132", + "1.10.2-12.18.2.2134", + "1.10.2-12.18.2.2139", + "1.10.2-12.18.2.2140", + "1.10.2-12.18.2.2147", + "1.10.2-12.18.2.2151", + "1.10.2-12.18.2.2166", + "1.10.2-12.18.2.2170", + "1.10.2-12.18.2.2171", + "1.10.2-12.18.2.2179", + "1.10.2-12.18.2.2182", + "1.10.2-12.18.2.2183", + "1.10.2-12.18.3.2185", + "1.10.2-12.18.3.2202", + "1.10.2-12.18.3.2209", + "1.10.2-12.18.3.2215", + "1.10.2-12.18.3.2217", + "1.10.2-12.18.3.2219", + "1.10.2-12.18.3.2221", + "1.10.2-12.18.3.2234", + "1.10.2-12.18.3.2239", + "1.10.2-12.18.3.2254", + "1.10.2-12.18.3.2272", + "1.10.2-12.18.3.2281", + "1.10.2-12.18.3.2297", + "1.10.2-12.18.3.2316", + "1.10.2-12.18.3.2422", + "1.10.2-12.18.3.2477", + "1.10.2-12.18.3.2488", + "1.10.2-12.18.3.2511" + ], + "1.11": [ + "1.11-13.19.0.2126-1.11.x", + "1.11-13.19.0.2127-1.11.x", + "1.11-13.19.0.2128-1.11.x", + "1.11-13.19.0.2129-1.11.x", + "1.11-13.19.0.2130", + "1.11-13.19.0.2131", + "1.11-13.19.0.2133", + "1.11-13.19.0.2135", + "1.11-13.19.0.2136", + "1.11-13.19.0.2137", + "1.11-13.19.0.2138", + "1.11-13.19.0.2141", + "1.11-13.19.0.2142", + "1.11-13.19.0.2143", + "1.11-13.19.0.2144", + "1.11-13.19.0.2145", + "1.11-13.19.0.2146", + "1.11-13.19.0.2148", + "1.11-13.19.0.2149", + "1.11-13.19.0.2150", + "1.11-13.19.0.2152", + "1.11-13.19.0.2153", + "1.11-13.19.0.2154", + "1.11-13.19.0.2155", + "1.11-13.19.0.2156", + "1.11-13.19.0.2157", + "1.11-13.19.0.2159", + "1.11-13.19.0.2160", + "1.11-13.19.0.2161", + "1.11-13.19.0.2162", + "1.11-13.19.0.2163", + "1.11-13.19.0.2164", + "1.11-13.19.0.2165", + "1.11-13.19.0.2167", + "1.11-13.19.0.2168", + "1.11-13.19.0.2169", + "1.11-13.19.0.2172", + "1.11-13.19.0.2173", + "1.11-13.19.0.2174", + "1.11-13.19.0.2175", + "1.11-13.19.0.2176", + "1.11-13.19.0.2177", + "1.11-13.19.0.2178", + "1.11-13.19.0.2180", + "1.11-13.19.0.2181", + "1.11-13.19.0.2184", + "1.11-13.19.0.2186", + "1.11-13.19.0.2187", + "1.11-13.19.1.2188", + "1.11-13.19.1.2189", + "1.11-13.19.1.2190", + "1.11-13.19.1.2191", + "1.11-13.19.1.2192", + "1.11-13.19.1.2193", + "1.11-13.19.1.2194", + "1.11-13.19.1.2195", + "1.11-13.19.1.2196", + "1.11-13.19.1.2197", + "1.11-13.19.1.2198", + "1.11-13.19.1.2199" + ], + "1.11.2": [ + "1.11.2-13.20.0.2200", + "1.11.2-13.20.0.2201", + "1.11.2-13.20.0.2203", + "1.11.2-13.20.0.2204", + "1.11.2-13.20.0.2205", + "1.11.2-13.20.0.2206", + "1.11.2-13.20.0.2207", + "1.11.2-13.20.0.2208", + "1.11.2-13.20.0.2210", + "1.11.2-13.20.0.2211", + "1.11.2-13.20.0.2212", + "1.11.2-13.20.0.2213", + "1.11.2-13.20.0.2214", + "1.11.2-13.20.0.2216", + "1.11.2-13.20.0.2218", + "1.11.2-13.20.0.2220", + "1.11.2-13.20.0.2222", + "1.11.2-13.20.0.2223", + "1.11.2-13.20.0.2224", + "1.11.2-13.20.0.2225", + "1.11.2-13.20.0.2226", + "1.11.2-13.20.0.2227", + "1.11.2-13.20.0.2228", + "1.11.2-13.20.0.2229", + "1.11.2-13.20.0.2230", + "1.11.2-13.20.0.2231", + "1.11.2-13.20.0.2232", + "1.11.2-13.20.0.2233", + "1.11.2-13.20.0.2235", + "1.11.2-13.20.0.2236", + "1.11.2-13.20.0.2237", + "1.11.2-13.20.0.2238", + "1.11.2-13.20.0.2240", + "1.11.2-13.20.0.2241", + "1.11.2-13.20.0.2242", + "1.11.2-13.20.0.2243", + "1.11.2-13.20.0.2244", + "1.11.2-13.20.0.2245-3630", + "1.11.2-13.20.0.2246", + "1.11.2-13.20.0.2247", + "1.11.2-13.20.0.2248", + "1.11.2-13.20.0.2249", + "1.11.2-13.20.0.2250", + "1.11.2-13.20.0.2251", + "1.11.2-13.20.0.2252", + "1.11.2-13.20.0.2253", + "1.11.2-13.20.0.2255", + "1.11.2-13.20.0.2256", + "1.11.2-13.20.0.2257", + "1.11.2-13.20.0.2258", + "1.11.2-13.20.0.2259", + "1.11.2-13.20.0.2260", + "1.11.2-13.20.0.2261", + "1.11.2-13.20.0.2262", + "1.11.2-13.20.0.2263", + "1.11.2-13.20.0.2264", + "1.11.2-13.20.0.2265", + "1.11.2-13.20.0.2266", + "1.11.2-13.20.0.2267", + "1.11.2-13.20.0.2268", + "1.11.2-13.20.0.2269", + "1.11.2-13.20.0.2270", + "1.11.2-13.20.0.2271", + "1.11.2-13.20.0.2273", + "1.11.2-13.20.0.2274", + "1.11.2-13.20.0.2276", + "1.11.2-13.20.0.2277", + "1.11.2-13.20.0.2278", + "1.11.2-13.20.0.2279", + "1.11.2-13.20.0.2280", + "1.11.2-13.20.0.2282", + "1.11.2-13.20.0.2283", + "1.11.2-13.20.0.2284", + "1.11.2-13.20.0.2285", + "1.11.2-13.20.0.2286", + "1.11.2-13.20.0.2287", + "1.11.2-13.20.0.2288", + "1.11.2-13.20.0.2289", + "1.11.2-13.20.0.2290", + "1.11.2-13.20.0.2291", + "1.11.2-13.20.0.2292", + "1.11.2-13.20.0.2293", + "1.11.2-13.20.0.2294", + "1.11.2-13.20.0.2295", + "1.11.2-13.20.0.2296", + "1.11.2-13.20.0.2298", + "1.11.2-13.20.0.2299", + "1.11.2-13.20.0.2300", + "1.11.2-13.20.0.2301", + "1.11.2-13.20.0.2302", + "1.11.2-13.20.0.2303", + "1.11.2-13.20.0.2304", + "1.11.2-13.20.0.2305", + "1.11.2-13.20.0.2306", + "1.11.2-13.20.0.2307", + "1.11.2-13.20.0.2308", + "1.11.2-13.20.0.2309", + "1.11.2-13.20.0.2310", + "1.11.2-13.20.0.2311", + "1.11.2-13.20.0.2312", + "1.11.2-13.20.0.2313", + "1.11.2-13.20.0.2314", + "1.11.2-13.20.0.2315", + "1.11.2-13.20.0.2345", + "1.11.2-13.20.0.2356", + "1.11.2-13.20.0.2366", + "1.11.2-13.20.1.2386", + "1.11.2-13.20.1.2388", + "1.11.2-13.20.1.2391", + "1.11.2-13.20.1.2393", + "1.11.2-13.20.1.2414", + "1.11.2-13.20.1.2421", + "1.11.2-13.20.1.2425", + "1.11.2-13.20.1.2429", + "1.11.2-13.20.1.2454", + "1.11.2-13.20.1.2476", + "1.11.2-13.20.1.2504", + "1.11.2-13.20.1.2505", + "1.11.2-13.20.1.2506", + "1.11.2-13.20.1.2507", + "1.11.2-13.20.1.2510", + "1.11.2-13.20.1.2513", + "1.11.2-13.20.1.2516", + "1.11.2-13.20.1.2530", + "1.11.2-13.20.1.2563", + "1.11.2-13.20.1.2579", + "1.11.2-13.20.1.2588" + ], + "1.12": [ + "1.12-14.21.0.2320", + "1.12-14.21.0.2321", + "1.12-14.21.0.2322", + "1.12-14.21.0.2323", + "1.12-14.21.0.2324", + "1.12-14.21.0.2325", + "1.12-14.21.0.2326", + "1.12-14.21.0.2327", + "1.12-14.21.0.2328", + "1.12-14.21.0.2329", + "1.12-14.21.0.2330", + "1.12-14.21.0.2331", + "1.12-14.21.0.2332", + "1.12-14.21.0.2333", + "1.12-14.21.0.2334", + "1.12-14.21.0.2335", + "1.12-14.21.0.2336", + "1.12-14.21.0.2337", + "1.12-14.21.0.2338", + "1.12-14.21.0.2339", + "1.12-14.21.0.2340", + "1.12-14.21.0.2341", + "1.12-14.21.0.2342", + "1.12-14.21.0.2343", + "1.12-14.21.0.2344", + "1.12-14.21.0.2346", + "1.12-14.21.0.2347", + "1.12-14.21.0.2348", + "1.12-14.21.0.2349", + "1.12-14.21.0.2350", + "1.12-14.21.0.2351", + "1.12-14.21.0.2352", + "1.12-14.21.0.2353", + "1.12-14.21.0.2354", + "1.12-14.21.0.2355", + "1.12-14.21.0.2357", + "1.12-14.21.0.2358", + "1.12-14.21.0.2359", + "1.12-14.21.0.2360", + "1.12-14.21.0.2361", + "1.12-14.21.0.2362", + "1.12-14.21.0.2363", + "1.12-14.21.0.2364", + "1.12-14.21.0.2365", + "1.12-14.21.0.2367", + "1.12-14.21.0.2368", + "1.12-14.21.0.2369", + "1.12-14.21.0.2370", + "1.12-14.21.0.2371", + "1.12-14.21.0.2372", + "1.12-14.21.0.2373", + "1.12-14.21.0.2374", + "1.12-14.21.0.2375", + "1.12-14.21.0.2376", + "1.12-14.21.0.2377", + "1.12-14.21.0.2378", + "1.12-14.21.0.2379", + "1.12-14.21.0.2380", + "1.12-14.21.0.2381", + "1.12-14.21.0.2382", + "1.12-14.21.0.2383", + "1.12-14.21.0.2384", + "1.12-14.21.0.2385", + "1.12-14.21.1.2387", + "1.12-14.21.1.2389", + "1.12-14.21.1.2390", + "1.12-14.21.1.2392", + "1.12-14.21.1.2394", + "1.12-14.21.1.2395", + "1.12-14.21.1.2396", + "1.12-14.21.1.2397", + "1.12-14.21.1.2398", + "1.12-14.21.1.2399", + "1.12-14.21.1.2400", + "1.12-14.21.1.2401", + "1.12-14.21.1.2402", + "1.12-14.21.1.2403", + "1.12-14.21.1.2404", + "1.12-14.21.1.2405", + "1.12-14.21.1.2406", + "1.12-14.21.1.2407", + "1.12-14.21.1.2408", + "1.12-14.21.1.2409", + "1.12-14.21.1.2410", + "1.12-14.21.1.2411", + "1.12-14.21.1.2412", + "1.12-14.21.1.2413", + "1.12-14.21.1.2415", + "1.12-14.21.1.2416", + "1.12-14.21.1.2417", + "1.12-14.21.1.2418", + "1.12-14.21.1.2419", + "1.12-14.21.1.2420", + "1.12-14.21.1.2423", + "1.12-14.21.1.2424", + "1.12-14.21.1.2426", + "1.12-14.21.1.2427", + "1.12-14.21.1.2428", + "1.12-14.21.1.2430", + "1.12-14.21.1.2431", + "1.12-14.21.1.2432", + "1.12-14.21.1.2433", + "1.12-14.21.1.2434", + "1.12-14.21.1.2435", + "1.12-14.21.1.2436", + "1.12-14.21.1.2437", + "1.12-14.21.1.2438", + "1.12-14.21.1.2439", + "1.12-14.21.1.2440", + "1.12-14.21.1.2441", + "1.12-14.21.1.2442", + "1.12-14.21.1.2443" + ], + "1.12.1": [ + "1.12.1-14.22.0.2444", + "1.12.1-14.22.0.2445", + "1.12.1-14.22.0.2446", + "1.12.1-14.22.0.2447", + "1.12.1-14.22.0.2448", + "1.12.1-14.22.0.2449", + "1.12.1-14.22.0.2450", + "1.12.1-14.22.0.2451", + "1.12.1-14.22.0.2452", + "1.12.1-14.22.0.2453", + "1.12.1-14.22.0.2455", + "1.12.1-14.22.0.2456", + "1.12.1-14.22.0.2457", + "1.12.1-14.22.0.2458", + "1.12.1-14.22.0.2459", + "1.12.1-14.22.0.2460", + "1.12.1-14.22.0.2461", + "1.12.1-14.22.0.2462", + "1.12.1-14.22.0.2463", + "1.12.1-14.22.0.2464", + "1.12.1-14.22.0.2465", + "1.12.1-14.22.0.2466", + "1.12.1-14.22.0.2467", + "1.12.1-14.22.0.2468", + "1.12.1-14.22.0.2469", + "1.12.1-14.22.0.2470", + "1.12.1-14.22.0.2471", + "1.12.1-14.22.0.2472", + "1.12.1-14.22.0.2473", + "1.12.1-14.22.0.2474", + "1.12.1-14.22.0.2475", + "1.12.1-14.22.1.2478", + "1.12.1-14.22.1.2479", + "1.12.1-14.22.1.2480", + "1.12.1-14.22.1.2481", + "1.12.1-14.22.1.2482", + "1.12.1-14.22.1.2483", + "1.12.1-14.22.1.2484", + "1.12.1-14.22.1.2485" + ], + "1.12.2": [ + "1.12.2-14.23.0.2486", + "1.12.2-14.23.0.2487", + "1.12.2-14.23.0.2489", + "1.12.2-14.23.0.2490", + "1.12.2-14.23.0.2491", + "1.12.2-14.23.0.2492", + "1.12.2-14.23.0.2493", + "1.12.2-14.23.0.2494", + "1.12.2-14.23.0.2495", + "1.12.2-14.23.0.2496", + "1.12.2-14.23.0.2497", + "1.12.2-14.23.0.2498", + "1.12.2-14.23.0.2499", + "1.12.2-14.23.0.2500", + "1.12.2-14.23.0.2501", + "1.12.2-14.23.0.2502", + "1.12.2-14.23.0.2503", + "1.12.2-14.23.0.2508", + "1.12.2-14.23.0.2509", + "1.12.2-14.23.0.2512", + "1.12.2-14.23.0.2514", + "1.12.2-14.23.0.2515", + "1.12.2-14.23.0.2517", + "1.12.2-14.23.0.2518", + "1.12.2-14.23.0.2519", + "1.12.2-14.23.0.2520", + "1.12.2-14.23.0.2521", + "1.12.2-14.23.0.2522", + "1.12.2-14.23.0.2523", + "1.12.2-14.23.0.2524", + "1.12.2-14.23.0.2525", + "1.12.2-14.23.0.2526", + "1.12.2-14.23.0.2527", + "1.12.2-14.23.0.2528", + "1.12.2-14.23.0.2529", + "1.12.2-14.23.0.2531", + "1.12.2-14.23.0.2532", + "1.12.2-14.23.0.2533", + "1.12.2-14.23.0.2534", + "1.12.2-14.23.0.2535", + "1.12.2-14.23.0.2536", + "1.12.2-14.23.0.2537", + "1.12.2-14.23.0.2538", + "1.12.2-14.23.0.2539", + "1.12.2-14.23.0.2540", + "1.12.2-14.23.0.2541", + "1.12.2-14.23.0.2542", + "1.12.2-14.23.0.2543", + "1.12.2-14.23.0.2544", + "1.12.2-14.23.0.2545", + "1.12.2-14.23.0.2546", + "1.12.2-14.23.0.2547", + "1.12.2-14.23.0.2548", + "1.12.2-14.23.0.2549", + "1.12.2-14.23.0.2550", + "1.12.2-14.23.0.2551", + "1.12.2-14.23.0.2552", + "1.12.2-14.23.0.2553", + "1.12.2-14.23.1.2554", + "1.12.2-14.23.1.2555", + "1.12.2-14.23.1.2556", + "1.12.2-14.23.1.2557", + "1.12.2-14.23.1.2558", + "1.12.2-14.23.1.2559", + "1.12.2-14.23.1.2560", + "1.12.2-14.23.1.2561", + "1.12.2-14.23.1.2562", + "1.12.2-14.23.1.2564", + "1.12.2-14.23.1.2565", + "1.12.2-14.23.1.2566", + "1.12.2-14.23.1.2567", + "1.12.2-14.23.1.2568", + "1.12.2-14.23.1.2569", + "1.12.2-14.23.1.2570", + "1.12.2-14.23.1.2571", + "1.12.2-14.23.1.2572", + "1.12.2-14.23.1.2573", + "1.12.2-14.23.1.2574", + "1.12.2-14.23.1.2575", + "1.12.2-14.23.1.2576", + "1.12.2-14.23.1.2577", + "1.12.2-14.23.1.2578", + "1.12.2-14.23.1.2580", + "1.12.2-14.23.1.2581", + "1.12.2-14.23.1.2582", + "1.12.2-14.23.1.2583", + "1.12.2-14.23.1.2584", + "1.12.2-14.23.1.2585", + "1.12.2-14.23.1.2586", + "1.12.2-14.23.1.2587", + "1.12.2-14.23.1.2589", + "1.12.2-14.23.1.2590", + "1.12.2-14.23.1.2591", + "1.12.2-14.23.1.2592", + "1.12.2-14.23.1.2593", + "1.12.2-14.23.1.2594", + "1.12.2-14.23.1.2595", + "1.12.2-14.23.1.2596", + "1.12.2-14.23.1.2597", + "1.12.2-14.23.1.2598", + "1.12.2-14.23.1.2599", + "1.12.2-14.23.1.2600", + "1.12.2-14.23.1.2601", + "1.12.2-14.23.1.2602", + "1.12.2-14.23.1.2603", + "1.12.2-14.23.1.2604", + "1.12.2-14.23.1.2605", + "1.12.2-14.23.1.2606", + "1.12.2-14.23.1.2607", + "1.12.2-14.23.1.2608", + "1.12.2-14.23.1.2609", + "1.12.2-14.23.1.2610", + "1.12.2-14.23.2.2611", + "1.12.2-14.23.2.2612", + "1.12.2-14.23.2.2613", + "1.12.2-14.23.2.2614", + "1.12.2-14.23.2.2615", + "1.12.2-14.23.2.2616", + "1.12.2-14.23.2.2617", + "1.12.2-14.23.2.2618", + "1.12.2-14.23.2.2619", + "1.12.2-14.23.2.2620", + "1.12.2-14.23.2.2621", + "1.12.2-14.23.2.2622", + "1.12.2-14.23.2.2623", + "1.12.2-14.23.2.2624", + "1.12.2-14.23.2.2625", + "1.12.2-14.23.2.2626", + "1.12.2-14.23.2.2627", + "1.12.2-14.23.2.2628", + "1.12.2-14.23.2.2629", + "1.12.2-14.23.2.2630", + "1.12.2-14.23.2.2631", + "1.12.2-14.23.2.2632", + "1.12.2-14.23.2.2633", + "1.12.2-14.23.2.2634", + "1.12.2-14.23.2.2635", + "1.12.2-14.23.2.2636", + "1.12.2-14.23.2.2637", + "1.12.2-14.23.2.2638", + "1.12.2-14.23.2.2639", + "1.12.2-14.23.2.2640", + "1.12.2-14.23.2.2641", + "1.12.2-14.23.2.2642", + "1.12.2-14.23.2.2643", + "1.12.2-14.23.2.2644", + "1.12.2-14.23.2.2645", + "1.12.2-14.23.2.2646", + "1.12.2-14.23.2.2647", + "1.12.2-14.23.2.2648", + "1.12.2-14.23.2.2649", + "1.12.2-14.23.2.2650", + "1.12.2-14.23.2.2651", + "1.12.2-14.23.2.2652", + "1.12.2-14.23.2.2653", + "1.12.2-14.23.2.2654", + "1.12.2-14.23.3.2655", + "1.12.2-14.23.3.2658", + "1.12.2-14.23.3.2659", + "1.12.2-14.23.3.2660", + "1.12.2-14.23.3.2661", + "1.12.2-14.23.3.2662", + "1.12.2-14.23.3.2663", + "1.12.2-14.23.3.2664", + "1.12.2-14.23.3.2665", + "1.12.2-14.23.3.2666", + "1.12.2-14.23.3.2667", + "1.12.2-14.23.3.2668", + "1.12.2-14.23.3.2669", + "1.12.2-14.23.3.2670", + "1.12.2-14.23.3.2671", + "1.12.2-14.23.3.2672", + "1.12.2-14.23.3.2673", + "1.12.2-14.23.3.2674", + "1.12.2-14.23.3.2675", + "1.12.2-14.23.3.2676", + "1.12.2-14.23.3.2677", + "1.12.2-14.23.3.2678", + "1.12.2-14.23.3.2679", + "1.12.2-14.23.3.2680", + "1.12.2-14.23.3.2681", + "1.12.2-14.23.3.2682", + "1.12.2-14.23.3.2683", + "1.12.2-14.23.3.2684", + "1.12.2-14.23.3.2685", + "1.12.2-14.23.3.2686", + "1.12.2-14.23.3.2688", + "1.12.2-14.23.3.2689", + "1.12.2-14.23.3.2690", + "1.12.2-14.23.3.2691", + "1.12.2-14.23.3.2692", + "1.12.2-14.23.3.2693", + "1.12.2-14.23.3.2694", + "1.12.2-14.23.3.2695", + "1.12.2-14.23.3.2696", + "1.12.2-14.23.3.2697", + "1.12.2-14.23.3.2698", + "1.12.2-14.23.3.2699", + "1.12.2-14.23.3.2700", + "1.12.2-14.23.3.2701", + "1.12.2-14.23.3.2702", + "1.12.2-14.23.4.2703", + "1.12.2-14.23.4.2704", + "1.12.2-14.23.4.2705", + "1.12.2-14.23.4.2706", + "1.12.2-14.23.4.2707", + "1.12.2-14.23.4.2708", + "1.12.2-14.23.4.2709", + "1.12.2-14.23.4.2710", + "1.12.2-14.23.4.2711", + "1.12.2-14.23.4.2712", + "1.12.2-14.23.4.2713", + "1.12.2-14.23.4.2714", + "1.12.2-14.23.4.2715", + "1.12.2-14.23.4.2716", + "1.12.2-14.23.4.2717", + "1.12.2-14.23.4.2718", + "1.12.2-14.23.4.2719", + "1.12.2-14.23.4.2720-4627", + "1.12.2-14.23.4.2721", + "1.12.2-14.23.4.2722", + "1.12.2-14.23.4.2723", + "1.12.2-14.23.4.2724", + "1.12.2-14.23.4.2725", + "1.12.2-14.23.4.2726", + "1.12.2-14.23.4.2727", + "1.12.2-14.23.4.2728", + "1.12.2-14.23.4.2729", + "1.12.2-14.23.4.2730", + "1.12.2-14.23.4.2732", + "1.12.2-14.23.4.2733", + "1.12.2-14.23.4.2734", + "1.12.2-14.23.4.2735", + "1.12.2-14.23.4.2736", + "1.12.2-14.23.4.2737", + "1.12.2-14.23.4.2738", + "1.12.2-14.23.4.2739", + "1.12.2-14.23.4.2740", + "1.12.2-14.23.4.2741", + "1.12.2-14.23.4.2742", + "1.12.2-14.23.4.2743", + "1.12.2-14.23.4.2744", + "1.12.2-14.23.4.2745", + "1.12.2-14.23.4.2746", + "1.12.2-14.23.4.2747", + "1.12.2-14.23.4.2748", + "1.12.2-14.23.4.2749", + "1.12.2-14.23.4.2750", + "1.12.2-14.23.4.2751", + "1.12.2-14.23.4.2752", + "1.12.2-14.23.4.2753", + "1.12.2-14.23.4.2754", + "1.12.2-14.23.4.2755", + "1.12.2-14.23.4.2756", + "1.12.2-14.23.4.2757", + "1.12.2-14.23.4.2758", + "1.12.2-14.23.4.2759", + "1.12.2-14.23.4.2760", + "1.12.2-14.23.4.2761", + "1.12.2-14.23.4.2762", + "1.12.2-14.23.4.2763", + "1.12.2-14.23.4.2764", + "1.12.2-14.23.4.2765", + "1.12.2-14.23.4.2766", + "1.12.2-14.23.4.2767", + "1.12.2-14.23.5.2768", + "1.12.2-14.23.5.2769", + "1.12.2-14.23.5.2770", + "1.12.2-14.23.5.2771", + "1.12.2-14.23.5.2772", + "1.12.2-14.23.5.2773", + "1.12.2-14.23.5.2774", + "1.12.2-14.23.5.2775", + "1.12.2-14.23.5.2776", + "1.12.2-14.23.5.2777", + "1.12.2-14.23.5.2778", + "1.12.2-14.23.5.2779", + "1.12.2-14.23.5.2780", + "1.12.2-14.23.5.2781", + "1.12.2-14.23.5.2782", + "1.12.2-14.23.5.2783", + "1.12.2-14.23.5.2784", + "1.12.2-14.23.5.2785", + "1.12.2-14.23.5.2786", + "1.12.2-14.23.5.2787", + "1.12.2-14.23.5.2788", + "1.12.2-14.23.5.2789", + "1.12.2-14.23.5.2793", + "1.12.2-14.23.5.2794", + "1.12.2-14.23.5.2795", + "1.12.2-14.23.5.2796", + "1.12.2-14.23.5.2797", + "1.12.2-14.23.5.2798", + "1.12.2-14.23.5.2799", + "1.12.2-14.23.5.2800", + "1.12.2-14.23.5.2801", + "1.12.2-14.23.5.2802", + "1.12.2-14.23.5.2803", + "1.12.2-14.23.5.2804", + "1.12.2-14.23.5.2805", + "1.12.2-14.23.5.2806", + "1.12.2-14.23.5.2807", + "1.12.2-14.23.5.2808", + "1.12.2-14.23.5.2809", + "1.12.2-14.23.5.2810", + "1.12.2-14.23.5.2811", + "1.12.2-14.23.5.2812", + "1.12.2-14.23.5.2813", + "1.12.2-14.23.5.2814", + "1.12.2-14.23.5.2815", + "1.12.2-14.23.5.2816", + "1.12.2-14.23.5.2817", + "1.12.2-14.23.5.2818", + "1.12.2-14.23.5.2819", + "1.12.2-14.23.5.2820", + "1.12.2-14.23.5.2821", + "1.12.2-14.23.5.2822", + "1.12.2-14.23.5.2823", + "1.12.2-14.23.5.2824", + "1.12.2-14.23.5.2825", + "1.12.2-14.23.5.2826", + "1.12.2-14.23.5.2827", + "1.12.2-14.23.5.2828", + "1.12.2-14.23.5.2829", + "1.12.2-14.23.5.2830", + "1.12.2-14.23.5.2831", + "1.12.2-14.23.5.2832", + "1.12.2-14.23.5.2833", + "1.12.2-14.23.5.2834", + "1.12.2-14.23.5.2835", + "1.12.2-14.23.5.2836", + "1.12.2-14.23.5.2837", + "1.12.2-14.23.5.2838", + "1.12.2-14.23.5.2839", + "1.12.2-14.23.5.2840", + "1.12.2-14.23.5.2841", + "1.12.2-14.23.5.2842", + "1.12.2-14.23.5.2843", + "1.12.2-14.23.5.2844", + "1.12.2-14.23.5.2845", + "1.12.2-14.23.5.2846", + "1.12.2-14.23.5.2847", + "1.12.2-14.23.5.2851", + "1.12.2-14.23.5.2852", + "1.12.2-14.23.5.2854", + "1.12.2-14.23.5.2855", + "1.12.2-14.23.5.2856", + "1.12.2-14.23.5.2857", + "1.12.2-14.23.5.2858", + "1.12.2-14.23.5.2859", + "1.12.2-14.23.5.2860" + ], + "1.13.2": [ + "1.13.2-25.0.9", + "1.13.2-25.0.10", + "1.13.2-25.0.11", + "1.13.2-25.0.12", + "1.13.2-25.0.13", + "1.13.2-25.0.14", + "1.13.2-25.0.17", + "1.13.2-25.0.20", + "1.13.2-25.0.21", + "1.13.2-25.0.22", + "1.13.2-25.0.23", + "1.13.2-25.0.26", + "1.13.2-25.0.27", + "1.13.2-25.0.28", + "1.13.2-25.0.29", + "1.13.2-25.0.30", + "1.13.2-25.0.31", + "1.13.2-25.0.32", + "1.13.2-25.0.33", + "1.13.2-25.0.34", + "1.13.2-25.0.35", + "1.13.2-25.0.36", + "1.13.2-25.0.37", + "1.13.2-25.0.40", + "1.13.2-25.0.41", + "1.13.2-25.0.42", + "1.13.2-25.0.43", + "1.13.2-25.0.44", + "1.13.2-25.0.45", + "1.13.2-25.0.47", + "1.13.2-25.0.48", + "1.13.2-25.0.49", + "1.13.2-25.0.50", + "1.13.2-25.0.51", + "1.13.2-25.0.52", + "1.13.2-25.0.53", + "1.13.2-25.0.54", + "1.13.2-25.0.55", + "1.13.2-25.0.56", + "1.13.2-25.0.57", + "1.13.2-25.0.58", + "1.13.2-25.0.59", + "1.13.2-25.0.60", + "1.13.2-25.0.61", + "1.13.2-25.0.63", + "1.13.2-25.0.64", + "1.13.2-25.0.66", + "1.13.2-25.0.68", + "1.13.2-25.0.69", + "1.13.2-25.0.70", + "1.13.2-25.0.71", + "1.13.2-25.0.73", + "1.13.2-25.0.74", + "1.13.2-25.0.76", + "1.13.2-25.0.77", + "1.13.2-25.0.78", + "1.13.2-25.0.79", + "1.13.2-25.0.80", + "1.13.2-25.0.81", + "1.13.2-25.0.82", + "1.13.2-25.0.84", + "1.13.2-25.0.85", + "1.13.2-25.0.87", + "1.13.2-25.0.88", + "1.13.2-25.0.89", + "1.13.2-25.0.90", + "1.13.2-25.0.91", + "1.13.2-25.0.92", + "1.13.2-25.0.93", + "1.13.2-25.0.94", + "1.13.2-25.0.95", + "1.13.2-25.0.96", + "1.13.2-25.0.99", + "1.13.2-25.0.100", + "1.13.2-25.0.102", + "1.13.2-25.0.103", + "1.13.2-25.0.107", + "1.13.2-25.0.108", + "1.13.2-25.0.109", + "1.13.2-25.0.110", + "1.13.2-25.0.114", + "1.13.2-25.0.121", + "1.13.2-25.0.128", + "1.13.2-25.0.134", + "1.13.2-25.0.135", + "1.13.2-25.0.141", + "1.13.2-25.0.142", + "1.13.2-25.0.144", + "1.13.2-25.0.145", + "1.13.2-25.0.146", + "1.13.2-25.0.147", + "1.13.2-25.0.149", + "1.13.2-25.0.154", + "1.13.2-25.0.160", + "1.13.2-25.0.168", + "1.13.2-25.0.174", + "1.13.2-25.0.175", + "1.13.2-25.0.182", + "1.13.2-25.0.183", + "1.13.2-25.0.187", + "1.13.2-25.0.189", + "1.13.2-25.0.190", + "1.13.2-25.0.191", + "1.13.2-25.0.192", + "1.13.2-25.0.193", + "1.13.2-25.0.194", + "1.13.2-25.0.198", + "1.13.2-25.0.205", + "1.13.2-25.0.206", + "1.13.2-25.0.207", + "1.13.2-25.0.208", + "1.13.2-25.0.209", + "1.13.2-25.0.210", + "1.13.2-25.0.214", + "1.13.2-25.0.215", + "1.13.2-25.0.216", + "1.13.2-25.0.218", + "1.13.2-25.0.219", + "1.13.2-25.0.222", + "1.13.2-25.0.223" + ], + "1.14.2": [ + "1.14.2-26.0.0", + "1.14.2-26.0.2", + "1.14.2-26.0.3", + "1.14.2-26.0.4", + "1.14.2-26.0.5", + "1.14.2-26.0.6", + "1.14.2-26.0.7", + "1.14.2-26.0.8", + "1.14.2-26.0.10", + "1.14.2-26.0.12", + "1.14.2-26.0.13", + "1.14.2-26.0.14", + "1.14.2-26.0.15", + "1.14.2-26.0.16", + "1.14.2-26.0.17", + "1.14.2-26.0.18", + "1.14.2-26.0.19", + "1.14.2-26.0.21", + "1.14.2-26.0.22", + "1.14.2-26.0.23", + "1.14.2-26.0.25", + "1.14.2-26.0.28", + "1.14.2-26.0.29", + "1.14.2-26.0.30", + "1.14.2-26.0.32", + "1.14.2-26.0.33", + "1.14.2-26.0.35", + "1.14.2-26.0.37", + "1.14.2-26.0.39", + "1.14.2-26.0.40", + "1.14.2-26.0.41", + "1.14.2-26.0.42", + "1.14.2-26.0.43", + "1.14.2-26.0.47", + "1.14.2-26.0.48", + "1.14.2-26.0.49", + "1.14.2-26.0.50", + "1.14.2-26.0.51", + "1.14.2-26.0.52", + "1.14.2-26.0.54", + "1.14.2-26.0.55", + "1.14.2-26.0.56", + "1.14.2-26.0.57", + "1.14.2-26.0.60", + "1.14.2-26.0.61", + "1.14.2-26.0.62", + "1.14.2-26.0.63" + ], + "1.14.3": [ + "1.14.3-27.0.0", + "1.14.3-27.0.1", + "1.14.3-27.0.2", + "1.14.3-27.0.3", + "1.14.3-27.0.4", + "1.14.3-27.0.5", + "1.14.3-27.0.7", + "1.14.3-27.0.8", + "1.14.3-27.0.9", + "1.14.3-27.0.10", + "1.14.3-27.0.11", + "1.14.3-27.0.12", + "1.14.3-27.0.13", + "1.14.3-27.0.14", + "1.14.3-27.0.15", + "1.14.3-27.0.16", + "1.14.3-27.0.17", + "1.14.3-27.0.18", + "1.14.3-27.0.19", + "1.14.3-27.0.20", + "1.14.3-27.0.21", + "1.14.3-27.0.22", + "1.14.3-27.0.23", + "1.14.3-27.0.24", + "1.14.3-27.0.25", + "1.14.3-27.0.26", + "1.14.3-27.0.29", + "1.14.3-27.0.30", + "1.14.3-27.0.31", + "1.14.3-27.0.38", + "1.14.3-27.0.40", + "1.14.3-27.0.42", + "1.14.3-27.0.43", + "1.14.3-27.0.47", + "1.14.3-27.0.49", + "1.14.3-27.0.50", + "1.14.3-27.0.51", + "1.14.3-27.0.52", + "1.14.3-27.0.53", + "1.14.3-27.0.54", + "1.14.3-27.0.55", + "1.14.3-27.0.56", + "1.14.3-27.0.57", + "1.14.3-27.0.58", + "1.14.3-27.0.59", + "1.14.3-27.0.60" + ], + "1.14.4": [ + "1.14.4-28.0.1", + "1.14.4-28.0.2", + "1.14.4-28.0.3", + "1.14.4-28.0.4", + "1.14.4-28.0.5", + "1.14.4-28.0.9", + "1.14.4-28.0.11", + "1.14.4-28.0.12", + "1.14.4-28.0.13", + "1.14.4-28.0.14", + "1.14.4-28.0.16", + "1.14.4-28.0.17", + "1.14.4-28.0.18", + "1.14.4-28.0.19", + "1.14.4-28.0.20", + "1.14.4-28.0.21", + "1.14.4-28.0.22", + "1.14.4-28.0.23", + "1.14.4-28.0.24", + "1.14.4-28.0.25", + "1.14.4-28.0.26", + "1.14.4-28.0.27", + "1.14.4-28.0.28", + "1.14.4-28.0.29", + "1.14.4-28.0.30", + "1.14.4-28.0.32", + "1.14.4-28.0.34", + "1.14.4-28.0.35", + "1.14.4-28.0.37", + "1.14.4-28.0.38", + "1.14.4-28.0.39", + "1.14.4-28.0.40", + "1.14.4-28.0.41", + "1.14.4-28.0.45", + "1.14.4-28.0.46", + "1.14.4-28.0.47", + "1.14.4-28.0.48", + "1.14.4-28.0.49", + "1.14.4-28.0.51", + "1.14.4-28.0.55", + "1.14.4-28.0.56", + "1.14.4-28.0.58", + "1.14.4-28.0.62", + "1.14.4-28.0.63", + "1.14.4-28.0.65", + "1.14.4-28.0.67", + "1.14.4-28.0.68", + "1.14.4-28.0.69", + "1.14.4-28.0.70", + "1.14.4-28.0.73", + "1.14.4-28.0.74", + "1.14.4-28.0.75", + "1.14.4-28.0.76", + "1.14.4-28.0.81", + "1.14.4-28.0.82", + "1.14.4-28.0.83", + "1.14.4-28.0.84", + "1.14.4-28.0.85", + "1.14.4-28.0.86", + "1.14.4-28.0.87", + "1.14.4-28.0.88", + "1.14.4-28.0.90", + "1.14.4-28.0.91", + "1.14.4-28.0.92", + "1.14.4-28.0.93", + "1.14.4-28.0.94", + "1.14.4-28.0.95", + "1.14.4-28.0.100", + "1.14.4-28.0.101", + "1.14.4-28.0.102", + "1.14.4-28.0.104", + "1.14.4-28.0.105", + "1.14.4-28.0.106", + "1.14.4-28.0.107", + "1.14.4-28.1.0", + "1.14.4-28.1.1", + "1.14.4-28.1.2", + "1.14.4-28.1.3", + "1.14.4-28.1.4", + "1.14.4-28.1.5", + "1.14.4-28.1.6", + "1.14.4-28.1.7", + "1.14.4-28.1.8", + "1.14.4-28.1.10", + "1.14.4-28.1.11", + "1.14.4-28.1.14", + "1.14.4-28.1.15", + "1.14.4-28.1.16", + "1.14.4-28.1.17", + "1.14.4-28.1.18", + "1.14.4-28.1.19", + "1.14.4-28.1.20", + "1.14.4-28.1.22", + "1.14.4-28.1.23", + "1.14.4-28.1.24", + "1.14.4-28.1.25", + "1.14.4-28.1.26", + "1.14.4-28.1.28", + "1.14.4-28.1.30", + "1.14.4-28.1.31", + "1.14.4-28.1.32", + "1.14.4-28.1.33", + "1.14.4-28.1.34", + "1.14.4-28.1.35", + "1.14.4-28.1.36", + "1.14.4-28.1.37", + "1.14.4-28.1.38", + "1.14.4-28.1.39", + "1.14.4-28.1.40", + "1.14.4-28.1.41", + "1.14.4-28.1.42", + "1.14.4-28.1.44", + "1.14.4-28.1.45", + "1.14.4-28.1.46", + "1.14.4-28.1.47", + "1.14.4-28.1.48", + "1.14.4-28.1.49", + "1.14.4-28.1.50", + "1.14.4-28.1.56", + "1.14.4-28.1.58", + "1.14.4-28.1.59", + "1.14.4-28.1.60", + "1.14.4-28.1.61", + "1.14.4-28.1.62", + "1.14.4-28.1.64", + "1.14.4-28.1.65", + "1.14.4-28.1.66", + "1.14.4-28.1.67", + "1.14.4-28.1.68", + "1.14.4-28.1.69", + "1.14.4-28.1.70", + "1.14.4-28.1.71", + "1.14.4-28.1.72", + "1.14.4-28.1.73", + "1.14.4-28.1.74", + "1.14.4-28.1.75", + "1.14.4-28.1.76", + "1.14.4-28.1.77", + "1.14.4-28.1.78", + "1.14.4-28.1.79", + "1.14.4-28.1.80", + "1.14.4-28.1.81", + "1.14.4-28.1.85", + "1.14.4-28.1.86", + "1.14.4-28.1.87", + "1.14.4-28.1.88", + "1.14.4-28.1.90", + "1.14.4-28.1.91", + "1.14.4-28.1.92", + "1.14.4-28.1.93", + "1.14.4-28.1.94", + "1.14.4-28.1.95", + "1.14.4-28.1.96", + "1.14.4-28.1.97", + "1.14.4-28.1.98", + "1.14.4-28.1.99", + "1.14.4-28.1.102", + "1.14.4-28.1.103", + "1.14.4-28.1.104", + "1.14.4-28.1.105", + "1.14.4-28.1.106", + "1.14.4-28.1.107", + "1.14.4-28.1.108", + "1.14.4-28.1.109", + "1.14.4-28.1.110", + "1.14.4-28.1.111", + "1.14.4-28.1.113", + "1.14.4-28.1.114", + "1.14.4-28.1.115", + "1.14.4-28.1.116", + "1.14.4-28.1.117", + "1.14.4-28.1.118", + "1.14.4-28.2.0", + "1.14.4-28.2.1", + "1.14.4-28.2.2", + "1.14.4-28.2.3", + "1.14.4-28.2.4", + "1.14.4-28.2.5", + "1.14.4-28.2.6", + "1.14.4-28.2.10", + "1.14.4-28.2.11", + "1.14.4-28.2.12", + "1.14.4-28.2.13", + "1.14.4-28.2.14", + "1.14.4-28.2.15", + "1.14.4-28.2.16", + "1.14.4-28.2.17", + "1.14.4-28.2.18", + "1.14.4-28.2.19", + "1.14.4-28.2.20", + "1.14.4-28.2.21", + "1.14.4-28.2.23", + "1.14.4-28.2.25", + "1.14.4-28.2.26" + ], + "1.15": [ + "1.15-29.0.0", + "1.15-29.0.1", + "1.15-29.0.2", + "1.15-29.0.3", + "1.15-29.0.4" + ], + "1.15.1": [ + "1.15.1-30.0.0", + "1.15.1-30.0.2", + "1.15.1-30.0.4", + "1.15.1-30.0.5", + "1.15.1-30.0.7", + "1.15.1-30.0.8", + "1.15.1-30.0.10", + "1.15.1-30.0.11", + "1.15.1-30.0.12", + "1.15.1-30.0.13", + "1.15.1-30.0.14", + "1.15.1-30.0.15", + "1.15.1-30.0.16", + "1.15.1-30.0.17", + "1.15.1-30.0.18", + "1.15.1-30.0.19", + "1.15.1-30.0.20", + "1.15.1-30.0.21", + "1.15.1-30.0.22", + "1.15.1-30.0.24", + "1.15.1-30.0.25", + "1.15.1-30.0.26", + "1.15.1-30.0.27", + "1.15.1-30.0.28", + "1.15.1-30.0.29", + "1.15.1-30.0.30", + "1.15.1-30.0.31", + "1.15.1-30.0.32", + "1.15.1-30.0.33", + "1.15.1-30.0.35", + "1.15.1-30.0.36", + "1.15.1-30.0.38", + "1.15.1-30.0.39", + "1.15.1-30.0.40", + "1.15.1-30.0.41", + "1.15.1-30.0.42", + "1.15.1-30.0.43", + "1.15.1-30.0.48", + "1.15.1-30.0.49", + "1.15.1-30.0.50", + "1.15.1-30.0.51" + ], + "1.15.2": [ + "1.15.2-31.0.0", + "1.15.2-31.0.1", + "1.15.2-31.0.2", + "1.15.2-31.0.4", + "1.15.2-31.0.7", + "1.15.2-31.0.8", + "1.15.2-31.0.9", + "1.15.2-31.0.11", + "1.15.2-31.0.12", + "1.15.2-31.0.13", + "1.15.2-31.0.14", + "1.15.2-31.0.15", + "1.15.2-31.0.16", + "1.15.2-31.0.17", + "1.15.2-31.0.19", + "1.15.2-31.1.0", + "1.15.2-31.1.1", + "1.15.2-31.1.2", + "1.15.2-31.1.3", + "1.15.2-31.1.5", + "1.15.2-31.1.8", + "1.15.2-31.1.9", + "1.15.2-31.1.10", + "1.15.2-31.1.11", + "1.15.2-31.1.12", + "1.15.2-31.1.13", + "1.15.2-31.1.14", + "1.15.2-31.1.15", + "1.15.2-31.1.16", + "1.15.2-31.1.17", + "1.15.2-31.1.18", + "1.15.2-31.1.19", + "1.15.2-31.1.20", + "1.15.2-31.1.21", + "1.15.2-31.1.22", + "1.15.2-31.1.23", + "1.15.2-31.1.24", + "1.15.2-31.1.25", + "1.15.2-31.1.26", + "1.15.2-31.1.27", + "1.15.2-31.1.28", + "1.15.2-31.1.29", + "1.15.2-31.1.30", + "1.15.2-31.1.32", + "1.15.2-31.1.34", + "1.15.2-31.1.35", + "1.15.2-31.1.36", + "1.15.2-31.1.37", + "1.15.2-31.1.39", + "1.15.2-31.1.40", + "1.15.2-31.1.41", + "1.15.2-31.1.42", + "1.15.2-31.1.43", + "1.15.2-31.1.44", + "1.15.2-31.1.45", + "1.15.2-31.1.46", + "1.15.2-31.1.47", + "1.15.2-31.1.48", + "1.15.2-31.1.49", + "1.15.2-31.1.50", + "1.15.2-31.1.51", + "1.15.2-31.1.55", + "1.15.2-31.1.57", + "1.15.2-31.1.59", + "1.15.2-31.1.60", + "1.15.2-31.1.61", + "1.15.2-31.1.62", + "1.15.2-31.1.63", + "1.15.2-31.1.64", + "1.15.2-31.1.65", + "1.15.2-31.1.66", + "1.15.2-31.1.67", + "1.15.2-31.1.70", + "1.15.2-31.1.71", + "1.15.2-31.1.72", + "1.15.2-31.1.73", + "1.15.2-31.1.74", + "1.15.2-31.1.75", + "1.15.2-31.1.76", + "1.15.2-31.1.77", + "1.15.2-31.1.78", + "1.15.2-31.1.79", + "1.15.2-31.1.80", + "1.15.2-31.1.81", + "1.15.2-31.1.84", + "1.15.2-31.1.85", + "1.15.2-31.1.86", + "1.15.2-31.1.87", + "1.15.2-31.1.88", + "1.15.2-31.1.89", + "1.15.2-31.1.91", + "1.15.2-31.1.92", + "1.15.2-31.1.93", + "1.15.2-31.1.95", + "1.15.2-31.1.97", + "1.15.2-31.1.98", + "1.15.2-31.1.99", + "1.15.2-31.2.0", + "1.15.2-31.2.1", + "1.15.2-31.2.2", + "1.15.2-31.2.3", + "1.15.2-31.2.4", + "1.15.2-31.2.5", + "1.15.2-31.2.7", + "1.15.2-31.2.8", + "1.15.2-31.2.9", + "1.15.2-31.2.10", + "1.15.2-31.2.12", + "1.15.2-31.2.13", + "1.15.2-31.2.15", + "1.15.2-31.2.16", + "1.15.2-31.2.17", + "1.15.2-31.2.18", + "1.15.2-31.2.19", + "1.15.2-31.2.20", + "1.15.2-31.2.21", + "1.15.2-31.2.22", + "1.15.2-31.2.23", + "1.15.2-31.2.24", + "1.15.2-31.2.25", + "1.15.2-31.2.26", + "1.15.2-31.2.27", + "1.15.2-31.2.28", + "1.15.2-31.2.29", + "1.15.2-31.2.30", + "1.15.2-31.2.31", + "1.15.2-31.2.33", + "1.15.2-31.2.35", + "1.15.2-31.2.36", + "1.15.2-31.2.37", + "1.15.2-31.2.38", + "1.15.2-31.2.40", + "1.15.2-31.2.41", + "1.15.2-31.2.43", + "1.15.2-31.2.44", + "1.15.2-31.2.45", + "1.15.2-31.2.46", + "1.15.2-31.2.47", + "1.15.2-31.2.48", + "1.15.2-31.2.49", + "1.15.2-31.2.50", + "1.15.2-31.2.52", + "1.15.2-31.2.53", + "1.15.2-31.2.54", + "1.15.2-31.2.55", + "1.15.2-31.2.56", + "1.15.2-31.2.57", + "1.15.2-31.2.60" + ], + "1.16.1": [ + "1.16.1-32.0.1", + "1.16.1-32.0.2", + "1.16.1-32.0.6", + "1.16.1-32.0.7", + "1.16.1-32.0.8", + "1.16.1-32.0.9", + "1.16.1-32.0.10", + "1.16.1-32.0.12", + "1.16.1-32.0.13", + "1.16.1-32.0.14", + "1.16.1-32.0.15", + "1.16.1-32.0.16", + "1.16.1-32.0.17", + "1.16.1-32.0.18", + "1.16.1-32.0.19", + "1.16.1-32.0.20", + "1.16.1-32.0.21", + "1.16.1-32.0.23", + "1.16.1-32.0.24", + "1.16.1-32.0.25", + "1.16.1-32.0.26", + "1.16.1-32.0.27", + "1.16.1-32.0.28", + "1.16.1-32.0.29", + "1.16.1-32.0.30", + "1.16.1-32.0.31", + "1.16.1-32.0.32", + "1.16.1-32.0.33", + "1.16.1-32.0.34", + "1.16.1-32.0.35", + "1.16.1-32.0.36", + "1.16.1-32.0.38", + "1.16.1-32.0.39", + "1.16.1-32.0.40", + "1.16.1-32.0.41", + "1.16.1-32.0.43", + "1.16.1-32.0.44", + "1.16.1-32.0.46", + "1.16.1-32.0.47", + "1.16.1-32.0.48", + "1.16.1-32.0.49", + "1.16.1-32.0.50", + "1.16.1-32.0.51", + "1.16.1-32.0.52", + "1.16.1-32.0.53", + "1.16.1-32.0.54", + "1.16.1-32.0.55", + "1.16.1-32.0.56", + "1.16.1-32.0.57", + "1.16.1-32.0.59", + "1.16.1-32.0.60", + "1.16.1-32.0.61", + "1.16.1-32.0.62", + "1.16.1-32.0.63", + "1.16.1-32.0.64", + "1.16.1-32.0.65", + "1.16.1-32.0.66", + "1.16.1-32.0.67", + "1.16.1-32.0.68", + "1.16.1-32.0.69", + "1.16.1-32.0.70", + "1.16.1-32.0.71", + "1.16.1-32.0.72", + "1.16.1-32.0.73", + "1.16.1-32.0.74", + "1.16.1-32.0.75", + "1.16.1-32.0.76", + "1.16.1-32.0.77", + "1.16.1-32.0.79", + "1.16.1-32.0.80", + "1.16.1-32.0.81", + "1.16.1-32.0.82", + "1.16.1-32.0.83", + "1.16.1-32.0.84", + "1.16.1-32.0.85", + "1.16.1-32.0.86", + "1.16.1-32.0.88", + "1.16.1-32.0.90", + "1.16.1-32.0.91", + "1.16.1-32.0.92", + "1.16.1-32.0.93", + "1.16.1-32.0.95", + "1.16.1-32.0.96", + "1.16.1-32.0.97", + "1.16.1-32.0.98", + "1.16.1-32.0.99", + "1.16.1-32.0.101", + "1.16.1-32.0.104", + "1.16.1-32.0.105", + "1.16.1-32.0.106", + "1.16.1-32.0.107", + "1.16.1-32.0.108" + ], + "1.16.2": [ + "1.16.2-33.0.0", + "1.16.2-33.0.2", + "1.16.2-33.0.3", + "1.16.2-33.0.5", + "1.16.2-33.0.6", + "1.16.2-33.0.7", + "1.16.2-33.0.8", + "1.16.2-33.0.9", + "1.16.2-33.0.10", + "1.16.2-33.0.11", + "1.16.2-33.0.12", + "1.16.2-33.0.13", + "1.16.2-33.0.14", + "1.16.2-33.0.15", + "1.16.2-33.0.16", + "1.16.2-33.0.17", + "1.16.2-33.0.18", + "1.16.2-33.0.19", + "1.16.2-33.0.20", + "1.16.2-33.0.21", + "1.16.2-33.0.22", + "1.16.2-33.0.23", + "1.16.2-33.0.30", + "1.16.2-33.0.31", + "1.16.2-33.0.32", + "1.16.2-33.0.34", + "1.16.2-33.0.35", + "1.16.2-33.0.36", + "1.16.2-33.0.37", + "1.16.2-33.0.40", + "1.16.2-33.0.41", + "1.16.2-33.0.42", + "1.16.2-33.0.43", + "1.16.2-33.0.44", + "1.16.2-33.0.45", + "1.16.2-33.0.46", + "1.16.2-33.0.49", + "1.16.2-33.0.50", + "1.16.2-33.0.54", + "1.16.2-33.0.55", + "1.16.2-33.0.56", + "1.16.2-33.0.57", + "1.16.2-33.0.58", + "1.16.2-33.0.59", + "1.16.2-33.0.60", + "1.16.2-33.0.61" + ], + "1.16.3": [ + "1.16.3-34.0.0", + "1.16.3-34.0.1", + "1.16.3-34.0.2", + "1.16.3-34.0.3", + "1.16.3-34.0.4", + "1.16.3-34.0.5", + "1.16.3-34.0.6", + "1.16.3-34.0.7", + "1.16.3-34.0.8", + "1.16.3-34.0.9", + "1.16.3-34.0.10", + "1.16.3-34.0.11", + "1.16.3-34.0.12", + "1.16.3-34.0.13", + "1.16.3-34.0.14", + "1.16.3-34.0.16", + "1.16.3-34.0.17", + "1.16.3-34.0.18", + "1.16.3-34.0.19", + "1.16.3-34.0.20", + "1.16.3-34.0.21", + "1.16.3-34.1.0", + "1.16.3-34.1.1", + "1.16.3-34.1.2", + "1.16.3-34.1.3", + "1.16.3-34.1.4", + "1.16.3-34.1.5", + "1.16.3-34.1.7", + "1.16.3-34.1.9", + "1.16.3-34.1.10", + "1.16.3-34.1.11", + "1.16.3-34.1.12", + "1.16.3-34.1.13", + "1.16.3-34.1.14", + "1.16.3-34.1.15", + "1.16.3-34.1.16", + "1.16.3-34.1.17", + "1.16.3-34.1.18", + "1.16.3-34.1.19", + "1.16.3-34.1.20", + "1.16.3-34.1.21", + "1.16.3-34.1.22", + "1.16.3-34.1.23", + "1.16.3-34.1.24", + "1.16.3-34.1.25", + "1.16.3-34.1.27", + "1.16.3-34.1.28", + "1.16.3-34.1.29", + "1.16.3-34.1.30", + "1.16.3-34.1.31", + "1.16.3-34.1.32", + "1.16.3-34.1.33", + "1.16.3-34.1.34", + "1.16.3-34.1.35", + "1.16.3-34.1.39", + "1.16.3-34.1.40", + "1.16.3-34.1.41", + "1.16.3-34.1.42" + ], + "1.16.4": [ + "1.16.4-35.0.0", + "1.16.4-35.0.1", + "1.16.4-35.0.2", + "1.16.4-35.0.3", + "1.16.4-35.0.4", + "1.16.4-35.0.5", + "1.16.4-35.0.6", + "1.16.4-35.0.7", + "1.16.4-35.0.9", + "1.16.4-35.0.10", + "1.16.4-35.0.11", + "1.16.4-35.0.12", + "1.16.4-35.0.13", + "1.16.4-35.0.14", + "1.16.4-35.0.15", + "1.16.4-35.0.16", + "1.16.4-35.0.17", + "1.16.4-35.0.18", + "1.16.4-35.0.19", + "1.16.4-35.0.20", + "1.16.4-35.0.22", + "1.16.4-35.1.0", + "1.16.4-35.1.1", + "1.16.4-35.1.2", + "1.16.4-35.1.3", + "1.16.4-35.1.4", + "1.16.4-35.1.5", + "1.16.4-35.1.6", + "1.16.4-35.1.7", + "1.16.4-35.1.8", + "1.16.4-35.1.9", + "1.16.4-35.1.10", + "1.16.4-35.1.11", + "1.16.4-35.1.12", + "1.16.4-35.1.13", + "1.16.4-35.1.15", + "1.16.4-35.1.16", + "1.16.4-35.1.17", + "1.16.4-35.1.18", + "1.16.4-35.1.20", + "1.16.4-35.1.21", + "1.16.4-35.1.22", + "1.16.4-35.1.23", + "1.16.4-35.1.24", + "1.16.4-35.1.26", + "1.16.4-35.1.27", + "1.16.4-35.1.28", + "1.16.4-35.1.29", + "1.16.4-35.1.31", + "1.16.4-35.1.32", + "1.16.4-35.1.33", + "1.16.4-35.1.34", + "1.16.4-35.1.35", + "1.16.4-35.1.36", + "1.16.4-35.1.37" + ], + "1.16.5": [ + "1.16.5-36.0.0", + "1.16.5-36.0.1", + "1.16.5-36.0.2", + "1.16.5-36.0.4", + "1.16.5-36.0.7", + "1.16.5-36.0.8", + "1.16.5-36.0.9", + "1.16.5-36.0.10", + "1.16.5-36.0.11", + "1.16.5-36.0.12", + "1.16.5-36.0.13", + "1.16.5-36.0.14", + "1.16.5-36.0.15", + "1.16.5-36.0.16", + "1.16.5-36.0.17", + "1.16.5-36.0.18", + "1.16.5-36.0.19", + "1.16.5-36.0.20", + "1.16.5-36.0.21", + "1.16.5-36.0.22", + "1.16.5-36.0.23", + "1.16.5-36.0.24", + "1.16.5-36.0.25", + "1.16.5-36.0.26", + "1.16.5-36.0.27", + "1.16.5-36.0.28", + "1.16.5-36.0.29", + "1.16.5-36.0.30", + "1.16.5-36.0.31", + "1.16.5-36.0.32", + "1.16.5-36.0.33", + "1.16.5-36.0.34", + "1.16.5-36.0.35", + "1.16.5-36.0.36", + "1.16.5-36.0.37", + "1.16.5-36.0.39", + "1.16.5-36.0.40", + "1.16.5-36.0.41", + "1.16.5-36.0.42", + "1.16.5-36.0.43", + "1.16.5-36.0.44", + "1.16.5-36.0.45", + "1.16.5-36.0.46", + "1.16.5-36.0.48", + "1.16.5-36.0.52", + "1.16.5-36.0.53", + "1.16.5-36.0.54", + "1.16.5-36.0.55", + "1.16.5-36.0.58", + "1.16.5-36.0.59", + "1.16.5-36.0.60", + "1.16.5-36.0.61", + "1.16.5-36.0.62", + "1.16.5-36.0.63", + "1.16.5-36.1.0", + "1.16.5-36.1.1", + "1.16.5-36.1.2", + "1.16.5-36.1.3", + "1.16.5-36.1.4", + "1.16.5-36.1.6", + "1.16.5-36.1.7", + "1.16.5-36.1.8", + "1.16.5-36.1.9", + "1.16.5-36.1.10", + "1.16.5-36.1.12", + "1.16.5-36.1.13", + "1.16.5-36.1.14", + "1.16.5-36.1.15", + "1.16.5-36.1.16", + "1.16.5-36.1.17", + "1.16.5-36.1.18", + "1.16.5-36.1.19", + "1.16.5-36.1.20", + "1.16.5-36.1.21", + "1.16.5-36.1.22", + "1.16.5-36.1.23", + "1.16.5-36.1.24", + "1.16.5-36.1.25", + "1.16.5-36.1.26", + "1.16.5-36.1.27", + "1.16.5-36.1.28", + "1.16.5-36.1.29", + "1.16.5-36.1.30", + "1.16.5-36.1.31", + "1.16.5-36.1.32", + "1.16.5-36.1.33", + "1.16.5-36.1.34", + "1.16.5-36.1.35", + "1.16.5-36.1.36", + "1.16.5-36.1.51", + "1.16.5-36.1.52", + "1.16.5-36.1.53", + "1.16.5-36.1.58", + "1.16.5-36.1.61", + "1.16.5-36.1.62", + "1.16.5-36.1.63", + "1.16.5-36.1.65", + "1.16.5-36.1.66", + "1.16.5-36.2.0", + "1.16.5-36.2.1", + "1.16.5-36.2.2", + "1.16.5-36.2.3", + "1.16.5-36.2.4", + "1.16.5-36.2.5", + "1.16.5-36.2.6", + "1.16.5-36.2.8", + "1.16.5-36.2.9", + "1.16.5-36.2.10", + "1.16.5-36.2.11", + "1.16.5-36.2.12", + "1.16.5-36.2.13", + "1.16.5-36.2.14", + "1.16.5-36.2.15", + "1.16.5-36.2.16", + "1.16.5-36.2.17", + "1.16.5-36.2.18", + "1.16.5-36.2.19", + "1.16.5-36.2.20", + "1.16.5-36.2.21", + "1.16.5-36.2.22", + "1.16.5-36.2.23", + "1.16.5-36.2.25", + "1.16.5-36.2.26", + "1.16.5-36.2.27", + "1.16.5-36.2.28", + "1.16.5-36.2.29", + "1.16.5-36.2.30", + "1.16.5-36.2.31", + "1.16.5-36.2.32", + "1.16.5-36.2.33", + "1.16.5-36.2.34", + "1.16.5-36.2.35", + "1.16.5-36.2.39", + "1.16.5-36.2.40", + "1.16.5-36.2.41", + "1.16.5-36.2.42" + ], + "1.17.1": [ + "1.17.1-37.0.0", + "1.17.1-37.0.1", + "1.17.1-37.0.2", + "1.17.1-37.0.3", + "1.17.1-37.0.5", + "1.17.1-37.0.7", + "1.17.1-37.0.8", + "1.17.1-37.0.9", + "1.17.1-37.0.10", + "1.17.1-37.0.11", + "1.17.1-37.0.12", + "1.17.1-37.0.13", + "1.17.1-37.0.15", + "1.17.1-37.0.16", + "1.17.1-37.0.17", + "1.17.1-37.0.18", + "1.17.1-37.0.19", + "1.17.1-37.0.20", + "1.17.1-37.0.21", + "1.17.1-37.0.22", + "1.17.1-37.0.24", + "1.17.1-37.0.25", + "1.17.1-37.0.26", + "1.17.1-37.0.27", + "1.17.1-37.0.28", + "1.17.1-37.0.29", + "1.17.1-37.0.30", + "1.17.1-37.0.31", + "1.17.1-37.0.32", + "1.17.1-37.0.33", + "1.17.1-37.0.34", + "1.17.1-37.0.35", + "1.17.1-37.0.36", + "1.17.1-37.0.37", + "1.17.1-37.0.38", + "1.17.1-37.0.39", + "1.17.1-37.0.40", + "1.17.1-37.0.41", + "1.17.1-37.0.42", + "1.17.1-37.0.43", + "1.17.1-37.0.44", + "1.17.1-37.0.45", + "1.17.1-37.0.46", + "1.17.1-37.0.47", + "1.17.1-37.0.48", + "1.17.1-37.0.49", + "1.17.1-37.0.50", + "1.17.1-37.0.51", + "1.17.1-37.0.52", + "1.17.1-37.0.53", + "1.17.1-37.0.54", + "1.17.1-37.0.55", + "1.17.1-37.0.56", + "1.17.1-37.0.57", + "1.17.1-37.0.58", + "1.17.1-37.0.59", + "1.17.1-37.0.60", + "1.17.1-37.0.61", + "1.17.1-37.0.62", + "1.17.1-37.0.64", + "1.17.1-37.0.65", + "1.17.1-37.0.66", + "1.17.1-37.0.67", + "1.17.1-37.0.68", + "1.17.1-37.0.69", + "1.17.1-37.0.70", + "1.17.1-37.0.71", + "1.17.1-37.0.72", + "1.17.1-37.0.73", + "1.17.1-37.0.74", + "1.17.1-37.0.75", + "1.17.1-37.0.76", + "1.17.1-37.0.78", + "1.17.1-37.0.80", + "1.17.1-37.0.81", + "1.17.1-37.0.82", + "1.17.1-37.0.83", + "1.17.1-37.0.84", + "1.17.1-37.0.85", + "1.17.1-37.0.86", + "1.17.1-37.0.87", + "1.17.1-37.0.88", + "1.17.1-37.0.89", + "1.17.1-37.0.90", + "1.17.1-37.0.91", + "1.17.1-37.0.94", + "1.17.1-37.0.95", + "1.17.1-37.0.97", + "1.17.1-37.0.98", + "1.17.1-37.0.102", + "1.17.1-37.0.103", + "1.17.1-37.0.104", + "1.17.1-37.0.105", + "1.17.1-37.0.107", + "1.17.1-37.0.108", + "1.17.1-37.0.109", + "1.17.1-37.0.110", + "1.17.1-37.0.111", + "1.17.1-37.0.112", + "1.17.1-37.0.113", + "1.17.1-37.0.114", + "1.17.1-37.0.116", + "1.17.1-37.0.117", + "1.17.1-37.0.118", + "1.17.1-37.0.119", + "1.17.1-37.0.120", + "1.17.1-37.0.121", + "1.17.1-37.0.122", + "1.17.1-37.0.123", + "1.17.1-37.0.125", + "1.17.1-37.0.126", + "1.17.1-37.0.127", + "1.17.1-37.1.0", + "1.17.1-37.1.1" + ], + "1.18": [ + "1.18-38.0.0", + "1.18-38.0.1", + "1.18-38.0.2", + "1.18-38.0.3", + "1.18-38.0.4", + "1.18-38.0.5", + "1.18-38.0.6", + "1.18-38.0.8", + "1.18-38.0.10", + "1.18-38.0.11", + "1.18-38.0.12", + "1.18-38.0.13", + "1.18-38.0.14", + "1.18-38.0.15", + "1.18-38.0.16", + "1.18-38.0.17" + ], + "1.18.1": [ + "1.18.1-39.0.0", + "1.18.1-39.0.1", + "1.18.1-39.0.2", + "1.18.1-39.0.3", + "1.18.1-39.0.4", + "1.18.1-39.0.5", + "1.18.1-39.0.6", + "1.18.1-39.0.7", + "1.18.1-39.0.8", + "1.18.1-39.0.9", + "1.18.1-39.0.10", + "1.18.1-39.0.11", + "1.18.1-39.0.12", + "1.18.1-39.0.13", + "1.18.1-39.0.14", + "1.18.1-39.0.15", + "1.18.1-39.0.16", + "1.18.1-39.0.17", + "1.18.1-39.0.18", + "1.18.1-39.0.19", + "1.18.1-39.0.20", + "1.18.1-39.0.22", + "1.18.1-39.0.36", + "1.18.1-39.0.37", + "1.18.1-39.0.38", + "1.18.1-39.0.40", + "1.18.1-39.0.43", + "1.18.1-39.0.44", + "1.18.1-39.0.45", + "1.18.1-39.0.46", + "1.18.1-39.0.47", + "1.18.1-39.0.48", + "1.18.1-39.0.49", + "1.18.1-39.0.50", + "1.18.1-39.0.51", + "1.18.1-39.0.52", + "1.18.1-39.0.53", + "1.18.1-39.0.54", + "1.18.1-39.0.55", + "1.18.1-39.0.56", + "1.18.1-39.0.57", + "1.18.1-39.0.58", + "1.18.1-39.0.59", + "1.18.1-39.0.60", + "1.18.1-39.0.61", + "1.18.1-39.0.62", + "1.18.1-39.0.63", + "1.18.1-39.0.64", + "1.18.1-39.0.65", + "1.18.1-39.0.66", + "1.18.1-39.0.67", + "1.18.1-39.0.68", + "1.18.1-39.0.69", + "1.18.1-39.0.70", + "1.18.1-39.0.71", + "1.18.1-39.0.72", + "1.18.1-39.0.73", + "1.18.1-39.0.74", + "1.18.1-39.0.75", + "1.18.1-39.0.76", + "1.18.1-39.0.77", + "1.18.1-39.0.78", + "1.18.1-39.0.79", + "1.18.1-39.0.80", + "1.18.1-39.0.81", + "1.18.1-39.0.82", + "1.18.1-39.0.83", + "1.18.1-39.0.84", + "1.18.1-39.0.85", + "1.18.1-39.0.86", + "1.18.1-39.0.87", + "1.18.1-39.0.88", + "1.18.1-39.0.89", + "1.18.1-39.0.90", + "1.18.1-39.1.0", + "1.18.1-39.1.1", + "1.18.1-39.1.2" + ], + "1.18.2": [ + "1.18.2-40.0.0", + "1.18.2-40.0.1", + "1.18.2-40.0.2", + "1.18.2-40.0.3", + "1.18.2-40.0.4", + "1.18.2-40.0.5", + "1.18.2-40.0.6", + "1.18.2-40.0.7", + "1.18.2-40.0.8", + "1.18.2-40.0.9", + "1.18.2-40.0.10", + "1.18.2-40.0.11", + "1.18.2-40.0.12", + "1.18.2-40.0.13", + "1.18.2-40.0.14", + "1.18.2-40.0.15", + "1.18.2-40.0.16", + "1.18.2-40.0.17", + "1.18.2-40.0.18", + "1.18.2-40.0.19", + "1.18.2-40.0.20", + "1.18.2-40.0.21", + "1.18.2-40.0.22", + "1.18.2-40.0.23", + "1.18.2-40.0.24", + "1.18.2-40.0.25", + "1.18.2-40.0.26", + "1.18.2-40.0.27", + "1.18.2-40.0.28", + "1.18.2-40.0.29", + "1.18.2-40.0.30", + "1.18.2-40.0.31", + "1.18.2-40.0.32", + "1.18.2-40.0.33", + "1.18.2-40.0.34", + "1.18.2-40.0.35", + "1.18.2-40.0.36", + "1.18.2-40.0.38", + "1.18.2-40.0.39", + "1.18.2-40.0.40", + "1.18.2-40.0.41", + "1.18.2-40.0.42", + "1.18.2-40.0.43", + "1.18.2-40.0.44", + "1.18.2-40.0.45", + "1.18.2-40.0.46", + "1.18.2-40.0.47", + "1.18.2-40.0.48", + "1.18.2-40.0.49", + "1.18.2-40.0.51", + "1.18.2-40.0.52", + "1.18.2-40.0.53", + "1.18.2-40.0.54", + "1.18.2-40.1.0", + "1.18.2-40.1.1", + "1.18.2-40.1.2", + "1.18.2-40.1.3", + "1.18.2-40.1.4", + "1.18.2-40.1.5", + "1.18.2-40.1.6", + "1.18.2-40.1.8", + "1.18.2-40.1.10", + "1.18.2-40.1.11", + "1.18.2-40.1.12", + "1.18.2-40.1.13", + "1.18.2-40.1.14", + "1.18.2-40.1.15", + "1.18.2-40.1.16", + "1.18.2-40.1.17", + "1.18.2-40.1.18", + "1.18.2-40.1.19", + "1.18.2-40.1.20", + "1.18.2-40.1.22", + "1.18.2-40.1.21", + "1.18.2-40.1.23", + "1.18.2-40.1.24", + "1.18.2-40.1.25", + "1.18.2-40.1.26", + "1.18.2-40.1.27", + "1.18.2-40.1.28", + "1.18.2-40.1.29", + "1.18.2-40.1.30", + "1.18.2-40.1.31", + "1.18.2-40.1.34", + "1.18.2-40.1.35", + "1.18.2-40.1.36", + "1.18.2-40.1.41", + "1.18.2-40.1.44", + "1.18.2-40.1.45", + "1.18.2-40.1.46", + "1.18.2-40.1.47", + "1.18.2-40.1.48", + "1.18.2-40.1.51", + "1.18.2-40.1.52", + "1.18.2-40.1.53", + "1.18.2-40.1.54", + "1.18.2-40.1.55", + "1.18.2-40.1.56", + "1.18.2-40.1.57", + "1.18.2-40.1.59", + "1.18.2-40.1.60", + "1.18.2-40.1.61", + "1.18.2-40.1.62", + "1.18.2-40.1.67", + "1.18.2-40.1.68", + "1.18.2-40.1.69", + "1.18.2-40.1.70", + "1.18.2-40.1.71", + "1.18.2-40.1.73", + "1.18.2-40.1.74", + "1.18.2-40.1.75", + "1.18.2-40.1.76", + "1.18.2-40.1.78", + "1.18.2-40.1.79", + "1.18.2-40.1.80", + "1.18.2-40.1.81", + "1.18.2-40.1.82", + "1.18.2-40.1.84", + "1.18.2-40.1.85", + "1.18.2-40.1.86", + "1.18.2-40.1.87", + "1.18.2-40.1.92", + "1.18.2-40.1.93", + "1.18.2-40.2.0", + "1.18.2-40.2.1", + "1.18.2-40.2.2", + "1.18.2-40.2.4", + "1.18.2-40.2.5", + "1.18.2-40.2.6", + "1.18.2-40.2.7", + "1.18.2-40.2.8", + "1.18.2-40.2.9", + "1.18.2-40.2.10", + "1.18.2-40.2.11", + "1.18.2-40.2.13", + "1.18.2-40.2.14", + "1.18.2-40.2.15", + "1.18.2-40.2.17", + "1.18.2-40.2.18", + "1.18.2-40.2.19", + "1.18.2-40.2.21", + "1.18.2-40.2.24", + "1.18.2-40.2.25", + "1.18.2-40.2.26", + "1.18.2-40.2.27", + "1.18.2-40.2.29", + "1.18.2-40.2.31", + "1.18.2-40.2.33", + "1.18.2-40.2.34", + "1.18.2-40.3.0", + "1.18.2-40.3.1", + "1.18.2-40.3.3", + "1.18.2-40.3.6", + "1.18.2-40.3.7", + "1.18.2-40.3.9", + "1.18.2-40.3.10", + "1.18.2-40.3.11" + ], + "1.19": [ + "1.19-41.0.1", + "1.19-41.0.2", + "1.19-41.0.3", + "1.19-41.0.4", + "1.19-41.0.5", + "1.19-41.0.7", + "1.19-41.0.9", + "1.19-41.0.11", + "1.19-41.0.12", + "1.19-41.0.13", + "1.19-41.0.14", + "1.19-41.0.15", + "1.19-41.0.16", + "1.19-41.0.17", + "1.19-41.0.18", + "1.19-41.0.19", + "1.19-41.0.20", + "1.19-41.0.21", + "1.19-41.0.22", + "1.19-41.0.24", + "1.19-41.0.26", + "1.19-41.0.25", + "1.19-41.0.27", + "1.19-41.0.28", + "1.19-41.0.29", + "1.19-41.0.30", + "1.19-41.0.31", + "1.19-41.0.32", + "1.19-41.0.33", + "1.19-41.0.34", + "1.19-41.0.35", + "1.19-41.0.36", + "1.19-41.0.37", + "1.19-41.0.38", + "1.19-41.0.42", + "1.19-41.0.43", + "1.19-41.0.44", + "1.19-41.0.45", + "1.19-41.0.46", + "1.19-41.0.47", + "1.19-41.0.48", + "1.19-41.0.49", + "1.19-41.0.50", + "1.19-41.0.51", + "1.19-41.0.54", + "1.19-41.0.56", + "1.19-41.0.57", + "1.19-41.0.60", + "1.19-41.0.61", + "1.19-41.0.62", + "1.19-41.0.63", + "1.19-41.0.64", + "1.19-41.0.67", + "1.19-41.0.68", + "1.19-41.0.69", + "1.19-41.0.70", + "1.19-41.0.71", + "1.19-41.0.76", + "1.19-41.0.77", + "1.19-41.0.78", + "1.19-41.0.79", + "1.19-41.0.80", + "1.19-41.0.81", + "1.19-41.0.82", + "1.19-41.0.83", + "1.19-41.0.84", + "1.19-41.0.85", + "1.19-41.0.86", + "1.19-41.0.87", + "1.19-41.0.88", + "1.19-41.0.91", + "1.19-41.0.92", + "1.19-41.0.93", + "1.19-41.0.94", + "1.19-41.0.96", + "1.19-41.0.98", + "1.19-41.0.99", + "1.19-41.0.100", + "1.19-41.0.103", + "1.19-41.0.104", + "1.19-41.0.105", + "1.19-41.0.106", + "1.19-41.0.107", + "1.19-41.0.108", + "1.19-41.0.109", + "1.19-41.0.110", + "1.19-41.0.111", + "1.19-41.0.112", + "1.19-41.0.113", + "1.19-41.1.0" + ], + "1.19.1": [ + "1.19.1-42.0.0", + "1.19.1-42.0.1", + "1.19.1-42.0.2", + "1.19.1-42.0.3", + "1.19.1-42.0.4", + "1.19.1-42.0.5", + "1.19.1-42.0.8", + "1.19.1-42.0.9" + ], + "1.19.2": [ + "1.19.2-43.0.0", + "1.19.2-43.0.1", + "1.19.2-43.0.2", + "1.19.2-43.0.3", + "1.19.2-43.0.4", + "1.19.2-43.0.5", + "1.19.2-43.0.7", + "1.19.2-43.0.8", + "1.19.2-43.0.10", + "1.19.2-43.0.11", + "1.19.2-43.0.12", + "1.19.2-43.0.13", + "1.19.2-43.0.15", + "1.19.2-43.0.16", + "1.19.2-43.0.17", + "1.19.2-43.0.18", + "1.19.2-43.0.19", + "1.19.2-43.0.20", + "1.19.2-43.0.21", + "1.19.2-43.0.22", + "1.19.2-43.1.0", + "1.19.2-43.1.1", + "1.19.2-43.1.2", + "1.19.2-43.1.3", + "1.19.2-43.1.4", + "1.19.2-43.1.7", + "1.19.2-43.1.10", + "1.19.2-43.1.12", + "1.19.2-43.1.13", + "1.19.2-43.1.14", + "1.19.2-43.1.15", + "1.19.2-43.1.16", + "1.19.2-43.1.17", + "1.19.2-43.1.23", + "1.19.2-43.1.24", + "1.19.2-43.1.25", + "1.19.2-43.1.26", + "1.19.2-43.1.27", + "1.19.2-43.1.28", + "1.19.2-43.1.29", + "1.19.2-43.1.30", + "1.19.2-43.1.32", + "1.19.2-43.1.33", + "1.19.2-43.1.34", + "1.19.2-43.1.35", + "1.19.2-43.1.36", + "1.19.2-43.1.37", + "1.19.2-43.1.38", + "1.19.2-43.1.39", + "1.19.2-43.1.40", + "1.19.2-43.1.41", + "1.19.2-43.1.42", + "1.19.2-43.1.43", + "1.19.2-43.1.47", + "1.19.2-43.1.52", + "1.19.2-43.1.53", + "1.19.2-43.1.55", + "1.19.2-43.1.57", + "1.19.2-43.1.58", + "1.19.2-43.1.59", + "1.19.2-43.1.60", + "1.19.2-43.1.64", + "1.19.2-43.1.65", + "1.19.2-43.2.0", + "1.19.2-43.1.63", + "1.19.2-43.2.1", + "1.19.2-43.2.2", + "1.19.2-43.2.3", + "1.19.2-43.2.4", + "1.19.2-43.2.5", + "1.19.2-43.2.6", + "1.19.2-43.2.7", + "1.19.2-43.2.8", + "1.19.2-43.2.9", + "1.19.2-43.2.10", + "1.19.2-43.2.11", + "1.19.2-43.2.12", + "1.19.2-43.2.13", + "1.19.2-43.2.14", + "1.19.2-43.2.17", + "1.19.2-43.2.18", + "1.19.2-43.2.19", + "1.19.2-43.2.20", + "1.19.2-43.2.21", + "1.19.2-43.2.22", + "1.19.2-43.2.23", + "1.19.2-43.3.0", + "1.19.2-43.3.2", + "1.19.2-43.3.5", + "1.19.2-43.3.6", + "1.19.2-43.3.7", + "1.19.2-43.3.8", + "1.19.2-43.3.9", + "1.19.2-43.3.10", + "1.19.2-43.3.12", + "1.19.2-43.3.13", + "1.19.2-43.3.14", + "1.19.2-43.4.0", + "1.19.2-43.4.1", + "1.19.2-43.4.2", + "1.19.2-43.4.4", + "1.19.2-43.4.5", + "1.19.2-43.4.6", + "1.19.2-43.4.7", + "1.19.2-43.4.9", + "1.19.2-43.4.10", + "1.19.2-43.4.12", + "1.19.2-43.4.13", + "1.19.2-43.4.16", + "1.19.2-43.4.19", + "1.19.2-43.4.20", + "1.19.2-43.4.22", + "1.19.2-43.4.23", + "1.19.2-43.5.0", + "1.19.2-43.5.1" + ], + "1.19.3": [ + "1.19.3-44.0.0", + "1.19.3-44.0.1", + "1.19.3-44.0.4", + "1.19.3-44.0.5", + "1.19.3-44.0.6", + "1.19.3-44.0.7", + "1.19.3-44.0.10", + "1.19.3-44.0.11", + "1.19.3-44.0.17", + "1.19.3-44.0.18", + "1.19.3-44.0.22", + "1.19.3-44.0.23", + "1.19.3-44.0.29", + "1.19.3-44.0.30", + "1.19.3-44.0.35", + "1.19.3-44.0.36", + "1.19.3-44.0.37", + "1.19.3-44.0.40", + "1.19.3-44.0.41", + "1.19.3-44.0.42", + "1.19.3-44.0.48", + "1.19.3-44.0.49", + "1.19.3-44.1.0", + "1.19.3-44.1.1", + "1.19.3-44.1.2", + "1.19.3-44.1.3", + "1.19.3-44.1.4", + "1.19.3-44.1.5", + "1.19.3-44.1.6", + "1.19.3-44.1.7", + "1.19.3-44.1.8", + "1.19.3-44.1.9", + "1.19.3-44.1.10", + "1.19.3-44.1.12", + "1.19.3-44.1.16", + "1.19.3-44.1.17", + "1.19.3-44.1.18", + "1.19.3-44.1.20", + "1.19.3-44.1.21", + "1.19.3-44.1.22", + "1.19.3-44.1.23" + ], + "1.19.4": [ + "1.19.4-45.0.0", + "1.19.4-45.0.1", + "1.19.4-45.0.4", + "1.19.4-45.0.6", + "1.19.4-45.0.8", + "1.19.4-45.0.9", + "1.19.4-45.0.10", + "1.19.4-45.0.11", + "1.19.4-45.0.12", + "1.19.4-45.0.13", + "1.19.4-45.0.18", + "1.19.4-45.0.19", + "1.19.4-45.0.20", + "1.19.4-45.0.22", + "1.19.4-45.0.23", + "1.19.4-45.0.24", + "1.19.4-45.0.25", + "1.19.4-45.0.26", + "1.19.4-45.0.27", + "1.19.4-45.0.29", + "1.19.4-45.0.30", + "1.19.4-45.0.31", + "1.19.4-45.0.35", + "1.19.4-45.0.36", + "1.19.4-45.0.37", + "1.19.4-45.0.38", + "1.19.4-45.0.39", + "1.19.4-45.0.40", + "1.19.4-45.0.41", + "1.19.4-45.0.42", + "1.19.4-45.0.43", + "1.19.4-45.0.44", + "1.19.4-45.0.45", + "1.19.4-45.0.46", + "1.19.4-45.0.47", + "1.19.4-45.0.49", + "1.19.4-45.0.50", + "1.19.4-45.0.52", + "1.19.4-45.0.53", + "1.19.4-45.0.55", + "1.19.4-45.0.57", + "1.19.4-45.0.58", + "1.19.4-45.0.59", + "1.19.4-45.0.63", + "1.19.4-45.0.64", + "1.19.4-45.0.66", + "1.19.4-45.1.0", + "1.19.4-45.1.2", + "1.19.4-45.1.4", + "1.19.4-45.1.5", + "1.19.4-45.1.6", + "1.19.4-45.1.7", + "1.19.4-45.1.8", + "1.19.4-45.1.9", + "1.19.4-45.1.10", + "1.19.4-45.1.11", + "1.19.4-45.1.12", + "1.19.4-45.1.13", + "1.19.4-45.1.14", + "1.19.4-45.1.15", + "1.19.4-45.1.16", + "1.19.4-45.1.17", + "1.19.4-45.1.18", + "1.19.4-45.1.19", + "1.19.4-45.2.0", + "1.19.4-45.2.2", + "1.19.4-45.2.3", + "1.19.4-45.2.4", + "1.19.4-45.2.6", + "1.19.4-45.2.7", + "1.19.4-45.2.8", + "1.19.4-45.2.9", + "1.19.4-45.2.10", + "1.19.4-45.2.12", + "1.19.4-45.2.14", + "1.19.4-45.2.15", + "1.19.4-45.2.16", + "1.19.4-45.3.0", + "1.19.4-45.3.1", + "1.19.4-45.3.2", + "1.19.4-45.3.3", + "1.19.4-45.3.6", + "1.19.4-45.3.7", + "1.19.4-45.3.8", + "1.19.4-45.3.9", + "1.19.4-45.3.10", + "1.19.4-45.3.12", + "1.19.4-45.3.13", + "1.19.4-45.3.15", + "1.19.4-45.3.17", + "1.19.4-45.3.20", + "1.19.4-45.3.23", + "1.19.4-45.3.24", + "1.19.4-45.3.27", + "1.19.4-45.4.0", + "1.19.4-45.4.1", + "1.19.4-45.4.2" + ], + "1.20": [ + "1.20-46.0.1", + "1.20-46.0.2", + "1.20-46.0.10", + "1.20-46.0.11", + "1.20-46.0.12", + "1.20-46.0.13", + "1.20-46.0.14" + ], + "1.20.1": [ + "1.20.1-47.0.0", + "1.20.1-47.0.1", + "1.20.1-47.0.2", + "1.20.1-47.0.3", + "1.20.1-47.0.4", + "1.20.1-47.0.5", + "1.20.1-47.0.6", + "1.20.1-47.0.14", + "1.20.1-47.0.15", + "1.20.1-47.0.16", + "1.20.1-47.0.17", + "1.20.1-47.0.18", + "1.20.1-47.0.19", + "1.20.1-47.0.34", + "1.20.1-47.0.35", + "1.20.1-47.0.39", + "1.20.1-47.0.42", + "1.20.1-47.0.43", + "1.20.1-47.0.44", + "1.20.1-47.0.45", + "1.20.1-47.0.46", + "1.20.1-47.0.49", + "1.20.1-47.0.50", + "1.20.1-47.1.0", + "1.20.1-47.1.1", + "1.20.1-47.1.3", + "1.20.1-47.1.4", + "1.20.1-47.1.5", + "1.20.1-47.1.6", + "1.20.1-47.1.7", + "1.20.1-47.1.8", + "1.20.1-47.1.10", + "1.20.1-47.1.12", + "1.20.1-47.1.13", + "1.20.1-47.1.14", + "1.20.1-47.1.15", + "1.20.1-47.1.16", + "1.20.1-47.1.17", + "1.20.1-47.1.18", + "1.20.1-47.1.19", + "1.20.1-47.1.21", + "1.20.1-47.1.22", + "1.20.1-47.1.23", + "1.20.1-47.1.25", + "1.20.1-47.1.26", + "1.20.1-47.1.27", + "1.20.1-47.1.28", + "1.20.1-47.1.29", + "1.20.1-47.1.30", + "1.20.1-47.1.32", + "1.20.1-47.1.33", + "1.20.1-47.1.36", + "1.20.1-47.1.37", + "1.20.1-47.1.39", + "1.20.1-47.1.40", + "1.20.1-47.1.41", + "1.20.1-47.1.42", + "1.20.1-47.1.43", + "1.20.1-47.1.44", + "1.20.1-47.1.46", + "1.20.1-47.1.47", + "1.20.1-47.2.0", + "1.20.1-47.2.1", + "1.20.1-47.2.4", + "1.20.1-47.2.5", + "1.20.1-47.2.6", + "1.20.1-47.2.7", + "1.20.1-47.2.8", + "1.20.1-47.2.14", + "1.20.1-47.2.16", + "1.20.1-47.2.17", + "1.20.1-47.2.18", + "1.20.1-47.2.19", + "1.20.1-47.2.20", + "1.20.1-47.2.21", + "1.20.1-47.2.23", + "1.20.1-47.2.29", + "1.20.1-47.2.30", + "1.20.1-47.2.31", + "1.20.1-47.2.32", + "1.20.1-47.2.36", + "1.20.1-47.3.0", + "1.20.1-47.3.1", + "1.20.1-47.3.2", + "1.20.1-47.3.3", + "1.20.1-47.3.4", + "1.20.1-47.3.5", + "1.20.1-47.3.6", + "1.20.1-47.3.7", + "1.20.1-47.3.10", + "1.20.1-47.3.11", + "1.20.1-47.3.12", + "1.20.1-47.3.13", + "1.20.1-47.3.14", + "1.20.1-47.3.15", + "1.20.1-47.3.16", + "1.20.1-47.3.19", + "1.20.1-47.3.20", + "1.20.1-47.3.22", + "1.20.1-47.3.24", + "1.20.1-47.3.25", + "1.20.1-47.3.27", + "1.20.1-47.3.28", + "1.20.1-47.3.29", + "1.20.1-47.3.31", + "1.20.1-47.3.32", + "1.20.1-47.3.33", + "1.20.1-47.3.34", + "1.20.1-47.3.37", + "1.20.1-47.3.38", + "1.20.1-47.3.39", + "1.20.1-47.4.0", + "1.20.1-47.4.1", + "1.20.1-47.4.2", + "1.20.1-47.4.3", + "1.20.1-47.4.4", + "1.20.1-47.4.5", + "1.20.1-47.4.6", + "1.20.1-47.4.8" + ], + "1.20.2": [ + "1.20.2-48.0.0", + "1.20.2-48.0.1", + "1.20.2-48.0.4", + "1.20.2-48.0.6", + "1.20.2-48.0.7", + "1.20.2-48.0.8", + "1.20.2-48.0.10", + "1.20.2-48.0.11", + "1.20.2-48.0.12", + "1.20.2-48.0.13", + "1.20.2-48.0.17", + "1.20.2-48.0.18", + "1.20.2-48.0.19", + "1.20.2-48.0.20", + "1.20.2-48.0.22", + "1.20.2-48.0.23", + "1.20.2-48.0.24", + "1.20.2-48.0.26", + "1.20.2-48.0.29", + "1.20.2-48.0.30", + "1.20.2-48.0.31", + "1.20.2-48.0.32", + "1.20.2-48.0.33", + "1.20.2-48.0.34", + "1.20.2-48.0.35", + "1.20.2-48.0.36", + "1.20.2-48.0.37", + "1.20.2-48.0.38", + "1.20.2-48.0.39", + "1.20.2-48.0.40", + "1.20.2-48.0.41", + "1.20.2-48.0.42", + "1.20.2-48.0.43", + "1.20.2-48.0.44", + "1.20.2-48.0.45", + "1.20.2-48.0.47", + "1.20.2-48.0.48", + "1.20.2-48.0.49", + "1.20.2-48.1.0" + ], + "1.20.3": [ + "1.20.3-49.0.1", + "1.20.3-49.0.2" + ], + "1.20.4": [ + "1.20.4-49.0.3", + "1.20.4-49.0.4", + "1.20.4-49.0.7", + "1.20.4-49.0.8", + "1.20.4-49.0.9", + "1.20.4-49.0.10", + "1.20.4-49.0.11", + "1.20.4-49.0.12", + "1.20.4-49.0.13", + "1.20.4-49.0.14", + "1.20.4-49.0.16", + "1.20.4-49.0.19", + "1.20.4-49.0.21", + "1.20.4-49.0.22", + "1.20.4-49.0.24", + "1.20.4-49.0.26", + "1.20.4-49.0.27", + "1.20.4-49.0.28", + "1.20.4-49.0.30", + "1.20.4-49.0.31", + "1.20.4-49.0.32", + "1.20.4-49.0.33", + "1.20.4-49.0.34", + "1.20.4-49.0.38", + "1.20.4-49.0.39", + "1.20.4-49.0.40", + "1.20.4-49.0.41", + "1.20.4-49.0.42", + "1.20.4-49.0.43", + "1.20.4-49.0.45", + "1.20.4-49.0.46", + "1.20.4-49.0.48", + "1.20.4-49.0.49", + "1.20.4-49.0.50", + "1.20.4-49.0.51", + "1.20.4-49.0.52", + "1.20.4-49.0.53", + "1.20.4-49.1.0", + "1.20.4-49.1.1", + "1.20.4-49.1.2", + "1.20.4-49.1.3", + "1.20.4-49.1.4", + "1.20.4-49.1.5", + "1.20.4-49.1.8", + "1.20.4-49.1.10", + "1.20.4-49.1.12", + "1.20.4-49.1.13", + "1.20.4-49.1.14", + "1.20.4-49.1.15", + "1.20.4-49.1.16", + "1.20.4-49.1.17", + "1.20.4-49.1.19", + "1.20.4-49.1.21", + "1.20.4-49.1.22", + "1.20.4-49.1.23", + "1.20.4-49.1.25", + "1.20.4-49.1.26", + "1.20.4-49.1.27", + "1.20.4-49.1.29", + "1.20.4-49.1.32", + "1.20.4-49.1.33", + "1.20.4-49.1.34", + "1.20.4-49.1.35", + "1.20.4-49.1.36", + "1.20.4-49.1.37", + "1.20.4-49.1.40", + "1.20.4-49.1.41", + "1.20.4-49.2.0", + "1.20.4-49.2.1" + ], + "1.20.6": [ + "1.20.6-50.0.0", + "1.20.6-50.0.1", + "1.20.6-50.0.2", + "1.20.6-50.0.4", + "1.20.6-50.0.5", + "1.20.6-50.0.6", + "1.20.6-50.0.7", + "1.20.6-50.0.8", + "1.20.6-50.0.9", + "1.20.6-50.0.10", + "1.20.6-50.0.11", + "1.20.6-50.0.12", + "1.20.6-50.0.13", + "1.20.6-50.0.14", + "1.20.6-50.0.15", + "1.20.6-50.0.16", + "1.20.6-50.0.17", + "1.20.6-50.0.18", + "1.20.6-50.0.19", + "1.20.6-50.0.20", + "1.20.6-50.0.21", + "1.20.6-50.0.22", + "1.20.6-50.0.24", + "1.20.6-50.0.25", + "1.20.6-50.0.26", + "1.20.6-50.0.28", + "1.20.6-50.0.29", + "1.20.6-50.0.30", + "1.20.6-50.0.31", + "1.20.6-50.0.34", + "1.20.6-50.0.36", + "1.20.6-50.0.37", + "1.20.6-50.1.0", + "1.20.6-50.1.1", + "1.20.6-50.1.2", + "1.20.6-50.1.3", + "1.20.6-50.1.4", + "1.20.6-50.1.5", + "1.20.6-50.1.6", + "1.20.6-50.1.7", + "1.20.6-50.1.8", + "1.20.6-50.1.9", + "1.20.6-50.1.10", + "1.20.6-50.1.12", + "1.20.6-50.1.13", + "1.20.6-50.1.14", + "1.20.6-50.1.15", + "1.20.6-50.1.16", + "1.20.6-50.1.17", + "1.20.6-50.1.18", + "1.20.6-50.1.19", + "1.20.6-50.1.20", + "1.20.6-50.1.21", + "1.20.6-50.1.22", + "1.20.6-50.1.23", + "1.20.6-50.1.24", + "1.20.6-50.1.25", + "1.20.6-50.1.26", + "1.20.6-50.1.27", + "1.20.6-50.1.29", + "1.20.6-50.1.30", + "1.20.6-50.1.31", + "1.20.6-50.1.32", + "1.20.6-50.1.34", + "1.20.6-50.1.35", + "1.20.6-50.1.37", + "1.20.6-50.1.39", + "1.20.6-50.1.42", + "1.20.6-50.1.43", + "1.20.6-50.1.45", + "1.20.6-50.1.46", + "1.20.6-50.1.47", + "1.20.6-50.1.48", + "1.20.6-50.1.51", + "1.20.6-50.2.0", + "1.20.6-50.2.1" + ], + "1.21": [ + "1.21-51.0.0", + "1.21-51.0.1", + "1.21-51.0.3", + "1.21-51.0.4", + "1.21-51.0.5", + "1.21-51.0.6", + "1.21-51.0.7", + "1.21-51.0.8", + "1.21-51.0.13", + "1.21-51.0.15", + "1.21-51.0.16", + "1.21-51.0.17", + "1.21-51.0.18", + "1.21-51.0.21", + "1.21-51.0.22", + "1.21-51.0.23", + "1.21-51.0.24", + "1.21-51.0.25", + "1.21-51.0.26", + "1.21-51.0.28", + "1.21-51.0.29", + "1.21-51.0.30", + "1.21-51.0.31", + "1.21-51.0.32", + "1.21-51.0.33" + ], + "1.21.1": [ + "1.21.1-52.0.0", + "1.21.1-52.0.1", + "1.21.1-52.0.2", + "1.21.1-52.0.3", + "1.21.1-52.0.4", + "1.21.1-52.0.5", + "1.21.1-52.0.6", + "1.21.1-52.0.7", + "1.21.1-52.0.8", + "1.21.1-52.0.9", + "1.21.1-52.0.10", + "1.21.1-52.0.11", + "1.21.1-52.0.12", + "1.21.1-52.0.13", + "1.21.1-52.0.15", + "1.21.1-52.0.16", + "1.21.1-52.0.17", + "1.21.1-52.0.18", + "1.21.1-52.0.19", + "1.21.1-52.0.20", + "1.21.1-52.0.21", + "1.21.1-52.0.22", + "1.21.1-52.0.23", + "1.21.1-52.0.24", + "1.21.1-52.0.25", + "1.21.1-52.0.26", + "1.21.1-52.0.27", + "1.21.1-52.0.28", + "1.21.1-52.0.29", + "1.21.1-52.0.30", + "1.21.1-52.0.32", + "1.21.1-52.0.33", + "1.21.1-52.0.34", + "1.21.1-52.0.35", + "1.21.1-52.0.36", + "1.21.1-52.0.37", + "1.21.1-52.0.38", + "1.21.1-52.0.39", + "1.21.1-52.0.40", + "1.21.1-52.0.42", + "1.21.1-52.0.45", + "1.21.1-52.0.47", + "1.21.1-52.0.48", + "1.21.1-52.0.49", + "1.21.1-52.0.50", + "1.21.1-52.0.51", + "1.21.1-52.0.52", + "1.21.1-52.0.53", + "1.21.1-52.0.56", + "1.21.1-52.0.57", + "1.21.1-52.1.0", + "1.21.1-52.1.1", + "1.21.1-52.1.2", + "1.21.1-52.1.3" + ], + "1.21.3": [ + "1.21.3-53.0.0", + "1.21.3-53.0.1", + "1.21.3-53.0.2", + "1.21.3-53.0.3", + "1.21.3-53.0.4", + "1.21.3-53.0.5", + "1.21.3-53.0.7", + "1.21.3-53.0.8", + "1.21.3-53.0.9", + "1.21.3-53.0.10", + "1.21.3-53.0.11", + "1.21.3-53.0.12", + "1.21.3-53.0.13", + "1.21.3-53.0.14", + "1.21.3-53.0.17", + "1.21.3-53.0.19", + "1.21.3-53.0.21", + "1.21.3-53.0.23", + "1.21.3-53.0.24", + "1.21.3-53.0.25", + "1.21.3-53.0.26", + "1.21.3-53.0.27", + "1.21.3-53.0.28", + "1.21.3-53.0.29", + "1.21.3-53.0.30", + "1.21.3-53.0.32", + "1.21.3-53.0.33", + "1.21.3-53.0.34", + "1.21.3-53.0.35", + "1.21.3-53.0.36", + "1.21.3-53.0.37", + "1.21.3-53.0.39", + "1.21.3-53.0.42", + "1.21.3-53.0.44", + "1.21.3-53.0.45", + "1.21.3-53.0.47", + "1.21.3-53.0.48", + "1.21.3-53.0.49", + "1.21.3-53.0.51", + "1.21.3-53.0.52", + "1.21.3-53.0.53", + "1.21.3-53.0.55", + "1.21.3-53.1.0", + "1.21.3-53.1.1", + "1.21.3-53.1.2" + ], + "1.21.4": [ + "1.21.4-54.0.0", + "1.21.4-54.0.1", + "1.21.4-54.0.3", + "1.21.4-54.0.5", + "1.21.4-54.0.6", + "1.21.4-54.0.7", + "1.21.4-54.0.9", + "1.21.4-54.0.10", + "1.21.4-54.0.11", + "1.21.4-54.0.12", + "1.21.4-54.0.13", + "1.21.4-54.0.14", + "1.21.4-54.0.15", + "1.21.4-54.0.16", + "1.21.4-54.0.17", + "1.21.4-54.0.18", + "1.21.4-54.0.20", + "1.21.4-54.0.21", + "1.21.4-54.0.24", + "1.21.4-54.0.25", + "1.21.4-54.0.26", + "1.21.4-54.0.27", + "1.21.4-54.0.29", + "1.21.4-54.0.30", + "1.21.4-54.0.31", + "1.21.4-54.0.32", + "1.21.4-54.0.33", + "1.21.4-54.0.34", + "1.21.4-54.0.35", + "1.21.4-54.0.36", + "1.21.4-54.0.37", + "1.21.4-54.0.38", + "1.21.4-54.1.0", + "1.21.4-54.1.1", + "1.21.4-54.1.2", + "1.21.4-54.1.3", + "1.21.4-54.1.4", + "1.21.4-54.1.5", + "1.21.4-54.1.6" + ], + "1.21.5": [ + "1.21.5-55.0.0", + "1.21.5-55.0.1", + "1.21.5-55.0.2", + "1.21.5-55.0.3", + "1.21.5-55.0.4", + "1.21.5-55.0.5", + "1.21.5-55.0.6", + "1.21.5-55.0.7", + "1.21.5-55.0.8", + "1.21.5-55.0.9", + "1.21.5-55.0.10", + "1.21.5-55.0.11", + "1.21.5-55.0.12", + "1.21.5-55.0.14", + "1.21.5-55.0.15", + "1.21.5-55.0.16", + "1.21.5-55.0.17", + "1.21.5-55.0.19", + "1.21.5-55.0.20", + "1.21.5-55.0.21", + "1.21.5-55.0.22", + "1.21.5-55.0.23", + "1.21.5-55.0.24", + "1.21.5-55.0.25", + "1.21.5-55.1.0" + ], + "1.21.6": [ + "1.21.6-56.0.0", + "1.21.6-56.0.1", + "1.21.6-56.0.2", + "1.21.6-56.0.3", + "1.21.6-56.0.4", + "1.21.6-56.0.5", + "1.21.6-56.0.6", + "1.21.6-56.0.7", + "1.21.6-56.0.8", + "1.21.6-56.0.9" + ], + "1.21.7": [ + "1.21.7-57.0.0", + "1.21.7-57.0.1", + "1.21.7-57.0.2", + "1.21.7-57.0.3" + ], + "1.21.8": [ + "1.21.8-58.0.0", + "1.21.8-58.0.1", + "1.21.8-58.0.2", + "1.21.8-58.0.3", + "1.21.8-58.0.4", + "1.21.8-58.0.5", + "1.21.8-58.0.6", + "1.21.8-58.0.7", + "1.21.8-58.0.8", + "1.21.8-58.0.9", + "1.21.8-58.0.10", + "1.21.8-58.1.0", + "1.21.8-58.1.1", + "1.21.8-58.1.2" + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d15a3d6a..634ac74d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.5", + "version": "4.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.5", + "version": "4.1.6", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 5daaf7e4..649c85ff 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.5", + "version": "4.1.6", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Minecraft-Loader/loader/forge/forge.ts b/src/Minecraft-Loader/loader/forge/forge.ts index acdc2bc5..61f56b91 100644 --- a/src/Minecraft-Loader/loader/forge/forge.ts +++ b/src/Minecraft-Loader/loader/forge/forge.ts @@ -118,9 +118,16 @@ export default class ForgeMC extends EventEmitter { */ public async downloadInstaller(Loader: any): Promise { // Fetch metadata for the given Forge version - let metaDataList: string[] = await fetch(Loader.metaData) - .then(res => res.json()) - .then(json => json[this.options.loader.version]); + let metaDataList: any = await fetch(Loader.metaData); + + if (!metaDataList.ok) { + metaDataList = fs.readFileSync(path.resolve(__dirname, '../../../../assets/forge', 'forge-metadata.json'), 'utf-8'); + metaDataList = JSON.parse(metaDataList); + } else { + metaDataList = await metaDataList.json(); + } + + metaDataList = metaDataList[this.options.loader.version]; if (!metaDataList) { return { error: `Forge ${this.options.loader.version} not supported` }; diff --git a/test/index.js b/test/index.js index 080146f3..3dc8191b 100755 --- a/test/index.js +++ b/test/index.js @@ -21,7 +21,7 @@ let mc } await launcher.Launch({ - url: "http://launcher.luuxis.fr/files?instance=Hypixel", + url: "https://luuxcraft.fr/api/user/69414f32-4018-4eca-948b-109c46cd119c/instance", path: './minecraft', authenticator: mc, version: '1.8.9', From 89ee4006faf9926b73e72a9d57cbb4ced4ce90f3 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:37:48 +0200 Subject: [PATCH 170/185] Include refresh_token in error responses Adds the OAuth2 refresh_token to all error responses in the Microsoft authentication flow, ensuring clients can access the token for recovery or retry scenarios. Also attaches hasGame to the final AuthResponse object. --- src/Authenticator/Microsoft.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 99a9fcb4..2d1a5e11 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -213,7 +213,7 @@ export default class Microsoft { }) }); if (xblResponse.error) { - return { ...xblResponse, errorType: 'xbl' }; + return { ...xblResponse, errorType: 'xbl', refresh_token: oauth2.refresh_token }; } // 2. Authorize with XSTS for Minecraft services @@ -230,7 +230,7 @@ export default class Microsoft { }) }); if (xstsResponse.error) { - return { ...xstsResponse, errorType: 'xsts' }; + return { ...xstsResponse, errorType: 'xsts', refresh_token: oauth2.refresh_token }; } // 3. Authorize for the standard Xbox Live realm (useful for xuid/gamertag) @@ -247,7 +247,7 @@ export default class Microsoft { }) }); if (xboxAccount.error) { - return { ...xboxAccount, errorType: 'xboxAccount' }; + return { ...xboxAccount, errorType: 'xboxAccount', refresh_token: oauth2.refresh_token }; } // 4. Get a launcher token from Minecraft services @@ -260,7 +260,7 @@ export default class Microsoft { }) }); if (launchResponse.error) { - return { ...launchResponse, errorType: 'launch' }; + return { ...launchResponse, errorType: 'launch', refresh_token: oauth2.refresh_token }; } // 5. Login with Xbox token to get a Minecraft token @@ -272,7 +272,7 @@ export default class Microsoft { }) }); if (mcLogin.error) { - return { ...mcLogin, errorType: 'mcLogin' }; + return { ...mcLogin, errorType: 'mcLogin', refresh_token: oauth2.refresh_token }; } // 6. Check if the account has purchased Minecraft @@ -285,14 +285,15 @@ export default class Microsoft { if (!hasGame.items?.find((i: any) => i.name === 'product_minecraft' || i.name === 'game_minecraft')) { return { error: "You don't own the game", - errorType: 'game' + errorType: 'game', + refresh_token: oauth2.refresh_token }; } // 7. Fetch the user profile (skins, capes, etc.) const profile = await this.getProfile(mcLogin); if ('error' in profile) { - return { ...profile, errorType: 'profile' }; + return { ...profile, errorType: 'profile', refresh_token: oauth2.refresh_token }; } // Build and return the final AuthResponse object @@ -316,7 +317,8 @@ export default class Microsoft { profile: { skins: profile.skins, capes: profile.capes - } + }, + hasGame: hasGame }; } From e4a5d8b9f5921fc8c7d9734843c4fc1a514b0322 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:38:23 +0200 Subject: [PATCH 171/185] Remove unused hasGame property from profile object The hasGame property was removed from the returned profile object in the Microsoft authenticator. This streamlines the response and eliminates an unused field. --- src/Authenticator/Microsoft.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 2d1a5e11..865c5303 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -317,8 +317,7 @@ export default class Microsoft { profile: { skins: profile.skins, capes: profile.capes - }, - hasGame: hasGame + } }; } From 9d44b20932b359f61bf42ca51a1c40ff9718190f Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:25:24 +0200 Subject: [PATCH 172/185] Refactor Microsoft authentication flow Replaces the fetchJSON helper with direct fetch calls and streamlines the authentication process for Xbox Live, XSTS, and Minecraft services. Improves error handling, updates token and profile retrieval logic, and uses crypto.randomUUID for client_token generation. Cleans up redundant code and enhances clarity in the authentication sequence. --- src/Authenticator/Microsoft.ts | 162 +++++++++++++-------------------- 1 file changed, 62 insertions(+), 100 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 865c5303..5a0b4ff9 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -60,9 +60,7 @@ export interface AuthResponse { // Utility function to fetch and convert an image to base64 async function getBase64(url: string): Promise { const response = await fetch(url); - const arrayBuffer = await response.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - return buffer.toString('base64'); + return Buffer.from(await response.bytes()).toString('base64'); } export default class Microsoft { @@ -198,136 +196,116 @@ export default class Microsoft { * @returns A fully populated AuthResponse object or an error. */ private async getAccount(oauth2: any): Promise { - // 1. Authenticate with Xbox Live - const xblResponse = await this.fetchJSON('https://user.auth.xboxlive.com/user/authenticate', { + const authenticateResponse = await fetch('https://user.auth.xboxlive.com/user/authenticate', { method: 'POST', - headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ Properties: { AuthMethod: 'RPS', SiteName: 'user.auth.xboxlive.com', - RpsTicket: `d=${oauth2.access_token}` + RpsTicket: `d=${oauth2.access_token}`, }, RelyingParty: 'http://auth.xboxlive.com', - TokenType: 'JWT' - }) + TokenType: 'JWT', + }), }); - if (xblResponse.error) { - return { ...xblResponse, errorType: 'xbl', refresh_token: oauth2.refresh_token }; + const xbl = await authenticateResponse.json(); + if (xbl.error) { + return { error: xbl.error, errorType: 'xbl', ...xbl, refresh_token: oauth2.refresh_token }; } - // 2. Authorize with XSTS for Minecraft services - const xstsResponse = await this.fetchJSON('https://xsts.auth.xboxlive.com/xsts/authorize', { + const authorizeResponse = await fetch('https://xsts.auth.xboxlive.com/xsts/authorize', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ Properties: { SandboxId: 'RETAIL', - UserTokens: [xblResponse.Token] + UserTokens: [xbl.Token], }, RelyingParty: 'rp://api.minecraftservices.com/', - TokenType: 'JWT' - }) + TokenType: 'JWT', + }), }); - if (xstsResponse.error) { - return { ...xstsResponse, errorType: 'xsts', refresh_token: oauth2.refresh_token }; + const xsts = await authorizeResponse.json(); + if (xsts.error) { + return { error: xsts.error, errorType: 'xsts', ...xsts, refresh_token: oauth2.refresh_token }; } - // 3. Authorize for the standard Xbox Live realm (useful for xuid/gamertag) - const xboxAccount = await this.fetchJSON('https://xsts.auth.xboxlive.com/xsts/authorize', { + const mcLoginResponse = await fetch('https://api.minecraftservices.com/authentication/login_with_xbox', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ - Properties: { - SandboxId: 'RETAIL', - UserTokens: [xblResponse.Token] - }, - RelyingParty: 'http://xboxlive.com', - TokenType: 'JWT' - }) + identityToken: `XBL3.0 x=${xbl.DisplayClaims.xui[0].uhs};${xsts.Token}` + }), }); - if (xboxAccount.error) { - return { ...xboxAccount, errorType: 'xboxAccount', refresh_token: oauth2.refresh_token }; + const mcLogin = await mcLoginResponse.json(); + if (mcLogin.error) { + return { error: mcLogin.error, errorType: 'mcLogin', ...mcLogin, refresh_token: oauth2.refresh_token }; } - - // 4. Get a launcher token from Minecraft services - const launchResponse = await this.fetchJSON('https://api.minecraftservices.com/launcher/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - xtoken: `XBL3.0 x=${xblResponse.DisplayClaims.xui[0].uhs};${xstsResponse.Token}`, - platform: 'PC_LAUNCHER' - }) - }); - if (launchResponse.error) { - return { ...launchResponse, errorType: 'launch', refresh_token: oauth2.refresh_token }; + if (!mcLogin.username) { + return { error: 'NO_MINECRAFT_ACCOUNT', errorType: 'mcLogin', ...mcLogin, refresh_token: oauth2.refresh_token }; } - // 5. Login with Xbox token to get a Minecraft token - const mcLogin = await this.fetchJSON('https://api.minecraftservices.com/authentication/login_with_xbox', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - identityToken: `XBL3.0 x=${xblResponse.DisplayClaims.xui[0].uhs};${xstsResponse.Token}` - }) + const mcstoreResponse = await fetch('https://api.minecraftservices.com/entitlements/mcstore', { + method: 'GET', + headers: { 'Authorization': `Bearer ${mcLogin.access_token}` }, }); - if (mcLogin.error) { - return { ...mcLogin, errorType: 'mcLogin', refresh_token: oauth2.refresh_token }; + const mcstore = await mcstoreResponse.json(); + if (mcstore.error) { + return { error: mcstore.error, errorType: 'mcStore', ...mcstore, refresh_token: oauth2.refresh_token }; } - // 6. Check if the account has purchased Minecraft - const hasGame = await this.fetchJSON('https://api.minecraftservices.com/entitlements/mcstore', { - method: 'GET', - headers: { - Authorization: `Bearer ${mcLogin.access_token}` - } - }); - if (!hasGame.items?.find((i: any) => i.name === 'product_minecraft' || i.name === 'game_minecraft')) { - return { - error: "You don't own the game", - errorType: 'game', - refresh_token: oauth2.refresh_token - }; + if (!mcstore.items.some((item: { name: string }) => item.name === "game_minecraft" || item.name === "product_minecraft")) { + return { error: 'NO_MINECRAFT_ENTITLEMENTS', errorType: 'mcStore', ...mcstore, refresh_token: oauth2.refresh_token }; } - // 7. Fetch the user profile (skins, capes, etc.) + const profile = await this.getProfile(mcLogin); if ('error' in profile) { - return { ...profile, errorType: 'profile', refresh_token: oauth2.refresh_token }; + return { error: profile.error, errorType: 'mcProfile', ...profile, refresh_token: oauth2.refresh_token }; + } + + const xboxAccountResponse = await fetch('https://xsts.auth.xboxlive.com/xsts/authorize', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + Properties: { + SandboxId: 'RETAIL', + UserTokens: [xbl.Token] + }, + RelyingParty: 'http://xboxlive.com', + TokenType: 'JWT' + }) + }); + const xboxAccount = await xboxAccountResponse.json(); + if (xboxAccount.error) { + return { error: xboxAccount.error, errorType: 'xboxAccount', ...xboxAccount, refresh_token: oauth2.refresh_token }; } - // Build and return the final AuthResponse object return { access_token: mcLogin.access_token, - client_token: crypto.randomBytes(16).toString('hex'), + client_token: crypto.randomUUID(), uuid: profile.id, name: profile.name, refresh_token: oauth2.refresh_token, - user_properties: '{}', + user_properties: "{}", meta: { type: 'Xbox', - access_token_expires_in: mcLogin.expires_in + Math.floor(Date.now() / 1000), - demo: false // If there's an error retrieving the profile, you can set this to true + access_token_expires_in: Date.now() + (oauth2.expires_in * 1000), + demo: false }, xboxAccount: { xuid: xboxAccount.DisplayClaims.xui[0].xid, - gamertag: xboxAccount.DisplayClaims.xui[0].gtg, - ageGroup: xboxAccount.DisplayClaims.xui[0].agg + gamertag: xboxAccount.DisplayClaims.xui[0].gt, + ageGroup: xboxAccount.DisplayClaims.xui[0].ag }, profile: { - skins: profile.skins, - capes: profile.capes + skins: [...profile.skins], + capes: [...profile.capes] } }; } - /** - * Fetches the Minecraft profile (including skins and capes) for a given access token, - * then converts each skin/cape URL to base64. - * - * @param mcLogin An object containing `access_token` to call the Minecraft profile API. - * @returns The user's Minecraft profile or an error object. - */ public async getProfile(mcLogin: { access_token: string }): Promise { try { const response = await fetch('https://api.minecraftservices.com/minecraft/profile', { @@ -342,7 +320,6 @@ export default class Microsoft { return { error: profile.error }; } - // Convert each skin and cape to base64 if (Array.isArray(profile.skins)) { for (const skin of profile.skins) { if (skin.url) { @@ -368,19 +345,4 @@ export default class Microsoft { return { error: err.message }; } } - - /** - * A helper method to perform fetch and parse JSON. - * @param url The endpoint URL. - * @param options fetch options (method, headers, body, etc.). - * @returns The parsed JSON or an object with an error field if something goes wrong. - */ - private async fetchJSON(url: string, options: Record): Promise { - try { - const response = await fetch(url, options); - return response.json(); - } catch (err: any) { - return { error: err.message }; - } - } -} +} \ No newline at end of file From 1314abf4164097ded2cfd9e6efc6ea29ed3cbb37 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:26:12 +0200 Subject: [PATCH 173/185] Bump version to 4.1.7 Update package.json and package-lock.json to reflect new version 4.1.7. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 634ac74d..52ebad06 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.6", + "version": "4.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.6", + "version": "4.1.7", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 649c85ff..5f16ff7e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.6", + "version": "4.1.7", "types": "./build/Index.d.ts", "exports": { ".": { From 60e0f4c5e11749cd3796090b2394480041d25991 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:18:07 +0200 Subject: [PATCH 174/185] Refactor test script error handling and event listeners Replaces account refresh error handling with process exit on error and refactors launcher event listeners to use separate statements for improved readability. --- test/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/index.js b/test/index.js index 3dc8191b..b14f52e9 100755 --- a/test/index.js +++ b/test/index.js @@ -15,12 +15,12 @@ let mc fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); } else { mc = await new Microsoft().refresh(mc); - if (mc.error) mc = await new Microsoft().getAuth(); fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); + if (mc.error) process.exit(1); } } - await launcher.Launch({ + const opt = { url: "https://luuxcraft.fr/api/user/69414f32-4018-4eca-948b-109c46cd119c/instance", path: './minecraft', authenticator: mc, @@ -45,12 +45,11 @@ let mc min: '14G', max: '16G' }, - }); + }; - launcher - .on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)) - .on('patch', pacth => process.stdout.write(pacth)) - .on('data', line => process.stdout.write(line)) - .on('error', err => console.error(err)) - .on('close', () => console.log('Game exited.')); + launcher.Launch(opt); + launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)); + launcher.on('patch', pacth => process.stdout.write(pacth)); + launcher.on('data', line => process.stdout.write(line)); + launcher.on('error', err => console.error(err)); })(); From 80d8c53a5cdbb98dc1dbf3c907b02b8ef1360a58 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:32:20 +0200 Subject: [PATCH 175/185] Update README example with improved authentication Replaces the previous ESM example with a CommonJS example that demonstrates persistent Microsoft authentication using a local account.json file, token refresh handling, and updated launcher options. The new example provides a more robust and production-ready approach for managing user authentication and launching Minecraft instances. --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 91a2e2d8..49214c88 100755 --- a/README.md +++ b/README.md @@ -31,28 +31,61 @@ yarn add minecraft-java-core ### Standard Example (ESM) ```ts -import { Launch, Microsoft } from 'minecraft-java-core'; - -// ⚠️ In production, perform auth **before** initialising the launcher -// so you can handle refresh / error flows cleanly. -const auth = await Microsoft.auth({ - client_id: '00000000-0000-0000-0000-000000000000', - type: 'terminal' // 'electron' | 'nwjs' -}); - +const { Launch, Microsoft } = require('minecraft-java-core'); const launcher = new Launch(); -launcher.on('progress', p => console.log(`[DL] ${p}%`)) - .on('data', line => process.stdout.write(line)) - .on('close', () => console.log('Game exited.')); - -await launcher.launch({ - root: './minecraft', - authenticator: auth, - version: '1.20.4', - loader: { type: 'fabric', build: '0.15.9' }, - memory: { min: '2G', max: '4G' } -}); +const fs = require('fs'); +let mc + +(async () => { + if (!fs.existsSync('./account.json')) { + mc = await new Microsoft().getAuth(); + fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); + } else { + mc = JSON.parse(fs.readFileSync('./account.json')); + if (!mc.refresh_token) { + mc = await new Microsoft().getAuth(); + fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); + } else { + mc = await new Microsoft().refresh(mc); + fs.writeFileSync('./account.json', JSON.stringify(mc, null, 4)); + if (mc.error) process.exit(1); + } + } + + const opt = { + url: "https://luuxcraft.fr/api/user/69414f32-4018-4eca-948b-109c46cd119c/instance", + path: './minecraft', + authenticator: mc, + version: '1.8.9', + intelEnabledMac: true, + instance: "Hypixel", + + ignored: [ + "config", + "logs", + "resourcepacks", + "options.txt", + "optionsof.txt" + ], + + loader: { + type: 'forge', + build: 'latest', + enable: true + }, + memory: { + min: '14G', + max: '16G' + }, + }; + + launcher.Launch(opt); + launcher.on('progress', (progress, size) => console.log(`[DL] ${((progress / size) * 100).toFixed(2)}%`)); + launcher.on('patch', pacth => process.stdout.write(pacth)); + launcher.on('data', line => process.stdout.write(line)); + launcher.on('error', err => console.error(err)); +})(); ``` --- From 49f55a7a906141358b38eb7d377d4f0710c9993c Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:03:58 +0200 Subject: [PATCH 176/185] Update version to 4.2.0 and improve image fetch Bumped package version to 4.2.0 in package.json and package-lock.json. Improved getBase64 utility in Microsoft.ts to handle fetch errors and use arrayBuffer for base64 conversion. --- package-lock.json | 4 ++-- package.json | 2 +- src/Authenticator/Microsoft.ts | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52ebad06..2d89fd1b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.1.7", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.1.7", + "version": "4.2.0", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 5f16ff7e..7243e8d6 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.1.7", + "version": "4.2.0", "types": "./build/Index.d.ts", "exports": { ".": { diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 5a0b4ff9..b95fc533 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -60,7 +60,13 @@ export interface AuthResponse { // Utility function to fetch and convert an image to base64 async function getBase64(url: string): Promise { const response = await fetch(url); - return Buffer.from(await response.bytes()).toString('base64'); + if (response.ok) { + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + return buffer.toString('base64'); + } else { + return ''; + } } export default class Microsoft { From 6d1e8c9981911927d52eee37836e89e749de65f1 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:53:22 +0200 Subject: [PATCH 177/185] Fix token expiry and Xbox account property mapping Corrects timestamp calculation for token expiry checks and updates property names for Xbox account mapping. Also ensures the correct expires_in value is used for access token expiration. --- src/Authenticator/Microsoft.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index b95fc533..4ce6e6a1 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -161,7 +161,7 @@ export default class Microsoft { * @returns Updated AuthResponse (with new token if needed) or an error object. */ public async refresh(acc: AuthResponse | any): Promise { - const timeStamp = Math.floor(Date.now() / 1000); + const timeStamp = Math.floor(Date.now()); // If the token is still valid for at least 2 more hours, just re-fetch the profile if (timeStamp < (acc?.meta?.access_token_expires_in - 7200)) { @@ -297,18 +297,19 @@ export default class Microsoft { user_properties: "{}", meta: { type: 'Xbox', - access_token_expires_in: Date.now() + (oauth2.expires_in * 1000), + access_token_expires_in: Date.now() + (mcLogin.expires_in * 1000), demo: false }, xboxAccount: { xuid: xboxAccount.DisplayClaims.xui[0].xid, - gamertag: xboxAccount.DisplayClaims.xui[0].gt, - ageGroup: xboxAccount.DisplayClaims.xui[0].ag + gamertag: xboxAccount.DisplayClaims.xui[0].gtg, + ageGroup: xboxAccount.DisplayClaims.xui[0].agg }, profile: { skins: [...profile.skins], capes: [...profile.capes] - } + }, + oauth2 }; } From f6f55618c9da966d55a09bd421d55a9bfad8a499 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:53:31 +0200 Subject: [PATCH 178/185] Bump version to 4.2.1 Update package.json and package-lock.json to reflect new version 4.2.1. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d89fd1b..ff03ae35 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.2.0", + "version": "4.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.2.0", + "version": "4.2.1", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 7243e8d6..207db4bd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.2.0", + "version": "4.2.1", "types": "./build/Index.d.ts", "exports": { ".": { From 7a6cc6d6e91d1996c4cf26d867517cbf49d4f2fa Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:53:41 +0200 Subject: [PATCH 179/185] Update API URL Changed the user instance API URL in both README.md and test/index.js to use a new UUID. --- README.md | 2 +- test/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 49214c88..f10f0f13 100755 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ let mc } const opt = { - url: "https://luuxcraft.fr/api/user/69414f32-4018-4eca-948b-109c46cd119c/instance", + url: "https://luuxcraft.fr/api/user/48c74227-13d1-48d6-931b-0f12b73da340/instance", path: './minecraft', authenticator: mc, version: '1.8.9', diff --git a/test/index.js b/test/index.js index b14f52e9..ab4f47cc 100755 --- a/test/index.js +++ b/test/index.js @@ -21,7 +21,7 @@ let mc } const opt = { - url: "https://luuxcraft.fr/api/user/69414f32-4018-4eca-948b-109c46cd119c/instance", + url: "https://luuxcraft.fr/api/user/48c74227-13d1-48d6-931b-0f12b73da340/instance", path: './minecraft', authenticator: mc, version: '1.8.9', From 41a1b4c1fcffb0cc661d174fc102e3e2fe0c547a Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:31:40 +0200 Subject: [PATCH 180/185] Set default Java type if version is specified Adds a check to set 'java.type' to 'jre' if 'java.version' is present and 'java.type' is not a string. Ensures proper initialization of Java options before starting. --- src/Launch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Launch.ts b/src/Launch.ts index 496e33ff..d1663f31 100755 --- a/src/Launch.ts +++ b/src/Launch.ts @@ -264,6 +264,7 @@ export default class Launch extends EventEmitter { if (this.options.downloadFileMultiple < 1) this.options.downloadFileMultiple = 1 if (this.options.downloadFileMultiple > 30) this.options.downloadFileMultiple = 30 if (typeof this.options.loader.path !== 'string') this.options.loader.path = `./loader/${this.options.loader.type}`; + if (this.options.java.version && typeof this.options.java.type !== 'string') this.options.java.type = 'jre'; this.start(); } From e868609e3cc34504888ccf1723c9d247cab5cdef Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:42:20 +0200 Subject: [PATCH 181/185] Remove unused oauth2 property from profile object The oauth2 property was removed from the returned profile object in the Microsoft authenticator. This cleans up the response structure and eliminates unnecessary data. --- src/Authenticator/Microsoft.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 4ce6e6a1..6738692b 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -308,8 +308,7 @@ export default class Microsoft { profile: { skins: [...profile.skins], capes: [...profile.capes] - }, - oauth2 + } }; } From 1b22bcfe46ff806c347a5e8df7c0dd2666656c03 Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:42:47 +0200 Subject: [PATCH 182/185] Bump version to 4.2.2 Update package.json and package-lock.json to reflect new version 4.2.2. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff03ae35..069ab966 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.2.1", + "version": "4.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.2.1", + "version": "4.2.2", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 207db4bd..5930270c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.2.1", + "version": "4.2.2", "types": "./build/Index.d.ts", "exports": { ".": { From 883e8c3ac7eeaf97c7608e54e1c606f9a24586dc Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:36:06 +0200 Subject: [PATCH 183/185] Fix version prefix formatting in NeoForge loader Adds a trailing dot to the shortVersion string when filtering versions, ensuring correct matching of version prefixes in the new API approach. --- src/Minecraft-Loader/loader/neoForge/neoForge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Minecraft-Loader/loader/neoForge/neoForge.ts b/src/Minecraft-Loader/loader/neoForge/neoForge.ts index 4265c184..6baf73f0 100644 --- a/src/Minecraft-Loader/loader/neoForge/neoForge.ts +++ b/src/Minecraft-Loader/loader/neoForge/neoForge.ts @@ -112,7 +112,7 @@ export default class NeoForgeMC extends EventEmitter { // 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}`; + const shortVersion = `${splitted[1]}.${splitted[2] || 0}.`; versions = metaData.versions.filter((v: string) => v.startsWith(shortVersion)); oldAPI = false; } From 9f65746bfdabd120fe62209718c2f7537f6fb09d Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:36:58 +0200 Subject: [PATCH 184/185] Bump version to 4.2.3 Update package.json and package-lock.json to reflect new version 4.2.3. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 069ab966..928d4338 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "minecraft-java-core", - "version": "4.2.2", + "version": "4.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minecraft-java-core", - "version": "4.2.2", + "version": "4.2.3", "license": "CCANC", "dependencies": { "prompt": "^1.3.0", diff --git a/package.json b/package.json index 5930270c..56072a91 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-java-core", - "version": "4.2.2", + "version": "4.2.3", "types": "./build/Index.d.ts", "exports": { ".": { From 159fe8fcfd083ce8961846f3b70b3bbde60091be Mon Sep 17 00:00:00 2001 From: Luuxis <61792917+luuxis@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:59:00 +0100 Subject: [PATCH 185/185] Add configurable redirect_uri for Microsoft OAuth Refactored Microsoft authentication to allow specifying a custom redirect_uri, improving flexibility for different OAuth flows. Updated Electron and NW.js GUI modules to accept and use the redirect_uri parameter. Removed the default hardcoded redirect_uri from multiple locations and centralized its configuration in the Microsoft class constructor. Also deleted the .vscode/settings.json file. --- src/Authenticator/GUI/Electron.ts | 4 ++-- src/Authenticator/GUI/NW.ts | 4 ++-- src/Authenticator/Microsoft.ts | 26 ++++++++++---------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Authenticator/GUI/Electron.ts b/src/Authenticator/GUI/Electron.ts index a0f6f628..7196a921 100755 --- a/src/Authenticator/GUI/Electron.ts +++ b/src/Authenticator/GUI/Electron.ts @@ -14,7 +14,7 @@ const defaultProperties = { icon: path.join(__dirname, '../../../assets/icons', `Microsoft.${(process.platform === 'win32') ? 'ico' : 'png'}`), } -module.exports = async function (url: string) { +module.exports = async function (url: string, redirect_uri: string = "https://login.live.com/oauth20_desktop.srf") { await new Promise((resolve: any) => { app.whenReady().then(() => { session.defaultSession.cookies.get({ domain: 'live.com' }).then((cookies: any) => { @@ -40,7 +40,7 @@ module.exports = async function (url: string) { mainWindow.webContents.on("did-finish-load", () => { const loc = mainWindow.webContents.getURL(); - if (loc.startsWith("https://login.live.com/oauth20_desktop.srf")) { + if (loc.startsWith(redirect_uri)) { const urlParams = new URLSearchParams(loc.substr(loc.indexOf("?") + 1)).get("code"); if (urlParams) { resolve(urlParams); diff --git a/src/Authenticator/GUI/NW.ts b/src/Authenticator/GUI/NW.ts index aac345c1..1fe3f8d7 100755 --- a/src/Authenticator/GUI/NW.ts +++ b/src/Authenticator/GUI/NW.ts @@ -14,7 +14,7 @@ const defaultProperties = { icon: path.join(__dirname, '../../../assets/icons/Microsoft.png') } -module.exports = async function (url: string) { +module.exports = async function (url: string, redirect_uri: string = "https://login.live.com/oauth20_desktop.srf") { await new Promise((resolve: any) => { //@ts-ignore nw.Window.get().cookies.getAll({ domain: "live.com" }, async (cookies) => { @@ -33,7 +33,7 @@ module.exports = async function (url: string) { let interval = null; let code; interval = setInterval(() => { - if (Window.window.document.location.href.startsWith("https://login.live.com/oauth20_desktop.srf")) { + if (Window.window.document.location.href.startsWith(redirect_uri)) { clearInterval(interval); try { code = Window.window.document.location.href.split("code=")[1].split("&")[0]; diff --git a/src/Authenticator/Microsoft.ts b/src/Authenticator/Microsoft.ts index 6738692b..8b7aa6cd 100755 --- a/src/Authenticator/Microsoft.ts +++ b/src/Authenticator/Microsoft.ts @@ -72,16 +72,15 @@ async function getBase64(url: string): Promise { export default class Microsoft { public client_id: string; public type: MicrosoftClientType; + public redirect_uri: string; /** * Creates a Microsoft auth instance. * @param client_id Your Microsoft OAuth client ID (default: '00000000402b5328' if none provided). */ - constructor(client_id: string) { - if (!client_id) { - client_id = '00000000402b5328'; - } - this.client_id = client_id; + constructor(client_id: string, redirect_uri?: string) { + this.client_id = client_id || '00000000402b5328'; + this.redirect_uri = redirect_uri || 'https://login.live.com/oauth20_desktop.srf'; // Determine if we're running under Electron, NW.js, or just in a terminal if (typeof process !== 'undefined' && process.versions && process.versions.electron) { @@ -103,30 +102,25 @@ export default class Microsoft { */ public async getAuth(type?: MicrosoftClientType, url?: string): Promise { const finalType = type || this.type; - const finalUrl = url || - `https://login.live.com/oauth20_authorize.srf?client_id=${this.client_id}&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=XboxLive.signin%20offline_access&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account`; + const finalUrl = url || `https://login.live.com/oauth20_authorize.srf?client_id=${this.client_id}&response_type=code&redirect_uri=${this.redirect_uri}&scope=XboxLive.signin%20offline_access&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&prompt=select_account`; - // Dynamically require different GUI modules depending on environment let userCode: string | 'cancel'; switch (finalType) { case 'electron': - userCode = await (require('./GUI/Electron.js'))(finalUrl); + userCode = await (require('./GUI/Electron.js'))(finalUrl, this.redirect_uri); break; case 'nwjs': - userCode = await (require('./GUI/NW.js'))(finalUrl); + userCode = await (require('./GUI/NW.js'))(finalUrl, this.redirect_uri); break; case 'terminal': - userCode = await (require('./GUI/Terminal.js'))(finalUrl); + userCode = await (require('./GUI/Terminal.js'))(finalUrl, this.redirect_uri); break; default: return false; } - if (userCode === 'cancel') { - return false; - } - // Exchange the code for an OAuth2 token, then retrieve account data + if (userCode === 'cancel') return false; return this.exchangeCodeForToken(userCode); } @@ -140,7 +134,7 @@ export default class Microsoft { const response = await fetch('https://login.live.com/oauth20_token.srf', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=https://login.live.com/oauth20_desktop.srf` + body: `client_id=${this.client_id}&code=${code}&grant_type=authorization_code&redirect_uri=${this.redirect_uri}` }); const oauth2 = await response.json();