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": {
"es2017": true,
"es2022": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2019,
"ecmaVersion": 2022,
"sourceType": "module"
},
"rules": {

View File

@ -17,7 +17,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: 16
node-version: 18
- name: Set up Python
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>
[<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>
@ -84,7 +84,7 @@ This section details the setup of a basic developmentment environment.
**System Requirements**
* [Node.js][nodejs] v16
* [Node.js][nodejs] v18
---

View File

@ -1228,6 +1228,59 @@ body, button {
font-size: 12px;
}
/* Selected server content container */
.settingsSelServContainer {
background: rgba(0, 0, 0, 0.25);
width: 75%;
border-radius: 3px;
display: flex;
justify-content: space-between;
margin: 15px 0px;
}
/* Div which will be populated with the selected server's information. */
.settingsSelServContent {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px 0px;
}
/* Wrapper container for the switch server button. */
.settingsSwitchServerContainer {
display: flex;
align-items: center;
padding: 15px;
}
/* Button to switch server configurations on the mods tab. */
.settingsSwitchServerButton {
opacity: 0;
border: 1px solid rgb(255, 255, 255);
color: rgb(255, 255, 255);
background: none;
font-size: 12px;
border-radius: 3px;
font-family: 'Avenir Medium';
transition: 0.25s ease;
cursor: pointer;
outline: none;
}
.settingsSwitchServerButton:hover,
.settingsSwitchServerButton:focus {
box-shadow: 0px 0px 20px rgb(255, 255, 255);
background: rgba(255, 255, 255, 0.25);
}
.settingsSwitchServerButton:active {
box-shadow: 0px 0px 20px rgb(187, 187, 187);
background: rgba(187, 187, 187, 0.25);
border: 1px solid rgb(187, 187, 187);
color: rgb(187, 187, 187);
}
.settingsSelServContainer:hover .settingsSwitchServerButton {
opacity: 1;
}
/* Remove spin button from number inputs. */
#settingsContainer input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
@ -1638,59 +1691,6 @@ input:checked + .toggleSwitchSlider:before {
* Settings View (Mods Tab)
* * */
/* Selected server content container */
#settingsSelServContainer {
background: rgba(0, 0, 0, 0.25);
width: 75%;
border-radius: 3px;
display: flex;
justify-content: space-between;
margin: 15px 0px;
}
/* Div which will be populated with the selected server's information. */
#settingsSelServContent {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px 0px;
}
/* Wrapper container for the switch server button. */
#settingsSwitchServerContainer {
display: flex;
align-items: center;
padding: 15px;
}
/* Button to switch server configurations on the mods tab. */
#settingsSwitchServerButton {
opacity: 0;
border: 1px solid rgb(255, 255, 255);
color: rgb(255, 255, 255);
background: none;
font-size: 12px;
border-radius: 3px;
font-family: 'Avenir Medium';
transition: 0.25s ease;
cursor: pointer;
outline: none;
}
#settingsSwitchServerButton:hover,
#settingsSwitchServerButton:focus {
box-shadow: 0px 0px 20px rgb(255, 255, 255);
background: rgba(255, 255, 255, 0.25);
}
#settingsSwitchServerButton:active {
box-shadow: 0px 0px 20px rgb(187, 187, 187);
background: rgba(187, 187, 187, 0.25);
border: 1px solid rgb(187, 187, 187);
color: rgb(187, 187, 187);
}
#settingsSelServContainer:hover #settingsSwitchServerButton {
opacity: 1;
}
/* Main content container for the mod elements. */
#settingsModsContainer {
width: 75%;

View File

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

View File

