Revert "Merge branch 'master' of github.com:dscalzi/HeliosLauncher into dscalzi-master"

This reverts commit 6c7196ad2cf86e0289cc8ca9e3d4f1ea3ad0dd9a, reversing
changes made to 3d3055560f06ad47d6c5d3663254852cce1bb9fb.
This commit is contained in:
DrEmixam 2024-01-27 20:17:13 +01:00 committed by busybox11
parent 2643830278
commit b23c9d0317
No known key found for this signature in database
16 changed files with 206 additions and 216 deletions

View File

@ -5,17 +5,15 @@ const logger = LoggerUtil.getLogger('DiscordWrapper')
const { Client } = require('discord-rpc-patch')
const Lang = require('./langloader')
let client
let activity
exports.initRPC = function(genSettings, servSettings, initialDetails = Lang.queryJS('discord.waiting')){
exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){
client = new Client({ transport: 'ipc' })
activity = {
details: initialDetails,
state: Lang.queryJS('discord.state', {shortId: servSettings.shortId}),
state: 'Server: ' + servSettings.shortId,
largeImageKey: servSettings.largeImageKey,
largeImageText: servSettings.largeImageText,
smallImageKey: genSettings.smallImageKey,

View File

@ -1,27 +1,19 @@
const fs = require('fs-extra')
const path = require('path')
const toml = require('toml')
const merge = require('lodash.merge')
let lang
exports.loadLanguage = function(id){
lang = merge(lang || {}, toml.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.toml`))) || {})
lang = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.json`))) || {}
}
exports.query = function(id, placeHolders){
exports.query = function(id){
let query = id.split('.')
let res = lang
for(let q of query){
res = res[q]
}
let text = res === lang ? '' : res
if (placeHolders) {
Object.entries(placeHolders).forEach(([key, value]) => {
text = text.replace(`{${key}}`, value)
})
}
return text
return res === lang ? {} : res
}
exports.queryJS = function(id, placeHolders){

View File

@ -12,23 +12,14 @@ 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, vanillaManifest, modManifest, authUser, launcherVersion){
constructor(distroServer, versionData, forgeData, authUser, launcherVersion){
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id)
this.commonDir = ConfigManager.getCommonDirectory()
this.server = distroServer
this.vanillaManifest = vanillaManifest
this.modManifest = modManifest
this.versionData = versionData
this.forgeData = forgeData
this.authUser = authUser
this.launcherVersion = launcherVersion
this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+
@ -37,7 +28,6 @@ class ProcessBuilder {
this.libPath = path.join(this.commonDir, 'libraries')
this.usingLiteLoader = false
this.usingFabricLoader = false
this.llPath = null
}
@ -50,12 +40,9 @@ 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){
@ -179,7 +166,7 @@ class ProcessBuilder {
for(let mdl of mdls){
const type = mdl.rawModule.type
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
const o = !mdl.getRequired().value
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired())
if(!o || (o && e)){
@ -191,7 +178,7 @@ class ProcessBuilder {
continue
}
}
if(type === Type.ForgeMod || type === Type.FabricMod){
if(type === Type.ForgeMod){
fMods.push(mdl)
} else {
lMods.push(mdl)
@ -207,7 +194,7 @@ class ProcessBuilder {
}
_lteMinorVersion(version) {
return Number(this.modManifest.id.split('-')[0].split('.')[1]) <= Number(version)
return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= Number(version)
}
/**
@ -219,7 +206,7 @@ class ProcessBuilder {
if(this._lteMinorVersion(9)) {
return false
}
const ver = this.modManifest.id.split('-')[2]
const ver = this.forgeData.id.split('-')[2]
const pts = ver.split('.')
const min = [14, 23, 3, 2655]
for(let i=0; i<pts.length; i++){
@ -295,21 +282,18 @@ class ProcessBuilder {
// }
/**
* Construct the mod argument list for forge 1.13 and Fabric
* Construct the mod argument list for forge 1.13
*
* @param {Array.<Object>} mods An array of mods to add to the mod list.
*/
constructModList(mods) {
const writeBuffer = mods.map(mod => {
return this.usingFabricLoader ? mod.getPath() : mod.getExtensionlessMavenIdentifier()
return mod.getExtensionlessMavenIdentifier()
}).join('\n')
if(writeBuffer) {
fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8')
return this.usingFabricLoader ? [
'--fabric.addMods',
`@${this.forgeModListFile}`
] : [
return [
'--fml.mavenRoots',
path.join('..', '..', 'common', 'modstore'),
'--fml.modLists',
@ -323,17 +307,12 @@ class ProcessBuilder {
_processAutoConnectArg(args){
if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
if(mcVersionAtLeast('1.20', this.server.rawServer.minecraftVersion)){
args.push('--quickPlayMultiplayer')
args.push(`${this.server.hostname}:${this.server.port}`)
} else {
args.push('--server')
args.push(this.server.hostname)
args.push('--port')
args.push(this.server.port)
}
}
}
/**
* Construct the argument array that will be passed to the JVM process.
@ -377,7 +356,7 @@ class ProcessBuilder {
args.push('-Djava.library.path=' + tempNativePath)
// Main Java Class
args.push(this.modManifest.mainClass)
args.push(this.forgeData.mainClass)
// Forge Arguments
args = args.concat(this._resolveForgeArgs())
@ -400,17 +379,17 @@ class ProcessBuilder {
const argDiscovery = /\${*(.*)}/
// JVM Arguments First
let args = this.vanillaManifest.arguments.jvm
let args = this.versionData.arguments.jvm
// Debug securejarhandler
// args.push('-Dbsl.debug=true')
if(this.modManifest.arguments.jvm != null) {
for(const argStr of this.modManifest.arguments.jvm) {
if(this.forgeData.arguments.jvm != null) {
for(const argStr of this.forgeData.arguments.jvm) {
args.push(argStr
.replaceAll('${library_directory}', this.libPath)
.replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator())
.replaceAll('${version_name}', this.modManifest.id)
.replaceAll('${version_name}', this.forgeData.id)
)
}
}
@ -427,10 +406,10 @@ class ProcessBuilder {
args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
// Main Java Class
args.push(this.modManifest.mainClass)
args.push(this.forgeData.mainClass)
// Vanilla Arguments
args = args.concat(this.vanillaManifest.arguments.game)
args = args.concat(this.versionData.arguments.game)
for(let i=0; i<args.length; i++){
if(typeof args[i] === 'object' && args[i].rules != null){
@ -487,7 +466,7 @@ class ProcessBuilder {
val = this.authUser.displayName.trim()
break
case 'version_name':
//val = vanillaManifest.id
//val = versionData.id
val = this.server.rawServer.id
break
case 'game_directory':
@ -497,7 +476,7 @@ class ProcessBuilder {
val = path.join(this.commonDir, 'assets')
break
case 'assets_index_name':
val = this.vanillaManifest.assets
val = this.versionData.assets
break
case 'auth_uuid':
val = this.authUser.uuid.trim()
@ -509,7 +488,7 @@ class ProcessBuilder {
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
break
case 'version_type':
val = this.vanillaManifest.type
val = this.versionData.type
break
case 'resolution_width':
val = ConfigManager.getGameWidth()
@ -538,11 +517,25 @@ 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)
}
// Forge Specific Arguments
args = args.concat(this.modManifest.arguments.game)
args = args.concat(this.forgeData.arguments.game)
// Filter null values
args = args.filter(arg => {
@ -558,7 +551,7 @@ class ProcessBuilder {
* @returns {Array.<string>} An array containing the arguments required by forge.
*/
_resolveForgeArgs(){
const mcArgs = this.modManifest.minecraftArguments.split(' ')
const mcArgs = this.forgeData.minecraftArguments.split(' ')
const argDiscovery = /\${*(.*)}/
// Replace the declared variables with their proper values.
@ -571,7 +564,7 @@ class ProcessBuilder {
val = this.authUser.displayName.trim()
break
case 'version_name':
//val = vanillaManifest.id
//val = versionData.id
val = this.server.rawServer.id
break
case 'game_directory':
@ -581,7 +574,7 @@ class ProcessBuilder {
val = path.join(this.commonDir, 'assets')
break
case 'assets_index_name':
val = this.vanillaManifest.assets
val = this.versionData.assets
break
case 'auth_uuid':
val = this.authUser.uuid.trim()
@ -596,7 +589,7 @@ class ProcessBuilder {
val = '{}'
break
case 'version_type':
val = this.vanillaManifest.type
val = this.versionData.type
break
}
if(val != null){
@ -671,10 +664,10 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){
let cpArgs = []
if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion) || this.usingFabricLoader) {
if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion)) {
// Add the version.jar to the classpath.
// Must not be added to the classpath for Forge 1.17+.
const version = this.vanillaManifest.id
const version = this.versionData.id
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
}
@ -713,7 +706,7 @@ class ProcessBuilder {
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
const libs = {}
const libArr = this.vanillaManifest.libraries
const libArr = this.versionData.libraries
fs.ensureDirSync(tempNativePath)
for(let i=0; i<libArr.length; i++){
const lib = libArr[i]
@ -832,10 +825,10 @@ class ProcessBuilder {
const mdls = this.server.modules
let libs = {}
// Locate Forge/Fabric/Libraries
// Locate Forge/Libraries
for(let mdl of mdls){
const type = mdl.rawModule.type
if(type === Type.ForgeHosted || type === Type.Fabric || type === Type.Library){
if(type === Type.ForgeHosted || type === Type.Library){
libs[mdl.getVersionlessMavenIdentifier()] = mdl.getPath()
if(mdl.subModules.length > 0){
const res = this._resolveModuleLibraries(mdl)
@ -889,6 +882,24 @@ 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

View File

@ -220,7 +220,10 @@ loginButton.addEventListener('click', () => {
} else {
// Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = Lang.queryJS('login.error.unknown')
actualDisplayableError = {
title: 'Unknown Error During Login',
desc: 'An unknown error has occurred. Please see the console for details.'
}
}
setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))

View File

@ -130,7 +130,7 @@ async function toggleServerSelection(toggleState){
* @param {string} acknowledge Acknowledge button text.
* @param {string} dismiss Dismiss button text.
*/
function setOverlayContent(title, description, acknowledge, dismiss = Lang.queryJS('overlay.dismiss')){
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){
document.getElementById('overlayTitle').innerHTML = title
document.getElementById('overlayDesc').innerHTML = description
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
@ -289,7 +289,7 @@ async function populateServerListings(){
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
</svg>
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
<span class="serverListingStarTooltip">Main Server</span>
</div>` : ''}
</div>
</div>

View File

@ -59,8 +59,8 @@ function bindFileSelectors(){
if(isJavaExecSel && process.platform === 'win32') {
options.filters = [
{ name: Lang.queryJS('settings.fileSelectors.executables'), extensions: ['exe'] },
{ name: Lang.queryJS('settings.fileSelectors.allFiles'), extensions: ['*'] }
{ name: 'Executables', extensions: ['exe'] },
{ name: 'All Files', extensions: ['*'] }
]
}
@ -374,9 +374,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
// Unexpected error.
setOverlayContent(
Lang.queryJS('settings.msftLogin.errorTitle'),
Lang.queryJS('settings.msftLogin.errorMessage'),
Lang.queryJS('settings.msftLogin.okButton')
'Something Went Wrong',
'Microsoft authentication failed. Please try again.',
'OK'
)
setOverlayHandler(() => {
toggleOverlay(false)
@ -401,7 +401,7 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
setOverlayContent(
error,
errorDesc,
Lang.queryJS('settings.msftLogin.okButton')
'OK'
)
setOverlayHandler(() => {
toggleOverlay(false)
@ -429,7 +429,10 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
} else {
// Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = Lang.queryJS('login.error.unknown')
actualDisplayableError = {
title: 'Unknown Error During Login',
desc: 'An unknown error has occurred. Please see the console for details.'
}
}
switchView(getCurrentView(), viewOnClose, 500, 500, () => {
@ -458,11 +461,11 @@ function bindAuthAccountSelect(){
for(let i=0; i<selectBtns.length; i++){
if(selectBtns[i].hasAttribute('selected')){
selectBtns[i].removeAttribute('selected')
selectBtns[i].innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton')
selectBtns[i].innerHTML = 'Select Account'
}
}
val.setAttribute('selected', '')
val.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton')
val.innerHTML = 'Selected Account &#10004;'
setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
}
})
@ -480,10 +483,10 @@ function bindAuthAccountLogOut(){
if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
isLastAccount = true
setOverlayContent(
Lang.queryJS('settings.authAccountLogout.lastAccountWarningTitle'),
Lang.queryJS('settings.authAccountLogout.lastAccountWarningMessage'),
Lang.queryJS('settings.authAccountLogout.confirmButton'),
Lang.queryJS('settings.authAccountLogout.cancelButton')
'Warning<br>This is Your Last Account',
'In order to use the launcher you must be logged into at least one account. You will need to login again after.<br><br>Are you sure you want to log out?',
'I\'m Sure',
'Cancel'
)
setOverlayHandler(() => {
processLogOut(val, isLastAccount)
@ -552,9 +555,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGOUT, (_, ...arguments_) => {
// Unexpected error.
setOverlayContent(
Lang.queryJS('settings.msftLogout.errorTitle'),
Lang.queryJS('settings.msftLogout.errorMessage'),
Lang.queryJS('settings.msftLogout.okButton')
'Something Went Wrong',
'Microsoft logout failed. Please try again.',
'OK'
)
setOverlayHandler(() => {
toggleOverlay(false)
@ -608,12 +611,12 @@ function refreshAuthAccountSelected(uuid){
const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
if(uuid === val.getAttribute('uuid')){
selBtn.setAttribute('selected', '')
selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton')
selBtn.innerHTML = 'Selected Account &#10004;'
} else {
if(selBtn.hasAttribute('selected')){
selBtn.removeAttribute('selected')
}
selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton')
selBtn.innerHTML = 'Select Account'
}
})
}
@ -645,18 +648,18 @@ function populateAuthAccounts(){
<div class="settingsAuthAccountRight">
<div class="settingsAuthAccountDetails">
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.username')}</div>
<div class="settingsAuthAccountDetailTitle">Username</div>
<div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
</div>
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.uuid')}</div>
<div class="settingsAuthAccountDetailTitle">UUID</div>
<div class="settingsAuthAccountDetailValue">${acc.uuid}</div>
</div>
</div>
<div class="settingsAuthAccountActions">
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>' + Lang.queryJS('settings.authAccountPopulate.selectedAccount') : '>' + Lang.queryJS('settings.authAccountPopulate.selectAccount')}</button>
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account &#10004;' : '>Select Account'}</button>
<div class="settingsAuthAccountWrapper">
<button class="settingsAuthAccountLogOut">${Lang.queryJS('settings.authAccountPopulate.logout')}</button>
<button class="settingsAuthAccountLogOut">Log Out</button>
</div>
</div>
</div>
@ -736,7 +739,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 || mdl.rawModule.type === Type.FabricMod){
if(mdl.rawModule.type === Type.ForgeMod || mdl.rawModule.type === Type.LiteMod || mdl.rawModule.type === Type.LiteLoader){
if(mdl.getRequired().value){
@ -870,7 +873,7 @@ async function resolveDropinModsForUI(){
<div class="settingsModDetails">
<span class="settingsModName">${dropin.name}</span>
<div class="settingsDropinRemoveWrapper">
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">${Lang.queryJS('settings.dropinMods.removeButton')}</button>
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">Remove</button>
</div>
</div>
</div>
@ -898,9 +901,9 @@ function bindDropinModsRemoveButton(){
document.getElementById(fullName).remove()
} else {
setOverlayContent(
Lang.queryJS('settings.dropinMods.deleteFailedTitle', { fullName }),
Lang.queryJS('settings.dropinMods.deleteFailedMessage'),
Lang.queryJS('settings.dropinMods.okButton')
`Failed to Delete<br>Drop-in Mod ${fullName}`,
'Make sure the file is not in use and try again.',
'Okay'
)
setOverlayHandler(null)
toggleOverlay(true)
@ -953,9 +956,9 @@ function saveDropinModConfiguration(){
DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => {
if(!isOverlayVisible()){
setOverlayContent(
Lang.queryJS('settings.dropinMods.failedToggleTitle'),
'Failed to Toggle<br>One or More Drop-in Mods',
err.message,
Lang.queryJS('settings.dropinMods.okButton')
'Okay'
)
setOverlayHandler(null)
toggleOverlay(true)
@ -1090,7 +1093,7 @@ async function loadSelectedServerOnModsTab(){
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
</svg>
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
<span class="serverListingStarTooltip">Main Server</span>
</div>` : ''}
</div>
</div>
@ -1341,19 +1344,19 @@ async function populateJavaExecDetails(execPath){
const details = await validateSelectedJvm(ensureJavaDirIsRoot(execPath), server.effectiveJavaOptions.supported)
if(details != null) {
settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.selectedJava', { version: details.semverStr, vendor: details.vendor })
settingsJavaExecDetails.innerHTML = `Selected: Java ${details.semverStr} (${details.vendor})`
} else {
settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.invalidSelection')
settingsJavaExecDetails.innerHTML = 'Invalid Selection'
}
}
function populateJavaReqDesc(server) {
settingsJavaReqDesc.innerHTML = Lang.queryJS('settings.java.requiresJava', { major: server.effectiveJavaOptions.suggestedMajor })
settingsJavaReqDesc.innerHTML = `Requires Java ${server.effectiveJavaOptions.suggestedMajor} x64.`
}
function populateJvmOptsLink(server) {
const major = server.effectiveJavaOptions.suggestedMajor
settingsJvmOptsLink.innerHTML = Lang.queryJS('settings.java.availableOptions', { major: major })
settingsJvmOptsLink.innerHTML = `Available Options for Java ${major} (HotSpot VM)`
if(major >= 12) {
settingsJvmOptsLink.href = `https://docs.oracle.com/en/java/javase/${major}/docs/specs/man/java.html#extra-options-for-java`
}
@ -1430,11 +1433,11 @@ function isPrerelease(version){
function populateVersionInformation(version, valueElement, titleElement, checkElement){
valueElement.innerHTML = version
if(isPrerelease(version)){
titleElement.innerHTML = Lang.queryJS('settings.about.preReleaseTitle')
titleElement.innerHTML = 'Pre-release'
titleElement.style.color = '#ff886d'
checkElement.style.background = '#ff886d'
} else {
titleElement.innerHTML = Lang.queryJS('settings.about.stableReleaseTitle')
titleElement.innerHTML = 'Stable Release'
titleElement.style.color = null
checkElement.style.background = null
}
@ -1473,7 +1476,7 @@ function populateReleaseNotes(){
},
timeout: 2500
}).catch(err => {
settingsAboutChangelogText.innerHTML = Lang.queryJS('settings.about.releaseNotesFailed')
settingsAboutChangelogText.innerHTML = 'Failed to load release notes.'
})
}
@ -1521,27 +1524,27 @@ function settingsUpdateButtonStatus(text, disabled = false, handler = null){
*/
function populateSettingsUpdateInformation(data){
if(data != null){
settingsUpdateTitle.innerHTML = isPrerelease(data.version) ? Lang.queryJS('settings.updates.newPreReleaseTitle') : Lang.queryJS('settings.updates.newReleaseTitle')
settingsUpdateTitle.innerHTML = `New ${isPrerelease(data.version) ? 'Pre-release' : 'Release'} Available`
settingsUpdateChangelogCont.style.display = null
settingsUpdateChangelogTitle.innerHTML = data.releaseName
settingsUpdateChangelogText.innerHTML = data.releaseNotes
populateVersionInformation(data.version, settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck)
if(process.platform === 'darwin'){
settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadButton'), false, () => {
settingsUpdateButtonStatus('Download from GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Close the launcher and run the dmg to update.</span>', false, () => {
shell.openExternal(data.darwindownload)
})
} else {
settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadingButton'), true)
settingsUpdateButtonStatus('Downloading..', true)
}
} else {
settingsUpdateTitle.innerHTML = Lang.queryJS('settings.updates.latestVersionTitle')
settingsUpdateTitle.innerHTML = 'You Are Running the Latest Version'
settingsUpdateChangelogCont.style.display = 'none'
populateVersionInformation(remote.app.getVersion(), settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck)
settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkForUpdatesButton'), false, () => {
settingsUpdateButtonStatus('Check for Updates', false, () => {
if(!isDev){
ipcRenderer.send('autoUpdateAction', 'checkForUpdate')
settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkingForUpdatesButton'), true)
settingsUpdateButtonStatus('Checking for Updates..', true)
}
})
}

View File

@ -9,6 +9,7 @@ const { Type } = require('helios-distribution-types')
const AuthManager = require('./assets/js/authmanager')
const ConfigManager = require('./assets/js/configmanager')
const { DistroAPI } = require('./assets/js/distromanager')
const Lang = require('./assets/js/langloader')
let rscShouldLoad = false
let fatalStartupError = false
@ -114,9 +115,9 @@ function showFatalStartupError(){
$('#loadingContainer').fadeOut(250, () => {
document.getElementById('overlayContainer').style.background = 'none'
setOverlayContent(
Lang.queryJS('uibinder.startup.fatalErrorTitle'),
Lang.queryJS('uibinder.startup.fatalErrorMessage'),
Lang.queryJS('uibinder.startup.closeButton')
'Fatal Error: Unable to Load Distribution Index',
'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.',
'Close'
)
setOverlayHandler(() => {
const window = remote.getCurrentWindow()
@ -163,7 +164,7 @@ function syncModConfigurations(data){
for(let mdl of mdls){
const type = mdl.rawModule.type
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
if(!mdl.getRequired().value){
const mdlID = mdl.getVersionlessMavenIdentifier()
if(modsOld[mdlID] == null){
@ -198,7 +199,7 @@ function syncModConfigurations(data){
for(let mdl of mdls){
const type = mdl.rawModule.type
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
} else {
@ -253,7 +254,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 || type === Type.FabricMod){
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
// It is optional.
if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
@ -332,12 +333,10 @@ async function validateSelectedAccount(){
ConfigManager.save()
const accLen = Object.keys(ConfigManager.getAuthAccounts()).length
setOverlayContent(
Lang.queryJS('uibinder.validateAccount.failedMessageTitle'),
accLen > 0
? Lang.queryJS('uibinder.validateAccount.failedMessage', { 'account': selectedAcc.displayName })
: Lang.queryJS('uibinder.validateAccount.failedMessageSelectAnotherAccount', { 'account': selectedAcc.displayName }),
Lang.queryJS('uibinder.validateAccount.loginButton'),
Lang.queryJS('uibinder.validateAccount.selectAnotherAccountButton')
'Failed to Refresh Login',
`We were unable to refresh the login for <strong>${selectedAcc.displayName}</strong>. Please ${accLen > 0 ? 'select another account or ' : ''} login again.`,
'Login',
'Select Another Account'
)
setOverlayHandler(() => {

View File

@ -10,7 +10,6 @@ const {ipcRenderer, shell, webFrame} = require('electron')
const remote = require('@electron/remote')
const isDev = require('./assets/js/isdev')
const { LoggerUtil } = require('helios-core')
const Lang = require('./assets/js/langloader')
const loggerUICore = LoggerUtil.getLogger('UICore')
const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater')
@ -43,7 +42,7 @@ if(!isDev){
switch(arg){
case 'checking-for-update':
loggerAutoUpdater.info('Checking for update..')
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkingForUpdateButton'), true)
settingsUpdateButtonStatus('Checking for Updates..', true)
break
case 'update-available':
loggerAutoUpdater.info('New update available', info.version)
@ -57,7 +56,7 @@ if(!isDev){
break
case 'update-downloaded':
loggerAutoUpdater.info('Update ' + info.version + ' ready to be installed.')
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.installNowButton'), false, () => {
settingsUpdateButtonStatus('Install Now', false, () => {
if(!isDev){
ipcRenderer.send('autoUpdateAction', 'installUpdateNow')
}
@ -66,7 +65,7 @@ if(!isDev){
break
case 'update-not-available':
loggerAutoUpdater.info('No new update found.')
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkForUpdatesButton'))
settingsUpdateButtonStatus('Check for Updates')
break
case 'ready':
updateCheckListener = setInterval(() => {

View File

@ -1,20 +0,0 @@
# Custom Language File for Launcher Customizer
[ejs.app]
title = "Helios Launcher"
[ejs.landing]
mediaGitHubURL = "https://github.com/dscalzi/HeliosLauncher"
mediaTwitterURL = "#"
mediaInstagramURL = "#"
mediaYouTubeURL = "#"
mediaDiscordURL = "https://discord.gg/zNWUXdt"
[ejs.settings]
sourceGithubLink = "https://github.com/dscalZi/HeliosLauncher"
supportLink = "https://github.com/dscalZi/HeliosLauncher/issues"
[ejs.welcome]
welcomeHeader = "WELCOME TO WESTEROSCRAFT"
welcomeDescription = "Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you arent afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming."
welcomeDescCTA = "You are just a few clicks away from Westeros."

View File

@ -0,0 +1,49 @@
{
"html": {
"avatarOverlay": "Edit"
},
"js": {
"login": {
"error": {
"invalidValue": "* Invalid Value",
"requiredValue": "* Required",
"userMigrated": {
"title": "Error During Login:<br>Invalid Credentials",
"desc": "You've attempted to login with a migrated account. Try again using the account email as the username."
},
"invalidCredentials": {
"title": "Error During Login:<br>Invalid Credentials",
"desc": "The email or password you've entered is incorrect. Please try again."
},
"rateLimit": {
"title": "Error During Login:<br>Too Many Attempts",
"desc": "There have been too many login attempts with this account recently. Please try again later."
},
"noInternet": {
"title": "Error During Login:<br>No Internet Connection",
"desc": "You must be connected to the internet in order to login. Please connect and try again."
},
"authDown": {
"title": "Error During Login:<br>Authentication Server Offline",
"desc": "Mojang's authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on <a href=\"https://help.mojang.com/\">Mojang's help portal</a>."
},
"notPaid": {
"title": "Error During Login:<br>Game Not Purchased",
"desc": "The account you are trying to login with has not purchased a copy of Minecraft.<br>You may purchase a copy on <a href=\"https://minecraft.net/\">Minecraft.net</a>"
},
"unknown": {
"title": "Error During Login:<br>Unknown Error"
}
},
"login": "LOGIN",
"loggingIn": "LOGGING IN",
"success": "SUCCESS",
"tryAgain": "Try Again"
},
"landing": {
"launch": {
"pleaseWait": "Please wait.."
}
}
}
}

View File

@ -1,7 +1,7 @@
<div id="loginOptionsContainer" style="display: none;">
<div id="loginOptionsContent">
<div class="loginOptionsMainContent">
<h2><%- lang('loginOptions.loginOptionsTitle') %></h2>
<h2>Login Options</h2>
<div class="loginOptionActions">
<div class="loginOptionButtonContainer">
<button id="loginOptionMicrosoft" class="loginOptionButton">
@ -11,7 +11,7 @@
<path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" />
</svg>
<span><%- lang('loginOptions.loginWithMicrosoft') %></span>
<span>Login with Microsoft</span>
</button>
</div>
<div class="loginOptionButtonContainer">
@ -21,12 +21,12 @@
<path d="M2.598.022h7.07L9.665 7c-.003 1.334-1.113 2.46-2.402 2.654H0V2.542C.134 1.2 1.3.195 2.598.022z" fill="#db2331" />
<path d="M1.54 2.844c.314-.76 1.31-.46 1.954-.528.785-.083 1.503.272 2.1.758l.164-.9c.327.345.587.756.964 1.052.28.254.655-.342.86-.013.42.864.408 1.86.54 2.795l-.788-.373C6.9 4.17 5.126 3.052 3.656 3.685c-1.294.592-1.156 2.65.06 3.255 1.354.703 2.953.51 4.405.292-.07.42-.34.87-.834.816l-4.95.002c-.5.055-.886-.413-.838-.89l.04-4.315z" fill="#fff" />
</svg>
<span><%- lang('loginOptions.loginWithMojang') %></span>
<span>Login with Mojang</span>
</button>
</div>
</div>
<div id="loginOptionCancelContainer" style="display: none;">
<button id="loginOptionCancelButton"><%- lang('loginOptions.cancelButton') %></button>
<button id="loginOptionCancelButton">Cancel</button>
</div>
</div>
</div>

View File

@ -1,29 +1,29 @@
<div id="overlayContainer" style="display: none;">
<div id="serverSelectContent" style="display: none;">
<span id="serverSelectHeader"><%- lang('overlay.serverSelectHeader') %></span>
<span id="serverSelectHeader">Available Servers</span>
<div id="serverSelectList">
<div id="serverSelectListScrollable">
<!-- Server listings populated here. -->
</div>
</div>
<div id="serverSelectActions">
<button id="serverSelectConfirm" class="overlayKeybindEnter" type="submit"><%- lang('overlay.serverSelectConfirm') %></button>
<button id="serverSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
<div id="serverSelectCancelWrapper">
<button id="serverSelectCancel" class="overlayKeybindEsc"><%- lang('overlay.serverSelectCancel') %></button>
<button id="serverSelectCancel" class="overlayKeybindEsc">Cancel</button>
</div>
</div>
</div>
<div id="accountSelectContent" style="display: none;">
<span id="accountSelectHeader"><%- lang('overlay.accountSelectHeader') %></span>
<span id="accountSelectHeader">Select an Account</span>
<div id="accountSelectList">
<div id="accountSelectListScrollable">
<!-- Accounts populated here. -->
</div>
</div>
<div id="accountSelectActions">
<button id="accountSelectConfirm" class="overlayKeybindEnter" type="submit"><%- lang('overlay.accountSelectConfirm') %></button>
<button id="accountSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
<div id="accountSelectCancelWrapper">
<button id="accountSelectCancel" class="overlayKeybindEsc"><%- lang('overlay.accountSelectCancel') %></button>
<button id="accountSelectCancel" class="overlayKeybindEsc">Cancel</button>
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@
<div id="waitingContent">
<div class="waitingSpinner"></div>
<div id="waitingTextContainer">
<h2><%- lang('waiting.waitingText') %></h2>
<h2>Waiting for Microsoft..</h2>
</div>
</div>
</div>

View File

@ -5,13 +5,13 @@
</div>-->
<div id="welcomeContent">
<img id="welcomeImageSeal" src="assets/images/SealCircle.png"/>
<span id="welcomeHeader"><%- lang('welcome.welcomeHeader') %></span>
<span id="welcomeDescription"><%- lang('welcome.welcomeDescription') %></span>
<span id="welcomeHeader">WELCOME TO WESTEROSCRAFT</span>
<span id="welcomeDescription">Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you arent afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming.</span>
<br>
<span id="welcomeDescCTA"><%- lang('welcome.welcomeDescCTA') %></span>
<span id="welcomeDescCTA">You are just a few clicks away from Westeros.</span>
<button id="welcomeButton">
<div id="welcomeButtonContent">
<%- lang('welcome.continueButton') %>
CONTINUE
<svg id="welcomeSVG" viewBox="0 0 24.87 13.97">
<defs>
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>

View File

@ -360,12 +360,10 @@ 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.
@ -410,7 +408,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 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.
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.
Ex.
@ -445,40 +443,6 @@ 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.

View File

@ -11,10 +11,6 @@ const path = require('path')
const semver = require('semver')
const { pathToFileURL } = require('url')
const { AZURE_CLIENT_ID, MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR, SHELL_OPCODE } = require('./app/assets/js/ipcconstants')
const LangLoader = require('./app/assets/js/langloader')
// Setup Lang
LangLoader.setupLanguage()
// Setup auto updater.
function initAutoUpdater(event, data) {
@ -125,7 +121,7 @@ ipcMain.on(MSFT_OPCODE.OPEN_LOGIN, (ipcEvent, ...arguments_) => {
msftAuthViewSuccess = arguments_[0]
msftAuthViewOnClose = arguments_[1]
msftAuthWindow = new BrowserWindow({
title: LangLoader.queryJS('index.microsoftLoginTitle'),
title: 'Microsoft Login',
backgroundColor: '#222222',
width: 520,
height: 600,
@ -178,7 +174,7 @@ ipcMain.on(MSFT_OPCODE.OPEN_LOGOUT, (ipcEvent, uuid, isLastAccount) => {
msftLogoutSuccess = false
msftLogoutSuccessSent = false
msftLogoutWindow = new BrowserWindow({
title: LangLoader.queryJS('index.microsoftLogoutTitle'),
title: 'Microsoft Logout',
backgroundColor: '#222222',
width: 520,
height: 600,
@ -240,11 +236,7 @@ function createWindow() {
})
remoteMain.enable(win.webContents)
const data = {
bkid: Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)),
lang: (str, placeHolders) => LangLoader.queryEJS(str, placeHolders)
}
Object.entries(data).forEach(([key, val]) => ejse.data(key, val))
ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)))
win.loadURL(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString())