Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Fabric #313

Merged
merged 5 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 47 additions & 63 deletions app/assets/js/processbuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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+
Expand All @@ -28,6 +37,7 @@ class ProcessBuilder {
this.libPath = path.join(this.commonDir, 'libraries')

this.usingLiteLoader = false
this.usingFabricLoader = false
this.llPath = null
}

Expand All @@ -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){
Expand Down Expand Up @@ -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)){
Expand All @@ -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)
Expand All @@ -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)
}

/**
Expand All @@ -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<pts.length; i++){
Expand Down Expand Up @@ -282,18 +295,21 @@ class ProcessBuilder {
// }

/**
* Construct the mod argument list for forge 1.13
* Construct the mod argument list for forge 1.13 and Fabric
*
* @param {Array.<Object>} 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',
Expand Down Expand Up @@ -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())
Expand All @@ -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)
)
}
}
Expand All @@ -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<args.length; i++){
if(typeof args[i] === 'object' && args[i].rules != null){
Expand Down Expand Up @@ -471,7 +487,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':
Expand All @@ -481,7 +497,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()
Expand All @@ -493,7 +509,7 @@ class ProcessBuilder {
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
break
case 'version_type':
val = this.versionData.type
val = this.vanillaManifest.type
break
case 'resolution_width':
val = ConfigManager.getGameWidth()
Expand Down Expand Up @@ -522,25 +538,11 @@ class ProcessBuilder {
}

// Autoconnect
let isAutoconnectBroken
try {
isAutoconnectBroken = ProcessBuilder.isAutoconnectBroken(this.forgeData.id.split('-')[2])
} catch(err) {
logger.error(err)
logger.error('Forge version format changed.. assuming autoconnect works.')
logger.debug('Forge version:', this.forgeData.id)
}

if(isAutoconnectBroken) {
logger.error('Server autoconnect disabled on Forge 1.15.2 for builds earlier than 31.2.15 due to OpenGL Stack Overflow issue.')
logger.error('Please upgrade your Forge version to at least 31.2.15!')
} else {
this._processAutoConnectArg(args)
}
this._processAutoConnectArg(args)


// Forge Specific Arguments
args = args.concat(this.forgeData.arguments.game)
args = args.concat(this.modManifest.arguments.game)

// Filter null values
args = args.filter(arg => {
Expand All @@ -556,7 +558,7 @@ class ProcessBuilder {
* @returns {Array.<string>} 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.
Expand All @@ -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':
Expand All @@ -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()
Expand All @@ -594,7 +596,7 @@ class ProcessBuilder {
val = '{}'
break
case 'version_type':
val = this.versionData.type
val = this.vanillaManifest.type
break
}
if(val != null){
Expand Down Expand Up @@ -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'))
}

Expand Down Expand Up @@ -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<libArr.length; i++){
const lib = libArr[i]
Expand Down Expand Up @@ -830,10 +832,10 @@ class ProcessBuilder {
const mdls = this.server.modules
let libs = {}

// Locate Forge/Libraries
// Locate Forge/Fabric/Libraries
for(let mdl of mdls){
const type = mdl.rawModule.type
if(type === Type.ForgeHosted || type === Type.Library){
if(type === Type.ForgeHosted || type === Type.Fabric || type === Type.Library){
libs[mdl.getVersionlessMavenIdentifier()] = mdl.getPath()
if(mdl.subModules.length > 0){
const res = this._resolveModuleLibraries(mdl)
Expand Down Expand Up @@ -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.length; i++) {
if(verSplit[i] > minWorking[i]) {
return false
} else if(verSplit[i] < minWorking[i]) {
return true
}
}
}

return false
}

}

module.exports = ProcessBuilder
6 changes: 3 additions & 3 deletions app/assets/js/scripts/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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/
Expand Down
2 changes: 1 addition & 1 deletion app/assets/js/scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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){

Expand Down
6 changes: 3 additions & 3 deletions app/assets/js/scripts/uibinder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Loading