From fb1cb7b41554c403feb231a94b532e06d5db9c3c Mon Sep 17 00:00:00 2001 From: jebibot <83044352+jebibot@users.noreply.github.com> Date: Mon, 4 Dec 2023 08:02:57 +0900 Subject: [PATCH] feat: support Fabric (#313) * feat: support Fabric * fix: GAME_LAUNCH_REGEX for Fabric * Small refactor. * Update documentation. * Upgrade helios-distribution-types, helios-core. --------- Co-authored-by: Daniel Scalzi --- app/assets/js/processbuilder.js | 110 +++++++++++++----------------- app/assets/js/scripts/landing.js | 6 +- app/assets/js/scripts/settings.js | 2 +- app/assets/js/scripts/uibinder.js | 6 +- docs/distro.md | 38 ++++++++++- package-lock.json | 24 +++---- package.json | 4 +- 7 files changed, 105 insertions(+), 85 deletions(-) diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js index 18c48fb..0e95562 100644 --- a/app/assets/js/processbuilder.js +++ b/app/assets/js/processbuilder.js @@ -12,14 +12,23 @@ const ConfigManager = require('./configmanager') const logger = LoggerUtil.getLogger('ProcessBuilder') + +/** + * Only forge and fabric are top level mod loaders. + * + * Forge 1.13+ launch logic is similar to fabrics, for now using usingFabricLoader flag to + * change minor details when needed. + * + * Rewrite of this module may be needed in the future. + */ class ProcessBuilder { - constructor(distroServer, versionData, forgeData, authUser, launcherVersion){ + constructor(distroServer, vanillaManifest, modManifest, authUser, launcherVersion){ this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id) this.commonDir = ConfigManager.getCommonDirectory() this.server = distroServer - this.versionData = versionData - this.forgeData = forgeData + this.vanillaManifest = vanillaManifest + this.modManifest = modManifest this.authUser = authUser this.launcherVersion = launcherVersion this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+ @@ -28,6 +37,7 @@ class ProcessBuilder { this.libPath = path.join(this.commonDir, 'libraries') this.usingLiteLoader = false + this.usingFabricLoader = false this.llPath = null } @@ -40,9 +50,12 @@ class ProcessBuilder { process.throwDeprecation = true this.setupLiteLoader() logger.info('Using liteloader:', this.usingLiteLoader) + this.usingFabricLoader = this.server.modules.some(mdl => mdl.rawModule.type === Type.Fabric) + logger.info('Using fabric loader:', this.usingFabricLoader) const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.rawServer.id).mods, this.server.modules) // Mod list below 1.13 + // Fabric only supports 1.14+ if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){ this.constructJSONModList('forge', modObj.fMods, true) if(this.usingLiteLoader){ @@ -166,7 +179,7 @@ class ProcessBuilder { for(let mdl of mdls){ const type = mdl.rawModule.type - if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){ + if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){ const o = !mdl.getRequired().value const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired()) if(!o || (o && e)){ @@ -178,7 +191,7 @@ class ProcessBuilder { continue } } - if(type === Type.ForgeMod){ + if(type === Type.ForgeMod || type === Type.FabricMod){ fMods.push(mdl) } else { lMods.push(mdl) @@ -194,7 +207,7 @@ class ProcessBuilder { } _lteMinorVersion(version) { - return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= Number(version) + return Number(this.modManifest.id.split('-')[0].split('.')[1]) <= Number(version) } /** @@ -206,7 +219,7 @@ class ProcessBuilder { if(this._lteMinorVersion(9)) { return false } - const ver = this.forgeData.id.split('-')[2] + const ver = this.modManifest.id.split('-')[2] const pts = ver.split('.') const min = [14, 23, 3, 2655] for(let i=0; i} mods An array of mods to add to the mod list. */ constructModList(mods) { const writeBuffer = mods.map(mod => { - return mod.getExtensionlessMavenIdentifier() + return this.usingFabricLoader ? mod.getPath() : mod.getExtensionlessMavenIdentifier() }).join('\n') if(writeBuffer) { fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8') - return [ + return this.usingFabricLoader ? [ + '--fabric.addMods', + `@${this.forgeModListFile}` + ] : [ '--fml.mavenRoots', path.join('..', '..', 'common', 'modstore'), '--fml.modLists', @@ -361,7 +377,7 @@ class ProcessBuilder { args.push('-Djava.library.path=' + tempNativePath) // Main Java Class - args.push(this.forgeData.mainClass) + args.push(this.modManifest.mainClass) // Forge Arguments args = args.concat(this._resolveForgeArgs()) @@ -384,17 +400,17 @@ class ProcessBuilder { const argDiscovery = /\${*(.*)}/ // JVM Arguments First - let args = this.versionData.arguments.jvm + let args = this.vanillaManifest.arguments.jvm // Debug securejarhandler // args.push('-Dbsl.debug=true') - if(this.forgeData.arguments.jvm != null) { - for(const argStr of this.forgeData.arguments.jvm) { + if(this.modManifest.arguments.jvm != null) { + for(const argStr of this.modManifest.arguments.jvm) { args.push(argStr .replaceAll('${library_directory}', this.libPath) .replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator()) - .replaceAll('${version_name}', this.forgeData.id) + .replaceAll('${version_name}', this.modManifest.id) ) } } @@ -411,10 +427,10 @@ class ProcessBuilder { args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id)) // Main Java Class - args.push(this.forgeData.mainClass) + args.push(this.modManifest.mainClass) // Vanilla Arguments - args = args.concat(this.versionData.arguments.game) + args = args.concat(this.vanillaManifest.arguments.game) for(let i=0; i { @@ -556,7 +558,7 @@ class ProcessBuilder { * @returns {Array.} An array containing the arguments required by forge. */ _resolveForgeArgs(){ - const mcArgs = this.forgeData.minecraftArguments.split(' ') + const mcArgs = this.modManifest.minecraftArguments.split(' ') const argDiscovery = /\${*(.*)}/ // Replace the declared variables with their proper values. @@ -569,7 +571,7 @@ class ProcessBuilder { val = this.authUser.displayName.trim() break case 'version_name': - //val = versionData.id + //val = vanillaManifest.id val = this.server.rawServer.id break case 'game_directory': @@ -579,7 +581,7 @@ class ProcessBuilder { val = path.join(this.commonDir, 'assets') break case 'assets_index_name': - val = this.versionData.assets + val = this.vanillaManifest.assets break case 'auth_uuid': val = this.authUser.uuid.trim() @@ -594,7 +596,7 @@ class ProcessBuilder { val = '{}' break case 'version_type': - val = this.versionData.type + val = this.vanillaManifest.type break } if(val != null){ @@ -669,10 +671,10 @@ class ProcessBuilder { classpathArg(mods, tempNativePath){ let cpArgs = [] - if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion)) { + if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion) || this.usingFabricLoader) { // Add the version.jar to the classpath. // Must not be added to the classpath for Forge 1.17+. - const version = this.versionData.id + const version = this.vanillaManifest.id cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) } @@ -711,7 +713,7 @@ class ProcessBuilder { const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ const libs = {} - const libArr = this.versionData.libraries + const libArr = this.vanillaManifest.libraries fs.ensureDirSync(tempNativePath) for(let i=0; i 0){ const res = this._resolveModuleLibraries(mdl) @@ -887,24 +889,6 @@ class ProcessBuilder { return libs } - static isAutoconnectBroken(forgeVersion) { - - const minWorking = [31, 2, 15] - const verSplit = forgeVersion.split('.').map(v => Number(v)) - - if(verSplit[0] === 31) { - for(let i=0; i minWorking[i]) { - return false - } else if(verSplit[i] < minWorking[i]) { - return true - } - } - } - - return false - } - } module.exports = ProcessBuilder \ No newline at end of file diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 1c73e04..1a1c176 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -442,7 +442,7 @@ let hasRPC = false // Joined server regex // Change this if your server uses something different. const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+)$/ +const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ const MIN_LINGER = 5000 async function dlAsync(login = true) { @@ -548,13 +548,13 @@ async function dlAsync(login = true) { serv.rawServer.id ) - const forgeData = await distributionIndexProcessor.loadForgeVersionJson(serv) + const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv) const versionData = await mojangIndexProcessor.getVersionJson() if(login) { const authUser = ConfigManager.getSelectedAccount() loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) - let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion()) + let pb = new ProcessBuilder(serv, versionData, modLoaderData, authUser, remote.app.getVersion()) setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')) // const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/ diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 628c63c..81a65a7 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -736,7 +736,7 @@ function parseModulesForUI(mdls, submodules, servConf){ for(const mdl of mdls){ - if(mdl.rawModule.type === Type.ForgeMod || mdl.rawModule.type === Type.LiteMod || mdl.rawModule.type === Type.LiteLoader){ + if(mdl.rawModule.type === Type.ForgeMod || mdl.rawModule.type === Type.LiteMod || mdl.rawModule.type === Type.LiteLoader || mdl.rawModule.type === Type.FabricMod){ if(mdl.getRequired().value){ diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index 469f49f..5fe79df 100644 --- a/app/assets/js/scripts/uibinder.js +++ b/app/assets/js/scripts/uibinder.js @@ -163,7 +163,7 @@ function syncModConfigurations(data){ for(let mdl of mdls){ const type = mdl.rawModule.type - if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){ + if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){ if(!mdl.getRequired().value){ const mdlID = mdl.getVersionlessMavenIdentifier() if(modsOld[mdlID] == null){ @@ -198,7 +198,7 @@ function syncModConfigurations(data){ for(let mdl of mdls){ const type = mdl.rawModule.type - if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){ + if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){ if(!mdl.getRequired().value){ mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl) } else { @@ -253,7 +253,7 @@ function scanOptionalSubModules(mdls, origin){ for(let mdl of mdls){ const type = mdl.rawModule.type // Optional types. - if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){ + if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){ // It is optional. if(!mdl.getRequired().value){ mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl) diff --git a/docs/distro.md b/docs/distro.md index 52eff8a..762bb81 100644 --- a/docs/distro.md +++ b/docs/distro.md @@ -360,10 +360,12 @@ The resolved/provided paths are appended to a base path depending on the module' | Type | Path | | ---- | ---- | | `ForgeHosted` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | +| `Fabric` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `LiteLoader` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `Library` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `ForgeMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) | | `LiteMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) | +| `FabricMod` | ({`commonDirectory`}/mods/fabric/{`path` OR resolved}) | | `File` | ({`instanceDirectory`}/{`Server.id`}/{`path` OR resolved}) | The `commonDirectory` and `instanceDirectory` values are stored in the launcher's config.json. @@ -408,7 +410,7 @@ If the module is enabled by default. Has no effect unless `Required.value` is fa ### ForgeHosted -The module type `ForgeHosted` represents forge itself. Currently, the launcher only supports forge servers, as vanilla servers can be connected to via the mojang launcher. The `Hosted` part is key, this means that the forge module must declare its required libraries as submodules. +The module type `ForgeHosted` represents forge itself. Currently, the launcher only supports modded servers, as vanilla servers can be connected to via the mojang launcher. The `Hosted` part is key, this means that the forge module must declare its required libraries as submodules. Ex. @@ -443,6 +445,40 @@ There were plans to add a `Forge` type, in which the required libraries would be --- +### Fabric + +The module type `Fabric` represents the fabric mod loader. Currently, the launcher only supports modded servers, as vanilla servers can be connected to via the mojang launcher. + +Ex. + +```json +{ + "id": "net.fabricmc:fabric-loader:0.15.0", + "name": "Fabric (fabric-loader)", + "type": "Fabric", + "artifact": { + "size": 1196222, + "MD5": "a43d5a142246801343b6cedef1c102c4", + "url": "http://localhost:8080/repo/lib/net/fabricmc/fabric-loader/0.15.0/fabric-loader-0.15.0.jar" + }, + "subModules": [ + { + "id": "1.20.1-fabric-0.15.0", + "name": "Fabric (version.json)", + "type": "VersionManifest", + "artifact": { + "size": 2847, + "MD5": "69a2bd43452325ba1bc882fa0904e054", + "url": "http://localhost:8080/repo/versions/1.20.1-fabric-0.15.0/1.20.1-fabric-0.15.0.json" + } + } +} +``` + +Fabric works similarly to Forge 1.13+. + +--- + ### LiteLoader The module type `LiteLoader` represents liteloader. It is handled as a library and added to the classpath at runtime. Special launch conditions are executed when liteloader is present and enabled. This module can be optional and toggled similarly to `ForgeMod` and `Litemod` modules. diff --git a/package-lock.json b/package-lock.json index 19dbc24..8724b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,8 @@ "fs-extra": "^11.1.1", "github-syntax-dark": "^0.5.0", "got": "^11.8.5", - "helios-core": "~2.0.6", - "helios-distribution-types": "^1.2.0", + "helios-core": "~2.1.0", + "helios-distribution-types": "^1.3.0", "jquery": "^3.7.1", "lodash.merge": "^4.6.2", "semver": "^7.5.4", @@ -2032,9 +2032,9 @@ } }, "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2331,12 +2331,12 @@ } }, "node_modules/helios-core": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/helios-core/-/helios-core-2.0.6.tgz", - "integrity": "sha512-QxUP6BZ0HDCmJjKNi2262vM2Sh222Gl8Ro4/qAxBWkmCxkpyD2Car9hSk5VZV4vcECTr7xg3SS55FuT4HdMF3g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/helios-core/-/helios-core-2.1.0.tgz", + "integrity": "sha512-3RvGMJ0RDzgdZF3e0o2VtBz/NCeucDVIopz9p3GA9tjy2cl7ll8HVHSXiLG5YE1JiINU5Akacv5L6MT1g6xZuA==", "dependencies": { "fastq": "^1.15.0", - "fs-extra": "^11.1.1", + "fs-extra": "^11.2.0", "got": "^11.8.6", "luxon": "^3.4.4", "node-stream-zip": "^1.15.0", @@ -2348,9 +2348,9 @@ } }, "node_modules/helios-distribution-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/helios-distribution-types/-/helios-distribution-types-1.2.0.tgz", - "integrity": "sha512-C8mRJGK0zAc7rRnA06Sj0LYwVqhY445UYNTmXU876AmfBirRR2F+A3LsD3osdgTxRMzrgkxBXvYZ0QbYW6j+6Q==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/helios-distribution-types/-/helios-distribution-types-1.3.0.tgz", + "integrity": "sha512-MP66JRHvmuE9yDoZoKeFDh3stsHger0w/cRcJAlV7UYw5ztR3m/uLbWdbfFV68B1Yc0+hDIiuFsuJT/Ve9xuiw==" }, "node_modules/hosted-git-info": { "version": "4.1.0", diff --git a/package.json b/package.json index 606b3c0..58dec06 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "fs-extra": "^11.1.1", "github-syntax-dark": "^0.5.0", "got": "^11.8.5", - "helios-core": "~2.0.6", - "helios-distribution-types": "^1.2.0", + "helios-core": "~2.1.0", + "helios-distribution-types": "^1.3.0", "jquery": "^3.7.1", "lodash.merge": "^4.6.2", "semver": "^7.5.4",