Compare commits

...

10 Commits

Author SHA1 Message Date
Daniel Scalzi eb683f89ec
Support 1.20.5, electron 30, dependency upgrade. 2024-04-28 17:35:19 -04:00
Daniel Scalzi ae0e9e227d
2.2.1
CVE-2024-27303
2024-03-06 21:57:24 -05:00
Daniel Scalzi dc15bbfde8
2.2.0 2024-02-22 11:50:18 -05:00
Daniel Scalzi 0d23f5c45b
Upgrade to Electron 29, Node.js 20. 2024-02-22 11:42:13 -05:00
Kamesuta fc4823a01f
Localize Microsoft/Mojang authentication error messages (#331)
* Move Microsoft/Mojang error message to lang file.

* Add mstfLogin to language file
2024-02-22 11:23:23 -05:00
Daniel Scalzi 95eebc18a7
2.1.1 2024-01-04 19:09:30 -05:00
Daniel Scalzi d03ff90f78
Remove mojang authserver as it has been permanently shut down. 2024-01-04 19:09:03 -05:00
Daniel Scalzi 258cd0d421
2.1.0 2023-12-03 18:06:25 -05:00
Daniel Scalzi f65eb2f2d6
Dependency upgrade. 2023-12-03 18:05:47 -05:00
jebibot fb1cb7b415
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 <d_scalzi@yahoo.com>
2023-12-03 18:02:57 -05:00
14 changed files with 1155 additions and 263 deletions

View File

@ -20,7 +20,7 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4

2
.nvmrc
View File

@ -1 +1 @@
18 20

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2022 Daniel D. Scalzi Copyright (c) 2017-2024 Daniel D. Scalzi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -84,7 +84,7 @@ This section details the setup of a basic developmentment environment.
**System Requirements** **System Requirements**
* [Node.js][nodejs] v18 * [Node.js][nodejs] v20
--- ---

View File

@ -12,12 +12,122 @@
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('helios-core')
const { RestResponseStatus } = require('helios-core/common') const { RestResponseStatus } = require('helios-core/common')
const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang') const { MojangRestAPI, MojangErrorCode } = require('helios-core/mojang')
const { MicrosoftAuth, microsoftErrorDisplayable, MicrosoftErrorCode } = require('helios-core/microsoft') const { MicrosoftAuth, MicrosoftErrorCode } = require('helios-core/microsoft')
const { AZURE_CLIENT_ID } = require('./ipcconstants') const { AZURE_CLIENT_ID } = require('./ipcconstants')
const Lang = require('./langloader')
const log = LoggerUtil.getLogger('AuthManager') const log = LoggerUtil.getLogger('AuthManager')
// Error messages
function microsoftErrorDisplayable(errorCode) {
switch (errorCode) {
case MicrosoftErrorCode.NO_PROFILE:
return {
title: Lang.queryJS('auth.microsoft.error.noProfileTitle'),
desc: Lang.queryJS('auth.microsoft.error.noProfileDesc')
}
case MicrosoftErrorCode.NO_XBOX_ACCOUNT:
return {
title: Lang.queryJS('auth.microsoft.error.noXboxAccountTitle'),
desc: Lang.queryJS('auth.microsoft.error.noXboxAccountDesc')
}
case MicrosoftErrorCode.XBL_BANNED:
return {
title: Lang.queryJS('auth.microsoft.error.xblBannedTitle'),
desc: Lang.queryJS('auth.microsoft.error.xblBannedDesc')
}
case MicrosoftErrorCode.UNDER_18:
return {
title: Lang.queryJS('auth.microsoft.error.under18Title'),
desc: Lang.queryJS('auth.microsoft.error.under18Desc')
}
case MicrosoftErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.microsoft.error.unknownTitle'),
desc: Lang.queryJS('auth.microsoft.error.unknownDesc')
}
}
}
function mojangErrorDisplayable(errorCode) {
switch(errorCode) {
case MojangErrorCode.ERROR_METHOD_NOT_ALLOWED:
return {
title: Lang.queryJS('auth.mojang.error.methodNotAllowedTitle'),
desc: Lang.queryJS('auth.mojang.error.methodNotAllowedDesc')
}
case MojangErrorCode.ERROR_NOT_FOUND:
return {
title: Lang.queryJS('auth.mojang.error.notFoundTitle'),
desc: Lang.queryJS('auth.mojang.error.notFoundDesc')
}
case MojangErrorCode.ERROR_USER_MIGRATED:
return {
title: Lang.queryJS('auth.mojang.error.accountMigratedTitle'),
desc: Lang.queryJS('auth.mojang.error.accountMigratedDesc')
}
case MojangErrorCode.ERROR_INVALID_CREDENTIALS:
return {
title: Lang.queryJS('auth.mojang.error.invalidCredentialsTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidCredentialsDesc')
}
case MojangErrorCode.ERROR_RATELIMIT:
return {
title: Lang.queryJS('auth.mojang.error.tooManyAttemptsTitle'),
desc: Lang.queryJS('auth.mojang.error.tooManyAttemptsDesc')
}
case MojangErrorCode.ERROR_INVALID_TOKEN:
return {
title: Lang.queryJS('auth.mojang.error.invalidTokenTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidTokenDesc')
}
case MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE:
return {
title: Lang.queryJS('auth.mojang.error.tokenHasProfileTitle'),
desc: Lang.queryJS('auth.mojang.error.tokenHasProfileDesc')
}
case MojangErrorCode.ERROR_CREDENTIALS_MISSING:
return {
title: Lang.queryJS('auth.mojang.error.credentialsMissingTitle'),
desc: Lang.queryJS('auth.mojang.error.credentialsMissingDesc')
}
case MojangErrorCode.ERROR_INVALID_SALT_VERSION:
return {
title: Lang.queryJS('auth.mojang.error.invalidSaltVersionTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidSaltVersionDesc')
}
case MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE:
return {
title: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeTitle'),
desc: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeDesc')
}
case MojangErrorCode.ERROR_GONE:
return {
title: Lang.queryJS('auth.mojang.error.accountGoneTitle'),
desc: Lang.queryJS('auth.mojang.error.accountGoneDesc')
}
case MojangErrorCode.ERROR_UNREACHABLE:
return {
title: Lang.queryJS('auth.mojang.error.unreachableTitle'),
desc: Lang.queryJS('auth.mojang.error.unreachableDesc')
}
case MojangErrorCode.ERROR_NOT_PAID:
return {
title: Lang.queryJS('auth.mojang.error.gameNotPurchasedTitle'),
desc: Lang.queryJS('auth.mojang.error.gameNotPurchasedDesc')
}
case MojangErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.mojang.error.unknownErrorTitle'),
desc: Lang.queryJS('auth.mojang.error.unknownErrorDesc')
}
default:
throw new Error(`Unknown error code: ${errorCode}`)
}
}
// Functions // Functions
/** /**

View File

@ -12,14 +12,23 @@ const ConfigManager = require('./configmanager')
const logger = LoggerUtil.getLogger('ProcessBuilder') 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 { class ProcessBuilder {
constructor(distroServer, versionData, forgeData, authUser, launcherVersion){ constructor(distroServer, vanillaManifest, modManifest, authUser, launcherVersion){
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id) this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id)
this.commonDir = ConfigManager.getCommonDirectory() this.commonDir = ConfigManager.getCommonDirectory()
this.server = distroServer this.server = distroServer
this.versionData = versionData this.vanillaManifest = vanillaManifest
this.forgeData = forgeData this.modManifest = modManifest
this.authUser = authUser this.authUser = authUser
this.launcherVersion = launcherVersion this.launcherVersion = launcherVersion
this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+ this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+
@ -28,6 +37,7 @@ class ProcessBuilder {
this.libPath = path.join(this.commonDir, 'libraries') this.libPath = path.join(this.commonDir, 'libraries')
this.usingLiteLoader = false this.usingLiteLoader = false
this.usingFabricLoader = false
this.llPath = null this.llPath = null
} }
@ -40,9 +50,12 @@ class ProcessBuilder {
process.throwDeprecation = true process.throwDeprecation = true
this.setupLiteLoader() this.setupLiteLoader()
logger.info('Using liteloader:', this.usingLiteLoader) 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) const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.rawServer.id).mods, this.server.modules)
// Mod list below 1.13 // Mod list below 1.13
// Fabric only supports 1.14+
if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){ if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
this.constructJSONModList('forge', modObj.fMods, true) this.constructJSONModList('forge', modObj.fMods, true)
if(this.usingLiteLoader){ if(this.usingLiteLoader){
@ -166,7 +179,7 @@ class ProcessBuilder {
for(let mdl of mdls){ for(let mdl of mdls){
const type = mdl.rawModule.type 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 o = !mdl.getRequired().value
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired()) const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired())
if(!o || (o && e)){ if(!o || (o && e)){
@ -178,7 +191,7 @@ class ProcessBuilder {
continue continue
} }
} }
if(type === Type.ForgeMod){ if(type === Type.ForgeMod || type === Type.FabricMod){
fMods.push(mdl) fMods.push(mdl)
} else { } else {
lMods.push(mdl) lMods.push(mdl)
@ -194,7 +207,7 @@ class ProcessBuilder {
} }
_lteMinorVersion(version) { _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)) { if(this._lteMinorVersion(9)) {
return false return false
} }
const ver = this.forgeData.id.split('-')[2] const ver = this.modManifest.id.split('-')[2]
const pts = ver.split('.') const pts = ver.split('.')
const min = [14, 23, 3, 2655] const min = [14, 23, 3, 2655]
for(let i=0; i<pts.length; i++){ for(let i=0; i<pts.length; i++){
@ -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. * @param {Array.<Object>} mods An array of mods to add to the mod list.
*/ */
constructModList(mods) { constructModList(mods) {
const writeBuffer = mods.map(mod => { const writeBuffer = mods.map(mod => {
return mod.getExtensionlessMavenIdentifier() return this.usingFabricLoader ? mod.getPath() : mod.getExtensionlessMavenIdentifier()
}).join('\n') }).join('\n')
if(writeBuffer) { if(writeBuffer) {
fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8') fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8')
return [ return this.usingFabricLoader ? [
'--fabric.addMods',
`@${this.forgeModListFile}`
] : [
'--fml.mavenRoots', '--fml.mavenRoots',
path.join('..', '..', 'common', 'modstore'), path.join('..', '..', 'common', 'modstore'),
'--fml.modLists', '--fml.modLists',
@ -361,7 +377,7 @@ class ProcessBuilder {
args.push('-Djava.library.path=' + tempNativePath) args.push('-Djava.library.path=' + tempNativePath)
// Main Java Class // Main Java Class
args.push(this.forgeData.mainClass) args.push(this.modManifest.mainClass)
// Forge Arguments // Forge Arguments
args = args.concat(this._resolveForgeArgs()) args = args.concat(this._resolveForgeArgs())
@ -384,17 +400,17 @@ class ProcessBuilder {
const argDiscovery = /\${*(.*)}/ const argDiscovery = /\${*(.*)}/
// JVM Arguments First // JVM Arguments First
let args = this.versionData.arguments.jvm let args = this.vanillaManifest.arguments.jvm
// Debug securejarhandler // Debug securejarhandler
// args.push('-Dbsl.debug=true') // args.push('-Dbsl.debug=true')
if(this.forgeData.arguments.jvm != null) { if(this.modManifest.arguments.jvm != null) {
for(const argStr of this.forgeData.arguments.jvm) { for(const argStr of this.modManifest.arguments.jvm) {
args.push(argStr args.push(argStr
.replaceAll('${library_directory}', this.libPath) .replaceAll('${library_directory}', this.libPath)
.replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator()) .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)) args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
// Main Java Class // Main Java Class
args.push(this.forgeData.mainClass) args.push(this.modManifest.mainClass)
// Vanilla Arguments // Vanilla Arguments
args = args.concat(this.versionData.arguments.game) args = args.concat(this.vanillaManifest.arguments.game)
for(let i=0; i<args.length; i++){ for(let i=0; i<args.length; i++){
if(typeof args[i] === 'object' && args[i].rules != null){ if(typeof args[i] === 'object' && args[i].rules != null){
@ -471,7 +487,7 @@ class ProcessBuilder {
val = this.authUser.displayName.trim() val = this.authUser.displayName.trim()
break break
case 'version_name': case 'version_name':
//val = versionData.id //val = vanillaManifest.id
val = this.server.rawServer.id val = this.server.rawServer.id
break break
case 'game_directory': case 'game_directory':
@ -481,7 +497,7 @@ class ProcessBuilder {
val = path.join(this.commonDir, 'assets') val = path.join(this.commonDir, 'assets')
break break
case 'assets_index_name': case 'assets_index_name':
val = this.versionData.assets val = this.vanillaManifest.assets
break break
case 'auth_uuid': case 'auth_uuid':
val = this.authUser.uuid.trim() val = this.authUser.uuid.trim()
@ -493,7 +509,7 @@ class ProcessBuilder {
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
break break
case 'version_type': case 'version_type':
val = this.versionData.type val = this.vanillaManifest.type
break break
case 'resolution_width': case 'resolution_width':
val = ConfigManager.getGameWidth() val = ConfigManager.getGameWidth()
@ -522,25 +538,11 @@ class ProcessBuilder {
} }
// Autoconnect // 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 // Forge Specific Arguments
args = args.concat(this.forgeData.arguments.game) args = args.concat(this.modManifest.arguments.game)
// Filter null values // Filter null values
args = args.filter(arg => { args = args.filter(arg => {
@ -556,7 +558,7 @@ class ProcessBuilder {
* @returns {Array.<string>} An array containing the arguments required by forge. * @returns {Array.<string>} An array containing the arguments required by forge.
*/ */
_resolveForgeArgs(){ _resolveForgeArgs(){
const mcArgs = this.forgeData.minecraftArguments.split(' ') const mcArgs = this.modManifest.minecraftArguments.split(' ')
const argDiscovery = /\${*(.*)}/ const argDiscovery = /\${*(.*)}/
// Replace the declared variables with their proper values. // Replace the declared variables with their proper values.
@ -569,7 +571,7 @@ class ProcessBuilder {
val = this.authUser.displayName.trim() val = this.authUser.displayName.trim()
break break
case 'version_name': case 'version_name':
//val = versionData.id //val = vanillaManifest.id
val = this.server.rawServer.id val = this.server.rawServer.id
break break
case 'game_directory': case 'game_directory':
@ -579,7 +581,7 @@ class ProcessBuilder {
val = path.join(this.commonDir, 'assets') val = path.join(this.commonDir, 'assets')
break break
case 'assets_index_name': case 'assets_index_name':
val = this.versionData.assets val = this.vanillaManifest.assets
break break
case 'auth_uuid': case 'auth_uuid':
val = this.authUser.uuid.trim() val = this.authUser.uuid.trim()
@ -594,7 +596,7 @@ class ProcessBuilder {
val = '{}' val = '{}'
break break
case 'version_type': case 'version_type':
val = this.versionData.type val = this.vanillaManifest.type
break break
} }
if(val != null){ if(val != null){
@ -669,10 +671,10 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){ classpathArg(mods, tempNativePath){
let cpArgs = [] 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. // Add the version.jar to the classpath.
// Must not be added to the classpath for Forge 1.17+. // 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')) cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
} }
@ -711,7 +713,7 @@ class ProcessBuilder {
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
const libs = {} const libs = {}
const libArr = this.versionData.libraries const libArr = this.vanillaManifest.libraries
fs.ensureDirSync(tempNativePath) fs.ensureDirSync(tempNativePath)
for(let i=0; i<libArr.length; i++){ for(let i=0; i<libArr.length; i++){
const lib = libArr[i] const lib = libArr[i]
@ -830,10 +832,10 @@ class ProcessBuilder {
const mdls = this.server.modules const mdls = this.server.modules
let libs = {} let libs = {}
// Locate Forge/Libraries // Locate Forge/Fabric/Libraries
for(let mdl of mdls){ for(let mdl of mdls){
const type = mdl.rawModule.type 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() libs[mdl.getVersionlessMavenIdentifier()] = mdl.getPath()
if(mdl.subModules.length > 0){ if(mdl.subModules.length > 0){
const res = this._resolveModuleLibraries(mdl) const res = this._resolveModuleLibraries(mdl)
@ -887,24 +889,6 @@ class ProcessBuilder {
return libs 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 module.exports = ProcessBuilder

View File

@ -442,7 +442,7 @@ let hasRPC = false
// Joined server regex // Joined server regex
// Change this if your server uses something different. // Change this if your server uses something different.
const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ 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 const MIN_LINGER = 5000
async function dlAsync(login = true) { async function dlAsync(login = true) {
@ -548,13 +548,13 @@ async function dlAsync(login = true) {
serv.rawServer.id serv.rawServer.id
) )
const forgeData = await distributionIndexProcessor.loadForgeVersionJson(serv) const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv)
const versionData = await mojangIndexProcessor.getVersionJson() const versionData = await mojangIndexProcessor.getVersionJson()
if(login) { if(login) {
const authUser = ConfigManager.getSelectedAccount() const authUser = ConfigManager.getSelectedAccount()
loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) 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')) setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame'))
// const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/ // const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/

View File

@ -736,7 +736,7 @@ function parseModulesForUI(mdls, submodules, servConf){
for(const mdl of mdls){ 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){ if(mdl.getRequired().value){

View File

@ -163,7 +163,7 @@ function syncModConfigurations(data){
for(let mdl of mdls){ for(let mdl of mdls){
const type = mdl.rawModule.type 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){ if(!mdl.getRequired().value){
const mdlID = mdl.getVersionlessMavenIdentifier() const mdlID = mdl.getVersionlessMavenIdentifier()
if(modsOld[mdlID] == null){ if(modsOld[mdlID] == null){
@ -198,7 +198,7 @@ function syncModConfigurations(data){
for(let mdl of mdls){ for(let mdl of mdls){
const type = mdl.rawModule.type 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){ if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl) mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
} else { } else {
@ -253,7 +253,7 @@ function scanOptionalSubModules(mdls, origin){
for(let mdl of mdls){ for(let mdl of mdls){
const type = mdl.rawModule.type const type = mdl.rawModule.type
// Optional types. // 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. // It is optional.
if(!mdl.getRequired().value){ if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl) mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)

View File

@ -279,6 +279,11 @@ latestVersionTitle = "You Are Running the Latest Version"
checkForUpdatesButton = "Check for Updates" checkForUpdatesButton = "Check for Updates"
checkingForUpdatesButton = "Checking for Updates.." checkingForUpdatesButton = "Checking for Updates.."
[js.settings.msftLogin]
errorTitle = "Microsoft Login Failed"
errorMessage = "We were unable to authenticate your Microsoft account. Please try again."
okButton = "OK"
[js.uibinder.startup] [js.uibinder.startup]
fatalErrorTitle = "Fatal Error: Unable to Load Distribution Index" fatalErrorTitle = "Fatal Error: Unable to Load Distribution Index"
fatalErrorMessage = "A connection could not be established to our servers to download the distribution index. No local copies were available to load. <br><br>The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application." fatalErrorMessage = "A connection could not be established to our servers to download the distribution index. No local copies were available to load. <br><br>The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application."
@ -295,3 +300,45 @@ selectAnotherAccountButton = "Select Another Account"
checkingForUpdateButton = "Checking for Updates..." checkingForUpdateButton = "Checking for Updates..."
installNowButton = "Install Now" installNowButton = "Install Now"
checkForUpdatesButton = "Check for Updates" checkForUpdatesButton = "Check for Updates"
[js.auth.microsoft.error]
noProfileTitle = "Error During Login:<br>Profile Not Set Up"
noProfileDesc = "Your Microsoft account does not yet have a Minecraft profile set up. If you have recently purchased the game or redeemed it through Xbox Game Pass, you have to set up your profile on <a href=\"https://minecraft.net/\">Minecraft.net</a>.<br><br>If you have not yet purchased the game, you can also do that on <a href=\"https://minecraft.net/\">Minecraft.net</a>."
noXboxAccountTitle = "Error During Login:<br>No Xbox Account"
noXboxAccountDesc = "Your Microsoft account has no Xbox account associated with it."
xblBannedTitle = "Error During Login:<br>Xbox Live Unavailable"
xblBannedDesc = "Your Microsoft account is from a country where Xbox Live is not available or banned."
under18Title = "Error During Login:<br>Parental Approval Required"
under18Desc = "Accounts for users under the age of 18 must be added to a Family by an adult."
unknownTitle = "Unknown Error During Login"
unknownDesc = "An unknown error has occurred. Please see the console for details."
[js.auth.mojang.error]
methodNotAllowedTitle = "Internal Error:<br>Method Not Allowed"
methodNotAllowedDesc = "Method not allowed. Please report this error."
notFoundTitle = "Internal Error:<br>Not Found"
notFoundDesc = "The authentication endpoint was not found. Please report this issue."
accountMigratedTitle = "Error During Login:<br>Account Migrated"
accountMigratedDesc = "You've attempted to login with a migrated account. Try again using the account email as the username."
invalidCredentialsTitle = "Error During Login:<br>Invalid Credentials"
invalidCredentialsDesc = "The email or password you've entered is incorrect. Please try again."
tooManyAttemptsTitle = "Error During Login:<br>Too Many Attempts"
tooManyAttemptsDesc = "There have been too many login attempts with this account recently. Please try again later."
invalidTokenTitle = "Error During Login:<br>Invalid Token"
invalidTokenDesc = "The provided access token is invalid."
tokenHasProfileTitle = "Error During Login:<br>Token Has Profile"
tokenHasProfileDesc = "Access token already has a profile assigned. Selecting profiles is not implemented yet."
credentialsMissingTitle = "Error During Login:<br>Credentials Missing"
credentialsMissingDesc = "Username/password was not submitted or password is less than 3 characters."
invalidSaltVersionTitle = "Error During Login:<br>Invalid Salt Version"
invalidSaltVersionDesc = "Invalid salt version."
unsupportedMediaTypeTitle = "Internal Error:<br>Unsupported Media Type"
unsupportedMediaTypeDesc = "Unsupported media type. Please report this error."
accountGoneTitle = "Error During Login:<br>Account Migrated"
accountGoneDesc = "Account has been migrated to a Microsoft account. Please log in with Microsoft."
unreachableTitle = "Error During Login:<br>Unreachable"
unreachableDesc = "Unable to reach the authentication servers. Ensure that they are online and you are connected to the internet."
gameNotPurchasedTitle = "Error During Login:<br>Game Not Purchased"
gameNotPurchasedDesc = "The account you are trying to login with has not purchased a copy of Minecraft. You may purchase a copy on <a href=\"https://minecraft.net/\">Minecraft.net</a>"
unknownErrorTitle = "Unknown Error During Login"
unknownErrorDesc = "An unknown error has occurred. Please see the console for details."

View File

@ -360,10 +360,12 @@ The resolved/provided paths are appended to a base path depending on the module'
| Type | Path | | Type | Path |
| ---- | ---- | | ---- | ---- |
| `ForgeHosted` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `ForgeHosted` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
| `Fabric` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
| `LiteLoader` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `LiteLoader` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
| `Library` | ({`commonDirectory`}/libraries/{`path` OR resolved}) | | `Library` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
| `ForgeMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) | | `ForgeMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) |
| `LiteMod` | ({`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}) | | `File` | ({`instanceDirectory`}/{`Server.id`}/{`path` OR resolved}) |
The `commonDirectory` and `instanceDirectory` values are stored in the launcher's config.json. 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 ### 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. 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 ### 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. 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.

View File

@ -2,7 +2,7 @@ appId: 'helioslauncher'
productName: 'Helios Launcher' productName: 'Helios Launcher'
artifactName: '${productName}-setup-${version}.${ext}' artifactName: '${productName}-setup-${version}.${ext}'
copyright: 'Copyright © 2018-2022 Daniel Scalzi' copyright: 'Copyright © 2018-2024 Daniel Scalzi'
asar: true asar: true
compression: 'maximum' compression: 'maximum'

1059
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "helioslauncher", "name": "helioslauncher",
"version": "2.0.6", "version": "2.2.1",
"productName": "Helios Launcher", "productName": "Helios Launcher",
"description": "Modded Minecraft Launcher", "description": "Modded Minecraft Launcher",
"author": "Daniel Scalzi (https://github.com/dscalzi/)", "author": "Daniel Scalzi (https://github.com/dscalzi/)",
@ -20,29 +20,29 @@
"lint": "eslint --config .eslintrc.json ." "lint": "eslint --config .eslintrc.json ."
}, },
"engines": { "engines": {
"node": "18.x.x" "node": "20.x.x"
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2.1.0", "@electron/remote": "^2.1.2",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.12",
"discord-rpc-patch": "^4.0.1", "discord-rpc-patch": "^4.0.1",
"ejs": "^3.1.9", "ejs": "^3.1.10",
"ejs-electron": "^2.1.1", "ejs-electron": "^3.0.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.8",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"github-syntax-dark": "^0.5.0", "github-syntax-dark": "^0.5.0",
"got": "^11.8.5", "got": "^11.8.5",
"helios-core": "~2.0.6", "helios-core": "~2.2.1",
"helios-distribution-types": "^1.2.0", "helios-distribution-types": "^1.3.0",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"semver": "^7.5.4", "semver": "^7.6.0",
"toml": "^3.0.0" "toml": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"electron": "^27.1.2", "electron": "^30.0.1",
"electron-builder": "^24.9.1", "electron-builder": "^24.13.3",
"eslint": "^8.54.0" "eslint": "^8.57.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",