configmanager.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. const fs = require('fs')
  2. const mkpath = require('mkdirp')
  3. const os = require('os')
  4. const path = require('path')
  5. const uuidV4 = require('uuid/v4')
  6. const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
  7. const dataPath = path.join(sysRoot, '.westeroscraft')
  8. const firstLaunch = !fs.existsSync(dataPath)
  9. exports.getAbsoluteMinRAM = function(){
  10. const mem = os.totalmem()
  11. return mem >= 6000000000 ? 3 : 2
  12. }
  13. exports.getAbsoluteMaxRAM = function(){
  14. const mem = os.totalmem()
  15. const gT16 = mem-16000000000
  16. return Math.floor((mem-1000000000-(gT16 > 0 ? (Number.parseInt(gT16/8) + 16000000000/4) : mem/4))/1000000000)
  17. }
  18. function resolveMaxRAM(){
  19. const mem = os.totalmem()
  20. return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G')
  21. }
  22. function resolveMinRAM(){
  23. return resolveMaxRAM()
  24. }
  25. /**
  26. * Three types of values:
  27. * Static = Explicitly declared.
  28. * Dynamic = Calculated by a private function.
  29. * Resolved = Resolved externally, defaults to null.
  30. */
  31. const DEFAULT_CONFIG = {
  32. settings: {
  33. java: {
  34. minRAM: resolveMinRAM(),
  35. maxRAM: resolveMaxRAM(), // Dynamic
  36. executable: null,
  37. jvmOptions: [
  38. '-XX:+UseConcMarkSweepGC',
  39. '-XX:+CMSIncrementalMode',
  40. '-XX:-UseAdaptiveSizePolicy',
  41. '-Xmn128M'
  42. ],
  43. },
  44. game: {
  45. resWidth: 1280,
  46. resHeight: 720,
  47. fullscreen: false,
  48. autoConnect: true,
  49. launchDetached: true
  50. },
  51. launcher: {
  52. allowPrerelease: false
  53. }
  54. },
  55. newsCache: {
  56. date: null,
  57. content: null,
  58. dismissed: false
  59. },
  60. commonDirectory: path.join(dataPath, 'common'),
  61. instanceDirectory: path.join(dataPath, 'instances'),
  62. clientToken: uuidV4().replace(/-/g, ''),
  63. selectedServer: null, // Resolved
  64. selectedAccount: null,
  65. authenticationDatabase: {},
  66. modConfigurations: []
  67. }
  68. let config = null
  69. // Persistance Utility Functions
  70. /**
  71. * Save the current configuration to a file.
  72. */
  73. exports.save = function(){
  74. const filePath = path.join(dataPath, 'config.json')
  75. fs.writeFileSync(filePath, JSON.stringify(config, null, 4), 'UTF-8')
  76. }
  77. /**
  78. * Load the configuration into memory. If a configuration file exists,
  79. * that will be read and saved. Otherwise, a default configuration will
  80. * be generated. Note that "resolved" values default to null and will
  81. * need to be externally assigned.
  82. */
  83. exports.load = function(){
  84. // Determine the effective configuration.
  85. const filePath = path.join(dataPath, 'config.json')
  86. if(!fs.existsSync(filePath)){
  87. // Create all parent directories.
  88. mkpath.sync(path.join(filePath, '..'))
  89. config = DEFAULT_CONFIG
  90. exports.save()
  91. } else {
  92. let doValidate = false
  93. try {
  94. config = JSON.parse(fs.readFileSync(filePath, 'UTF-8'))
  95. doValidate = true
  96. } catch (err){
  97. console.error(err)
  98. console.log('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold', 'Configuration file contains malformed JSON or is corrupt.')
  99. console.log('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold', 'Generating a new configuration file.')
  100. mkpath.sync(path.join(filePath, '..'))
  101. config = DEFAULT_CONFIG
  102. exports.save()
  103. }
  104. if(doValidate){
  105. config = validateKeySet(DEFAULT_CONFIG, config)
  106. exports.save()
  107. }
  108. }
  109. console.log('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold', 'Successfully Loaded')
  110. }
  111. /**
  112. * @returns {boolean} Whether or not the manager has been loaded.
  113. */
  114. exports.isLoaded = function(){
  115. return config != null
  116. }
  117. /**
  118. * Validate that the destination object has at least every field
  119. * present in the source object. Assign a default value otherwise.
  120. *
  121. * @param {Object} srcObj The source object to reference against.
  122. * @param {Object} destObj The destination object.
  123. * @returns {Object} A validated destination object.
  124. */
  125. function validateKeySet(srcObj, destObj){
  126. if(srcObj == null){
  127. srcObj = {}
  128. }
  129. const validationBlacklist = ['authenticationDatabase']
  130. const keys = Object.keys(srcObj)
  131. for(let i=0; i<keys.length; i++){
  132. if(typeof destObj[keys[i]] === 'undefined'){
  133. destObj[keys[i]] = srcObj[keys[i]]
  134. } else if(typeof srcObj[keys[i]] === 'object' && srcObj[keys[i]] != null && !(srcObj[keys[i]] instanceof Array) && validationBlacklist.indexOf(keys[i]) === -1){
  135. destObj[keys[i]] = validateKeySet(srcObj[keys[i]], destObj[keys[i]])
  136. }
  137. }
  138. return destObj
  139. }
  140. /**
  141. * Retrieve the absolute path of the launcher directory.
  142. *
  143. * @returns {string} The absolute path of the launcher directory.
  144. */
  145. exports.getLauncherDirectory = function(){
  146. return dataPath
  147. }
  148. /**
  149. * Check to see if this is the first time the user has launched the
  150. * application. This is determined by the existance of the data path.
  151. *
  152. * @returns {boolean} True if this is the first launch, otherwise false.
  153. */
  154. exports.isFirstLaunch = function(){
  155. return firstLaunch
  156. }
  157. /**
  158. * Returns the name of the folder in the OS temp directory which we
  159. * will use to extract and store native dependencies for game launch.
  160. *
  161. * @returns {string} The name of the folder.
  162. */
  163. exports.getTempNativeFolder = function(){
  164. return 'WCNatives'
  165. }
  166. // System Settings (Unconfigurable on UI)
  167. /**
  168. * Retrieve the news cache to determine
  169. * whether or not there is newer news.
  170. *
  171. * @returns {Object} The news cache object.
  172. */
  173. exports.getNewsCache = function(){
  174. return config.newsCache
  175. }
  176. /**
  177. * Set the new news cache object.
  178. *
  179. * @param {Object} newsCache The new news cache object.
  180. */
  181. exports.setNewsCache = function(newsCache){
  182. config.newsCache = newsCache
  183. }
  184. /**
  185. * Set whether or not the news has been dismissed (checked)
  186. *
  187. * @param {boolean} dismissed Whether or not the news has been dismissed (checked).
  188. */
  189. exports.setNewsCacheDismissed = function(dismissed){
  190. config.newsCache.dismissed = dismissed
  191. }
  192. /**
  193. * Retrieve the common directory for shared
  194. * game files (assets, libraries, etc).
  195. *
  196. * @returns {string} The launcher's common directory.
  197. */
  198. exports.getCommonDirectory = function(){
  199. return config.commonDirectory
  200. }
  201. /**
  202. * Retrieve the instance directory for the per
  203. * server game directories.
  204. *
  205. * @returns {string} The launcher's instance directory.
  206. */
  207. exports.getInstanceDirectory = function(){
  208. return config.instanceDirectory
  209. }
  210. /**
  211. * Retrieve the launcher's Client Token.
  212. * There is no default client token.
  213. *
  214. * @returns {string} The launcher's Client Token.
  215. */
  216. exports.getClientToken = function(){
  217. return config.clientToken
  218. }
  219. /**
  220. * Set the launcher's Client Token.
  221. *
  222. * @param {string} clientToken The launcher's new Client Token.
  223. */
  224. exports.setClientToken = function(clientToken){
  225. config.clientToken = clientToken
  226. }
  227. /**
  228. * Retrieve the ID of the selected serverpack.
  229. *
  230. * @param {boolean} def Optional. If true, the default value will be returned.
  231. * @returns {string} The ID of the selected serverpack.
  232. */
  233. exports.getSelectedServer = function(def = false){
  234. return !def ? config.selectedServer : DEFAULT_CONFIG.clientToken
  235. }
  236. /**
  237. * Set the ID of the selected serverpack.
  238. *
  239. * @param {string} serverID The ID of the new selected serverpack.
  240. */
  241. exports.setSelectedServer = function(serverID){
  242. config.selectedServer = serverID
  243. }
  244. /**
  245. * Get an array of each account currently authenticated by the launcher.
  246. *
  247. * @returns {Array.<Object>} An array of each stored authenticated account.
  248. */
  249. exports.getAuthAccounts = function(){
  250. return config.authenticationDatabase
  251. }
  252. /**
  253. * Returns the authenticated account with the given uuid. Value may
  254. * be null.
  255. *
  256. * @param {string} uuid The uuid of the authenticated account.
  257. * @returns {Object} The authenticated account with the given uuid.
  258. */
  259. exports.getAuthAccount = function(uuid){
  260. return config.authenticationDatabase[uuid]
  261. }
  262. /**
  263. * Update the access token of an authenticated account.
  264. *
  265. * @param {string} uuid The uuid of the authenticated account.
  266. * @param {string} accessToken The new Access Token.
  267. *
  268. * @returns {Object} The authenticated account object created by this action.
  269. */
  270. exports.updateAuthAccount = function(uuid, accessToken){
  271. config.authenticationDatabase[uuid].accessToken = accessToken
  272. return config.authenticationDatabase[uuid]
  273. }
  274. /**
  275. * Adds an authenticated account to the database to be stored.
  276. *
  277. * @param {string} uuid The uuid of the authenticated account.
  278. * @param {string} accessToken The accessToken of the authenticated account.
  279. * @param {string} username The username (usually email) of the authenticated account.
  280. * @param {string} displayName The in game name of the authenticated account.
  281. *
  282. * @returns {Object} The authenticated account object created by this action.
  283. */
  284. exports.addAuthAccount = function(uuid, accessToken, username, displayName){
  285. config.selectedAccount = uuid
  286. config.authenticationDatabase[uuid] = {
  287. accessToken,
  288. username,
  289. uuid,
  290. displayName
  291. }
  292. return config.authenticationDatabase[uuid]
  293. }
  294. /**
  295. * Remove an authenticated account from the database. If the account
  296. * was also the selected account, a new one will be selected. If there
  297. * are no accounts, the selected account will be null.
  298. *
  299. * @param {string} uuid The uuid of the authenticated account.
  300. *
  301. * @returns {boolean} True if the account was removed, false if it never existed.
  302. */
  303. exports.removeAuthAccount = function(uuid){
  304. if(config.authenticationDatabase[uuid] != null){
  305. delete config.authenticationDatabase[uuid]
  306. if(config.selectedAccount === uuid){
  307. const keys = Object.keys(config.authenticationDatabase)
  308. if(keys.length > 0){
  309. config.selectedAccount = keys[0]
  310. } else {
  311. config.selectedAccount = null
  312. }
  313. }
  314. return true
  315. }
  316. return false
  317. }
  318. /**
  319. * Get the currently selected authenticated account.
  320. *
  321. * @returns {Object} The selected authenticated account.
  322. */
  323. exports.getSelectedAccount = function(){
  324. return config.authenticationDatabase[config.selectedAccount]
  325. }
  326. /**
  327. * Set the selected authenticated account.
  328. *
  329. * @param {string} uuid The UUID of the account which is to be set
  330. * as the selected account.
  331. *
  332. * @returns {Object} The selected authenticated account.
  333. */
  334. exports.setSelectedAccount = function(uuid){
  335. const authAcc = config.authenticationDatabase[uuid]
  336. if(authAcc != null) {
  337. config.selectedAccount = uuid
  338. }
  339. return authAcc
  340. }
  341. /**
  342. * Get an array of each mod configuration currently stored.
  343. *
  344. * @returns {Array.<Object>} An array of each stored mod configuration.
  345. */
  346. exports.getModConfigurations = function(){
  347. return config.modConfigurations
  348. }
  349. /**
  350. * Set the array of stored mod configurations.
  351. *
  352. * @param {Array.<Object>} configurations An array of mod configurations.
  353. */
  354. exports.setModConfigurations = function(configurations){
  355. config.modConfigurations = configurations
  356. }
  357. /**
  358. * Get the mod configuration for a specific server.
  359. *
  360. * @param {string} serverid The id of the server.
  361. * @returns {Object} The mod configuration for the given server.
  362. */
  363. exports.getModConfiguration = function(serverid){
  364. const cfgs = config.modConfigurations
  365. for(let i=0; i<cfgs.length; i++){
  366. if(cfgs[i].id === serverid){
  367. return cfgs[i]
  368. }
  369. }
  370. return null
  371. }
  372. /**
  373. * Set the mod configuration for a specific server. This overrides any existing value.
  374. *
  375. * @param {string} serverid The id of the server for the given mod configuration.
  376. * @param {Object} configuration The mod configuration for the given server.
  377. */
  378. exports.setModConfiguration = function(serverid, configuration){
  379. const cfgs = config.modConfigurations
  380. for(let i=0; i<cfgs.length; i++){
  381. if(cfgs[i].id === serverid){
  382. cfgs[i] = configuration
  383. return
  384. }
  385. }
  386. cfgs.push(configuration)
  387. }
  388. // User Configurable Settings
  389. // Java Settings
  390. /**
  391. * Retrieve the minimum amount of memory for JVM initialization. This value
  392. * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
  393. * 1024 MegaBytes, etc.
  394. *
  395. * @param {boolean} def Optional. If true, the default value will be returned.
  396. * @returns {string} The minimum amount of memory for JVM initialization.
  397. */
  398. exports.getMinRAM = function(def = false){
  399. return !def ? config.settings.java.minRAM : DEFAULT_CONFIG.settings.java.minRAM
  400. }
  401. /**
  402. * Set the minimum amount of memory for JVM initialization. This value should
  403. * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
  404. * 1024 MegaBytes, etc.
  405. *
  406. * @param {string} minRAM The new minimum amount of memory for JVM initialization.
  407. */
  408. exports.setMinRAM = function(minRAM){
  409. config.settings.java.minRAM = minRAM
  410. }
  411. /**
  412. * Retrieve the maximum amount of memory for JVM initialization. This value
  413. * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
  414. * 1024 MegaBytes, etc.
  415. *
  416. * @param {boolean} def Optional. If true, the default value will be returned.
  417. * @returns {string} The maximum amount of memory for JVM initialization.
  418. */
  419. exports.getMaxRAM = function(def = false){
  420. return !def ? config.settings.java.maxRAM : resolveMaxRAM()
  421. }
  422. /**
  423. * Set the maximum amount of memory for JVM initialization. This value should
  424. * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
  425. * 1024 MegaBytes, etc.
  426. *
  427. * @param {string} maxRAM The new maximum amount of memory for JVM initialization.
  428. */
  429. exports.setMaxRAM = function(maxRAM){
  430. config.settings.java.maxRAM = maxRAM
  431. }
  432. /**
  433. * Retrieve the path of the Java Executable.
  434. *
  435. * This is a resolved configuration value and defaults to null until externally assigned.
  436. *
  437. * @returns {string} The path of the Java Executable.
  438. */
  439. exports.getJavaExecutable = function(){
  440. return config.settings.java.executable
  441. }
  442. /**
  443. * Set the path of the Java Executable.
  444. *
  445. * @param {string} executable The new path of the Java Executable.
  446. */
  447. exports.setJavaExecutable = function(executable){
  448. config.settings.java.executable = executable
  449. }
  450. /**
  451. * Retrieve the additional arguments for JVM initialization. Required arguments,
  452. * such as memory allocation, will be dynamically resolved and will not be included
  453. * in this value.
  454. *
  455. * @param {boolean} def Optional. If true, the default value will be returned.
  456. * @returns {Array.<string>} An array of the additional arguments for JVM initialization.
  457. */
  458. exports.getJVMOptions = function(def = false){
  459. return !def ? config.settings.java.jvmOptions : DEFAULT_CONFIG.settings.java.jvmOptions
  460. }
  461. /**
  462. * Set the additional arguments for JVM initialization. Required arguments,
  463. * such as memory allocation, will be dynamically resolved and should not be
  464. * included in this value.
  465. *
  466. * @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
  467. * initialization.
  468. */
  469. exports.setJVMOptions = function(jvmOptions){
  470. config.settings.java.jvmOptions = jvmOptions
  471. }
  472. // Game Settings
  473. /**
  474. * Retrieve the width of the game window.
  475. *
  476. * @param {boolean} def Optional. If true, the default value will be returned.
  477. * @returns {number} The width of the game window.
  478. */
  479. exports.getGameWidth = function(def = false){
  480. return !def ? config.settings.game.resWidth : DEFAULT_CONFIG.settings.game.resWidth
  481. }
  482. /**
  483. * Set the width of the game window.
  484. *
  485. * @param {number} resWidth The new width of the game window.
  486. */
  487. exports.setGameWidth = function(resWidth){
  488. config.settings.game.resWidth = Number.parseInt(resWidth)
  489. }
  490. /**
  491. * Validate a potential new width value.
  492. *
  493. * @param {number} resWidth The width value to validate.
  494. * @returns {boolean} Whether or not the value is valid.
  495. */
  496. exports.validateGameWidth = function(resWidth){
  497. const nVal = Number.parseInt(resWidth)
  498. return Number.isInteger(nVal) && nVal >= 0
  499. }
  500. /**
  501. * Retrieve the height of the game window.
  502. *
  503. * @param {boolean} def Optional. If true, the default value will be returned.
  504. * @returns {number} The height of the game window.
  505. */
  506. exports.getGameHeight = function(def = false){
  507. return !def ? config.settings.game.resHeight : DEFAULT_CONFIG.settings.game.resHeight
  508. }
  509. /**
  510. * Set the height of the game window.
  511. *
  512. * @param {number} resHeight The new height of the game window.
  513. */
  514. exports.setGameHeight = function(resHeight){
  515. config.settings.game.resHeight = Number.parseInt(resHeight)
  516. }
  517. /**
  518. * Validate a potential new height value.
  519. *
  520. * @param {number} resHeight The height value to validate.
  521. * @returns {boolean} Whether or not the value is valid.
  522. */
  523. exports.validateGameHeight = function(resHeight){
  524. const nVal = Number.parseInt(resHeight)
  525. return Number.isInteger(nVal) && nVal >= 0
  526. }
  527. /**
  528. * Check if the game should be launched in fullscreen mode.
  529. *
  530. * @param {boolean} def Optional. If true, the default value will be returned.
  531. * @returns {boolean} Whether or not the game is set to launch in fullscreen mode.
  532. */
  533. exports.getFullscreen = function(def = false){
  534. return !def ? config.settings.game.fullscreen : DEFAULT_CONFIG.settings.game.fullscreen
  535. }
  536. /**
  537. * Change the status of if the game should be launched in fullscreen mode.
  538. *
  539. * @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode.
  540. */
  541. exports.setFullscreen = function(fullscreen){
  542. config.settings.game.fullscreen = fullscreen
  543. }
  544. /**
  545. * Check if the game should auto connect to servers.
  546. *
  547. * @param {boolean} def Optional. If true, the default value will be returned.
  548. * @returns {boolean} Whether or not the game should auto connect to servers.
  549. */
  550. exports.getAutoConnect = function(def = false){
  551. return !def ? config.settings.game.autoConnect : DEFAULT_CONFIG.settings.game.autoConnect
  552. }
  553. /**
  554. * Change the status of whether or not the game should auto connect to servers.
  555. *
  556. * @param {boolean} autoConnect Whether or not the game should auto connect to servers.
  557. */
  558. exports.setAutoConnect = function(autoConnect){
  559. config.settings.game.autoConnect = autoConnect
  560. }
  561. /**
  562. * Check if the game should launch as a detached process.
  563. *
  564. * @param {boolean} def Optional. If true, the default value will be returned.
  565. * @returns {boolean} Whether or not the game will launch as a detached process.
  566. */
  567. exports.getLaunchDetached = function(def = false){
  568. return !def ? config.settings.game.launchDetached : DEFAULT_CONFIG.settings.game.launchDetached
  569. }
  570. /**
  571. * Change the status of whether or not the game should launch as a detached process.
  572. *
  573. * @param {boolean} launchDetached Whether or not the game should launch as a detached process.
  574. */
  575. exports.setLaunchDetached = function(launchDetached){
  576. config.settings.game.launchDetached = launchDetached
  577. }
  578. // Launcher Settings
  579. /**
  580. * Check if the launcher should download prerelease versions.
  581. *
  582. * @param {boolean} def Optional. If true, the default value will be returned.
  583. * @returns {boolean} Whether or not the launcher should download prerelease versions.
  584. */
  585. exports.getAllowPrerelease = function(def = false){
  586. return !def ? config.settings.launcher.allowPrerelease : DEFAULT_CONFIG.settings.launcher.allowPrerelease
  587. }
  588. /**
  589. * Change the status of Whether or not the launcher should download prerelease versions.
  590. *
  591. * @param {boolean} launchDetached Whether or not the launcher should download prerelease versions.
  592. */
  593. exports.setAllowPrerelease = function(allowPrerelease){
  594. config.settings.launcher.allowPrerelease = allowPrerelease
  595. }