From 4636f7200ce46a37eff56aa460be589d4d8deffd Mon Sep 17 00:00:00 2001 From: Daniel Scalzi Date: Sat, 26 Nov 2022 16:57:30 -0500 Subject: [PATCH] Java settings by instance --- app/assets/js/assetguard.js | 39 ++++++--- app/assets/js/configmanager.js | 130 ++++++++++++++++++++++-------- app/assets/js/processbuilder.js | 14 ++-- app/assets/js/scripts/landing.js | 12 +-- app/assets/js/scripts/settings.js | 65 +++++++++++---- app/assets/js/scripts/uibinder.js | 27 +++++++ app/settings.ejs | 10 +-- package-lock.json | 22 ++++- package.json | 1 + 9 files changed, 241 insertions(+), 79 deletions(-) diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index 662a0369..57ece17d 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,13 +463,19 @@ 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){ - meta.version = verOb - ++checksum - if(checksum === goal){ - break + 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 >= 16) { @@ -804,13 +814,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. diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index 3dff9502..ac071f36 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/processbuilder.js b/app/assets/js/processbuilder.js index b55273a8..8c28275d 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() }) @@ -356,9 +356,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())) args.push('-Djava.library.path=' + tempNativePath) // Main Java Class @@ -407,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) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 19b5f6fd..fc60d92b 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -87,7 +87,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,7 +140,7 @@ updateSelectedAccount(ConfigManager.getSelectedAccount()) // Bind selected server function updateSelectedServer(serv){ if(getCurrentView() === VIEWS.settings){ - saveAllModConfigurations() + fullSettingsSave() } ConfigManager.setSelectedServer(serv != null ? serv.getID() : null) ConfigManager.save() @@ -332,7 +332,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){ ) setOverlayHandler(() => { setLaunchDetails('Preparing Java Download..') - sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable()]}) + sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())]}) sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory()]}) toggleOverlay(false) }) @@ -360,7 +360,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 +434,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 +506,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 2181dda4..e971d79b 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -2,7 +2,7 @@ const os = require('os') const semver = require('semver') -const { JavaGuard } = require('./assets/js/assetguard') +const { JavaGuard, Util } = require('./assets/js/assetguard') const DropinModUtil = require('./assets/js/dropinmodutil') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') @@ -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'){ populateJavaExecDetails(v.value) - v.value = gFn() + v.value = gFn.apply(null, gFnOpts) } 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) } @@ -1135,6 +1155,7 @@ 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') // Store maximum memory values. const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM() @@ -1342,12 +1363,24 @@ 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.' + } + +} + /** * Prepare the Java tab for display. */ function prepareJavaTab(){ bindRangeSlider() populateMemoryStatus() + populateJavaReqDesc() + populateJavaExecDetails() } /** diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index d3385140..39c5d518 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 39dbf45d..34274756 100644 --- a/app/settings.ejs +++ b/app/settings.ejs @@ -189,7 +189,7 @@
Maximum RAM
-
+
@@ -199,7 +199,7 @@
Minimum RAM
-
+
@@ -241,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
@@ -264,7 +264,7 @@
- +
Options to be provided to the JVM at runtime. -Xms and -Xmx should not be included.
Available Options for Java 8.
diff --git a/package-lock.json b/package-lock.json index a59285d6..eea6f028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "got": "^11.8.5", "helios-core": "~0.1.2", "jquery": "^3.6.1", + "node-disk-info": "^1.3.0", "node-stream-zip": "^1.15.0", "request": "^2.88.2", "semver": "^7.3.8", @@ -2439,7 +2440,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -2973,6 +2973,17 @@ "dev": true, "optional": true }, + "node_modules/node-disk-info": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-disk-info/-/node-disk-info-1.3.0.tgz", + "integrity": "sha512-NEx858vJZ0AoBtmD/ChBIHLjFTF28xCsDIgmFl4jtGKsvlUx9DU/OrMDjvj3qp/E4hzLN0HvTg7eJx5XFQvbeg==", + "dependencies": { + "iconv-lite": "^0.6.2" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -6107,7 +6118,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -6510,6 +6520,14 @@ "dev": true, "optional": true }, + "node-disk-info": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-disk-info/-/node-disk-info-1.3.0.tgz", + "integrity": "sha512-NEx858vJZ0AoBtmD/ChBIHLjFTF28xCsDIgmFl4jtGKsvlUx9DU/OrMDjvj3qp/E4hzLN0HvTg7eJx5XFQvbeg==", + "requires": { + "iconv-lite": "^0.6.2" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", diff --git a/package.json b/package.json index 10b5dc58..699b5c5f 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "got": "^11.8.5", "helios-core": "~0.1.2", "jquery": "^3.6.1", + "node-disk-info": "^1.3.0", "node-stream-zip": "^1.15.0", "request": "^2.88.2", "semver": "^7.3.8",