@ -5,6 +5,8 @@ const child_process = require('child_process')
const crypto = require('crypto')
const EventEmitter = require('events')
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const nodeDiskInfo = require('node-disk-info')
const StreamZip = require('node-stream-zip')
const path = require('path')
const Registry = require('winreg')
@ -16,6 +18,8 @@ const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
const isDev = require('./isdev')
const isARM64 = process.arch === 'arm64'
// Classes
/** Class representing a base asset. */
@ -214,6 +218,7 @@ class JavaGuard extends EventEmitter {
constructor(mcVersion){
super()
this.mcVersion = mcVersion
this.logger = LoggerUtil.getLogger('JavaGuard')
}
/**
@ -299,7 +304,8 @@ class JavaGuard extends EventEmitter {
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) => {
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.
*/
static isJavaExecPath(pth){
if(pth == null) {
return false
}
if(process.platform === 'win32'){
return pth.endsWith(path.join('bin', 'javaw.exe'))
} else if(process.platform === 'darwin'){
@ -447,7 +456,7 @@ class JavaGuard extends EventEmitter {
if(props[i].indexOf('sun.arch.data.model') > -1){
let arch = props[i].split('=')[1].trim()
arch = parseInt(arch)
console.log(props[i].trim())
this.logger.debug(props[i].trim())
if(arch === 64){
meta.arch = arch
++checksum
@ -457,10 +466,15 @@ class JavaGuard extends EventEmitter {
}
} else if(props[i].indexOf('java.runtime.version') > -1){
let verString = props[i].split('=')[1].trim()
console.log(props[i].trim())
this.logger.debug(props[i].trim())
const verOb = JavaGuard.parseJavaRuntimeVersion(verString)
// TODO implement a support matrix eventually. Right now this is good enough
// 1.7-1.16 = Java 8
// 1.17+ = Java 17
// Actual support may vary, but we're going with this rule for simplicity.
if(verOb.major < 9){
// Java 8
if(!Util.mcVersionAtLeast('1.17', this.mcVersion)){
if(verOb.major === 8 && verOb.update > 52){
meta.version = verOb
++checksum
@ -468,22 +482,24 @@ class JavaGuard extends EventEmitter {
break
}
}
} else {
}
} else if(verOb.major >= 17) {
// Java 9+
if(Util.mcVersionAtLeast('1.13', this.mcVersion)){
console.log('Java 9+ not yet tested.')
/* meta.version = verOb
if(Util.mcVersionAtLeast('1.17', this.mcVersion)){
meta.version = verOb
++checksum
if(checksum === goal){
break
} */
}
}
}
// Space included so we get only the vendor.
} else if(props[i].lastIndexOf('java.vendor ') > -1) {
let vendorName = props[i].split('=')[1].trim()
console.log(props[i].trim())
this.logger.debug(props[i].trim())
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})
} else if(fs.existsSync(binaryExecPath)){
// Workaround (javaw.exe no longer outputs this information.)
console.log(typeof binaryExecPath)
this.logger.debug(typeof binaryExecPath)
if(binaryExecPath.indexOf('javaw.exe') > -1) {
binaryExecPath.replace('javaw.exe', 'java.exe')
}
@ -804,15 +820,22 @@ class JavaGuard extends EventEmitter {
// Get possible paths from the registry.
let pathSet1 = await JavaGuard._scanRegistry()
if(pathSet1.size === 0){
// Do a manual file system scan of program files.
// Check all drives
const driveMounts = nodeDiskInfo.getDiskInfoSync().map(({ mounted }) => mounted)
for(const mount of driveMounts) {
pathSet1 = new Set([
...pathSet1,
...(await JavaGuard._scanFileSystem('C:\\Program Files\\Java')),
...(await JavaGuard._scanFileSystem('C:\\Program Files\\Eclipse Foundation')),
...(await JavaGuard._scanFileSystem('C:\\Program Files\\AdoptOpenJDK'))
...(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.
const pathSet2 = await JavaGuard._scanFileSystem(path.join(dataDir, 'runtime', 'x64'))
@ -848,6 +871,9 @@ class JavaGuard extends EventEmitter {
* @param {string} dataDir The base launcher directory.
* @returns {Promise.<string>} A Promise which resolves to the executable path of a valid
* 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){
@ -876,7 +902,16 @@ class JavaGuard extends EventEmitter {
pathArr = JavaGuard._sortValidJavaArray(pathArr)
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 {
return null
}
@ -942,6 +977,8 @@ class JavaGuard extends EventEmitter {
*/
class AssetGuard extends EventEmitter {
static logger = LoggerUtil.getLogger('AssetGuard')
/**
* Create an instance of AssetGuard.
* 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.
*/
static _extractPackXZ(filePaths, javaExecutable){
console.log('[PackXZExtract] Starting')
const extractLogger = LoggerUtil.getLogger('PackXZExtract')
extractLogger.info('Starting')
return new Promise((resolve, reject) => {
let libPath
@ -1117,13 +1155,13 @@ class AssetGuard extends EventEmitter {
const filePath = filePaths.join(',')
const child = child_process.spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath])
child.stdout.on('data', (data) => {
console.log('[PackXZExtract]', data.toString('utf8'))
extractLogger.info(data.toString('utf8'))
})
child.stderr.on('data', (data) => {
console.log('[PackXZExtract]', data.toString('utf8'))
extractLogger.info(data.toString('utf8'))
})
child.on('close', (code, signal) => {
console.log('[PackXZExtract]', 'Exited with code', code)
extractLogger.info('Exited with code', code)
resolve()
})
})
@ -1189,7 +1227,7 @@ class AssetGuard extends EventEmitter {
if(!fs.existsSync(versionFile) || force){
const url = await self._getVersionDataUrl(version)
//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)
const stream = request(url).pipe(fs.createWriteStream(versionFile))
stream.on('finish', () => {
@ -1271,7 +1309,7 @@ class AssetGuard extends EventEmitter {
let data = null
if(!fs.existsSync(assetIndexLoc) || force){
console.log('Downloading ' + versionData.id + ' asset index.')
AssetGuard.logger.info('Downloading ' + versionData.id + ' asset index.')
fs.ensureDirSync(indexPath)
const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc))
stream.on('finish', () => {
@ -1542,9 +1580,10 @@ class AssetGuard extends EventEmitter {
// Java (Category=''') Validation (download) Functions
// #region
_enqueueOpenJDK(dataDir){
_enqueueOpenJDK(dataDir, mcVersion){
return new Promise((resolve, reject) => {
JavaGuard._latestOpenJDK('8').then(verData => {
const major = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8'
JavaGuard._latestOpenJDK(major).then(verData => {
if(verData != null){
dataDir = path.join(dataDir, 'runtime', 'x64')
@ -1559,9 +1598,9 @@ class AssetGuard extends EventEmitter {
// Tar.gz
let h = null
fs.createReadStream(a.to)
.on('error', err => console.log(err))
.on('error', err => AssetGuard.logger.error(err))
.pipe(zlib.createGunzip())
.on('error', err => console.log(err))
.on('error', err => AssetGuard.logger.error(err))
.pipe(tar.extract(dataDir, {
map: (header) => {
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', () => {
fs.unlink(a.to, err => {
if(err){
console.log(err)
AssetGuard.logger.error(err)
}
if(h.indexOf('/') > -1){
h = h.substring(0, h.indexOf('/'))
@ -1606,14 +1645,14 @@ class AssetGuard extends EventEmitter {
const entries = await zip.entries()
pos = path.join(runtimeDir, Object.keys(entries)[0])
console.log('Extracting jdk..')
AssetGuard.logger.info('Extracting jdk..')
await zip.extract(null, runtimeDir)
console.log('Cleaning up..')
AssetGuard.logger.info('Cleaning up..')
await fs.remove(zipPath)
console.log('Jdk extraction complete.')
AssetGuard.logger.info('Jdk extraction complete.')
} catch(err) {
console.log(err)
AssetGuard.logger.error(err)
} finally {
zip.close()
self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos))
@ -1679,7 +1718,7 @@ class AssetGuard extends EventEmitter {
const dlQueue = dlTracker.dlqueue
if(dlQueue.length > 0){
console.log('DLQueue', dlQueue)
AssetGuard.logger.info('DLQueue', dlQueue)
async.eachLimit(dlQueue, limit, (asset, cb) => {
@ -1696,7 +1735,7 @@ class AssetGuard extends EventEmitter {
const contentLength = parseInt(resp.headers['content-length'])
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
// Adjust download
@ -1713,9 +1752,9 @@ class AssetGuard extends EventEmitter {
if(doHashCheck){
const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash)
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 {
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 {
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.emit('progress', 'download', self.progress, self.totaldlsize)
cb()
@ -1748,9 +1787,9 @@ class AssetGuard extends EventEmitter {
}, (err) => {
if(err){
console.log('An item in ' + identifier + ' failed to process')
AssetGuard.logger.warn('An item in ' + identifier + ' failed to process')
} else {
console.log('All ' + identifier + ' have been processed successfully')
AssetGuard.logger.info('All ' + identifier + ' have been processed successfully')
}
//self.totaldlsize -= dlTracker.dlsize

View File

@ -1,8 +1,9 @@
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const os = require('os')
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)
// TODO change
@ -63,6 +64,27 @@ function resolveMinRAM(){
return resolveMaxRAM()
}
/**
* TODO Copy pasted, should be in a utility file.
*
* Returns true if the actual version is greater than
* or equal to the desired version.
*
* @param {string} desired The desired version.
* @param {string} actual The actual version.
*/
function mcVersionAtLeast(desired, actual){
const des = desired.split('.')
const act = actual.split('.')
for(let i=0; i<des.length; i++){
if(!(parseInt(act[i]) >= parseInt(des[i]))){
return false
}
}
return true
}
/**
* Three types of values:
* Static = Explicitly declared.
@ -71,17 +93,6 @@ function resolveMinRAM(){
*/
const DEFAULT_CONFIG = {
settings: {
java: {
minRAM: resolveMinRAM(),
maxRAM: resolveMaxRAM(), // Dynamic
executable: null,
jvmOptions: [
'-XX:+UseConcMarkSweepGC',
'-XX:+CMSIncrementalMode',
'-XX:-UseAdaptiveSizePolicy',
'-Xmn128M'
],
},
game: {
resWidth: 1280,
resHeight: 720,
@ -103,7 +114,8 @@ const DEFAULT_CONFIG = {
selectedServer: null, // Resolved
selectedAccount: null,
authenticationDatabase: {},
modConfigurations: []
modConfigurations: [],
javaConfig: {}
}
let config = null
@ -144,8 +156,8 @@ exports.load = function(){
doValidate = true
} catch (err){
logger.error(err)
logger.log('Configuration file contains malformed JSON or is corrupt.')
logger.log('Generating a new configuration file.')
logger.info('Configuration file contains malformed JSON or is corrupt.')
logger.info('Generating a new configuration file.')
fs.ensureDirSync(path.join(configPath, '..'))
config = DEFAULT_CONFIG
exports.save()
@ -155,7 +167,7 @@ exports.load = function(){
exports.save()
}
}
logger.log('Successfully Loaded')
logger.info('Successfully Loaded')
}
/**
@ -177,7 +189,7 @@ function validateKeySet(srcObj, destObj){
if(srcObj == null){
srcObj = {}
}
const validationBlacklist = ['authenticationDatabase']
const validationBlacklist = ['authenticationDatabase', 'javaConfig']
const keys = Object.keys(srcObj)
for(let i=0; i<keys.length; i++){
if(typeof destObj[keys[i]] === 'undefined'){
@ -511,16 +523,66 @@ exports.setModConfiguration = function(serverid, configuration){
// 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
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @param {string} serverid The server id.
* @returns {string} The minimum amount of memory for JVM initialization.
*/
exports.getMinRAM = function(def = false){
return !def ? config.settings.java.minRAM : DEFAULT_CONFIG.settings.java.minRAM
exports.getMinRAM = function(serverid){
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' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @param {string} minRAM The new minimum amount of memory for JVM initialization.
*/
exports.setMinRAM = function(minRAM){
config.settings.java.minRAM = minRAM
exports.setMinRAM = function(serverid, 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' =
* 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.
*/
exports.getMaxRAM = function(def = false){
return !def ? config.settings.java.maxRAM : resolveMaxRAM()
exports.getMaxRAM = function(serverid){
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' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @param {string} maxRAM The new maximum amount of memory for JVM initialization.
*/
exports.setMaxRAM = function(maxRAM){
config.settings.java.maxRAM = maxRAM
exports.setMaxRAM = function(serverid, 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.
*
* @param {string} serverid The server id.
* @returns {string} The path of the Java Executable.
*/
exports.getJavaExecutable = function(){
return config.settings.java.executable
exports.getJavaExecutable = function(serverid){
return config.javaConfig[serverid].executable
}
/**
* Set the path of the Java Executable.
*
* @param {string} serverid The server id.
* @param {string} executable The new path of the Java Executable.
*/
exports.setJavaExecutable = function(executable){
config.settings.java.executable = executable
exports.setJavaExecutable = function(serverid, 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
* 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.
*/
exports.getJVMOptions = function(def = false){
return !def ? config.settings.java.jvmOptions : DEFAULT_CONFIG.settings.java.jvmOptions
exports.getJVMOptions = function(serverid){
return config.javaConfig[serverid].jvmOptions
}
/**
@ -594,11 +660,12 @@ exports.getJVMOptions = function(def = false){
* such as memory allocation, will be dynamically resolved and should not be
* included in this value.
*
* @param {string} serverid The server id.
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
* initialization.
*/
exports.setJVMOptions = function(jvmOptions){
config.settings.java.jvmOptions = jvmOptions
exports.setJVMOptions = function(serverid, jvmOptions){
config.javaConfig[serverid].jvmOptions = jvmOptions
}
// Game Settings

View File

@ -1,5 +1,7 @@
// Work in progress
const logger = require('./loggerutil')('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold')
const { LoggerUtil } = require('helios-core')
const logger = LoggerUtil.getLogger('DiscordWrapper')
const { Client } = require('discord-rpc-patch')
@ -21,15 +23,15 @@ exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting
}
client.on('ready', () => {
logger.log('Discord RPC Connected')
logger.info('Discord RPC Connected')
client.setActivity(activity)
})
client.login({clientId: genSettings.clientId}).catch(error => {
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 {
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 path = require('path')
const request = require('request')
const { LoggerUtil } = require('helios-core')
const ConfigManager = require('./configmanager')
const logger = require('./loggerutil')('%c[DistroManager]', 'color: #a02d2a; font-weight: bold')
const logger = LoggerUtil.getLogger('DistroManager')
/**
* Represents the download information
@ -118,7 +120,7 @@ class Module {
* @returns {Module} The parsed Module.
*/
static fromJSON(json, serverid){
return new Module(json.id, json.name, json.type, json.required, json.artifact, json.subModules, serverid)
return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid)
}
/**
@ -143,9 +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.type = type
this.classpath = classpath
this._resolveMetaData()
this.name = name
this.required = Required.fromJSON(required)
@ -306,6 +309,13 @@ class Module {
return this.type
}
/**
* @returns {boolean} Whether or not this library should be on the classpath.
*/
getClasspath(){
return this.classpath ?? true
}
}
exports.Module
@ -591,10 +601,10 @@ exports.pullLocal = function(){
exports.setDevMode = function(value){
if(value){
logger.log('Developer mode enabled.')
logger.log('If you don\'t know what that means, revert immediately.')
logger.info('Developer mode enabled.')
logger.info('If you don\'t know what that means, revert immediately.')
} else {
logger.log('Developer mode disabled.')
logger.info('Developer mode disabled.')
}
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

@ -6,9 +6,11 @@ const path = require('path')
const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
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
ConfigManager.load()
@ -21,7 +23,7 @@ function onDistroLoad(data){
// Resolve the selected server if its value has yet to be set.
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.save()
}
@ -31,26 +33,26 @@ function onDistroLoad(data){
// Ensure Distribution is downloaded and cached.
DistroManager.pullRemote().then((data) => {
logger.log('Loaded distribution index.')
logger.info('Loaded distribution index.')
onDistroLoad(data)
}).catch((err) => {
logger.log('Failed to load distribution index.')
logger.info('Failed to load distribution index.')
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.
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)
}).catch((err) => {
logger.log('Failed to load an older version of the distribution index.')
logger.log('Application cannot run.')
logger.info('Failed to load an older version of the distribution index.')
logger.info('Application cannot run.')
logger.error(err)
onDistroLoad(null)
@ -64,6 +66,6 @@ fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) =>
if(err){
logger.warn('Error while cleaning natives directory', err)
} 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 crypto = require('crypto')
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const os = require('os')
const path = require('path')
const { URL } = require('url')
@ -9,9 +10,8 @@ const { URL } = require('url')
const { Util, Library } = require('./assetguard')
const ConfigManager = require('./configmanager')
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 {
@ -40,7 +40,7 @@ class ProcessBuilder {
const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
process.throwDeprecation = true
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())
// Mod list below 1.13
@ -59,9 +59,9 @@ class ProcessBuilder {
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,
detached: ConfigManager.getLaunchDetached()
})
@ -73,22 +73,20 @@ class ProcessBuilder {
child.stdout.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) => {
loggerMCstdout.log(data)
data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`))
})
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) => {
logger.log('Exited with code', code)
logger.info('Exited with code', code)
fs.remove(tempNativePath, (err) => {
if(err){
logger.warn('Error while deleting temp dir', err)
} else {
logger.log('Temp dir deleted successfully.')
logger.info('Temp dir deleted successfully.')
}
})
})
@ -96,6 +94,16 @@ class ProcessBuilder {
return child
}
/**
* Get the platform specific classpath separator. On windows, this is a semicolon.
* On Unix, this is a colon.
*
* @returns {string} The classpath separator for the current operating system.
*/
static getClasspathSeparator() {
return process.platform === 'win32' ? ';' : ':'
}
/**
* Determine if an optional mod is enabled from its configuration value. If the
* configuration value is null, the required object will be used to
@ -339,16 +347,16 @@ class ProcessBuilder {
// Classpath Argument
args.push('-cp')
args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':'))
args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator()))
// Java Arguments
if(process.platform === 'darwin'){
args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
}
args.push('-Xmx' + ConfigManager.getMaxRAM())
args.push('-Xms' + ConfigManager.getMinRAM())
args = args.concat(ConfigManager.getJVMOptions())
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
args.push('-Djava.library.path=' + tempNativePath)
// Main Java Class
@ -377,6 +385,19 @@ class ProcessBuilder {
// JVM Arguments First
let args = this.versionData.arguments.jvm
// Debug securejarhandler
// args.push('-Dbsl.debug=true')
if(this.forgeData.arguments.jvm != null) {
for(const argStr of this.forgeData.arguments.jvm) {
args.push(argStr
.replaceAll('${library_directory}', this.libPath)
.replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator())
.replaceAll('${version_name}', this.forgeData.id)
)
}
}
//args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
// Java Arguments
@ -384,9 +405,9 @@ class ProcessBuilder {
args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
}
args.push('-Xmx' + ConfigManager.getMaxRAM())
args.push('-Xms' + ConfigManager.getMinRAM())
args = args.concat(ConfigManager.getJVMOptions())
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID()))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
// Main Java Class
args.push(this.forgeData.mainClass)
@ -489,7 +510,7 @@ class ProcessBuilder {
val = args[i].replace(argDiscovery, this.launcherVersion)
break
case 'classpath':
val = this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')
val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator())
break
}
if(val != null){
@ -647,9 +668,13 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){
let cpArgs = []
if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) {
// Add the version.jar to the classpath.
// Must not be added to the classpath for Forge 1.17+.
const version = this.versionData.id
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
}
if(this.usingLiteLoader){
cpArgs.push(this.llPath)
@ -682,6 +707,7 @@ class ProcessBuilder {
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
*/
_resolveMojangLibraries(tempNativePath){
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
const libs = {}
const libArr = this.versionData.libraries
@ -689,13 +715,9 @@ class ProcessBuilder {
for(let i=0; i<libArr.length; i++){
const lib = libArr[i]
if(Library.validateRules(lib.rules, lib.natives)){
if(lib.natives == null){
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
} else {
// Pre-1.19 has a natives object.
if(lib.natives != null) {
// Extract the native library.
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', ''))]
@ -730,6 +752,65 @@ 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,8 +869,11 @@ class ProcessBuilder {
let libs = []
for(let sm of mdl.getSubModules()){
if(sm.getType() === DistroManager.Types.Library){
if(sm.getClasspath()) {
libs.push(sm.getArtifact().getPath())
}
}
// If this module has submodules, we need to resolve the libraries for those.
// To avoid unnecessary recursive calls, base case is checked here.
if(mdl.hasSubModules()){

View File

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

View File

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

View File

@ -127,29 +127,34 @@ function initSettingsValues(){
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => {
const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const gFn = ConfigManager['get' + cVal]
const gFnOpts = []
if(serverDependent) {
gFnOpts.push(ConfigManager.getSelectedServer())
}
if(typeof gFn === 'function'){
if(v.tagName === 'INPUT'){
if(v.type === 'number' || v.type === 'text'){
// Special Conditions
if(cVal === 'JavaExecutable'){
v.value = gFn.apply(null, gFnOpts)
populateJavaExecDetails(v.value)
v.value = gFn()
} else if (cVal === 'DataDirectory'){
v.value = gFn()
v.value = gFn.apply(null, gFnOpts)
} else if(cVal === 'JVMOptions'){
v.value = gFn().join(' ')
v.value = gFn.apply(null, gFnOpts).join(' ')
} else {
v.value = gFn()
v.value = gFn.apply(null, gFnOpts)
}
} else if(v.type === 'checkbox'){
v.checked = gFn()
v.checked = gFn.apply(null, gFnOpts)
}
} else if(v.tagName === 'DIV'){
if(v.classList.contains('rangeSlider')){
// Special Conditions
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
let val = gFn()
let val = gFn.apply(null, gFnOpts)
if(val.endsWith('M')){
val = Number(val.substring(0, val.length-1))/1000
} else {
@ -158,7 +163,7 @@ function initSettingsValues(){
v.setAttribute('value', val)
} else {
v.setAttribute('value', Number.parseFloat(gFn()))
v.setAttribute('value', Number.parseFloat(gFn.apply(null, gFnOpts)))
}
}
}
@ -174,22 +179,31 @@ function saveSettingsValues(){
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => {
const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const sFn = ConfigManager['set' + cVal]
const sFnOpts = []
if(serverDependent) {
sFnOpts.push(ConfigManager.getSelectedServer())
}
if(typeof sFn === 'function'){
if(v.tagName === 'INPUT'){
if(v.type === 'number' || v.type === 'text'){
// Special Conditions
if(cVal === 'JVMOptions'){
if(!v.value.trim()) {
sFn([])
sFnOpts.push([])
sFn.apply(null, sFnOpts)
} else {
sFn(v.value.trim().split(/\s+/))
sFnOpts.push(v.value.trim().split(/\s+/))
sFn.apply(null, sFnOpts)
}
} else {
sFn(v.value)
sFnOpts.push(v.value)
sFn.apply(null, sFnOpts)
}
} else if(v.type === 'checkbox'){
sFn(v.checked)
sFnOpts.push(v.checked)
sFn.apply(null, sFnOpts)
// Special Conditions
if(cVal === 'AllowPrerelease'){
changeAllowPrerelease(v.checked)
@ -206,9 +220,11 @@ function saveSettingsValues(){
val = val + 'G'
}
sFn(val)
sFnOpts.push(val)
sFn.apply(null, sFnOpts)
} else {
sFn(v.getAttribute('value'))
sFnOpts.push(v.getAttribute('value'))
sFn.apply(null, sFnOpts)
}
}
}
@ -305,13 +321,17 @@ function settingsSaveDisabled(v){
settingsNavDone.disabled = v
}
/* Closes the settings view and saves all data. */
settingsNavDone.onclick = () => {
function fullSettingsSave() {
saveSettingsValues()
saveModConfiguration()
ConfigManager.save()
saveDropinModConfiguration()
saveShaderpackSettings()
}
/* Closes the settings view and saves all data. */
settingsNavDone.onclick = () => {
fullSettingsSave()
switchView(getCurrentView(), VIEWS.landing)
}
@ -374,12 +394,12 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
switchView(getCurrentView(), viewOnClose, 500, 500, () => {
// TODO Dont know what these errors are. Just show them I guess.
// 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)
console.log(errorDesc)
console.log('Full query map', queryMap)
let error = queryMap.error // Error might be 'access_denied' ?
let errorDesc = queryMap.error_description
console.log('Full query map: ', queryMap)
setOverlayContent(
error,
errorDesc,
@ -1060,7 +1080,8 @@ function bindShaderpackButton() {
function loadSelectedServerOnModsTab(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
document.getElementById('settingsSelServContent').innerHTML = `
for(const el of document.getElementsByClassName('settingsSelServContent')) {
el.innerHTML = `
<img class="serverListingImg" src="${serv.getIcon()}"/>
<div class="serverListingDetails">
<span class="serverListingName">${serv.getName()}</span>
@ -1082,12 +1103,15 @@ function loadSelectedServerOnModsTab(){
</div>
`
}
}
// Bind functionality to the server switch button.
document.getElementById('settingsSwitchServerButton').addEventListener('click', (e) => {
Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => {
el.addEventListener('click', (e) => {
e.target.blur()
toggleServerSelection(true)
})
})
/**
* Save mod configuration for the current selected server.
@ -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.
*/
function animateModsTabRefresh(){
$('#settingsTabMods').fadeOut(500, () => {
prepareModsTab()
$('#settingsTabMods').fadeIn(500)
function animateSettingsTabRefresh(){
$(`#${selectedSettingsTab}`).fadeOut(500, () => {
prepareSettings()
$(`#${selectedSettingsTab}`).fadeIn(500)
})
}
@ -1135,6 +1159,8 @@ const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails')
const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc')
const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink')
// Store maximum memory values.
const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM()
@ -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.
*/
function prepareJavaTab(){
bindRangeSlider()
populateMemoryStatus()
populateJavaReqDesc()
populateJvmOptsLink()
}
/**

View File

@ -60,7 +60,7 @@ function getCurrentView(){
function showMainUI(data){
if(!isDev){
loggerAutoUpdater.log('Initializing..')
loggerAutoUpdater.info('Initializing..')
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
}
@ -137,6 +137,7 @@ function onDistroRefresh(data){
refreshServerStatus()
initNews()
syncModConfigurations(data)
ensureJavaSettings(data)
}
/**
@ -223,6 +224,21 @@ function syncModConfigurations(data){
ConfigManager.save()
}
/**
* Ensure java configurations are present for the available servers.
*
* @param {Object} data The distro index object.
*/
function ensureJavaSettings(data) {
// Nothing too fancy for now.
for(const serv of data.getServers()){
ConfigManager.ensureJavaConfig(serv.getID(), serv.getMinecraftVersion())
}
ConfigManager.save()
}
/**
* Recursively scan for optional sub modules. If none are found,
* this function returns a boolean. If optional sub modules do exist,
@ -434,6 +450,7 @@ ipcRenderer.on('distributionIndexDone', (event, res) => {
if(res) {
const data = DistroManager.getDistribution()
syncModConfigurations(data)
ensureJavaSettings(data)
if(document.readyState === 'interactive' || document.readyState === 'complete'){
showMainUI(data)
} else {
@ -448,3 +465,13 @@ ipcRenderer.on('distributionIndexDone', (event, res) => {
}
}
})
// Util for development
function devModeToggle() {
DistroManager.setDevMode(true)
DistroManager.pullLocal().then((data) => {
ensureJavaSettings(data)
updateSelectedServer(data.getServers()[0])
syncModConfigurations(data)
})
}

View File

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

View File

@ -122,13 +122,13 @@
<span class="settingsTabHeaderText">Mod Settings</span>
<span class="settingsTabHeaderDesc">Enable or disable mods.</span>
</div>
<div id="settingsSelServContainer">
<div id="settingsSelServContent">
<div class="settingsSelServContainer">
<div class="settingsSelServContent">
</div>
<div id="settingsSwitchServerContainer">
<div id="settingsSwitchServerContent">
<button id="settingsSwitchServerButton">Switch</button>
<div class="settingsSwitchServerContainer">
<div class="settingsSwitchServerContent">
<button class="settingsSwitchServerButton">Switch</button>
</div>
</div>
</div>
@ -172,6 +172,16 @@
<span class="settingsTabHeaderText">Java Settings</span>
<span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span>
</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="settingsMemoryTitle">Memory</div>
<div id="settingsMemoryContent">
@ -179,7 +189,7 @@
<div class="settingsMemoryContentItem">
<span class="settingsMemoryHeader">Maximum RAM</span>
<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="rangeSliderTrack"></div>
</div>
@ -189,7 +199,7 @@
<div class="settingsMemoryContentItem">
<span class="settingsMemoryHeader">Minimum RAM</span>
<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="rangeSliderTrack"></div>
</div>
@ -231,11 +241,11 @@
</g>
</svg>
</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>
</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 id="settingsJVMOptsContainer">
<div id="settingsJVMOptsTitle">Additional JVM Options</div>
@ -254,9 +264,9 @@
</g>
</svg>
</div>
<input id="settingsJVMOptsVal" cValue="JVMOptions" type="text">
<input id="settingsJVMOptsVal" cValue="JVMOptions" serverDependent type="text">
</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 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.
### `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`
**OPTIONAL**

5719
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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