Merge branch 'master' into ms-login2.0

This commit is contained in:
GeekCorner 2023-03-22 16:24:14 +01:00 committed by GitHub
commit 5fb312628a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1538 additions and 5172 deletions

View File

@ -1,11 +1,11 @@
{ {
"env": { "env": {
"es2017": true, "es2022": true,
"node": true "node": true
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"parserOptions": { "parserOptions": {
"ecmaVersion": 2019, "ecmaVersion": 2022,
"sourceType": "module" "sourceType": "module"
}, },
"rules": { "rules": {

View File

@ -17,7 +17,7 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 16 node-version: 18
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2

2
.nvmrc
View File

@ -1 +1 @@
16 18

View File

@ -4,7 +4,7 @@
<em><h5 align="center">(formerly Electron Launcher)</h5></em> <em><h5 align="center">(formerly Electron Launcher)</h5></em>
[<p align="center"><img src="https://img.shields.io/github/workflow/status/dscalzi/HeliosLauncher/Build.svg?style=for-the-badge" alt="gh actions">](https://github.com/dscalzi/HeliosLauncher/actions) [<img src="https://img.shields.io/github/downloads/dscalzi/HeliosLauncher/total.svg?style=for-the-badge" alt="downloads">](https://github.com/dscalzi/HeliosLauncher/releases) <img src="https://forthebadge.com/images/badges/winter-is-coming.svg" height="28px" alt="winter-is-coming"></p> [<p align="center"><img src="https://img.shields.io/github/actions/workflow/status/dscalzi/HeliosLauncher/build.yml?branch=master&style=for-the-badge" alt="gh actions">](https://github.com/dscalzi/HeliosLauncher/actions) [<img src="https://img.shields.io/github/downloads/dscalzi/HeliosLauncher/total.svg?style=for-the-badge" alt="downloads">](https://github.com/dscalzi/HeliosLauncher/releases) <img src="https://forthebadge.com/images/badges/winter-is-coming.svg" height="28px" alt="winter-is-coming"></p>
<p align="center">Join modded servers without worrying about installing Java, Forge, or other mods. We'll handle that for you.</p> <p align="center">Join modded servers without worrying about installing Java, Forge, or other mods. We'll handle that for you.</p>
@ -84,7 +84,7 @@ This section details the setup of a basic developmentment environment.
**System Requirements** **System Requirements**
* [Node.js][nodejs] v16 * [Node.js][nodejs] v18
--- ---

View File

@ -1228,6 +1228,59 @@ body, button {
font-size: 12px; 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. */ /* Remove spin button from number inputs. */
#settingsContainer input[type=number]::-webkit-inner-spin-button { #settingsContainer input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
@ -1638,59 +1691,6 @@ input:checked + .toggleSwitchSlider:before {
* Settings View (Mods Tab) * 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. */ /* Main content container for the mod elements. */
#settingsModsContainer { #settingsModsContainer {
width: 75%; width: 75%;

View File

@ -6,8 +6,11 @@ if(target == null){
} }
let tracker = new target(...(process.argv.splice(3))) 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]) //const tracker = new AssetGuard(process.argv[2], process.argv[3])
console.log('AssetExec Started') logger.info('AssetExec Started')
// Temporary for debug purposes. // Temporary for debug purposes.
process.on('unhandledRejection', r => console.log(r)) process.on('unhandledRejection', r => console.log(r))
@ -66,6 +69,6 @@ process.on('message', (msg) => {
}) })
process.on('disconnect', () => { process.on('disconnect', () => {
console.log('AssetExec Disconnected') logger.info('AssetExec Disconnected')
process.exit(0) process.exit(0)
}) })

View File

@ -5,6 +5,8 @@ const child_process = require('child_process')
const crypto = require('crypto') const crypto = require('crypto')
const EventEmitter = require('events') const EventEmitter = require('events')
const fs = require('fs-extra') const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const nodeDiskInfo = require('node-disk-info')
const StreamZip = require('node-stream-zip') const StreamZip = require('node-stream-zip')
const path = require('path') const path = require('path')
const Registry = require('winreg') const Registry = require('winreg')
@ -16,6 +18,8 @@ const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager') const DistroManager = require('./distromanager')
const isDev = require('./isdev') const isDev = require('./isdev')
const isARM64 = process.arch === 'arm64'
// Classes // Classes
/** Class representing a base asset. */ /** Class representing a base asset. */
@ -214,6 +218,7 @@ class JavaGuard extends EventEmitter {
constructor(mcVersion){ constructor(mcVersion){
super() super()
this.mcVersion = mcVersion this.mcVersion = mcVersion
this.logger = LoggerUtil.getLogger('JavaGuard')
} }
/** /**
@ -299,7 +304,8 @@ class JavaGuard extends EventEmitter {
break 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) => { return new Promise((resolve, reject) => {
request.head({url, json: true}, (err, resp) => { 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. * @returns {boolean} True if the path points to a Java executable, otherwise false.
*/ */
static isJavaExecPath(pth){ static isJavaExecPath(pth){
if(pth == null) {
return false
}
if(process.platform === 'win32'){ if(process.platform === 'win32'){
return pth.endsWith(path.join('bin', 'javaw.exe')) return pth.endsWith(path.join('bin', 'javaw.exe'))
} else if(process.platform === 'darwin'){ } else if(process.platform === 'darwin'){
@ -447,7 +456,7 @@ class JavaGuard extends EventEmitter {
if(props[i].indexOf('sun.arch.data.model') > -1){ if(props[i].indexOf('sun.arch.data.model') > -1){
let arch = props[i].split('=')[1].trim() let arch = props[i].split('=')[1].trim()
arch = parseInt(arch) arch = parseInt(arch)
console.log(props[i].trim()) this.logger.debug(props[i].trim())
if(arch === 64){ if(arch === 64){
meta.arch = arch meta.arch = arch
++checksum ++checksum
@ -457,33 +466,40 @@ class JavaGuard extends EventEmitter {
} }
} else if(props[i].indexOf('java.runtime.version') > -1){ } else if(props[i].indexOf('java.runtime.version') > -1){
let verString = props[i].split('=')[1].trim() let verString = props[i].split('=')[1].trim()
console.log(props[i].trim()) this.logger.debug(props[i].trim())
const verOb = JavaGuard.parseJavaRuntimeVersion(verString) 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){ if(verOb.major < 9){
// Java 8 // 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 meta.version = verOb
++checksum ++checksum
if(checksum === goal){ if(checksum === goal){
break 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. // Space included so we get only the vendor.
} else if(props[i].lastIndexOf('java.vendor ') > -1) { } else if(props[i].lastIndexOf('java.vendor ') > -1) {
let vendorName = props[i].split('=')[1].trim() let vendorName = props[i].split('=')[1].trim()
console.log(props[i].trim()) this.logger.debug(props[i].trim())
meta.vendor = vendorName 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}) resolve({valid: false})
} else if(fs.existsSync(binaryExecPath)){ } else if(fs.existsSync(binaryExecPath)){
// Workaround (javaw.exe no longer outputs this information.) // Workaround (javaw.exe no longer outputs this information.)
console.log(typeof binaryExecPath) this.logger.debug(typeof binaryExecPath)
if(binaryExecPath.indexOf('javaw.exe') > -1) { if(binaryExecPath.indexOf('javaw.exe') > -1) {
binaryExecPath.replace('javaw.exe', 'java.exe') binaryExecPath.replace('javaw.exe', 'java.exe')
} }
@ -804,13 +820,20 @@ class JavaGuard extends EventEmitter {
// Get possible paths from the registry. // Get possible paths from the registry.
let pathSet1 = await JavaGuard._scanRegistry() let pathSet1 = await JavaGuard._scanRegistry()
if(pathSet1.size === 0){ if(pathSet1.size === 0){
// Do a manual file system scan of program files. // Do a manual file system scan of program files.
pathSet1 = new Set([ // Check all drives
...pathSet1, const driveMounts = nodeDiskInfo.getDiskInfoSync().map(({ mounted }) => mounted)
...(await JavaGuard._scanFileSystem('C:\\Program Files\\Java')), for(const mount of driveMounts) {
...(await JavaGuard._scanFileSystem('C:\\Program Files\\Eclipse Foundation')), pathSet1 = new Set([
...(await JavaGuard._scanFileSystem('C:\\Program Files\\AdoptOpenJDK')) ...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. // Get possible paths from the data directory.
@ -848,6 +871,9 @@ class JavaGuard extends EventEmitter {
* @param {string} dataDir The base launcher directory. * @param {string} dataDir The base launcher directory.
* @returns {Promise.<string>} A Promise which resolves to the executable path of a valid * @returns {Promise.<string>} A Promise which resolves to the executable path of a valid
* x64 Java installation. If none are found, null is returned. * 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){ async _darwinJavaValidate(dataDir){
@ -876,7 +902,16 @@ class JavaGuard extends EventEmitter {
pathArr = JavaGuard._sortValidJavaArray(pathArr) pathArr = JavaGuard._sortValidJavaArray(pathArr)
if(pathArr.length > 0){ 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 { } else {
return null return null
} }
@ -942,6 +977,8 @@ class JavaGuard extends EventEmitter {
*/ */
class AssetGuard extends EventEmitter { class AssetGuard extends EventEmitter {
static logger = LoggerUtil.getLogger('AssetGuard')
/** /**
* Create an instance of AssetGuard. * Create an instance of AssetGuard.
* On creation the object's properties are never-null default * On creation the object's properties are never-null default
@ -1100,7 +1137,8 @@ class AssetGuard extends EventEmitter {
* @returns {Promise.<void>} An empty promise to indicate the extraction has completed. * @returns {Promise.<void>} An empty promise to indicate the extraction has completed.
*/ */
static _extractPackXZ(filePaths, javaExecutable){ static _extractPackXZ(filePaths, javaExecutable){
console.log('[PackXZExtract] Starting') const extractLogger = LoggerUtil.getLogger('PackXZExtract')
extractLogger.info('Starting')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let libPath let libPath
@ -1117,13 +1155,13 @@ class AssetGuard extends EventEmitter {
const filePath = filePaths.join(',') const filePath = filePaths.join(',')
const child = child_process.spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath]) const child = child_process.spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath])
child.stdout.on('data', (data) => { child.stdout.on('data', (data) => {
console.log('[PackXZExtract]', data.toString('utf8')) extractLogger.info(data.toString('utf8'))
}) })
child.stderr.on('data', (data) => { child.stderr.on('data', (data) => {
console.log('[PackXZExtract]', data.toString('utf8')) extractLogger.info(data.toString('utf8'))
}) })
child.on('close', (code, signal) => { child.on('close', (code, signal) => {
console.log('[PackXZExtract]', 'Exited with code', code) extractLogger.info('Exited with code', code)
resolve() resolve()
}) })
}) })
@ -1189,7 +1227,7 @@ class AssetGuard extends EventEmitter {
if(!fs.existsSync(versionFile) || force){ if(!fs.existsSync(versionFile) || force){
const url = await self._getVersionDataUrl(version) const url = await self._getVersionDataUrl(version)
//This download will never be tracked as it's essential and trivial. //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) fs.ensureDirSync(versionPath)
const stream = request(url).pipe(fs.createWriteStream(versionFile)) const stream = request(url).pipe(fs.createWriteStream(versionFile))
stream.on('finish', () => { stream.on('finish', () => {
@ -1271,7 +1309,7 @@ class AssetGuard extends EventEmitter {
let data = null let data = null
if(!fs.existsSync(assetIndexLoc) || force){ if(!fs.existsSync(assetIndexLoc) || force){
console.log('Downloading ' + versionData.id + ' asset index.') AssetGuard.logger.info('Downloading ' + versionData.id + ' asset index.')
fs.ensureDirSync(indexPath) fs.ensureDirSync(indexPath)
const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc)) const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc))
stream.on('finish', () => { stream.on('finish', () => {
@ -1542,9 +1580,10 @@ class AssetGuard extends EventEmitter {
// Java (Category=''') Validation (download) Functions // Java (Category=''') Validation (download) Functions
// #region // #region
_enqueueOpenJDK(dataDir){ _enqueueOpenJDK(dataDir, mcVersion){
return new Promise((resolve, reject) => { 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){ if(verData != null){
dataDir = path.join(dataDir, 'runtime', 'x64') dataDir = path.join(dataDir, 'runtime', 'x64')
@ -1559,9 +1598,9 @@ class AssetGuard extends EventEmitter {
// Tar.gz // Tar.gz
let h = null let h = null
fs.createReadStream(a.to) fs.createReadStream(a.to)
.on('error', err => console.log(err)) .on('error', err => AssetGuard.logger.error(err))
.pipe(zlib.createGunzip()) .pipe(zlib.createGunzip())
.on('error', err => console.log(err)) .on('error', err => AssetGuard.logger.error(err))
.pipe(tar.extract(dataDir, { .pipe(tar.extract(dataDir, {
map: (header) => { map: (header) => {
if(h == null){ 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', () => { .on('finish', () => {
fs.unlink(a.to, err => { fs.unlink(a.to, err => {
if(err){ if(err){
console.log(err) AssetGuard.logger.error(err)
} }
if(h.indexOf('/') > -1){ if(h.indexOf('/') > -1){
h = h.substring(0, h.indexOf('/')) h = h.substring(0, h.indexOf('/'))
@ -1606,14 +1645,14 @@ class AssetGuard extends EventEmitter {
const entries = await zip.entries() const entries = await zip.entries()
pos = path.join(runtimeDir, Object.keys(entries)[0]) pos = path.join(runtimeDir, Object.keys(entries)[0])
console.log('Extracting jdk..') AssetGuard.logger.info('Extracting jdk..')
await zip.extract(null, runtimeDir) await zip.extract(null, runtimeDir)
console.log('Cleaning up..') AssetGuard.logger.info('Cleaning up..')
await fs.remove(zipPath) await fs.remove(zipPath)
console.log('Jdk extraction complete.') AssetGuard.logger.info('Jdk extraction complete.')
} catch(err) { } catch(err) {
console.log(err) AssetGuard.logger.error(err)
} finally { } finally {
zip.close() zip.close()
self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos))
@ -1679,7 +1718,7 @@ class AssetGuard extends EventEmitter {
const dlQueue = dlTracker.dlqueue const dlQueue = dlTracker.dlqueue
if(dlQueue.length > 0){ if(dlQueue.length > 0){
console.log('DLQueue', dlQueue) AssetGuard.logger.info('DLQueue', dlQueue)
async.eachLimit(dlQueue, limit, (asset, cb) => { async.eachLimit(dlQueue, limit, (asset, cb) => {
@ -1696,7 +1735,7 @@ class AssetGuard extends EventEmitter {
const contentLength = parseInt(resp.headers['content-length']) const contentLength = parseInt(resp.headers['content-length'])
if(contentLength !== asset.size){ 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 doHashCheck = true
// Adjust download // Adjust download
@ -1713,9 +1752,9 @@ class AssetGuard extends EventEmitter {
if(doHashCheck){ if(doHashCheck){
const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash) const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash)
if(v){ 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 { } 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 { } else {
req.abort() 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.progress += asset.size*1
self.emit('progress', 'download', self.progress, self.totaldlsize) self.emit('progress', 'download', self.progress, self.totaldlsize)
cb() cb()
@ -1748,9 +1787,9 @@ class AssetGuard extends EventEmitter {
}, (err) => { }, (err) => {
if(err){ if(err){
console.log('An item in ' + identifier + ' failed to process') AssetGuard.logger.warn('An item in ' + identifier + ' failed to process')
} else { } else {
console.log('All ' + identifier + ' have been processed successfully') AssetGuard.logger.info('All ' + identifier + ' have been processed successfully')
} }
//self.totaldlsize -= dlTracker.dlsize //self.totaldlsize -= dlTracker.dlsize
@ -1869,4 +1908,4 @@ module.exports = {
JavaGuard, JavaGuard,
Asset, Asset,
Library Library
} }

View File

@ -1,8 +1,9 @@
const fs = require('fs-extra') const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const os = require('os') const os = require('os')
const path = require('path') 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) const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
// TODO change // TODO change
@ -63,6 +64,27 @@ function resolveMinRAM(){
return resolveMaxRAM() 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<des.length; i++){
if(!(parseInt(act[i]) >= parseInt(des[i]))){
return false
}
}
return true
}
/** /**
* Three types of values: * Three types of values:
* Static = Explicitly declared. * Static = Explicitly declared.
@ -71,17 +93,6 @@ function resolveMinRAM(){
*/ */
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
settings: { settings: {
java: {
minRAM: resolveMinRAM(),
maxRAM: resolveMaxRAM(), // Dynamic
executable: null,
jvmOptions: [
'-XX:+UseConcMarkSweepGC',
'-XX:+CMSIncrementalMode',
'-XX:-UseAdaptiveSizePolicy',
'-Xmn128M'
],
},
game: { game: {
resWidth: 1280, resWidth: 1280,
resHeight: 720, resHeight: 720,
@ -103,7 +114,8 @@ const DEFAULT_CONFIG = {
selectedServer: null, // Resolved selectedServer: null, // Resolved
selectedAccount: null, selectedAccount: null,
authenticationDatabase: {}, authenticationDatabase: {},
modConfigurations: [] modConfigurations: [],
javaConfig: {}
} }
let config = null let config = null
@ -144,8 +156,8 @@ exports.load = function(){
doValidate = true doValidate = true
} catch (err){ } catch (err){
logger.error(err) logger.error(err)
logger.log('Configuration file contains malformed JSON or is corrupt.') logger.info('Configuration file contains malformed JSON or is corrupt.')
logger.log('Generating a new configuration file.') logger.info('Generating a new configuration file.')
fs.ensureDirSync(path.join(configPath, '..')) fs.ensureDirSync(path.join(configPath, '..'))
config = DEFAULT_CONFIG config = DEFAULT_CONFIG
exports.save() exports.save()
@ -155,7 +167,7 @@ exports.load = function(){
exports.save() exports.save()
} }
} }
logger.log('Successfully Loaded') logger.info('Successfully Loaded')
} }
/** /**
@ -177,7 +189,7 @@ function validateKeySet(srcObj, destObj){
if(srcObj == null){ if(srcObj == null){
srcObj = {} srcObj = {}
} }
const validationBlacklist = ['authenticationDatabase'] const validationBlacklist = ['authenticationDatabase', 'javaConfig']
const keys = Object.keys(srcObj) const keys = Object.keys(srcObj)
for(let i=0; i<keys.length; i++){ for(let i=0; i<keys.length; i++){
if(typeof destObj[keys[i]] === 'undefined'){ if(typeof destObj[keys[i]] === 'undefined'){
@ -511,16 +523,66 @@ exports.setModConfiguration = function(serverid, configuration){
// Java Settings // Java Settings
function defaultJavaConfig(mcVersion) {
if(mcVersionAtLeast('1.17', mcVersion)) {
return defaultJavaConfig117()
} else {
return defaultJavaConfigBelow117()
}
}
function defaultJavaConfigBelow117() {
return {
minRAM: resolveMinRAM(),
maxRAM: resolveMaxRAM(), // Dynamic
executable: null,
jvmOptions: [
'-XX:+UseConcMarkSweepGC',
'-XX:+CMSIncrementalMode',
'-XX:-UseAdaptiveSizePolicy',
'-Xmn128M'
],
}
}
function defaultJavaConfig117() {
return {
minRAM: resolveMinRAM(),
maxRAM: resolveMaxRAM(), // Dynamic
executable: null,
jvmOptions: [
'-XX:+UnlockExperimentalVMOptions',
'-XX:+UseG1GC',
'-XX:G1NewSizePercent=20',
'-XX:G1ReservePercent=20',
'-XX:MaxGCPauseMillis=50',
'-XX:G1HeapRegionSize=32M'
],
}
}
/**
* Ensure a java config property is set for the given server.
*
* @param {string} serverid The server id.
* @param {*} mcVersion The minecraft version of the server.
*/
exports.ensureJavaConfig = function(serverid, mcVersion) {
if(!Object.prototype.hasOwnProperty.call(config.javaConfig, serverid)) {
config.javaConfig[serverid] = defaultJavaConfig(mcVersion)
}
}
/** /**
* Retrieve the minimum amount of memory for JVM initialization. This value * Retrieve the minimum amount of memory for JVM initialization. This value
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc. * 1024 MegaBytes, etc.
* *
* @param {boolean} def Optional. If true, the default value will be returned. * @param {string} serverid The server id.
* @returns {string} The minimum amount of memory for JVM initialization. * @returns {string} The minimum amount of memory for JVM initialization.
*/ */
exports.getMinRAM = function(def = false){ exports.getMinRAM = function(serverid){
return !def ? config.settings.java.minRAM : DEFAULT_CONFIG.settings.java.minRAM return config.javaConfig[serverid].minRAM
} }
/** /**
@ -528,10 +590,11 @@ exports.getMinRAM = function(def = false){
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc. * 1024 MegaBytes, etc.
* *
* @param {string} serverid The server id.
* @param {string} minRAM The new minimum amount of memory for JVM initialization. * @param {string} minRAM The new minimum amount of memory for JVM initialization.
*/ */
exports.setMinRAM = function(minRAM){ exports.setMinRAM = function(serverid, minRAM){
config.settings.java.minRAM = minRAM config.javaConfig[serverid].minRAM = minRAM
} }
/** /**
@ -539,11 +602,11 @@ exports.setMinRAM = function(minRAM){
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc. * 1024 MegaBytes, etc.
* *
* @param {boolean} def Optional. If true, the default value will be returned. * @param {string} serverid The server id.
* @returns {string} The maximum amount of memory for JVM initialization. * @returns {string} The maximum amount of memory for JVM initialization.
*/ */
exports.getMaxRAM = function(def = false){ exports.getMaxRAM = function(serverid){
return !def ? config.settings.java.maxRAM : resolveMaxRAM() return config.javaConfig[serverid].maxRAM
} }
/** /**
@ -551,10 +614,11 @@ exports.getMaxRAM = function(def = false){
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc. * 1024 MegaBytes, etc.
* *
* @param {string} serverid The server id.
* @param {string} maxRAM The new maximum amount of memory for JVM initialization. * @param {string} maxRAM The new maximum amount of memory for JVM initialization.
*/ */
exports.setMaxRAM = function(maxRAM){ exports.setMaxRAM = function(serverid, maxRAM){
config.settings.java.maxRAM = maxRAM config.javaConfig[serverid].maxRAM = maxRAM
} }
/** /**
@ -562,19 +626,21 @@ exports.setMaxRAM = function(maxRAM){
* *
* This is a resolved configuration value and defaults to null until externally assigned. * This is a resolved configuration value and defaults to null until externally assigned.
* *
* @param {string} serverid The server id.
* @returns {string} The path of the Java Executable. * @returns {string} The path of the Java Executable.
*/ */
exports.getJavaExecutable = function(){ exports.getJavaExecutable = function(serverid){
return config.settings.java.executable return config.javaConfig[serverid].executable
} }
/** /**
* Set the path of the Java Executable. * Set the path of the Java Executable.
* *
* @param {string} serverid The server id.
* @param {string} executable The new path of the Java Executable. * @param {string} executable The new path of the Java Executable.
*/ */
exports.setJavaExecutable = function(executable){ exports.setJavaExecutable = function(serverid, executable){
config.settings.java.executable = executable config.javaConfig[serverid].executable = executable
} }
/** /**
@ -582,11 +648,11 @@ exports.setJavaExecutable = function(executable){
* such as memory allocation, will be dynamically resolved and will not be included * such as memory allocation, will be dynamically resolved and will not be included
* in this value. * in this value.
* *
* @param {boolean} def Optional. If true, the default value will be returned. * @param {string} serverid The server id.
* @returns {Array.<string>} An array of the additional arguments for JVM initialization. * @returns {Array.<string>} An array of the additional arguments for JVM initialization.
*/ */
exports.getJVMOptions = function(def = false){ exports.getJVMOptions = function(serverid){
return !def ? config.settings.java.jvmOptions : DEFAULT_CONFIG.settings.java.jvmOptions 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 * such as memory allocation, will be dynamically resolved and should not be
* included in this value. * included in this value.
* *
* @param {string} serverid The server id.
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM * @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
* initialization. * initialization.
*/ */
exports.setJVMOptions = function(jvmOptions){ exports.setJVMOptions = function(serverid, jvmOptions){
config.settings.java.jvmOptions = jvmOptions config.javaConfig[serverid].jvmOptions = jvmOptions
} }
// Game Settings // Game Settings

View File

@ -1,7 +1,9 @@
// Work in progress // 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 client
let activity let activity
@ -21,15 +23,15 @@ exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting
} }
client.on('ready', () => { client.on('ready', () => {
logger.log('Discord RPC Connected') logger.info('Discord RPC Connected')
client.setActivity(activity) client.setActivity(activity)
}) })
client.login({clientId: genSettings.clientId}).catch(error => { client.login({clientId: genSettings.clientId}).catch(error => {
if(error.message.includes('ENOENT')) { 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 { } else {
logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error) logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error)
} }
}) })
} }

View File

@ -1,9 +1,11 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const request = require('request') const request = require('request')
const { LoggerUtil } = require('helios-core')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const logger = require('./loggerutil')('%c[DistroManager]', 'color: #a02d2a; font-weight: bold')
const logger = LoggerUtil.getLogger('DistroManager')
/** /**
* Represents the download information * Represents the download information
@ -118,7 +120,7 @@ class Module {
* @returns {Module} The parsed Module. * @returns {Module} The parsed Module.
*/ */
static fromJSON(json, serverid){ 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.identifier = id
this.type = type this.type = type
this.classpath = classpath
this._resolveMetaData() this._resolveMetaData()
this.name = name this.name = name
this.required = Required.fromJSON(required) this.required = Required.fromJSON(required)
@ -306,6 +309,13 @@ class Module {
return this.type return this.type
} }
/**
* @returns {boolean} Whether or not this library should be on the classpath.
*/
getClasspath(){
return this.classpath ?? true
}
} }
exports.Module exports.Module
@ -591,10 +601,10 @@ exports.pullLocal = function(){
exports.setDevMode = function(value){ exports.setDevMode = function(value){
if(value){ if(value){
logger.log('Developer mode enabled.') logger.info('Developer mode enabled.')
logger.log('If you don\'t know what that means, revert immediately.') logger.info('If you don\'t know what that means, revert immediately.')
} else { } else {
logger.log('Developer mode disabled.') logger.info('Developer mode disabled.')
} }
DEV_MODE = value DEV_MODE = value
} }

View File

@ -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)
}

View File

@ -1,14 +1,16 @@
const {ipcRenderer} = require('electron') const {ipcRenderer} = require('electron')
const fs = require('fs-extra') const fs = require('fs-extra')
const os = require('os') const os = require('os')
const path = require('path') const path = require('path')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager') const DistroManager = require('./distromanager')
const LangLoader = require('./langloader') const LangLoader = require('./langloader')
const logger = require('./loggerutil')('%c[Preloader]', 'color: #a02d2a; font-weight: bold') const { LoggerUtil } = require('helios-core')
logger.log('Loading..') const logger = LoggerUtil.getLogger('Preloader')
logger.info('Loading..')
// Load ConfigManager // Load ConfigManager
ConfigManager.load() ConfigManager.load()
@ -21,7 +23,7 @@ function onDistroLoad(data){
// Resolve the selected server if its value has yet to be set. // Resolve the selected server if its value has yet to be set.
if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){ 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.setSelectedServer(data.getMainServer().getID())
ConfigManager.save() ConfigManager.save()
} }
@ -31,26 +33,26 @@ function onDistroLoad(data){
// Ensure Distribution is downloaded and cached. // Ensure Distribution is downloaded and cached.
DistroManager.pullRemote().then((data) => { DistroManager.pullRemote().then((data) => {
logger.log('Loaded distribution index.') logger.info('Loaded distribution index.')
onDistroLoad(data) onDistroLoad(data)
}).catch((err) => { }).catch((err) => {
logger.log('Failed to load distribution index.') logger.info('Failed to load distribution index.')
logger.error(err) 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. // Try getting a local copy, better than nothing.
DistroManager.pullLocal().then((data) => { 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) onDistroLoad(data)
}).catch((err) => { }).catch((err) => {
logger.log('Failed to load an older version of the distribution index.') logger.info('Failed to load an older version of the distribution index.')
logger.log('Application cannot run.') logger.info('Application cannot run.')
logger.error(err) logger.error(err)
onDistroLoad(null) onDistroLoad(null)
@ -64,6 +66,6 @@ fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) =>
if(err){ if(err){
logger.warn('Error while cleaning natives directory', err) logger.warn('Error while cleaning natives directory', err)
} else { } else {
logger.log('Cleaned natives directory.') logger.info('Cleaned natives directory.')
} }
}) })

View File

@ -2,6 +2,7 @@ const AdmZip = require('adm-zip')
const child_process = require('child_process') const child_process = require('child_process')
const crypto = require('crypto') const crypto = require('crypto')
const fs = require('fs-extra') const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const os = require('os') const os = require('os')
const path = require('path') const path = require('path')
const { URL } = require('url') const { URL } = require('url')
@ -9,9 +10,8 @@ const { URL } = require('url')
const { Util, Library } = require('./assetguard') const { Util, Library } = require('./assetguard')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager') 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 { class ProcessBuilder {
@ -40,7 +40,7 @@ class ProcessBuilder {
const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex')) const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
process.throwDeprecation = true process.throwDeprecation = true
this.setupLiteLoader() 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()) const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules())
// Mod list below 1.13 // Mod list below 1.13
@ -59,9 +59,9 @@ class ProcessBuilder {
args = args.concat(this.constructModList(modObj.fMods)) 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, cwd: this.gameDir,
detached: ConfigManager.getLaunchDetached() detached: ConfigManager.getLaunchDetached()
}) })
@ -73,22 +73,20 @@ class ProcessBuilder {
child.stdout.setEncoding('utf8') child.stdout.setEncoding('utf8')
child.stderr.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) => { 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) => { 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) => { child.on('close', (code, signal) => {
logger.log('Exited with code', code) logger.info('Exited with code', code)
fs.remove(tempNativePath, (err) => { fs.remove(tempNativePath, (err) => {
if(err){ if(err){
logger.warn('Error while deleting temp dir', err) logger.warn('Error while deleting temp dir', err)
} else { } else {
logger.log('Temp dir deleted successfully.') logger.info('Temp dir deleted successfully.')
} }
}) })
}) })
@ -96,6 +94,16 @@ class ProcessBuilder {
return child 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 * Determine if an optional mod is enabled from its configuration value. If the
* configuration value is null, the required object will be used to * configuration value is null, the required object will be used to
@ -339,16 +347,16 @@ class ProcessBuilder {
// Classpath Argument // Classpath Argument
args.push('-cp') args.push('-cp')
args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')) args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator()))
// Java Arguments // Java Arguments
if(process.platform === 'darwin'){ if(process.platform === 'darwin'){
args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
} }
args.push('-Xmx' + ConfigManager.getMaxRAM()) args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
args.push('-Xms' + ConfigManager.getMinRAM()) args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
args = args.concat(ConfigManager.getJVMOptions()) args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
args.push('-Djava.library.path=' + tempNativePath) args.push('-Djava.library.path=' + tempNativePath)
// Main Java Class // Main Java Class
@ -377,6 +385,19 @@ class ProcessBuilder {
// JVM Arguments First // JVM Arguments First
let args = this.versionData.arguments.jvm 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') //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
// Java Arguments // Java Arguments
@ -384,9 +405,9 @@ class ProcessBuilder {
args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
} }
args.push('-Xmx' + ConfigManager.getMaxRAM()) args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
args.push('-Xms' + ConfigManager.getMinRAM()) args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
args = args.concat(ConfigManager.getJVMOptions()) args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
// Main Java Class // Main Java Class
args.push(this.forgeData.mainClass) args.push(this.forgeData.mainClass)
@ -489,7 +510,7 @@ class ProcessBuilder {
val = args[i].replace(argDiscovery, this.launcherVersion) val = args[i].replace(argDiscovery, this.launcherVersion)
break break
case 'classpath': case 'classpath':
val = this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':') val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator())
break break
} }
if(val != null){ if(val != null){
@ -647,9 +668,13 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){ classpathArg(mods, tempNativePath){
let cpArgs = [] let cpArgs = []
// Add the version.jar to the classpath. if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) {
const version = this.versionData.id // Add the version.jar to the classpath.
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) // 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){ if(this.usingLiteLoader){
cpArgs.push(this.llPath) cpArgs.push(this.llPath)
@ -682,6 +707,7 @@ class ProcessBuilder {
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
*/ */
_resolveMojangLibraries(tempNativePath){ _resolveMojangLibraries(tempNativePath){
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
const libs = {} const libs = {}
const libArr = this.versionData.libraries const libArr = this.versionData.libraries
@ -689,27 +715,23 @@ class ProcessBuilder {
for(let i=0; i<libArr.length; i++){ for(let i=0; i<libArr.length; i++){
const lib = libArr[i] const lib = libArr[i]
if(Library.validateRules(lib.rules, lib.natives)){ if(Library.validateRules(lib.rules, lib.natives)){
if(lib.natives == null){
const dlInfo = lib.downloads // Pre-1.19 has a natives object.
const artifact = dlInfo.artifact if(lib.natives != null) {
const to = path.join(this.libPath, artifact.path)
const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':'))
libs[versionIndependentId] = to
} else {
// Extract the native library. // Extract the native library.
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/'] const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/']
const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
// Location of native zip. // Location of native zip.
const to = path.join(this.libPath, artifact.path) const to = path.join(this.libPath, artifact.path)
let zip = new AdmZip(to) let zip = new AdmZip(to)
let zipEntries = zip.getEntries() let zipEntries = zip.getEntries()
// Unzip the native zip. // Unzip the native zip.
for(let i=0; i<zipEntries.length; i++){ for(let i=0; i<zipEntries.length; i++){
const fileName = zipEntries[i].entryName const fileName = zipEntries[i].entryName
let shouldExclude = false let shouldExclude = false
// Exclude noted files. // Exclude noted files.
@ -727,9 +749,68 @@ class ProcessBuilder {
} }
}) })
} }
} }
} }
// 1.19+ logic
else if(lib.name.includes('natives-')) {
const regexTest = nativesRegex.exec(lib.name)
// const os = regexTest[1]
const arch = regexTest[2] ?? 'x64'
if(arch != process.arch) {
continue
}
// Extract the native library.
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/', '.git', '.sha1']
const artifact = lib.downloads.artifact
// Location of native zip.
const to = path.join(this.libPath, artifact.path)
let zip = new AdmZip(to)
let zipEntries = zip.getEntries()
// Unzip the native zip.
for(let i=0; i<zipEntries.length; i++){
if(zipEntries[i].isDirectory) {
continue
}
const fileName = zipEntries[i].entryName
let shouldExclude = false
// Exclude noted files.
exclusionArr.forEach(function(exclusion){
if(fileName.indexOf(exclusion) > -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 = [] let libs = []
for(let sm of mdl.getSubModules()){ for(let sm of mdl.getSubModules()){
if(sm.getType() === DistroManager.Types.Library){ 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. // If this module has submodules, we need to resolve the libraries for those.
// To avoid unnecessary recursive calls, base case is checked here. // To avoid unnecessary recursive calls, base case is checked here.

View File

@ -10,7 +10,9 @@ const { MojangRestAPI, getServerStatus } = require('helios-core/mojang')
// Internal Requirements // Internal Requirements
const DiscordWrapper = require('./assets/js/discordwrapper') const DiscordWrapper = require('./assets/js/discordwrapper')
const ProcessBuilder = require('./assets/js/processbuilder') const ProcessBuilder = require('./assets/js/processbuilder')
const { Util } = require('./assets/js/assetguard')
const { RestResponseStatus, isDisplayableError } = require('helios-core/common') const { RestResponseStatus, isDisplayableError } = require('helios-core/common')
const { stdout } = require('process')
// Launch Elements // Launch Elements
const launch_content = document.getElementById('launch_content') 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 server_selection_button = document.getElementById('server_selection_button')
const user_text = document.getElementById('user_text') 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 */ /* Launch Progress Wrapper Functions */
@ -85,9 +87,9 @@ function setLaunchEnabled(val){
// Bind launch button // Bind launch button
document.getElementById('launch_button').addEventListener('click', function(e){ 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 mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()
const jExe = ConfigManager.getJavaExecutable() const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())
if(jExe == null){ if(jExe == null){
asyncSystemScan(mcVersion) asyncSystemScan(mcVersion)
} else { } else {
@ -98,7 +100,7 @@ document.getElementById('launch_button').addEventListener('click', function(e){
const jg = new JavaGuard(mcVersion) const jg = new JavaGuard(mcVersion)
jg._validateJavaBinary(jExe).then((v) => { jg._validateJavaBinary(jExe).then((v) => {
loggerLanding.log('Java version meta', v) loggerLanding.info('Java version meta', v)
if(v.valid){ if(v.valid){
dlAsync() dlAsync()
} else { } else {
@ -140,13 +142,13 @@ updateSelectedAccount(ConfigManager.getSelectedAccount())
// Bind selected server // Bind selected server
function updateSelectedServer(serv){ function updateSelectedServer(serv){
if(getCurrentView() === VIEWS.settings){ if(getCurrentView() === VIEWS.settings){
saveAllModConfigurations() fullSettingsSave()
} }
ConfigManager.setSelectedServer(serv != null ? serv.getID() : null) ConfigManager.setSelectedServer(serv != null ? serv.getID() : null)
ConfigManager.save() ConfigManager.save()
server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected') server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected')
if(getCurrentView() === VIEWS.settings){ if(getCurrentView() === VIEWS.settings){
animateModsTabRefresh() animateSettingsTabRefresh()
} }
setLaunchEnabled(serv != null) setLaunchEnabled(serv != null)
} }
@ -159,7 +161,7 @@ server_selection_button.onclick = (e) => {
// Update Mojang Status Color // Update Mojang Status Color
const refreshMojangStatuses = async function(){ const refreshMojangStatuses = async function(){
loggerLanding.log('Refreshing Mojang Statuses..') loggerLanding.info('Refreshing Mojang Statuses..')
let status = 'grey' let status = 'grey'
let tooltipEssentialHTML = '' let tooltipEssentialHTML = ''
@ -219,7 +221,7 @@ const refreshMojangStatuses = async function(){
} }
const refreshServerStatus = async function(fade = false){ const refreshServerStatus = async function(fade = false){
loggerLanding.log('Refreshing Server Status') loggerLanding.info('Refreshing Server Status')
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
let pLabel = 'SERVER' let pLabel = 'SERVER'
@ -253,8 +255,9 @@ const refreshServerStatus = async function(fade = false){
refreshMojangStatuses() refreshMojangStatuses()
// Server Status is refreshed in uibinder.js on distributionIndexDone. // 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. // Set refresh rate to once every 5 minutes.
let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 300000)
let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000)
/** /**
@ -293,8 +296,6 @@ function asyncSystemScan(mcVersion, launchAfter = true){
toggleLaunchArea(true) toggleLaunchArea(true)
setLaunchPercentage(0, 100) setLaunchPercentage(0, 100)
const loggerSysAEx = LoggerUtil1('%c[SysAEx]', 'color: #353232; font-weight: bold')
const forkEnv = JSON.parse(JSON.stringify(process.env)) const forkEnv = JSON.parse(JSON.stringify(process.env))
forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory()
@ -309,13 +310,15 @@ function asyncSystemScan(mcVersion, launchAfter = true){
// Stdout // Stdout
sysAEx.stdio[1].setEncoding('utf8') sysAEx.stdio[1].setEncoding('utf8')
sysAEx.stdio[1].on('data', (data) => { sysAEx.stdio[1].on('data', (data) => {
loggerSysAEx.log(data) console.log(`\x1b[32m[SysAEx]\x1b[0m ${data}`)
}) })
// Stderr // Stderr
sysAEx.stdio[2].setEncoding('utf8') sysAEx.stdio[2].setEncoding('utf8')
sysAEx.stdio[2].on('data', (data) => { 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) => { sysAEx.on('message', (m) => {
@ -325,14 +328,14 @@ function asyncSystemScan(mcVersion, launchAfter = true){
// Show this information to the user. // Show this information to the user.
setOverlayContent( setOverlayContent(
'No Compatible<br>Java Installation Found', 'No Compatible<br>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 Java',
'Install Manually' 'Install Manually'
) )
setOverlayHandler(() => { setOverlayHandler(() => {
setLaunchDetails('Preparing Java Download..') 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()]}) sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory(), mcVersion]})
toggleOverlay(false) toggleOverlay(false)
}) })
setDismissHandler(() => { setDismissHandler(() => {
@ -340,7 +343,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){
//$('#overlayDismiss').toggle(false) //$('#overlayDismiss').toggle(false)
setOverlayContent( setOverlayContent(
'Java is Required<br>to Launch', 'Java is Required<br>to Launch',
'A valid x64 installation of Java 8 is required to launch.<br><br>Please refer to our <a href="https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java">Java Management Guide</a> for instructions on how to manually install Java.', `A valid x64 installation of Java ${javaVer} is required to launch.<br><br>Please refer to our <a href="https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java">Java Management Guide</a> for instructions on how to manually install Java.`,
'I Understand', 'I Understand',
'Go Back' 'Go Back'
) )
@ -359,7 +362,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){
} else { } else {
// Java installation found, use this to launch the game. // Java installation found, use this to launch the game.
ConfigManager.setJavaExecutable(m.result) ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.result)
ConfigManager.save() ConfigManager.save()
// We need to make sure that the updated value is on the settings UI. // 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) remote.getCurrentWindow().setProgressBar(-1)
// Extraction completed successfully. // Extraction completed successfully.
ConfigManager.setJavaExecutable(m.args[0]) ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.args[0])
ConfigManager.save() ConfigManager.save()
if(extractListener != null){ if(extractListener != null){
@ -495,8 +498,7 @@ function dlAsync(login = true){
toggleLaunchArea(true) toggleLaunchArea(true)
setLaunchPercentage(0, 100) setLaunchPercentage(0, 100)
const loggerAEx = LoggerUtil1('%c[AEx]', 'color: #353232; font-weight: bold') const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite')
const loggerLaunchSuite = LoggerUtil1('%c[LaunchSuite]', 'color: #000668; font-weight: bold')
const forkEnv = JSON.parse(JSON.stringify(process.env)) const forkEnv = JSON.parse(JSON.stringify(process.env))
forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory()
@ -505,7 +507,7 @@ function dlAsync(login = true){
aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
'AssetGuard', 'AssetGuard',
ConfigManager.getCommonDirectory(), ConfigManager.getCommonDirectory(),
ConfigManager.getJavaExecutable() ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())
], { ], {
env: forkEnv, env: forkEnv,
stdio: 'pipe' stdio: 'pipe'
@ -513,12 +515,12 @@ function dlAsync(login = true){
// Stdout // Stdout
aEx.stdio[1].setEncoding('utf8') aEx.stdio[1].setEncoding('utf8')
aEx.stdio[1].on('data', (data) => { aEx.stdio[1].on('data', (data) => {
loggerAEx.log(data) console.log(`\x1b[32m[AEx]\x1b[0m ${data}`)
}) })
// Stderr // Stderr
aEx.stdio[2].setEncoding('utf8') aEx.stdio[2].setEncoding('utf8')
aEx.stdio[2].on('data', (data) => { aEx.stdio[2].on('data', (data) => {
loggerAEx.log(data) console.log(`\x1b[31m[AEx]\x1b[0m ${data}`)
}) })
aEx.on('error', (err) => { aEx.on('error', (err) => {
loggerLaunchSuite.error('Error during launch', err) loggerLaunchSuite.error('Error during launch', err)
@ -538,27 +540,27 @@ function dlAsync(login = true){
switch(m.data){ switch(m.data){
case 'distribution': case 'distribution':
setLaunchPercentage(20, 100) setLaunchPercentage(20, 100)
loggerLaunchSuite.log('Validated distibution index.') loggerLaunchSuite.info('Validated distibution index.')
setLaunchDetails('Loading version information..') setLaunchDetails('Loading version information..')
break break
case 'version': case 'version':
setLaunchPercentage(40, 100) setLaunchPercentage(40, 100)
loggerLaunchSuite.log('Version data loaded.') loggerLaunchSuite.info('Version data loaded.')
setLaunchDetails('Validating asset integrity..') setLaunchDetails('Validating asset integrity..')
break break
case 'assets': case 'assets':
setLaunchPercentage(60, 100) setLaunchPercentage(60, 100)
loggerLaunchSuite.log('Asset Validation Complete') loggerLaunchSuite.info('Asset Validation Complete')
setLaunchDetails('Validating library integrity..') setLaunchDetails('Validating library integrity..')
break break
case 'libraries': case 'libraries':
setLaunchPercentage(80, 100) setLaunchPercentage(80, 100)
loggerLaunchSuite.log('Library validation complete.') loggerLaunchSuite.info('Library validation complete.')
setLaunchDetails('Validating miscellaneous file integrity..') setLaunchDetails('Validating miscellaneous file integrity..')
break break
case 'files': case 'files':
setLaunchPercentage(100, 100) setLaunchPercentage(100, 100)
loggerLaunchSuite.log('File validation complete.') loggerLaunchSuite.info('File validation complete.')
setLaunchDetails('Downloading files..') setLaunchDetails('Downloading files..')
break break
} }
@ -646,7 +648,7 @@ function dlAsync(login = true){
if(login && allGood) { if(login && allGood) {
const authUser = ConfigManager.getSelectedAccount() 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()) let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion())
setLaunchDetails('Launching game..') setLaunchDetails('Launching game..')
@ -713,7 +715,7 @@ function dlAsync(login = true){
DiscordWrapper.initRPC(distro.discord, serv.discord) DiscordWrapper.initRPC(distro.discord, serv.discord)
hasRPC = true hasRPC = true
proc.on('close', (code, signal) => { proc.on('close', (code, signal) => {
loggerLaunchSuite.log('Shutting down Discord Rich Presence..') loggerLaunchSuite.info('Shutting down Discord Rich Presence..')
DiscordWrapper.shutdownRPC() DiscordWrapper.shutdownRPC()
hasRPC = false hasRPC = false
proc = null proc = null
@ -744,7 +746,7 @@ function dlAsync(login = true){
serv = data.getServer(ConfigManager.getSelectedServer()) serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]})
}, (err) => { }, (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) => { refreshDistributionIndex(false, (data) => {
onDistroRefresh(data) onDistroRefresh(data)
serv = data.getServer(ConfigManager.getSelectedServer()) serv = data.getServer(ConfigManager.getSelectedServer())

View File

@ -21,8 +21,6 @@ const loginForm = document.getElementById('loginForm')
// Control variables. // Control variables.
let lu = false, lp = false let lu = false, lp = false
const loggerLogin = LoggerUtil1('%c[Login]', 'color: #000668; font-weight: bold')
/** /**
* Show a login error. * Show a login error.

View File

@ -127,29 +127,34 @@ function initSettingsValues(){
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => { Array.from(sEls).map((v, index, arr) => {
const cVal = v.getAttribute('cValue') const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const gFn = ConfigManager['get' + cVal] const gFn = ConfigManager['get' + cVal]
const gFnOpts = []
if(serverDependent) {
gFnOpts.push(ConfigManager.getSelectedServer())
}
if(typeof gFn === 'function'){ if(typeof gFn === 'function'){
if(v.tagName === 'INPUT'){ if(v.tagName === 'INPUT'){
if(v.type === 'number' || v.type === 'text'){ if(v.type === 'number' || v.type === 'text'){
// Special Conditions // Special Conditions
if(cVal === 'JavaExecutable'){ if(cVal === 'JavaExecutable'){
v.value = gFn.apply(null, gFnOpts)
populateJavaExecDetails(v.value) populateJavaExecDetails(v.value)
v.value = gFn()
} else if (cVal === 'DataDirectory'){ } else if (cVal === 'DataDirectory'){
v.value = gFn() v.value = gFn.apply(null, gFnOpts)
} else if(cVal === 'JVMOptions'){ } else if(cVal === 'JVMOptions'){
v.value = gFn().join(' ') v.value = gFn.apply(null, gFnOpts).join(' ')
} else { } else {
v.value = gFn() v.value = gFn.apply(null, gFnOpts)
} }
} else if(v.type === 'checkbox'){ } else if(v.type === 'checkbox'){
v.checked = gFn() v.checked = gFn.apply(null, gFnOpts)
} }
} else if(v.tagName === 'DIV'){ } else if(v.tagName === 'DIV'){
if(v.classList.contains('rangeSlider')){ if(v.classList.contains('rangeSlider')){
// Special Conditions // Special Conditions
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
let val = gFn() let val = gFn.apply(null, gFnOpts)
if(val.endsWith('M')){ if(val.endsWith('M')){
val = Number(val.substring(0, val.length-1))/1000 val = Number(val.substring(0, val.length-1))/1000
} else { } else {
@ -158,7 +163,7 @@ function initSettingsValues(){
v.setAttribute('value', val) v.setAttribute('value', val)
} else { } 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]') const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => { Array.from(sEls).map((v, index, arr) => {
const cVal = v.getAttribute('cValue') const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const sFn = ConfigManager['set' + cVal] const sFn = ConfigManager['set' + cVal]
const sFnOpts = []
if(serverDependent) {
sFnOpts.push(ConfigManager.getSelectedServer())
}
if(typeof sFn === 'function'){ if(typeof sFn === 'function'){
if(v.tagName === 'INPUT'){ if(v.tagName === 'INPUT'){
if(v.type === 'number' || v.type === 'text'){ if(v.type === 'number' || v.type === 'text'){
// Special Conditions // Special Conditions
if(cVal === 'JVMOptions'){ if(cVal === 'JVMOptions'){
if(!v.value.trim()) { if(!v.value.trim()) {
sFn([]) sFnOpts.push([])
sFn.apply(null, sFnOpts)
} else { } else {
sFn(v.value.trim().split(/\s+/)) sFnOpts.push(v.value.trim().split(/\s+/))
sFn.apply(null, sFnOpts)
} }
} else { } else {
sFn(v.value) sFnOpts.push(v.value)
sFn.apply(null, sFnOpts)
} }
} else if(v.type === 'checkbox'){ } else if(v.type === 'checkbox'){
sFn(v.checked) sFnOpts.push(v.checked)
sFn.apply(null, sFnOpts)
// Special Conditions // Special Conditions
if(cVal === 'AllowPrerelease'){ if(cVal === 'AllowPrerelease'){
changeAllowPrerelease(v.checked) changeAllowPrerelease(v.checked)
@ -206,9 +220,11 @@ function saveSettingsValues(){
val = val + 'G' val = val + 'G'
} }
sFn(val) sFnOpts.push(val)
sFn.apply(null, sFnOpts)
} else { } else {
sFn(v.getAttribute('value')) sFnOpts.push(v.getAttribute('value'))
sFn.apply(null, sFnOpts)
} }
} }
} }
@ -305,13 +321,17 @@ function settingsSaveDisabled(v){
settingsNavDone.disabled = v settingsNavDone.disabled = v
} }
/* Closes the settings view and saves all data. */ function fullSettingsSave() {
settingsNavDone.onclick = () => {
saveSettingsValues() saveSettingsValues()
saveModConfiguration() saveModConfiguration()
ConfigManager.save() ConfigManager.save()
saveDropinModConfiguration() saveDropinModConfiguration()
saveShaderpackSettings() saveShaderpackSettings()
}
/* Closes the settings view and saves all data. */
settingsNavDone.onclick = () => {
fullSettingsSave()
switchView(getCurrentView(), VIEWS.landing) switchView(getCurrentView(), VIEWS.landing)
} }
@ -373,13 +393,13 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
if (Object.prototype.hasOwnProperty.call(queryMap, 'error')) { if (Object.prototype.hasOwnProperty.call(queryMap, 'error')) {
switchView(getCurrentView(), viewOnClose, 500, 500, () => { switchView(getCurrentView(), viewOnClose, 500, 500, () => {
// TODO Dont know what these errors are. Just show them I guess. // 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 getting authCode, is Azure application registered correctly?')
console.log(error) console.log(error)
console.log(errorDesc) console.log(errorDesc)
console.log('Full query map', queryMap) console.log('Full query map: ', queryMap)
let error = queryMap.error // Error might be 'access_denied' ?
let errorDesc = queryMap.error_description
setOverlayContent( setOverlayContent(
error, error,
errorDesc, errorDesc,
@ -1060,33 +1080,37 @@ function bindShaderpackButton() {
function loadSelectedServerOnModsTab(){ function loadSelectedServerOnModsTab(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
document.getElementById('settingsSelServContent').innerHTML = ` for(const el of document.getElementsByClassName('settingsSelServContent')) {
<img class="serverListingImg" src="${serv.getIcon()}"/> el.innerHTML = `
<div class="serverListingDetails"> <img class="serverListingImg" src="${serv.getIcon()}"/>
<span class="serverListingName">${serv.getName()}</span> <div class="serverListingDetails">
<span class="serverListingDescription">${serv.getDescription()}</span> <span class="serverListingName">${serv.getName()}</span>
<div class="serverListingInfo"> <span class="serverListingDescription">${serv.getDescription()}</span>
<div class="serverListingVersion">${serv.getMinecraftVersion()}</div> <div class="serverListingInfo">
<div class="serverListingRevision">${serv.getVersion()}</div> <div class="serverListingVersion">${serv.getMinecraftVersion()}</div>
${serv.isMainServer() ? `<div class="serverListingStarWrapper"> <div class="serverListingRevision">${serv.getVersion()}</div>
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px"> ${serv.isMainServer() ? `<div class="serverListingStarWrapper">
<defs> <svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style> <defs>
</defs> <style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/> </defs>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/> <path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
</svg> <circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
<span class="serverListingStarTooltip">Main Server</span> </svg>
</div>` : ''} <span class="serverListingStarTooltip">Main Server</span>
</div>` : ''}
</div>
</div> </div>
</div> `
` }
} }
// Bind functionality to the server switch button. // Bind functionality to the server switch button.
document.getElementById('settingsSwitchServerButton').addEventListener('click', (e) => { Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => {
e.target.blur() el.addEventListener('click', (e) => {
toggleServerSelection(true) 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. * server is changed.
*/ */
function animateModsTabRefresh(){ function animateSettingsTabRefresh(){
$('#settingsTabMods').fadeOut(500, () => { $(`#${selectedSettingsTab}`).fadeOut(500, () => {
prepareModsTab() prepareSettings()
$('#settingsTabMods').fadeIn(500) $(`#${selectedSettingsTab}`).fadeIn(500)
}) })
} }
@ -1135,6 +1159,8 @@ const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
const settingsMemoryTotal = document.getElementById('settingsMemoryTotal') const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
const settingsMemoryAvail = document.getElementById('settingsMemoryAvail') const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails') const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails')
const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc')
const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink')
// Store maximum memory values. // Store maximum memory values.
const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM() 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. * Prepare the Java tab for display.
*/ */
function prepareJavaTab(){ function prepareJavaTab(){
bindRangeSlider() bindRangeSlider()
populateMemoryStatus() populateMemoryStatus()
populateJavaReqDesc()
populateJvmOptsLink()
} }
/** /**
@ -1538,4 +1586,4 @@ function prepareSettings(first = false) {
} }
// Prepare the settings UI on startup. // Prepare the settings UI on startup.
//prepareSettings(true) //prepareSettings(true)

View File

@ -60,7 +60,7 @@ function getCurrentView(){
function showMainUI(data){ function showMainUI(data){
if(!isDev){ if(!isDev){
loggerAutoUpdater.log('Initializing..') loggerAutoUpdater.info('Initializing..')
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease()) ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
} }
@ -137,6 +137,7 @@ function onDistroRefresh(data){
refreshServerStatus() refreshServerStatus()
initNews() initNews()
syncModConfigurations(data) syncModConfigurations(data)
ensureJavaSettings(data)
} }
/** /**
@ -223,6 +224,21 @@ function syncModConfigurations(data){
ConfigManager.save() 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, * Recursively scan for optional sub modules. If none are found,
* this function returns a boolean. If optional sub modules do exist, * this function returns a boolean. If optional sub modules do exist,
@ -434,6 +450,7 @@ ipcRenderer.on('distributionIndexDone', (event, res) => {
if(res) { if(res) {
const data = DistroManager.getDistribution() const data = DistroManager.getDistribution()
syncModConfigurations(data) syncModConfigurations(data)
ensureJavaSettings(data)
if(document.readyState === 'interactive' || document.readyState === 'complete'){ if(document.readyState === 'interactive' || document.readyState === 'complete'){
showMainUI(data) showMainUI(data)
} else { } 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)
})
}

View File

@ -10,11 +10,9 @@ const {ipcRenderer, shell, webFrame} = require('electron')
const remote = require('@electron/remote') const remote = require('@electron/remote')
const isDev = require('./assets/js/isdev') const isDev = require('./assets/js/isdev')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('helios-core')
const LoggerUtil1 = require('./assets/js/loggerutil')
const loggerUICore = LoggerUtil1('%c[UICore]', 'color: #000668; font-weight: bold') const loggerUICore = LoggerUtil.getLogger('UICore')
const loggerAutoUpdater = LoggerUtil1('%c[AutoUpdater]', 'color: #000668; font-weight: bold') const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater')
const loggerAutoUpdaterSuccess = LoggerUtil1('%c[AutoUpdater]', 'color: #209b07; font-weight: bold')
// Log deprecation and process warnings. // Log deprecation and process warnings.
process.traceProcessWarnings = true process.traceProcessWarnings = true
@ -43,11 +41,11 @@ if(!isDev){
ipcRenderer.on('autoUpdateNotification', (event, arg, info) => { ipcRenderer.on('autoUpdateNotification', (event, arg, info) => {
switch(arg){ switch(arg){
case 'checking-for-update': case 'checking-for-update':
loggerAutoUpdater.log('Checking for update..') loggerAutoUpdater.info('Checking for update..')
settingsUpdateButtonStatus('Checking for Updates..', true) settingsUpdateButtonStatus('Checking for Updates..', true)
break break
case 'update-available': case 'update-available':
loggerAutoUpdaterSuccess.log('New update available', info.version) loggerAutoUpdater.info('New update available', info.version)
if(process.platform === 'darwin'){ 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` 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) populateSettingsUpdateInformation(info)
break break
case 'update-downloaded': 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, () => { settingsUpdateButtonStatus('Install Now', false, () => {
if(!isDev){ if(!isDev){
ipcRenderer.send('autoUpdateAction', 'installUpdateNow') ipcRenderer.send('autoUpdateAction', 'installUpdateNow')
@ -66,7 +64,7 @@ if(!isDev){
showUpdateUI(info) showUpdateUI(info)
break break
case 'update-not-available': case 'update-not-available':
loggerAutoUpdater.log('No new update found.') loggerAutoUpdater.info('No new update found.')
settingsUpdateButtonStatus('Check for Updates') settingsUpdateButtonStatus('Check for Updates')
break break
case 'ready': case 'ready':
@ -78,9 +76,9 @@ if(!isDev){
case 'realerror': case 'realerror':
if(info != null && info.code != null){ if(info != null && info.code != null){
if(info.code === 'ERR_UPDATER_INVALID_RELEASE_FEED'){ 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'){ } else if(info.code === 'ERR_XML_MISSED_ELEMENT'){
loggerAutoUpdater.log('No releases found.') loggerAutoUpdater.info('No releases found.')
} else { } else {
loggerAutoUpdater.error('Error during update check..', info) loggerAutoUpdater.error('Error during update check..', info)
loggerAutoUpdater.debug('Error Code:', info.code) loggerAutoUpdater.debug('Error Code:', info.code)
@ -88,7 +86,7 @@ if(!isDev){
} }
break break
default: default:
loggerAutoUpdater.log('Unknown argument', arg) loggerAutoUpdater.info('Unknown argument', arg)
break break
} }
}) })
@ -131,12 +129,12 @@ function showUpdateUI(info){
/* jQuery Example /* jQuery Example
$(function(){ $(function(){
loggerUICore.log('UICore Initialized'); loggerUICore.info('UICore Initialized');
})*/ })*/
document.addEventListener('readystatechange', function () { document.addEventListener('readystatechange', function () {
if (document.readyState === 'interactive'){ if (document.readyState === 'interactive'){
loggerUICore.log('UICore Initializing..') loggerUICore.info('UICore Initializing..')
// Bind close button. // Bind close button.
Array.from(document.getElementsByClassName('fCb')).map((val) => { Array.from(document.getElementsByClassName('fCb')).map((val) => {

View File

@ -122,13 +122,13 @@
<span class="settingsTabHeaderText">Mod Settings</span> <span class="settingsTabHeaderText">Mod Settings</span>
<span class="settingsTabHeaderDesc">Enable or disable mods.</span> <span class="settingsTabHeaderDesc">Enable or disable mods.</span>
</div> </div>
<div id="settingsSelServContainer"> <div class="settingsSelServContainer">
<div id="settingsSelServContent"> <div class="settingsSelServContent">
</div> </div>
<div id="settingsSwitchServerContainer"> <div class="settingsSwitchServerContainer">
<div id="settingsSwitchServerContent"> <div class="settingsSwitchServerContent">
<button id="settingsSwitchServerButton">Switch</button> <button class="settingsSwitchServerButton">Switch</button>
</div> </div>
</div> </div>
</div> </div>
@ -172,6 +172,16 @@
<span class="settingsTabHeaderText">Java Settings</span> <span class="settingsTabHeaderText">Java Settings</span>
<span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span> <span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span>
</div> </div>
<div class="settingsSelServContainer">
<div class="settingsSelServContent">
</div>
<div class="settingsSwitchServerContainer">
<div class="settingsSwitchServerContent">
<button class="settingsSwitchServerButton">Switch</button>
</div>
</div>
</div>
<div id="settingsMemoryContainer"> <div id="settingsMemoryContainer">
<div id="settingsMemoryTitle">Memory</div> <div id="settingsMemoryTitle">Memory</div>
<div id="settingsMemoryContent"> <div id="settingsMemoryContent">
@ -179,7 +189,7 @@
<div class="settingsMemoryContentItem"> <div class="settingsMemoryContentItem">
<span class="settingsMemoryHeader">Maximum RAM</span> <span class="settingsMemoryHeader">Maximum RAM</span>
<div class="settingsMemoryActionContainer"> <div class="settingsMemoryActionContainer">
<div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" min="3" max="8" value="3" step="0.5"> <div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" serverDependent min="3" max="8" value="3" step="0.5">
<div class="rangeSliderBar"></div> <div class="rangeSliderBar"></div>
<div class="rangeSliderTrack"></div> <div class="rangeSliderTrack"></div>
</div> </div>
@ -189,7 +199,7 @@
<div class="settingsMemoryContentItem"> <div class="settingsMemoryContentItem">
<span class="settingsMemoryHeader">Minimum RAM</span> <span class="settingsMemoryHeader">Minimum RAM</span>
<div class="settingsMemoryActionContainer"> <div class="settingsMemoryActionContainer">
<div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" min="3" max="8" value="3" step="0.5"> <div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" serverDependent min="3" max="8" value="3" step="0.5">
<div class="rangeSliderBar"></div> <div class="rangeSliderBar"></div>
<div class="rangeSliderTrack"></div> <div class="rangeSliderTrack"></div>
</div> </div>
@ -231,11 +241,11 @@
</g> </g>
</svg> </svg>
</div> </div>
<input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" disabled> <input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" serverDependent disabled>
<button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="Select Java Executable" dialogDirectory="false">Choose File</button> <button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="Select Java Executable" dialogDirectory="false">Choose File</button>
</div> </div>
</div> </div>
<div class="settingsFileSelDesc">The Java executable is validated before game launch. <strong>Requires Java 8 x64.</strong><br>The path should end with <strong>bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %></strong>.</div> <div class="settingsFileSelDesc">The Java executable is validated before game launch. <strong id="settingsJavaReqDesc">Requires Java 8 x64.</strong><br>The path should end with <strong>bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %></strong>.</div>
</div> </div>
<div id="settingsJVMOptsContainer"> <div id="settingsJVMOptsContainer">
<div id="settingsJVMOptsTitle">Additional JVM Options</div> <div id="settingsJVMOptsTitle">Additional JVM Options</div>
@ -254,9 +264,9 @@
</g> </g>
</svg> </svg>
</div> </div>
<input id="settingsJVMOptsVal" cValue="JVMOptions" type="text"> <input id="settingsJVMOptsVal" cValue="JVMOptions" serverDependent type="text">
</div> </div>
<div id="settingsJVMOptsDesc">Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included.<br><a href="https://docs.oracle.com/javase/8/docs/technotes/tools/<%= process.platform === 'win32' ? 'windows' : 'unix' %>/java.html">Available Options for Java 8</a>.</div> <div id="settingsJVMOptsDesc">Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included.<br><a href="https://docs.oracle.com/javase/8/docs/technotes/tools/<%= process.platform === 'win32' ? 'windows' : 'unix' %>/java.html" id="settingsJvmOptsLink">Available Options for Java 8</a>.</div>
</div> </div>
</div> </div>
<div id="settingsTabLauncher" class="settingsTab" style="display: none;"> <div id="settingsTabLauncher" class="settingsTab" style="display: none;">

View File

@ -208,6 +208,12 @@ The name of the module. Used on the UI.
The type of the module. The type of the module.
### `Module.classpath: boolean`
**OPTIONAL**
If the module is of type `Library`, whether the library should be added to the classpath. Defaults to true.
### `Module.required: Required` ### `Module.required: Required`
**OPTIONAL** **OPTIONAL**

5727
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "helioslauncher", "name": "helioslauncher",
"version": "1.9.0", "version": "1.10.0",
"productName": "Helios Launcher", "productName": "Helios Launcher",
"description": "Modded Minecraft Launcher", "description": "Modded Minecraft Launcher",
"author": "Daniel Scalzi (https://github.com/dscalzi/)", "author": "Daniel Scalzi (https://github.com/dscalzi/)",
@ -20,31 +20,32 @@
"lint": "eslint --config .eslintrc.json ." "lint": "eslint --config .eslintrc.json ."
}, },
"engines": { "engines": {
"node": "16.x.x" "node": "18.x.x"
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.8", "@electron/remote": "^2.0.8",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.9",
"async": "^3.2.3", "async": "^3.2.4",
"discord-rpc-patch": "^4.0.1", "discord-rpc-patch": "^4.0.1",
"ejs": "^3.1.8", "ejs": "^3.1.8",
"ejs-electron": "^2.1.1", "ejs-electron": "^2.1.1",
"electron-updater": "^5.0.1", "electron-updater": "^5.3.0",
"fs-extra": "^10.1.0", "fs-extra": "^11.1.0",
"github-syntax-dark": "^0.5.0", "github-syntax-dark": "^0.5.0",
"got": "^11.8.3", "got": "^11.8.5",
"helios-core": "~0.1.0", "helios-core": "~0.1.2",
"jquery": "^3.6.0", "jquery": "^3.6.1",
"node-disk-info": "^1.3.0",
"node-stream-zip": "^1.15.0", "node-stream-zip": "^1.15.0",
"request": "^2.88.2", "request": "^2.88.2",
"semver": "^7.3.7", "semver": "^7.3.8",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"winreg": "^1.2.4" "winreg": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {
"electron": "^18.2.4", "electron": "^23.0.0",
"electron-builder": "^23.0.3", "electron-builder": "^23.6.0",
"eslint": "^8.16.0" "eslint": "^8.34.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",