diff --git a/.eslintrc.json b/.eslintrc.json index 006dfc05..dd036706 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,11 @@ { "env": { - "es2017": true, + "es2022": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { - "ecmaVersion": 2019, + "ecmaVersion": 2022, "sourceType": "module" }, "rules": { diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7336703..ee394f1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 18 - name: Set up Python uses: actions/setup-python@v2 diff --git a/.nvmrc b/.nvmrc index 19c7bdba..25bf17fc 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 \ No newline at end of file +18 \ No newline at end of file diff --git a/README.md b/README.md index 7e322311..28c5dbe0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
(formerly Electron Launcher)
-[

gh actions](https://github.com/dscalzi/HeliosLauncher/actions) [downloads](https://github.com/dscalzi/HeliosLauncher/releases) winter-is-coming

+[

gh actions](https://github.com/dscalzi/HeliosLauncher/actions) [downloads](https://github.com/dscalzi/HeliosLauncher/releases) winter-is-coming

Join modded servers without worrying about installing Java, Forge, or other mods. We'll handle that for you.

@@ -84,7 +84,7 @@ This section details the setup of a basic developmentment environment. **System Requirements** -* [Node.js][nodejs] v16 +* [Node.js][nodejs] v18 --- diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index e67984e4..e95bcb29 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -1228,6 +1228,59 @@ body, button { font-size: 12px; } +/* Selected server content container */ +.settingsSelServContainer { + background: rgba(0, 0, 0, 0.25); + width: 75%; + border-radius: 3px; + display: flex; + justify-content: space-between; + margin: 15px 0px; +} + +/* Div which will be populated with the selected server's information. */ +.settingsSelServContent { + display: flex; + align-items: center; + justify-content: flex-start; + padding: 5px 0px; +} + +/* Wrapper container for the switch server button. */ +.settingsSwitchServerContainer { + display: flex; + align-items: center; + padding: 15px; +} + +/* Button to switch server configurations on the mods tab. */ +.settingsSwitchServerButton { + opacity: 0; + border: 1px solid rgb(255, 255, 255); + color: rgb(255, 255, 255); + background: none; + font-size: 12px; + border-radius: 3px; + font-family: 'Avenir Medium'; + transition: 0.25s ease; + cursor: pointer; + outline: none; +} +.settingsSwitchServerButton:hover, +.settingsSwitchServerButton:focus { + box-shadow: 0px 0px 20px rgb(255, 255, 255); + background: rgba(255, 255, 255, 0.25); +} +.settingsSwitchServerButton:active { + box-shadow: 0px 0px 20px rgb(187, 187, 187); + background: rgba(187, 187, 187, 0.25); + border: 1px solid rgb(187, 187, 187); + color: rgb(187, 187, 187); +} +.settingsSelServContainer:hover .settingsSwitchServerButton { + opacity: 1; +} + /* Remove spin button from number inputs. */ #settingsContainer input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; @@ -1638,59 +1691,6 @@ input:checked + .toggleSwitchSlider:before { * Settings View (Mods Tab) * * */ -/* Selected server content container */ -#settingsSelServContainer { - background: rgba(0, 0, 0, 0.25); - width: 75%; - border-radius: 3px; - display: flex; - justify-content: space-between; - margin: 15px 0px; -} - -/* Div which will be populated with the selected server's information. */ -#settingsSelServContent { - display: flex; - align-items: center; - justify-content: flex-start; - padding: 5px 0px; -} - -/* Wrapper container for the switch server button. */ -#settingsSwitchServerContainer { - display: flex; - align-items: center; - padding: 15px; -} - -/* Button to switch server configurations on the mods tab. */ -#settingsSwitchServerButton { - opacity: 0; - border: 1px solid rgb(255, 255, 255); - color: rgb(255, 255, 255); - background: none; - font-size: 12px; - border-radius: 3px; - font-family: 'Avenir Medium'; - transition: 0.25s ease; - cursor: pointer; - outline: none; -} -#settingsSwitchServerButton:hover, -#settingsSwitchServerButton:focus { - box-shadow: 0px 0px 20px rgb(255, 255, 255); - background: rgba(255, 255, 255, 0.25); -} -#settingsSwitchServerButton:active { - box-shadow: 0px 0px 20px rgb(187, 187, 187); - background: rgba(187, 187, 187, 0.25); - border: 1px solid rgb(187, 187, 187); - color: rgb(187, 187, 187); -} -#settingsSelServContainer:hover #settingsSwitchServerButton { - opacity: 1; -} - /* Main content container for the mod elements. */ #settingsModsContainer { width: 75%; diff --git a/app/assets/js/assetexec.js b/app/assets/js/assetexec.js index 4d8061a3..f1af291b 100644 --- a/app/assets/js/assetexec.js +++ b/app/assets/js/assetexec.js @@ -6,8 +6,11 @@ if(target == null){ } let tracker = new target(...(process.argv.splice(3))) +const { LoggerUtil } = require('helios-core') +const logger = LoggerUtil.getLogger('AssetExec') + //const tracker = new AssetGuard(process.argv[2], process.argv[3]) -console.log('AssetExec Started') +logger.info('AssetExec Started') // Temporary for debug purposes. process.on('unhandledRejection', r => console.log(r)) @@ -66,6 +69,6 @@ process.on('message', (msg) => { }) process.on('disconnect', () => { - console.log('AssetExec Disconnected') + logger.info('AssetExec Disconnected') process.exit(0) }) \ No newline at end of file diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index 75f6bc54..d447854a 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -5,6 +5,8 @@ const child_process = require('child_process') const crypto = require('crypto') const EventEmitter = require('events') const fs = require('fs-extra') +const { LoggerUtil } = require('helios-core') +const nodeDiskInfo = require('node-disk-info') const StreamZip = require('node-stream-zip') const path = require('path') const Registry = require('winreg') @@ -16,6 +18,8 @@ const ConfigManager = require('./configmanager') const DistroManager = require('./distromanager') const isDev = require('./isdev') +const isARM64 = process.arch === 'arm64' + // Classes /** Class representing a base asset. */ @@ -214,6 +218,7 @@ class JavaGuard extends EventEmitter { constructor(mcVersion){ super() this.mcVersion = mcVersion + this.logger = LoggerUtil.getLogger('JavaGuard') } /** @@ -299,7 +304,8 @@ class JavaGuard extends EventEmitter { break } - const url = `https://corretto.aws/downloads/latest/amazon-corretto-${major}-x64-${sanitizedOS}-jdk.${ext}` + const arch = isARM64 ? 'aarch64' : 'x64' + const url = `https://corretto.aws/downloads/latest/amazon-corretto-${major}-${arch}-${sanitizedOS}-jdk.${ext}` return new Promise((resolve, reject) => { request.head({url, json: true}, (err, resp) => { @@ -342,6 +348,9 @@ class JavaGuard extends EventEmitter { * @returns {boolean} True if the path points to a Java executable, otherwise false. */ static isJavaExecPath(pth){ + if(pth == null) { + return false + } if(process.platform === 'win32'){ return pth.endsWith(path.join('bin', 'javaw.exe')) } else if(process.platform === 'darwin'){ @@ -447,7 +456,7 @@ class JavaGuard extends EventEmitter { if(props[i].indexOf('sun.arch.data.model') > -1){ let arch = props[i].split('=')[1].trim() arch = parseInt(arch) - console.log(props[i].trim()) + this.logger.debug(props[i].trim()) if(arch === 64){ meta.arch = arch ++checksum @@ -457,33 +466,40 @@ class JavaGuard extends EventEmitter { } } else if(props[i].indexOf('java.runtime.version') > -1){ let verString = props[i].split('=')[1].trim() - console.log(props[i].trim()) + this.logger.debug(props[i].trim()) const verOb = JavaGuard.parseJavaRuntimeVersion(verString) + // TODO implement a support matrix eventually. Right now this is good enough + // 1.7-1.16 = Java 8 + // 1.17+ = Java 17 + // Actual support may vary, but we're going with this rule for simplicity. if(verOb.major < 9){ // Java 8 - if(verOb.major === 8 && verOb.update > 52){ + if(!Util.mcVersionAtLeast('1.17', this.mcVersion)){ + if(verOb.major === 8 && verOb.update > 52){ + meta.version = verOb + ++checksum + if(checksum === goal){ + break + } + } + } + } else if(verOb.major >= 17) { + // Java 9+ + if(Util.mcVersionAtLeast('1.17', this.mcVersion)){ meta.version = verOb ++checksum if(checksum === goal){ break } } - } else { - // Java 9+ - if(Util.mcVersionAtLeast('1.13', this.mcVersion)){ - console.log('Java 9+ not yet tested.') - /* meta.version = verOb - ++checksum - if(checksum === goal){ - break - } */ - } } // Space included so we get only the vendor. } else if(props[i].lastIndexOf('java.vendor ') > -1) { let vendorName = props[i].split('=')[1].trim() - console.log(props[i].trim()) + this.logger.debug(props[i].trim()) meta.vendor = vendorName + } else if (props[i].indexOf('os.arch') > -1) { + meta.isARM = props[i].split('=')[1].trim() === 'aarch64' } } @@ -512,7 +528,7 @@ class JavaGuard extends EventEmitter { resolve({valid: false}) } else if(fs.existsSync(binaryExecPath)){ // Workaround (javaw.exe no longer outputs this information.) - console.log(typeof binaryExecPath) + this.logger.debug(typeof binaryExecPath) if(binaryExecPath.indexOf('javaw.exe') > -1) { binaryExecPath.replace('javaw.exe', 'java.exe') } @@ -804,13 +820,20 @@ class JavaGuard extends EventEmitter { // Get possible paths from the registry. let pathSet1 = await JavaGuard._scanRegistry() if(pathSet1.size === 0){ + // Do a manual file system scan of program files. - pathSet1 = new Set([ - ...pathSet1, - ...(await JavaGuard._scanFileSystem('C:\\Program Files\\Java')), - ...(await JavaGuard._scanFileSystem('C:\\Program Files\\Eclipse Foundation')), - ...(await JavaGuard._scanFileSystem('C:\\Program Files\\AdoptOpenJDK')) - ]) + // Check all drives + const driveMounts = nodeDiskInfo.getDiskInfoSync().map(({ mounted }) => mounted) + for(const mount of driveMounts) { + pathSet1 = new Set([ + ...pathSet1, + ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Java`)), + ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Eclipse Adoptium`)), + ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Eclipse Foundation`)), + ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\AdoptOpenJDK`)) + ]) + } + } // Get possible paths from the data directory. @@ -848,6 +871,9 @@ class JavaGuard extends EventEmitter { * @param {string} dataDir The base launcher directory. * @returns {Promise.} A Promise which resolves to the executable path of a valid * x64 Java installation. If none are found, null is returned. + * + * Added: On the system with ARM architecture attempts to find aarch64 Java. + * */ async _darwinJavaValidate(dataDir){ @@ -876,7 +902,16 @@ class JavaGuard extends EventEmitter { pathArr = JavaGuard._sortValidJavaArray(pathArr) if(pathArr.length > 0){ - return pathArr[0].execPath + + // TODO Revise this a bit, seems to work for now. Discovery logic should + // probably just filter out the invalid architectures before it even + // gets to this point. + if (isARM64) { + return pathArr.find(({ isARM }) => isARM)?.execPath ?? null + } else { + return pathArr.find(({ isARM }) => !isARM)?.execPath ?? null + } + } else { return null } @@ -942,6 +977,8 @@ class JavaGuard extends EventEmitter { */ class AssetGuard extends EventEmitter { + static logger = LoggerUtil.getLogger('AssetGuard') + /** * Create an instance of AssetGuard. * On creation the object's properties are never-null default @@ -1100,7 +1137,8 @@ class AssetGuard extends EventEmitter { * @returns {Promise.} An empty promise to indicate the extraction has completed. */ static _extractPackXZ(filePaths, javaExecutable){ - console.log('[PackXZExtract] Starting') + const extractLogger = LoggerUtil.getLogger('PackXZExtract') + extractLogger.info('Starting') return new Promise((resolve, reject) => { let libPath @@ -1117,13 +1155,13 @@ class AssetGuard extends EventEmitter { const filePath = filePaths.join(',') const child = child_process.spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath]) child.stdout.on('data', (data) => { - console.log('[PackXZExtract]', data.toString('utf8')) + extractLogger.info(data.toString('utf8')) }) child.stderr.on('data', (data) => { - console.log('[PackXZExtract]', data.toString('utf8')) + extractLogger.info(data.toString('utf8')) }) child.on('close', (code, signal) => { - console.log('[PackXZExtract]', 'Exited with code', code) + extractLogger.info('Exited with code', code) resolve() }) }) @@ -1189,7 +1227,7 @@ class AssetGuard extends EventEmitter { if(!fs.existsSync(versionFile) || force){ const url = await self._getVersionDataUrl(version) //This download will never be tracked as it's essential and trivial. - console.log('Preparing download of ' + version + ' assets.') + AssetGuard.logger.info('Preparing download of ' + version + ' assets.') fs.ensureDirSync(versionPath) const stream = request(url).pipe(fs.createWriteStream(versionFile)) stream.on('finish', () => { @@ -1271,7 +1309,7 @@ class AssetGuard extends EventEmitter { let data = null if(!fs.existsSync(assetIndexLoc) || force){ - console.log('Downloading ' + versionData.id + ' asset index.') + AssetGuard.logger.info('Downloading ' + versionData.id + ' asset index.') fs.ensureDirSync(indexPath) const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc)) stream.on('finish', () => { @@ -1542,9 +1580,10 @@ class AssetGuard extends EventEmitter { // Java (Category=''') Validation (download) Functions // #region - _enqueueOpenJDK(dataDir){ + _enqueueOpenJDK(dataDir, mcVersion){ return new Promise((resolve, reject) => { - JavaGuard._latestOpenJDK('8').then(verData => { + const major = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' + JavaGuard._latestOpenJDK(major).then(verData => { if(verData != null){ dataDir = path.join(dataDir, 'runtime', 'x64') @@ -1559,9 +1598,9 @@ class AssetGuard extends EventEmitter { // Tar.gz let h = null fs.createReadStream(a.to) - .on('error', err => console.log(err)) + .on('error', err => AssetGuard.logger.error(err)) .pipe(zlib.createGunzip()) - .on('error', err => console.log(err)) + .on('error', err => AssetGuard.logger.error(err)) .pipe(tar.extract(dataDir, { map: (header) => { if(h == null){ @@ -1569,11 +1608,11 @@ class AssetGuard extends EventEmitter { } } })) - .on('error', err => console.log(err)) + .on('error', err => AssetGuard.logger.error(err)) .on('finish', () => { fs.unlink(a.to, err => { if(err){ - console.log(err) + AssetGuard.logger.error(err) } if(h.indexOf('/') > -1){ h = h.substring(0, h.indexOf('/')) @@ -1606,14 +1645,14 @@ class AssetGuard extends EventEmitter { const entries = await zip.entries() pos = path.join(runtimeDir, Object.keys(entries)[0]) - console.log('Extracting jdk..') + AssetGuard.logger.info('Extracting jdk..') await zip.extract(null, runtimeDir) - console.log('Cleaning up..') + AssetGuard.logger.info('Cleaning up..') await fs.remove(zipPath) - console.log('Jdk extraction complete.') + AssetGuard.logger.info('Jdk extraction complete.') } catch(err) { - console.log(err) + AssetGuard.logger.error(err) } finally { zip.close() self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) @@ -1679,7 +1718,7 @@ class AssetGuard extends EventEmitter { const dlQueue = dlTracker.dlqueue if(dlQueue.length > 0){ - console.log('DLQueue', dlQueue) + AssetGuard.logger.info('DLQueue', dlQueue) async.eachLimit(dlQueue, limit, (asset, cb) => { @@ -1696,7 +1735,7 @@ class AssetGuard extends EventEmitter { const contentLength = parseInt(resp.headers['content-length']) if(contentLength !== asset.size){ - console.log(`WARN: Got ${contentLength} bytes for ${asset.id}: Expected ${asset.size}`) + AssetGuard.logger.warn(`WARN: Got ${contentLength} bytes for ${asset.id}: Expected ${asset.size}`) doHashCheck = true // Adjust download @@ -1713,9 +1752,9 @@ class AssetGuard extends EventEmitter { if(doHashCheck){ const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash) if(v){ - console.log(`Hashes match for ${asset.id}, byte mismatch is an issue in the distro index.`) + AssetGuard.logger.warn(`Hashes match for ${asset.id}, byte mismatch is an issue in the distro index.`) } else { - console.error(`Hashes do not match, ${asset.id} may be corrupted.`) + AssetGuard.logger.error(`Hashes do not match, ${asset.id} may be corrupted.`) } } @@ -1727,7 +1766,7 @@ class AssetGuard extends EventEmitter { } else { req.abort() - console.log(`Failed to download ${asset.id}(${typeof asset.from === 'object' ? asset.from.url : asset.from}). Response code ${resp.statusCode}`) + AssetGuard.logger.error(`Failed to download ${asset.id}(${typeof asset.from === 'object' ? asset.from.url : asset.from}). Response code ${resp.statusCode}`) self.progress += asset.size*1 self.emit('progress', 'download', self.progress, self.totaldlsize) cb() @@ -1748,9 +1787,9 @@ class AssetGuard extends EventEmitter { }, (err) => { if(err){ - console.log('An item in ' + identifier + ' failed to process') + AssetGuard.logger.warn('An item in ' + identifier + ' failed to process') } else { - console.log('All ' + identifier + ' have been processed successfully') + AssetGuard.logger.info('All ' + identifier + ' have been processed successfully') } //self.totaldlsize -= dlTracker.dlsize @@ -1869,4 +1908,4 @@ module.exports = { JavaGuard, Asset, Library -} \ No newline at end of file +} diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index 3dff9502..c3f74897 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -1,8 +1,9 @@ const fs = require('fs-extra') +const { LoggerUtil } = require('helios-core') const os = require('os') const path = require('path') -const logger = require('./loggerutil')('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold') +const logger = LoggerUtil.getLogger('ConfigManager') const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME) // TODO change @@ -63,6 +64,27 @@ function resolveMinRAM(){ return resolveMaxRAM() } +/** + * TODO Copy pasted, should be in a utility file. + * + * Returns true if the actual version is greater than + * or equal to the desired version. + * + * @param {string} desired The desired version. + * @param {string} actual The actual version. + */ +function mcVersionAtLeast(desired, actual){ + const des = desired.split('.') + const act = actual.split('.') + + for(let i=0; i= parseInt(des[i]))){ + return false + } + } + return true +} + /** * Three types of values: * Static = Explicitly declared. @@ -71,17 +93,6 @@ function resolveMinRAM(){ */ const DEFAULT_CONFIG = { settings: { - java: { - minRAM: resolveMinRAM(), - maxRAM: resolveMaxRAM(), // Dynamic - executable: null, - jvmOptions: [ - '-XX:+UseConcMarkSweepGC', - '-XX:+CMSIncrementalMode', - '-XX:-UseAdaptiveSizePolicy', - '-Xmn128M' - ], - }, game: { resWidth: 1280, resHeight: 720, @@ -103,7 +114,8 @@ const DEFAULT_CONFIG = { selectedServer: null, // Resolved selectedAccount: null, authenticationDatabase: {}, - modConfigurations: [] + modConfigurations: [], + javaConfig: {} } let config = null @@ -144,8 +156,8 @@ exports.load = function(){ doValidate = true } catch (err){ logger.error(err) - logger.log('Configuration file contains malformed JSON or is corrupt.') - logger.log('Generating a new configuration file.') + logger.info('Configuration file contains malformed JSON or is corrupt.') + logger.info('Generating a new configuration file.') fs.ensureDirSync(path.join(configPath, '..')) config = DEFAULT_CONFIG exports.save() @@ -155,7 +167,7 @@ exports.load = function(){ exports.save() } } - logger.log('Successfully Loaded') + logger.info('Successfully Loaded') } /** @@ -177,7 +189,7 @@ function validateKeySet(srcObj, destObj){ if(srcObj == null){ srcObj = {} } - const validationBlacklist = ['authenticationDatabase'] + const validationBlacklist = ['authenticationDatabase', 'javaConfig'] const keys = Object.keys(srcObj) for(let i=0; i} An array of the additional arguments for JVM initialization. */ -exports.getJVMOptions = function(def = false){ - return !def ? config.settings.java.jvmOptions : DEFAULT_CONFIG.settings.java.jvmOptions +exports.getJVMOptions = function(serverid){ + return config.javaConfig[serverid].jvmOptions } /** @@ -594,11 +660,12 @@ exports.getJVMOptions = function(def = false){ * such as memory allocation, will be dynamically resolved and should not be * included in this value. * + * @param {string} serverid The server id. * @param {Array.} jvmOptions An array of the new additional arguments for JVM * initialization. */ -exports.setJVMOptions = function(jvmOptions){ - config.settings.java.jvmOptions = jvmOptions +exports.setJVMOptions = function(serverid, jvmOptions){ + config.javaConfig[serverid].jvmOptions = jvmOptions } // Game Settings diff --git a/app/assets/js/discordwrapper.js b/app/assets/js/discordwrapper.js index 7b0ef03e..1e44e867 100644 --- a/app/assets/js/discordwrapper.js +++ b/app/assets/js/discordwrapper.js @@ -1,7 +1,9 @@ // Work in progress -const logger = require('./loggerutil')('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold') +const { LoggerUtil } = require('helios-core') -const {Client} = require('discord-rpc-patch') +const logger = LoggerUtil.getLogger('DiscordWrapper') + +const { Client } = require('discord-rpc-patch') let client let activity @@ -21,15 +23,15 @@ exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting } client.on('ready', () => { - logger.log('Discord RPC Connected') + logger.info('Discord RPC Connected') client.setActivity(activity) }) client.login({clientId: genSettings.clientId}).catch(error => { if(error.message.includes('ENOENT')) { - logger.log('Unable to initialize Discord Rich Presence, no client detected.') + logger.info('Unable to initialize Discord Rich Presence, no client detected.') } else { - logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error) + logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error) } }) } diff --git a/app/assets/js/distromanager.js b/app/assets/js/distromanager.js index 90a2ab8d..4fcc6852 100644 --- a/app/assets/js/distromanager.js +++ b/app/assets/js/distromanager.js @@ -1,9 +1,11 @@ const fs = require('fs') const path = require('path') const request = require('request') +const { LoggerUtil } = require('helios-core') const ConfigManager = require('./configmanager') -const logger = require('./loggerutil')('%c[DistroManager]', 'color: #a02d2a; font-weight: bold') + +const logger = LoggerUtil.getLogger('DistroManager') /** * Represents the download information @@ -118,7 +120,7 @@ class Module { * @returns {Module} The parsed Module. */ static fromJSON(json, serverid){ - return new Module(json.id, json.name, json.type, json.required, json.artifact, json.subModules, serverid) + return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid) } /** @@ -143,9 +145,10 @@ class Module { } } - constructor(id, name, type, required, artifact, subModules, serverid) { + constructor(id, name, type, classpath, required, artifact, subModules, serverid) { this.identifier = id this.type = type + this.classpath = classpath this._resolveMetaData() this.name = name this.required = Required.fromJSON(required) @@ -306,6 +309,13 @@ class Module { return this.type } + /** + * @returns {boolean} Whether or not this library should be on the classpath. + */ + getClasspath(){ + return this.classpath ?? true + } + } exports.Module @@ -591,10 +601,10 @@ exports.pullLocal = function(){ exports.setDevMode = function(value){ if(value){ - logger.log('Developer mode enabled.') - logger.log('If you don\'t know what that means, revert immediately.') + logger.info('Developer mode enabled.') + logger.info('If you don\'t know what that means, revert immediately.') } else { - logger.log('Developer mode disabled.') + logger.info('Developer mode disabled.') } DEV_MODE = value } diff --git a/app/assets/js/loggerutil.js b/app/assets/js/loggerutil.js deleted file mode 100644 index 73899418..00000000 --- a/app/assets/js/loggerutil.js +++ /dev/null @@ -1,32 +0,0 @@ -class LoggerUtil { - - constructor(prefix, style){ - this.prefix = prefix - this.style = style - } - - log(){ - console.log.apply(null, [this.prefix, this.style, ...arguments]) - } - - info(){ - console.info.apply(null, [this.prefix, this.style, ...arguments]) - } - - warn(){ - console.warn.apply(null, [this.prefix, this.style, ...arguments]) - } - - debug(){ - console.debug.apply(null, [this.prefix, this.style, ...arguments]) - } - - error(){ - console.error.apply(null, [this.prefix, this.style, ...arguments]) - } - -} - -module.exports = function (prefix, style){ - return new LoggerUtil(prefix, style) -} \ No newline at end of file diff --git a/app/assets/js/preloader.js b/app/assets/js/preloader.js index 792c5304..f43eda8e 100644 --- a/app/assets/js/preloader.js +++ b/app/assets/js/preloader.js @@ -1,14 +1,16 @@ -const {ipcRenderer} = require('electron') -const fs = require('fs-extra') -const os = require('os') -const path = require('path') +const {ipcRenderer} = require('electron') +const fs = require('fs-extra') +const os = require('os') +const path = require('path') -const ConfigManager = require('./configmanager') -const DistroManager = require('./distromanager') -const LangLoader = require('./langloader') -const logger = require('./loggerutil')('%c[Preloader]', 'color: #a02d2a; font-weight: bold') +const ConfigManager = require('./configmanager') +const DistroManager = require('./distromanager') +const LangLoader = require('./langloader') +const { LoggerUtil } = require('helios-core') -logger.log('Loading..') +const logger = LoggerUtil.getLogger('Preloader') + +logger.info('Loading..') // Load ConfigManager ConfigManager.load() @@ -21,7 +23,7 @@ function onDistroLoad(data){ // Resolve the selected server if its value has yet to be set. if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){ - logger.log('Determining default selected server..') + logger.info('Determining default selected server..') ConfigManager.setSelectedServer(data.getMainServer().getID()) ConfigManager.save() } @@ -31,26 +33,26 @@ function onDistroLoad(data){ // Ensure Distribution is downloaded and cached. DistroManager.pullRemote().then((data) => { - logger.log('Loaded distribution index.') + logger.info('Loaded distribution index.') onDistroLoad(data) }).catch((err) => { - logger.log('Failed to load distribution index.') + logger.info('Failed to load distribution index.') logger.error(err) - logger.log('Attempting to load an older version of the distribution index.') + logger.info('Attempting to load an older version of the distribution index.') // Try getting a local copy, better than nothing. DistroManager.pullLocal().then((data) => { - logger.log('Successfully loaded an older version of the distribution index.') + logger.info('Successfully loaded an older version of the distribution index.') onDistroLoad(data) }).catch((err) => { - logger.log('Failed to load an older version of the distribution index.') - logger.log('Application cannot run.') + logger.info('Failed to load an older version of the distribution index.') + logger.info('Application cannot run.') logger.error(err) onDistroLoad(null) @@ -64,6 +66,6 @@ fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => if(err){ logger.warn('Error while cleaning natives directory', err) } else { - logger.log('Cleaned natives directory.') + logger.info('Cleaned natives directory.') } }) \ No newline at end of file diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js index bf907c35..5efc1e62 100644 --- a/app/assets/js/processbuilder.js +++ b/app/assets/js/processbuilder.js @@ -2,6 +2,7 @@ const AdmZip = require('adm-zip') const child_process = require('child_process') const crypto = require('crypto') const fs = require('fs-extra') +const { LoggerUtil } = require('helios-core') const os = require('os') const path = require('path') const { URL } = require('url') @@ -9,9 +10,8 @@ const { URL } = require('url') const { Util, Library } = require('./assetguard') const ConfigManager = require('./configmanager') const DistroManager = require('./distromanager') -const LoggerUtil = require('./loggerutil') -const logger = LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold') +const logger = LoggerUtil.getLogger('ProcessBuilder') class ProcessBuilder { @@ -40,7 +40,7 @@ class ProcessBuilder { const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex')) process.throwDeprecation = true this.setupLiteLoader() - logger.log('Using liteloader:', this.usingLiteLoader) + logger.info('Using liteloader:', this.usingLiteLoader) const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules()) // Mod list below 1.13 @@ -59,9 +59,9 @@ class ProcessBuilder { args = args.concat(this.constructModList(modObj.fMods)) } - logger.log('Launch Arguments:', args) + logger.info('Launch Arguments:', args) - const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, { + const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.getID()), args, { cwd: this.gameDir, detached: ConfigManager.getLaunchDetached() }) @@ -73,22 +73,20 @@ class ProcessBuilder { child.stdout.setEncoding('utf8') child.stderr.setEncoding('utf8') - const loggerMCstdout = LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold') - const loggerMCstderr = LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold') - child.stdout.on('data', (data) => { - loggerMCstdout.log(data) + data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`)) + }) child.stderr.on('data', (data) => { - loggerMCstderr.log(data) + data.trim().split('\n').forEach(x => console.log(`\x1b[31m[Minecraft]\x1b[0m ${x}`)) }) child.on('close', (code, signal) => { - logger.log('Exited with code', code) + logger.info('Exited with code', code) fs.remove(tempNativePath, (err) => { if(err){ logger.warn('Error while deleting temp dir', err) } else { - logger.log('Temp dir deleted successfully.') + logger.info('Temp dir deleted successfully.') } }) }) @@ -96,6 +94,16 @@ class ProcessBuilder { return child } + /** + * Get the platform specific classpath separator. On windows, this is a semicolon. + * On Unix, this is a colon. + * + * @returns {string} The classpath separator for the current operating system. + */ + static getClasspathSeparator() { + return process.platform === 'win32' ? ';' : ':' + } + /** * Determine if an optional mod is enabled from its configuration value. If the * configuration value is null, the required object will be used to @@ -339,16 +347,16 @@ class ProcessBuilder { // Classpath Argument args.push('-cp') - args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')) + args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator())) // Java Arguments if(process.platform === 'darwin'){ args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) + args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID())) + args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID())) + args = args.concat(ConfigManager.getJVMOptions(this.server.getID())) args.push('-Djava.library.path=' + tempNativePath) // Main Java Class @@ -377,6 +385,19 @@ class ProcessBuilder { // JVM Arguments First let args = this.versionData.arguments.jvm + // Debug securejarhandler + // args.push('-Dbsl.debug=true') + + 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.forgeData.id) + ) + } + } + //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml') // Java Arguments @@ -384,9 +405,9 @@ class ProcessBuilder { args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) + args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID())) + args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID())) + args = args.concat(ConfigManager.getJVMOptions(this.server.getID())) // Main Java Class args.push(this.forgeData.mainClass) @@ -489,7 +510,7 @@ class ProcessBuilder { val = args[i].replace(argDiscovery, this.launcherVersion) break case 'classpath': - val = this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':') + val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator()) break } if(val != null){ @@ -647,9 +668,13 @@ class ProcessBuilder { classpathArg(mods, tempNativePath){ let cpArgs = [] - // Add the version.jar to the classpath. - const version = this.versionData.id - cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) + if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) { + // Add the version.jar to the classpath. + // Must not be added to the classpath for Forge 1.17+. + const version = this.versionData.id + cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) + } + if(this.usingLiteLoader){ cpArgs.push(this.llPath) @@ -682,6 +707,7 @@ class ProcessBuilder { * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. */ _resolveMojangLibraries(tempNativePath){ + const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ const libs = {} const libArr = this.versionData.libraries @@ -689,27 +715,23 @@ class ProcessBuilder { for(let i=0; i -1){ + shouldExclude = true + } + }) + + const extractName = fileName.includes('/') ? fileName.substring(fileName.lastIndexOf('/')) : fileName + + // Extract the file. + if(!shouldExclude){ + fs.writeFile(path.join(tempNativePath, extractName), zipEntries[i].getData(), (err) => { + if(err){ + logger.error('Error while extracting native library:', err) + } + }) + } + + } + } + // No natives + else { + const dlInfo = lib.downloads + const artifact = dlInfo.artifact + const to = path.join(this.libPath, artifact.path) + const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':')) + libs[versionIndependentId] = to + } } } @@ -788,7 +869,10 @@ class ProcessBuilder { let libs = [] for(let sm of mdl.getSubModules()){ if(sm.getType() === DistroManager.Types.Library){ - libs.push(sm.getArtifact().getPath()) + + if(sm.getClasspath()) { + libs.push(sm.getArtifact().getPath()) + } } // If this module has submodules, we need to resolve the libraries for those. // To avoid unnecessary recursive calls, base case is checked here. diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index c15896c8..a7a2d0c6 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -10,7 +10,9 @@ const { MojangRestAPI, getServerStatus } = require('helios-core/mojang') // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') +const { Util } = require('./assets/js/assetguard') const { RestResponseStatus, isDisplayableError } = require('helios-core/common') +const { stdout } = require('process') // Launch Elements const launch_content = document.getElementById('launch_content') @@ -21,7 +23,7 @@ const launch_details_text = document.getElementById('launch_details_text') const server_selection_button = document.getElementById('server_selection_button') const user_text = document.getElementById('user_text') -const loggerLanding = LoggerUtil1('%c[Landing]', 'color: #000668; font-weight: bold') +const loggerLanding = LoggerUtil.getLogger('Landing') /* Launch Progress Wrapper Functions */ @@ -85,9 +87,9 @@ function setLaunchEnabled(val){ // Bind launch button document.getElementById('launch_button').addEventListener('click', function(e){ - loggerLanding.log('Launching game..') + loggerLanding.info('Launching game..') const mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() - const jExe = ConfigManager.getJavaExecutable() + const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) if(jExe == null){ asyncSystemScan(mcVersion) } else { @@ -98,7 +100,7 @@ document.getElementById('launch_button').addEventListener('click', function(e){ const jg = new JavaGuard(mcVersion) jg._validateJavaBinary(jExe).then((v) => { - loggerLanding.log('Java version meta', v) + loggerLanding.info('Java version meta', v) if(v.valid){ dlAsync() } else { @@ -140,13 +142,13 @@ updateSelectedAccount(ConfigManager.getSelectedAccount()) // Bind selected server function updateSelectedServer(serv){ if(getCurrentView() === VIEWS.settings){ - saveAllModConfigurations() + fullSettingsSave() } ConfigManager.setSelectedServer(serv != null ? serv.getID() : null) ConfigManager.save() server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected') if(getCurrentView() === VIEWS.settings){ - animateModsTabRefresh() + animateSettingsTabRefresh() } setLaunchEnabled(serv != null) } @@ -159,7 +161,7 @@ server_selection_button.onclick = (e) => { // Update Mojang Status Color const refreshMojangStatuses = async function(){ - loggerLanding.log('Refreshing Mojang Statuses..') + loggerLanding.info('Refreshing Mojang Statuses..') let status = 'grey' let tooltipEssentialHTML = '' @@ -219,7 +221,7 @@ const refreshMojangStatuses = async function(){ } const refreshServerStatus = async function(fade = false){ - loggerLanding.log('Refreshing Server Status') + loggerLanding.info('Refreshing Server Status') const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) let pLabel = 'SERVER' @@ -253,8 +255,9 @@ const refreshServerStatus = async function(fade = false){ refreshMojangStatuses() // Server Status is refreshed in uibinder.js on distributionIndexDone. +// Refresh statuses every hour. The status page itself refreshes every day so... +let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 60*60*1000) // Set refresh rate to once every 5 minutes. -let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 300000) let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) /** @@ -293,8 +296,6 @@ function asyncSystemScan(mcVersion, launchAfter = true){ toggleLaunchArea(true) setLaunchPercentage(0, 100) - const loggerSysAEx = LoggerUtil1('%c[SysAEx]', 'color: #353232; font-weight: bold') - const forkEnv = JSON.parse(JSON.stringify(process.env)) forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() @@ -309,13 +310,15 @@ function asyncSystemScan(mcVersion, launchAfter = true){ // Stdout sysAEx.stdio[1].setEncoding('utf8') sysAEx.stdio[1].on('data', (data) => { - loggerSysAEx.log(data) + console.log(`\x1b[32m[SysAEx]\x1b[0m ${data}`) }) // Stderr sysAEx.stdio[2].setEncoding('utf8') sysAEx.stdio[2].on('data', (data) => { - loggerSysAEx.log(data) + console.log(`\x1b[31m[SysAEx]\x1b[0m ${data}`) }) + + const javaVer = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' sysAEx.on('message', (m) => { @@ -325,14 +328,14 @@ function asyncSystemScan(mcVersion, launchAfter = true){ // Show this information to the user. setOverlayContent( 'No Compatible
Java Installation Found', - 'In order to join WesterosCraft, you need a 64-bit installation of Java 8. Would you like us to install a copy?', + `In order to join WesterosCraft, you need a 64-bit installation of Java ${javaVer}. Would you like us to install a copy?`, 'Install Java', 'Install Manually' ) setOverlayHandler(() => { setLaunchDetails('Preparing Java Download..') - sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable()]}) - sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory()]}) + sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())]}) + sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory(), mcVersion]}) toggleOverlay(false) }) setDismissHandler(() => { @@ -340,7 +343,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){ //$('#overlayDismiss').toggle(false) setOverlayContent( 'Java is Required
to Launch', - 'A valid x64 installation of Java 8 is required to launch.

Please refer to our Java Management Guide for instructions on how to manually install Java.', + `A valid x64 installation of Java ${javaVer} is required to launch.

Please refer to our Java Management Guide for instructions on how to manually install Java.`, 'I Understand', 'Go Back' ) @@ -359,7 +362,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){ } else { // Java installation found, use this to launch the game. - ConfigManager.setJavaExecutable(m.result) + ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.result) ConfigManager.save() // We need to make sure that the updated value is on the settings UI. @@ -433,7 +436,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){ remote.getCurrentWindow().setProgressBar(-1) // Extraction completed successfully. - ConfigManager.setJavaExecutable(m.args[0]) + ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.args[0]) ConfigManager.save() if(extractListener != null){ @@ -495,8 +498,7 @@ function dlAsync(login = true){ toggleLaunchArea(true) setLaunchPercentage(0, 100) - const loggerAEx = LoggerUtil1('%c[AEx]', 'color: #353232; font-weight: bold') - const loggerLaunchSuite = LoggerUtil1('%c[LaunchSuite]', 'color: #000668; font-weight: bold') + const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') const forkEnv = JSON.parse(JSON.stringify(process.env)) forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() @@ -505,7 +507,7 @@ function dlAsync(login = true){ aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ 'AssetGuard', ConfigManager.getCommonDirectory(), - ConfigManager.getJavaExecutable() + ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) ], { env: forkEnv, stdio: 'pipe' @@ -513,12 +515,12 @@ function dlAsync(login = true){ // Stdout aEx.stdio[1].setEncoding('utf8') aEx.stdio[1].on('data', (data) => { - loggerAEx.log(data) + console.log(`\x1b[32m[AEx]\x1b[0m ${data}`) }) // Stderr aEx.stdio[2].setEncoding('utf8') aEx.stdio[2].on('data', (data) => { - loggerAEx.log(data) + console.log(`\x1b[31m[AEx]\x1b[0m ${data}`) }) aEx.on('error', (err) => { loggerLaunchSuite.error('Error during launch', err) @@ -538,27 +540,27 @@ function dlAsync(login = true){ switch(m.data){ case 'distribution': setLaunchPercentage(20, 100) - loggerLaunchSuite.log('Validated distibution index.') + loggerLaunchSuite.info('Validated distibution index.') setLaunchDetails('Loading version information..') break case 'version': setLaunchPercentage(40, 100) - loggerLaunchSuite.log('Version data loaded.') + loggerLaunchSuite.info('Version data loaded.') setLaunchDetails('Validating asset integrity..') break case 'assets': setLaunchPercentage(60, 100) - loggerLaunchSuite.log('Asset Validation Complete') + loggerLaunchSuite.info('Asset Validation Complete') setLaunchDetails('Validating library integrity..') break case 'libraries': setLaunchPercentage(80, 100) - loggerLaunchSuite.log('Library validation complete.') + loggerLaunchSuite.info('Library validation complete.') setLaunchDetails('Validating miscellaneous file integrity..') break case 'files': setLaunchPercentage(100, 100) - loggerLaunchSuite.log('File validation complete.') + loggerLaunchSuite.info('File validation complete.') setLaunchDetails('Downloading files..') break } @@ -646,7 +648,7 @@ function dlAsync(login = true){ if(login && allGood) { const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.log(`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()) setLaunchDetails('Launching game..') @@ -713,7 +715,7 @@ function dlAsync(login = true){ DiscordWrapper.initRPC(distro.discord, serv.discord) hasRPC = true proc.on('close', (code, signal) => { - loggerLaunchSuite.log('Shutting down Discord Rich Presence..') + loggerLaunchSuite.info('Shutting down Discord Rich Presence..') DiscordWrapper.shutdownRPC() hasRPC = false proc = null @@ -744,7 +746,7 @@ function dlAsync(login = true){ serv = data.getServer(ConfigManager.getSelectedServer()) aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) }, (err) => { - loggerLaunchSuite.log('Error while fetching a fresh copy of the distribution index.', err) + loggerLaunchSuite.info('Error while fetching a fresh copy of the distribution index.', err) refreshDistributionIndex(false, (data) => { onDistroRefresh(data) serv = data.getServer(ConfigManager.getSelectedServer()) diff --git a/app/assets/js/scripts/login.js b/app/assets/js/scripts/login.js index f91dc75b..7afe94ca 100644 --- a/app/assets/js/scripts/login.js +++ b/app/assets/js/scripts/login.js @@ -21,8 +21,6 @@ const loginForm = document.getElementById('loginForm') // Control variables. let lu = false, lp = false -const loggerLogin = LoggerUtil1('%c[Login]', 'color: #000668; font-weight: bold') - /** * Show a login error. diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index b5d019cd..06e18f2c 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -127,29 +127,34 @@ function initSettingsValues(){ const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') Array.from(sEls).map((v, index, arr) => { const cVal = v.getAttribute('cValue') + const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id. const gFn = ConfigManager['get' + cVal] + const gFnOpts = [] + if(serverDependent) { + gFnOpts.push(ConfigManager.getSelectedServer()) + } if(typeof gFn === 'function'){ if(v.tagName === 'INPUT'){ if(v.type === 'number' || v.type === 'text'){ // Special Conditions if(cVal === 'JavaExecutable'){ + v.value = gFn.apply(null, gFnOpts) populateJavaExecDetails(v.value) - v.value = gFn() } else if (cVal === 'DataDirectory'){ - v.value = gFn() + v.value = gFn.apply(null, gFnOpts) } else if(cVal === 'JVMOptions'){ - v.value = gFn().join(' ') + v.value = gFn.apply(null, gFnOpts).join(' ') } else { - v.value = gFn() + v.value = gFn.apply(null, gFnOpts) } } else if(v.type === 'checkbox'){ - v.checked = gFn() + v.checked = gFn.apply(null, gFnOpts) } } else if(v.tagName === 'DIV'){ if(v.classList.contains('rangeSlider')){ // Special Conditions if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ - let val = gFn() + let val = gFn.apply(null, gFnOpts) if(val.endsWith('M')){ val = Number(val.substring(0, val.length-1))/1000 } else { @@ -158,7 +163,7 @@ function initSettingsValues(){ v.setAttribute('value', val) } else { - v.setAttribute('value', Number.parseFloat(gFn())) + v.setAttribute('value', Number.parseFloat(gFn.apply(null, gFnOpts))) } } } @@ -174,22 +179,31 @@ function saveSettingsValues(){ const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') Array.from(sEls).map((v, index, arr) => { const cVal = v.getAttribute('cValue') + const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id. const sFn = ConfigManager['set' + cVal] + const sFnOpts = [] + if(serverDependent) { + sFnOpts.push(ConfigManager.getSelectedServer()) + } if(typeof sFn === 'function'){ if(v.tagName === 'INPUT'){ if(v.type === 'number' || v.type === 'text'){ // Special Conditions if(cVal === 'JVMOptions'){ if(!v.value.trim()) { - sFn([]) + sFnOpts.push([]) + sFn.apply(null, sFnOpts) } else { - sFn(v.value.trim().split(/\s+/)) + sFnOpts.push(v.value.trim().split(/\s+/)) + sFn.apply(null, sFnOpts) } } else { - sFn(v.value) + sFnOpts.push(v.value) + sFn.apply(null, sFnOpts) } } else if(v.type === 'checkbox'){ - sFn(v.checked) + sFnOpts.push(v.checked) + sFn.apply(null, sFnOpts) // Special Conditions if(cVal === 'AllowPrerelease'){ changeAllowPrerelease(v.checked) @@ -206,9 +220,11 @@ function saveSettingsValues(){ val = val + 'G' } - sFn(val) + sFnOpts.push(val) + sFn.apply(null, sFnOpts) } else { - sFn(v.getAttribute('value')) + sFnOpts.push(v.getAttribute('value')) + sFn.apply(null, sFnOpts) } } } @@ -305,13 +321,17 @@ function settingsSaveDisabled(v){ settingsNavDone.disabled = v } -/* Closes the settings view and saves all data. */ -settingsNavDone.onclick = () => { +function fullSettingsSave() { saveSettingsValues() saveModConfiguration() ConfigManager.save() saveDropinModConfiguration() saveShaderpackSettings() +} + +/* Closes the settings view and saves all data. */ +settingsNavDone.onclick = () => { + fullSettingsSave() switchView(getCurrentView(), VIEWS.landing) } @@ -373,13 +393,13 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => { if (Object.prototype.hasOwnProperty.call(queryMap, 'error')) { switchView(getCurrentView(), viewOnClose, 500, 500, () => { // TODO Dont know what these errors are. Just show them I guess. - // This is probably if you messed up the app registration with Azure. + // This is probably if you messed up the app registration with Azure. + let error = queryMap.error // Error might be 'access_denied' ? + let errorDesc = queryMap.error_description console.log('Error getting authCode, is Azure application registered correctly?') console.log(error) console.log(errorDesc) - console.log('Full query map', queryMap) - let error = queryMap.error // Error might be 'access_denied' ? - let errorDesc = queryMap.error_description + console.log('Full query map: ', queryMap) setOverlayContent( error, errorDesc, @@ -1060,33 +1080,37 @@ function bindShaderpackButton() { function loadSelectedServerOnModsTab(){ const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) - document.getElementById('settingsSelServContent').innerHTML = ` - -
- ${serv.getName()} - ${serv.getDescription()} -
-
${serv.getMinecraftVersion()}
-
${serv.getVersion()}
- ${serv.isMainServer() ? `
- - - - - - - - Main Server -
` : ''} + for(const el of document.getElementsByClassName('settingsSelServContent')) { + el.innerHTML = ` + +
+ ${serv.getName()} + ${serv.getDescription()} +
+
${serv.getMinecraftVersion()}
+
${serv.getVersion()}
+ ${serv.isMainServer() ? `
+ + + + + + + + Main Server +
` : ''} +
-
- ` + ` + } } // Bind functionality to the server switch button. -document.getElementById('settingsSwitchServerButton').addEventListener('click', (e) => { - e.target.blur() - toggleServerSelection(true) +Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => { + el.addEventListener('click', (e) => { + e.target.blur() + toggleServerSelection(true) + }) }) /** @@ -1099,13 +1123,13 @@ function saveAllModConfigurations(){ } /** - * Function to refresh the mods tab whenever the selected + * Function to refresh the current tab whenever the selected * server is changed. */ -function animateModsTabRefresh(){ - $('#settingsTabMods').fadeOut(500, () => { - prepareModsTab() - $('#settingsTabMods').fadeIn(500) +function animateSettingsTabRefresh(){ + $(`#${selectedSettingsTab}`).fadeOut(500, () => { + prepareSettings() + $(`#${selectedSettingsTab}`).fadeIn(500) }) } @@ -1135,6 +1159,8 @@ const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel') const settingsMemoryTotal = document.getElementById('settingsMemoryTotal') const settingsMemoryAvail = document.getElementById('settingsMemoryAvail') const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails') +const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc') +const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink') // Store maximum memory values. const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM() @@ -1342,12 +1368,34 @@ function populateJavaExecDetails(execPath){ }) } +function populateJavaReqDesc() { + const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() + if(Util.mcVersionAtLeast('1.17', mcVer)) { + settingsJavaReqDesc.innerHTML = 'Requires Java 17 x64.' + } else { + settingsJavaReqDesc.innerHTML = 'Requires Java 8 x64.' + } +} + +function populateJvmOptsLink() { + const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() + if(Util.mcVersionAtLeast('1.17', mcVer)) { + settingsJvmOptsLink.innerHTML = 'Available Options for Java 17 (HotSpot VM)' + settingsJvmOptsLink.href = 'https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#extra-options-for-java' + } else { + settingsJvmOptsLink.innerHTML = 'Available Options for Java 8 (HotSpot VM)' + settingsJvmOptsLink.href = `https://docs.oracle.com/javase/8/docs/technotes/tools/${process.platform === 'win32' ? 'windows' : 'unix'}/java.html` + } +} + /** * Prepare the Java tab for display. */ function prepareJavaTab(){ bindRangeSlider() populateMemoryStatus() + populateJavaReqDesc() + populateJvmOptsLink() } /** @@ -1538,4 +1586,4 @@ function prepareSettings(first = false) { } // Prepare the settings UI on startup. -//prepareSettings(true) \ No newline at end of file +//prepareSettings(true) diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index d3385140..835e3c9d 100644 --- a/app/assets/js/scripts/uibinder.js +++ b/app/assets/js/scripts/uibinder.js @@ -60,7 +60,7 @@ function getCurrentView(){ function showMainUI(data){ if(!isDev){ - loggerAutoUpdater.log('Initializing..') + loggerAutoUpdater.info('Initializing..') ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease()) } @@ -137,6 +137,7 @@ function onDistroRefresh(data){ refreshServerStatus() initNews() syncModConfigurations(data) + ensureJavaSettings(data) } /** @@ -223,6 +224,21 @@ function syncModConfigurations(data){ ConfigManager.save() } +/** + * Ensure java configurations are present for the available servers. + * + * @param {Object} data The distro index object. + */ +function ensureJavaSettings(data) { + + // Nothing too fancy for now. + for(const serv of data.getServers()){ + ConfigManager.ensureJavaConfig(serv.getID(), serv.getMinecraftVersion()) + } + + ConfigManager.save() +} + /** * Recursively scan for optional sub modules. If none are found, * this function returns a boolean. If optional sub modules do exist, @@ -434,6 +450,7 @@ ipcRenderer.on('distributionIndexDone', (event, res) => { if(res) { const data = DistroManager.getDistribution() syncModConfigurations(data) + ensureJavaSettings(data) if(document.readyState === 'interactive' || document.readyState === 'complete'){ showMainUI(data) } else { @@ -448,3 +465,13 @@ ipcRenderer.on('distributionIndexDone', (event, res) => { } } }) + +// Util for development +function devModeToggle() { + DistroManager.setDevMode(true) + DistroManager.pullLocal().then((data) => { + ensureJavaSettings(data) + updateSelectedServer(data.getServers()[0]) + syncModConfigurations(data) + }) +} diff --git a/app/assets/js/scripts/uicore.js b/app/assets/js/scripts/uicore.js index 71a5aa19..c35771ad 100644 --- a/app/assets/js/scripts/uicore.js +++ b/app/assets/js/scripts/uicore.js @@ -10,11 +10,9 @@ const {ipcRenderer, shell, webFrame} = require('electron') const remote = require('@electron/remote') const isDev = require('./assets/js/isdev') const { LoggerUtil } = require('helios-core') -const LoggerUtil1 = require('./assets/js/loggerutil') -const loggerUICore = LoggerUtil1('%c[UICore]', 'color: #000668; font-weight: bold') -const loggerAutoUpdater = LoggerUtil1('%c[AutoUpdater]', 'color: #000668; font-weight: bold') -const loggerAutoUpdaterSuccess = LoggerUtil1('%c[AutoUpdater]', 'color: #209b07; font-weight: bold') +const loggerUICore = LoggerUtil.getLogger('UICore') +const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater') // Log deprecation and process warnings. process.traceProcessWarnings = true @@ -43,11 +41,11 @@ if(!isDev){ ipcRenderer.on('autoUpdateNotification', (event, arg, info) => { switch(arg){ case 'checking-for-update': - loggerAutoUpdater.log('Checking for update..') + loggerAutoUpdater.info('Checking for update..') settingsUpdateButtonStatus('Checking for Updates..', true) break case 'update-available': - loggerAutoUpdaterSuccess.log('New update available', info.version) + loggerAutoUpdater.info('New update available', info.version) if(process.platform === 'darwin'){ info.darwindownload = `https://github.com/dscalzi/HeliosLauncher/releases/download/v${info.version}/Helios-Launcher-setup-${info.version}${process.arch === 'arm64' ? '-arm64' : '-x64'}.dmg` @@ -57,7 +55,7 @@ if(!isDev){ populateSettingsUpdateInformation(info) break case 'update-downloaded': - loggerAutoUpdaterSuccess.log('Update ' + info.version + ' ready to be installed.') + loggerAutoUpdater.info('Update ' + info.version + ' ready to be installed.') settingsUpdateButtonStatus('Install Now', false, () => { if(!isDev){ ipcRenderer.send('autoUpdateAction', 'installUpdateNow') @@ -66,7 +64,7 @@ if(!isDev){ showUpdateUI(info) break case 'update-not-available': - loggerAutoUpdater.log('No new update found.') + loggerAutoUpdater.info('No new update found.') settingsUpdateButtonStatus('Check for Updates') break case 'ready': @@ -78,9 +76,9 @@ if(!isDev){ case 'realerror': if(info != null && info.code != null){ if(info.code === 'ERR_UPDATER_INVALID_RELEASE_FEED'){ - loggerAutoUpdater.log('No suitable releases found.') + loggerAutoUpdater.info('No suitable releases found.') } else if(info.code === 'ERR_XML_MISSED_ELEMENT'){ - loggerAutoUpdater.log('No releases found.') + loggerAutoUpdater.info('No releases found.') } else { loggerAutoUpdater.error('Error during update check..', info) loggerAutoUpdater.debug('Error Code:', info.code) @@ -88,7 +86,7 @@ if(!isDev){ } break default: - loggerAutoUpdater.log('Unknown argument', arg) + loggerAutoUpdater.info('Unknown argument', arg) break } }) @@ -131,12 +129,12 @@ function showUpdateUI(info){ /* jQuery Example $(function(){ - loggerUICore.log('UICore Initialized'); + loggerUICore.info('UICore Initialized'); })*/ document.addEventListener('readystatechange', function () { if (document.readyState === 'interactive'){ - loggerUICore.log('UICore Initializing..') + loggerUICore.info('UICore Initializing..') // Bind close button. Array.from(document.getElementsByClassName('fCb')).map((val) => { diff --git a/app/settings.ejs b/app/settings.ejs index 65a1796d..aa1fa764 100644 --- a/app/settings.ejs +++ b/app/settings.ejs @@ -122,13 +122,13 @@ Mod Settings Enable or disable mods.
-
-
+
+
-
-
- +
+
+
@@ -172,6 +172,16 @@ Java Settings Manage the Java configuration (advanced).
+
+
+ +
+
+
+ +
+
+
Memory
@@ -179,7 +189,7 @@
Maximum RAM
-
+
@@ -189,7 +199,7 @@
Minimum RAM
-
+
@@ -231,11 +241,11 @@
- +
-
The Java executable is validated before game launch. Requires Java 8 x64.
The path should end with bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %>.
+
The Java executable is validated before game launch. Requires Java 8 x64.
The path should end with bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %>.
Additional JVM Options
@@ -254,9 +264,9 @@
- +
-
Options to be provided to the JVM at runtime. -Xms and -Xmx should not be included.
Available Options for Java 8.
+
Options to be provided to the JVM at runtime. -Xms and -Xmx should not be included.
Available Options for Java 8.