From e3ee03ef73f63bf9d8d9594bcbf4338cb91f0eb3 Mon Sep 17 00:00:00 2001 From: Daniel Scalzi Date: Sun, 27 Nov 2022 18:13:50 -0500 Subject: [PATCH] 1.17+ Support / Java Settings by Instance (#261) * Patches to get 1.17 working, need to revise into real solutions. * Add version.jar to cp until 1.17. * Add server selection button to Java settings tab in preparation for by-instance java settings. * Java settings by instance * Use classpath flag instead of hardcode. * Support 1.19. * Fix refresh of java exec details. * Add doc. * Fix auto download of jdk 17. * Dependency upgrade. --- .eslintrc.json | 2 +- app/assets/css/launcher.css | 106 ++++++++-------- app/assets/js/assetguard.js | 55 ++++++--- app/assets/js/configmanager.js | 130 +++++++++++++++----- app/assets/js/distromanager.js | 12 +- app/assets/js/processbuilder.js | 136 +++++++++++++++++---- app/assets/js/scripts/landing.js | 23 ++-- app/assets/js/scripts/settings.js | 134 ++++++++++++++------- app/assets/js/scripts/uibinder.js | 27 +++++ app/settings.ejs | 32 +++-- docs/distro.md | 6 + package-lock.json | 194 +++++++++++++++++------------- package.json | 5 +- 13 files changed, 579 insertions(+), 283 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 006dfc0..a254fe0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ }, "extends": "eslint:recommended", "parserOptions": { - "ecmaVersion": 2019, + "ecmaVersion": 2021, "sourceType": "module" }, "rules": { diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index e67984e..e95bcb2 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/assetguard.js b/app/assets/js/assetguard.js index 75f6bc5..dcfcd7f 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -5,6 +5,7 @@ const child_process = require('child_process') const crypto = require('crypto') const EventEmitter = require('events') const fs = require('fs-extra') +const nodeDiskInfo = require('node-disk-info') const StreamZip = require('node-stream-zip') const path = require('path') const Registry = require('winreg') @@ -342,6 +343,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'){ @@ -459,25 +463,30 @@ class JavaGuard extends EventEmitter { let verString = props[i].split('=')[1].trim() console.log(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) { @@ -804,13 +813,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. @@ -1542,9 +1558,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') diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index 3dff950..ac071f3 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -63,6 +63,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 +92,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 +113,8 @@ const DEFAULT_CONFIG = { selectedServer: null, // Resolved selectedAccount: null, authenticationDatabase: {}, - modConfigurations: [] + modConfigurations: [], + javaConfig: {} } let config = null @@ -177,7 +188,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 +659,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/distromanager.js b/app/assets/js/distromanager.js index 90a2ab8..5e42951 100644 --- a/app/assets/js/distromanager.js +++ b/app/assets/js/distromanager.js @@ -118,7 +118,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 +143,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 +307,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 diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js index bf907c3..54decc3 100644 --- a/app/assets/js/processbuilder.js +++ b/app/assets/js/processbuilder.js @@ -61,7 +61,7 @@ class ProcessBuilder { logger.log('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() }) @@ -96,6 +96,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 +349,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 +387,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 +407,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 +512,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 +670,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 +709,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 +717,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 +871,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 deb7bcf..f423268 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -10,6 +10,7 @@ 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') // Launch Elements @@ -87,7 +88,7 @@ function setLaunchEnabled(val){ document.getElementById('launch_button').addEventListener('click', function(e){ loggerLanding.log('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 { @@ -140,13 +141,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) } @@ -317,6 +318,8 @@ function asyncSystemScan(mcVersion, launchAfter = true){ sysAEx.stdio[2].on('data', (data) => { loggerSysAEx.log(data) }) + + const javaVer = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' sysAEx.on('message', (m) => { @@ -326,14 +329,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(() => { @@ -341,7 +344,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' ) @@ -360,7 +363,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. @@ -434,7 +437,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){ @@ -506,7 +509,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' diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index e86de97..a8cda8c 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) } @@ -1056,33 +1076,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) + }) }) /** @@ -1095,13 +1119,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) }) } @@ -1131,6 +1155,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() @@ -1338,12 +1364,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() } /** diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index d338514..39c5d51 100644 --- a/app/assets/js/scripts/uibinder.js +++ b/app/assets/js/scripts/uibinder.js @@ -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/settings.ejs b/app/settings.ejs index 65a1796..aa1fa76 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.