const fs = require('fs') const mkpath = require('mkdirp') const os = require('os') const path = require('path') const uuidV4 = require('uuid/v4') const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME) const dataPath = path.join(sysRoot, '.westeroscraft') const firstLaunch = !fs.existsSync(dataPath) function resolveMaxRAM(){ const mem = os.totalmem() return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G') } /** * Three types of values: * Static = Explicitly declared. * Dynamic = Calculated by a private function. * Resolved = Resolved externally, defaults to null. */ const DEFAULT_CONFIG = { settings: { java: { minRAM: '2G', maxRAM: resolveMaxRAM(), // Dynamic executable: null, jvmOptions: [ '-XX:+UseConcMarkSweepGC', '-XX:+CMSIncrementalMode', '-XX:-UseAdaptiveSizePolicy', '-Xmn128M' ], }, game: { resWidth: 1280, resHeight: 720, fullscreen: false, autoConnect: true, launchDetached: true }, launcher: { allowPrerelease: false } }, commonDirectory: path.join(dataPath, 'common'), instanceDirectory: path.join(dataPath, 'instances'), clientToken: uuidV4().replace(/-/g, ''), selectedServer: null, // Resolved selectedAccount: null, authenticationDatabase: {} } let config = null; // Persistance Utility Functions /** * Save the current configuration to a file. */ exports.save = function(){ const filePath = path.join(dataPath, 'config.json') fs.writeFileSync(filePath, JSON.stringify(config, null, 4), 'UTF-8') } /** * Load the configuration into memory. If a configuration file exists, * that will be read and saved. Otherwise, a default configuration will * be generated. Note that "resolved" values default to null and will * need to be externally assigned. */ exports.load = function(){ // Determine the effective configuration. //const EFFECTIVE_CONFIG = config == null ? DEFAULT_CONFIG : config const filePath = path.join(dataPath, 'config.json') if(!fs.existsSync(filePath)){ // Create all parent directories. mkpath.sync(path.join(filePath, '..')) config = DEFAULT_CONFIG exports.save() } else { config = JSON.parse(fs.readFileSync(filePath, 'UTF-8')) config = validateKeySet(DEFAULT_CONFIG, config) exports.save() } console.log('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold', 'Successfully Loaded') } /** * Validate that the destination object has at least every field * present in the source object. Assign a default value otherwise. * * @param {Object} srcObj The source object to reference against. * @param {Object} destObj The destination object. * @returns {Object} A validated destination object. */ function validateKeySet(srcObj, destObj){ if(srcObj == null){ srcObj = {} } const validationBlacklist = ['authenticationDatabase'] const keys = Object.keys(srcObj) for(let i=0; i} An array of each stored authenticated account. */ exports.getAuthAccounts = function(){ return config.authenticationDatabase } /** * Returns the authenticated account with the given uuid. Value may * be null. * * @param {string} uuid The uuid of the authenticated account. * @returns {Object} The authenticated account with the given uuid. */ exports.getAuthAccount = function(uuid){ return config.authenticationDatabase[uuid] } /** * Update the access token of an authenticated account. * * @param {string} uuid The uuid of the authenticated account. * @param {string} accessToken The new Access Token. * * @returns {Object} The authenticated account object created by this action. */ exports.updateAuthAccount = function(uuid, accessToken){ config.authenticationDatabase[uuid].accessToken = accessToken return config.authenticationDatabase[uuid] } /** * Adds an authenticated account to the database to be stored. * * @param {string} uuid The uuid of the authenticated account. * @param {string} accessToken The accessToken of the authenticated account. * @param {string} username The username (usually email) of the authenticated account. * @param {string} displayName The in game name of the authenticated account. * * @returns {Object} The authenticated account object created by this action. */ exports.addAuthAccount = function(uuid, accessToken, username, displayName){ config.selectedAccount = uuid config.authenticationDatabase[uuid] = { accessToken, username, uuid, displayName } return config.authenticationDatabase[uuid] } /** * Remove an authenticated account from the database. If the account * was also the selected account, a new one will be selected. If there * are no accounts, the selected account will be null. * * @param {string} uuid The uuid of the authenticated account. * * @returns {boolean} True if the account was removed, false if it never existed. */ exports.removeAuthAccount = function(uuid){ if(config.authenticationDatabase[uuid] != null){ delete config.authenticationDatabase[uuid] if(config.selectedAccount === uuid){ const keys = Object.keys(config.authenticationDatabase) if(keys.length > 0){ config.selectedAccount = keys[0] } else { config.selectedAccount = null } } return true } return false } /** * Get the currently selected authenticated account. * * @returns {Object} The selected authenticated account. */ exports.getSelectedAccount = function(){ return config.authenticationDatabase[config.selectedAccount] } /** * Set the selected authenticated account. * * @param {string} uuid The UUID of the account which is to be set * as the selected account. * * @returns {Object} The selected authenticated account. */ exports.setSelectedAccount = function(uuid){ const authAcc = config.authenticationDatabase[uuid] if(authAcc != null) { config.selectedAccount = uuid } return authAcc } // User Configurable Settings // Java Settings /** * Retrieve the minimum amount of memory for JVM initialization. This value * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * 1024 MegaBytes, etc. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {string} The minimum amount of memory for JVM initialization. */ exports.getMinRAM = function(def = false){ return !def ? config.settings.java.minRAM : DEFAULT_CONFIG.settings.java.minRAM } /** * Set the minimum amount of memory for JVM initialization. This value should * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * 1024 MegaBytes, etc. * * @param {string} minRAM The new minimum amount of memory for JVM initialization. */ exports.setMinRAM = function(minRAM){ config.settings.java.minRAM = minRAM } /** * Retrieve the maximum amount of memory for JVM initialization. This value * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * 1024 MegaBytes, etc. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {string} The maximum amount of memory for JVM initialization. */ exports.getMaxRAM = function(def = false){ return !def ? config.settings.java.maxRAM : resolveMaxRAM() } /** * Set the maximum amount of memory for JVM initialization. This value should * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * 1024 MegaBytes, etc. * * @param {string} maxRAM The new maximum amount of memory for JVM initialization. */ exports.setMaxRAM = function(maxRAM){ config.settings.java.maxRAM = maxRAM } /** * Retrieve the path of the Java Executable. * * This is a resolved configuration value and defaults to null until externally assigned. * * @returns {string} The path of the Java Executable. */ exports.getJavaExecutable = function(){ return config.settings.java.executable } /** * Set the path of the Java Executable. * * @param {string} executable The new path of the Java Executable. */ exports.setJavaExecutable = function(executable){ config.settings.java.executable = executable } /** * Retrieve the additional arguments for JVM initialization. Required arguments, * such as memory allocation, will be dynamically resolved and will not be included * in this value. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {Array.} 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 } /** * Set the additional arguments for JVM initialization. Required arguments, * such as memory allocation, will be dynamically resolved and should not be * included in this value. * * @param {Array.} jvmOptions An array of the new additional arguments for JVM * initialization. */ exports.setJVMOptions = function(jvmOptions){ config.settings.java.jvmOptions = jvmOptions } // Game Settings /** * Retrieve the width of the game window. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {number} The width of the game window. */ exports.getGameWidth = function(def = false){ return !def ? config.settings.game.resWidth : DEFAULT_CONFIG.settings.game.resWidth } /** * Set the width of the game window. * * @param {number} resWidth The new width of the game window. */ exports.setGameWidth = function(resWidth){ config.settings.game.resWidth = Number.parseInt(resWidth) } /** * Validate a potential new width value. * * @param {*} resWidth The width value to validate. */ exports.validateGameWidth = function(resWidth){ const nVal = Number.parseInt(resWidth) return Number.isInteger(nVal) && nVal >= 0 } /** * Retrieve the height of the game window. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {number} The height of the game window. */ exports.getGameHeight = function(def = false){ return !def ? config.settings.game.resHeight : DEFAULT_CONFIG.settings.game.resHeight } /** * Set the height of the game window. * * @param {number} resHeight The new height of the game window. */ exports.setGameHeight = function(resHeight){ config.settings.game.resHeight = Number.parseInt(resHeight) } /** * Validate a potential new height value. * * @param {*} resHeight The height value to validate. */ exports.validateGameHeight = function(resHeight){ const nVal = Number.parseInt(resHeight) return Number.isInteger(nVal) && nVal >= 0 } /** * Check if the game should be launched in fullscreen mode. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game is set to launch in fullscreen mode. */ exports.getFullscreen = function(def = false){ return !def ? config.settings.game.fullscreen : DEFAULT_CONFIG.settings.game.fullscreen } /** * Change the status of if the game should be launched in fullscreen mode. * * @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode. */ exports.setFullscreen = function(fullscreen){ config.settings.game.fullscreen = fullscreen } /** * Check if the game should auto connect to servers. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game should auto connect to servers. */ exports.getAutoConnect = function(def = false){ return !def ? config.settings.game.autoConnect : DEFAULT_CONFIG.settings.game.autoConnect } /** * Change the status of whether or not the game should auto connect to servers. * * @param {boolean} autoConnect Whether or not the game should auto connect to servers. */ exports.setAutoConnect = function(autoConnect){ config.settings.game.autoConnect = autoConnect } /** * Check if the game should launch as a detached process. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game will launch as a detached process. */ exports.getLaunchDetached = function(def = false){ return !def ? config.settings.game.launchDetached : DEFAULT_CONFIG.settings.game.launchDetached } /** * Change the status of whether or not the game should launch as a detached process. * * @param {boolean} launchDetached Whether or not the game should launch as a detached process. */ exports.setLaunchDetached = function(launchDetached){ config.settings.game.launchDetached = launchDetached } // Launcher Settings /** * Check if the launcher should download prerelease versions. * * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the launcher should download prerelease versions. */ exports.getAllowPrerelease = function(def = false){ return !def ? config.settings.launcher.allowPrerelease : DEFAULT_CONFIG.settings.launcher.allowPrerelease } /** * Change the status of Whether or not the launcher should download prerelease versions. * * @param {boolean} launchDetached Whether or not the launcher should download prerelease versions. */ exports.setAllowPrerelease = function(allowPrerelease){ config.settings.launcher.allowPrerelease = allowPrerelease }