Compare commits

...

9 Commits

Author SHA1 Message Date
Daniel Scalzi
e314599d99
fix rebase. 2023-03-07 21:30:15 -05:00
Daniel Scalzi
28c9c65220
remove more replaced code. 2023-03-07 21:10:48 -05:00
Daniel Scalzi
43b26ef1b9
delete more stuff 2023-03-07 21:10:48 -05:00
Daniel Scalzi
e9a5f80a36
Start to prune original asset guard to see what still needs to be replaced. 2023-03-07 21:10:48 -05:00
Daniel Scalzi
9a4129c11a
Inject common/instance dir. 2023-03-07 21:10:47 -05:00
Daniel Scalzi
b32857e7de
Progress checkin, mostly works. 2023-03-07 21:10:47 -05:00
Daniel Scalzi
a22bd32cb1
Replace distromanager, assetguard is probably broken. 2023-03-07 21:10:47 -05:00
Daniel Scalzi
45630c068c
Fix style violation. 2023-03-07 21:10:30 -05:00
Ritsu
fb586cca69
(Apple Silicon) aarch64 OpenJDK detect and install (#273)
* (Apple Silicon) aarch64 OpenJDK detect and install

* Fix undefined reference on amd64 macOS

* Revise logic a bit.

* variable isARM64, remove includes

* const isARM64

* forgot replace in _latestCorretto

* Update assetguard.js

* Update assetguard.js

---------

Co-authored-by: Daniel Scalzi <d_scalzi@yahoo.com>
2023-03-07 09:11:44 -05:00
12 changed files with 460 additions and 2074 deletions

View File

@ -1,5 +1,4 @@
// Requirements
const AdmZip = require('adm-zip')
const async = require('async')
const child_process = require('child_process')
const crypto = require('crypto')
@ -14,10 +13,10 @@ const request = require('request')
const tar = require('tar-fs')
const zlib = require('zlib')
const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
const isDev = require('./isdev')
const isARM64 = process.arch === 'arm64'
// Classes
/** Class representing a base asset. */
@ -40,86 +39,6 @@ class Asset {
}
}
/** Class representing a mojang library. */
class Library extends Asset {
/**
* Converts the process.platform OS names to match mojang's OS names.
*/
static mojangFriendlyOS(){
const opSys = process.platform
if (opSys === 'darwin') {
return 'osx'
} else if (opSys === 'win32'){
return 'windows'
} else if (opSys === 'linux'){
return 'linux'
} else {
return 'unknown_os'
}
}
/**
* Checks whether or not a library is valid for download on a particular OS, following
* the rule format specified in the mojang version data index. If the allow property has
* an OS specified, then the library can ONLY be downloaded on that OS. If the disallow
* property has instead specified an OS, the library can be downloaded on any OS EXCLUDING
* the one specified.
*
* If the rules are undefined, the natives property will be checked for a matching entry
* for the current OS.
*
* @param {Array.<Object>} rules The Library's download rules.
* @param {Object} natives The Library's natives object.
* @returns {boolean} True if the Library follows the specified rules, otherwise false.
*/
static validateRules(rules, natives){
if(rules == null) {
if(natives == null) {
return true
} else {
return natives[Library.mojangFriendlyOS()] != null
}
}
for(let rule of rules){
const action = rule.action
const osProp = rule.os
if(action != null && osProp != null){
const osName = osProp.name
const osMoj = Library.mojangFriendlyOS()
if(action === 'allow'){
return osName === osMoj
} else if(action === 'disallow'){
return osName !== osMoj
}
}
}
return true
}
}
class DistroModule extends Asset {
/**
* Create a DistroModule. This is for processing,
* not equivalent to the module objects in the
* distro index.
*
* @param {any} id The id of the asset.
* @param {string} hash The hash value of the asset.
* @param {number} size The size in bytes of the asset.
* @param {string} from The url where the asset can be found.
* @param {string} to The absolute local file path of the asset.
* @param {string} type The the module type.
*/
constructor(id, hash, size, from, to, type){
super(id, hash, size, from, to)
this.type = type
}
}
/**
* Class representing a download tracker. This is used to store meta data
* about a download queue, including the queue itself.
@ -162,34 +81,6 @@ class Util {
return true
}
static isForgeGradle3(mcVersion, forgeVersion) {
if(Util.mcVersionAtLeast('1.13', mcVersion)) {
return true
}
try {
const forgeVer = forgeVersion.split('-')[1]
const maxFG2 = [14, 23, 5, 2847]
const verSplit = forgeVer.split('.').map(v => Number(v))
for(let i=0; i<maxFG2.length; i++) {
if(verSplit[i] > maxFG2[i]) {
return true
} else if(verSplit[i] < maxFG2[i]) {
return false
}
}
return false
} catch(err) {
throw new Error('Forge version is complex (changed).. launcher requires a patch.')
}
}
static isAutoconnectBroken(forgeVersion) {
const minWorking = [31, 2, 15]
@ -302,7 +193,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) => {
@ -495,6 +387,8 @@ class JavaGuard extends EventEmitter {
let vendorName = props[i].split('=')[1].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'
}
}
@ -866,6 +760,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){
@ -894,7 +791,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
}
@ -1002,26 +908,6 @@ class AssetGuard extends EventEmitter {
return crypto.createHash(algo).update(buf).digest('hex')
}
/**
* Used to parse a checksums file. This is specifically designed for
* the checksums.sha1 files found inside the forge scala dependencies.
*
* @param {string} content The string content of the checksums file.
* @returns {Object} An object with keys being the file names, and values being the hashes.
*/
static _parseChecksumsFile(content){
let finalContent = {}
let lines = content.split('\n')
for(let i=0; i<lines.length; i++){
let bits = lines[i].split(' ')
if(bits[1] == null) {
continue
}
finalContent[bits[1]] = bits[0]
}
return finalContent
}
/**
* Validate that a file exists and matches a given hash value.
*
@ -1043,71 +929,6 @@ class AssetGuard extends EventEmitter {
return false
}
/**
* Validates a file in the style used by forge's version index.
*
* @param {string} filePath The path of the file to validate.
* @param {Array.<string>} checksums The checksums listed in the forge version index.
* @returns {boolean} True if the file exists and the hashes match, otherwise false.
*/
static _validateForgeChecksum(filePath, checksums){
if(fs.existsSync(filePath)){
if(checksums == null || checksums.length === 0){
return true
}
let buf = fs.readFileSync(filePath)
let calcdhash = AssetGuard._calculateHash(buf, 'sha1')
let valid = checksums.includes(calcdhash)
if(!valid && filePath.endsWith('.jar')){
valid = AssetGuard._validateForgeJar(filePath, checksums)
}
return valid
}
return false
}
/**
* Validates a forge jar file dependency who declares a checksums.sha1 file.
* This can be an expensive task as it usually requires that we calculate thousands
* of hashes.
*
* @param {Buffer} buf The buffer of the jar file.
* @param {Array.<string>} checksums The checksums listed in the forge version index.
* @returns {boolean} True if all hashes declared in the checksums.sha1 file match the actual hashes.
*/
static _validateForgeJar(buf, checksums){
// Double pass method was the quickest I found. I tried a version where we store data
// to only require a single pass, plus some quick cleanup but that seemed to take slightly more time.
const hashes = {}
let expected = {}
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
//First pass
for(let i=0; i<zipEntries.length; i++){
let entry = zipEntries[i]
if(entry.entryName === 'checksums.sha1'){
expected = AssetGuard._parseChecksumsFile(zip.readAsText(entry))
}
hashes[entry.entryName] = AssetGuard._calculateHash(entry.getData(), 'sha1')
}
if(!checksums.includes(hashes['checksums.sha1'])){
return false
}
//Check against expected
const expectedEntries = Object.keys(expected)
for(let i=0; i<expectedEntries.length; i++){
if(expected[expectedEntries[i]] !== hashes[expectedEntries[i]]){
return false
}
}
return true
}
// #endregion
// Miscellaneous Static Functions
@ -1150,416 +971,10 @@ class AssetGuard extends EventEmitter {
})
}
/**
* Function which finalizes the forge installation process. This creates a 'version'
* instance for forge and saves its version.json file into that instance. If that
* instance already exists, the contents of the version.json file are read and returned
* in a promise.
*
* @param {Asset} asset The Asset object representing Forge.
* @param {string} commonPath The common path for shared game files.
* @returns {Promise.<Object>} A promise which resolves to the contents of forge's version.json.
*/
static _finalizeForgeAsset(asset, commonPath){
return new Promise((resolve, reject) => {
fs.readFile(asset.to, (err, data) => {
const zip = new AdmZip(data)
const zipEntries = zip.getEntries()
for(let i=0; i<zipEntries.length; i++){
if(zipEntries[i].entryName === 'version.json'){
const forgeVersion = JSON.parse(zip.readAsText(zipEntries[i]))
const versionPath = path.join(commonPath, 'versions', forgeVersion.id)
const versionFile = path.join(versionPath, forgeVersion.id + '.json')
if(!fs.existsSync(versionFile)){
fs.ensureDirSync(versionPath)
fs.writeFileSync(path.join(versionPath, forgeVersion.id + '.json'), zipEntries[i].getData())
resolve(forgeVersion)
} else {
//Read the saved file to allow for user modifications.
resolve(JSON.parse(fs.readFileSync(versionFile, 'utf-8')))
}
return
}
}
//We didn't find forge's version.json.
reject('Unable to finalize Forge processing, version.json not found! Has forge changed their format?')
})
})
}
// #endregion
// #endregion
// Validation Functions
// #region
/**
* Loads the version data for a given minecraft version.
*
* @param {string} version The game version for which to load the index data.
* @param {boolean} force Optional. If true, the version index will be downloaded even if it exists locally. Defaults to false.
* @returns {Promise.<Object>} Promise which resolves to the version data object.
*/
loadVersionData(version, force = false){
const self = this
return new Promise(async (resolve, reject) => {
const versionPath = path.join(self.commonPath, 'versions', version)
const versionFile = path.join(versionPath, version + '.json')
if(!fs.existsSync(versionFile) || force){
const url = await self._getVersionDataUrl(version)
//This download will never be tracked as it's essential and trivial.
AssetGuard.logger.info('Preparing download of ' + version + ' assets.')
fs.ensureDirSync(versionPath)
const stream = request(url).pipe(fs.createWriteStream(versionFile))
stream.on('finish', () => {
resolve(JSON.parse(fs.readFileSync(versionFile)))
})
} else {
resolve(JSON.parse(fs.readFileSync(versionFile)))
}
})
}
/**
* Parses Mojang's version manifest and retrieves the url of the version
* data index.
*
* @param {string} version The version to lookup.
* @returns {Promise.<string>} Promise which resolves to the url of the version data index.
* If the version could not be found, resolves to null.
*/
_getVersionDataUrl(version){
return new Promise((resolve, reject) => {
request('https://launchermeta.mojang.com/mc/game/version_manifest.json', (error, resp, body) => {
if(error){
reject(error)
} else {
const manifest = JSON.parse(body)
for(let v of manifest.versions){
if(v.id === version){
resolve(v.url)
}
}
resolve(null)
}
})
})
}
// Asset (Category=''') Validation Functions
// #region
/**
* Public asset validation function. This function will handle the validation of assets.
* It will parse the asset index specified in the version data, analyzing each
* asset entry. In this analysis it will check to see if the local file exists and is valid.
* If not, it will be added to the download queue for the 'assets' identifier.
*
* @param {Object} versionData The version data for the assets.
* @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false.
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
validateAssets(versionData, force = false){
const self = this
return new Promise((resolve, reject) => {
self._assetChainIndexData(versionData, force).then(() => {
resolve()
})
})
}
//Chain the asset tasks to provide full async. The below functions are private.
/**
* Private function used to chain the asset validation process. This function retrieves
* the index data.
* @param {Object} versionData
* @param {boolean} force
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
_assetChainIndexData(versionData, force = false){
const self = this
return new Promise((resolve, reject) => {
//Asset index constants.
const assetIndex = versionData.assetIndex
const name = assetIndex.id + '.json'
const indexPath = path.join(self.commonPath, 'assets', 'indexes')
const assetIndexLoc = path.join(indexPath, name)
let data = null
if(!fs.existsSync(assetIndexLoc) || force){
AssetGuard.logger.info('Downloading ' + versionData.id + ' asset index.')
fs.ensureDirSync(indexPath)
const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc))
stream.on('finish', () => {
data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
self._assetChainValidateAssets(versionData, data).then(() => {
resolve()
})
})
} else {
data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
self._assetChainValidateAssets(versionData, data).then(() => {
resolve()
})
}
})
}
/**
* Private function used to chain the asset validation process. This function processes
* the assets and enqueues missing or invalid files.
* @param {Object} versionData
* @param {boolean} force
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
_assetChainValidateAssets(versionData, indexData){
const self = this
return new Promise((resolve, reject) => {
//Asset constants
const resourceURL = 'https://resources.download.minecraft.net/'
const localPath = path.join(self.commonPath, 'assets')
const objectPath = path.join(localPath, 'objects')
const assetDlQueue = []
let dlSize = 0
let acc = 0
const total = Object.keys(indexData.objects).length
//const objKeys = Object.keys(data.objects)
async.forEachOfLimit(indexData.objects, 10, (value, key, cb) => {
acc++
self.emit('progress', 'assets', acc, total)
const hash = value.hash
const assetName = path.join(hash.substring(0, 2), hash)
const urlName = hash.substring(0, 2) + '/' + hash
const ast = new Asset(key, hash, value.size, resourceURL + urlName, path.join(objectPath, assetName))
if(!AssetGuard._validateLocal(ast.to, 'sha1', ast.hash)){
dlSize += (ast.size*1)
assetDlQueue.push(ast)
}
cb()
}, (err) => {
self.assets = new DLTracker(assetDlQueue, dlSize)
resolve()
})
})
}
// #endregion
// Library (Category=''') Validation Functions
// #region
/**
* Public library validation function. This function will handle the validation of libraries.
* It will parse the version data, analyzing each library entry. In this analysis, it will
* check to see if the local file exists and is valid. If not, it will be added to the download
* queue for the 'libraries' identifier.
*
* @param {Object} versionData The version data for the assets.
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
validateLibraries(versionData){
const self = this
return new Promise((resolve, reject) => {
const libArr = versionData.libraries
const libPath = path.join(self.commonPath, 'libraries')
const libDlQueue = []
let dlSize = 0
//Check validity of each library. If the hashs don't match, download the library.
async.eachLimit(libArr, 5, (lib, cb) => {
if(Library.validateRules(lib.rules, lib.natives)){
let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
if(!AssetGuard._validateLocal(libItm.to, 'sha1', libItm.hash)){
dlSize += (libItm.size*1)
libDlQueue.push(libItm)
}
}
cb()
}, (err) => {
self.libraries = new DLTracker(libDlQueue, dlSize)
resolve()
})
})
}
// #endregion
// Miscellaneous (Category=files) Validation Functions
// #region
/**
* Public miscellaneous mojang file validation function. These files will be enqueued under
* the 'files' identifier.
*
* @param {Object} versionData The version data for the assets.
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
validateMiscellaneous(versionData){
const self = this
return new Promise(async (resolve, reject) => {
await self.validateClient(versionData)
await self.validateLogConfig(versionData)
resolve()
})
}
/**
* Validate client file - artifact renamed from client.jar to '{version}'.jar.
*
* @param {Object} versionData The version data for the assets.
* @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false.
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
validateClient(versionData, force = false){
const self = this
return new Promise((resolve, reject) => {
const clientData = versionData.downloads.client
const version = versionData.id
const targetPath = path.join(self.commonPath, 'versions', version)
const targetFile = version + '.jar'
let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, path.join(targetPath, targetFile))
if(!AssetGuard._validateLocal(client.to, 'sha1', client.hash) || force){
self.files.dlqueue.push(client)
self.files.dlsize += client.size*1
resolve()
} else {
resolve()
}
})
}
/**
* Validate log config.
*
* @param {Object} versionData The version data for the assets.
* @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false.
* @returns {Promise.<void>} An empty promise to indicate the async processing has completed.
*/
validateLogConfig(versionData){
const self = this
return new Promise((resolve, reject) => {
const client = versionData.logging.client
const file = client.file
const targetPath = path.join(self.commonPath, 'assets', 'log_configs')
let logConfig = new Asset(file.id, file.sha1, file.size, file.url, path.join(targetPath, file.id))
if(!AssetGuard._validateLocal(logConfig.to, 'sha1', logConfig.hash)){
self.files.dlqueue.push(logConfig)
self.files.dlsize += logConfig.size*1
resolve()
} else {
resolve()
}
})
}
// #endregion
// Distribution (Category=forge) Validation Functions
// #region
/**
* Validate the distribution.
*
* @param {Server} server The Server to validate.
* @returns {Promise.<Object>} A promise which resolves to the server distribution object.
*/
validateDistribution(server){
const self = this
return new Promise((resolve, reject) => {
self.forge = self._parseDistroModules(server.getModules(), server.getMinecraftVersion(), server.getID())
resolve(server)
})
}
_parseDistroModules(modules, version, servid){
let alist = []
let asize = 0
for(let ob of modules){
let obArtifact = ob.getArtifact()
let obPath = obArtifact.getPath()
let artifact = new DistroModule(ob.getIdentifier(), obArtifact.getHash(), obArtifact.getSize(), obArtifact.getURL(), obPath, ob.getType())
const validationPath = obPath.toLowerCase().endsWith('.pack.xz') ? obPath.substring(0, obPath.toLowerCase().lastIndexOf('.pack.xz')) : obPath
if(!AssetGuard._validateLocal(validationPath, 'MD5', artifact.hash)){
asize += artifact.size*1
alist.push(artifact)
if(validationPath !== obPath) this.extractQueue.push(obPath)
}
//Recursively process the submodules then combine the results.
if(ob.getSubModules() != null){
let dltrack = this._parseDistroModules(ob.getSubModules(), version, servid)
asize += dltrack.dlsize*1
alist = alist.concat(dltrack.dlqueue)
}
}
return new DLTracker(alist, asize)
}
/**
* Loads Forge's version.json data into memory for the specified server id.
*
* @param {string} server The Server to load Forge data for.
* @returns {Promise.<Object>} A promise which resolves to Forge's version.json data.
*/
loadForgeData(server){
const self = this
return new Promise(async (resolve, reject) => {
const modules = server.getModules()
for(let ob of modules){
const type = ob.getType()
if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Forge){
if(Util.isForgeGradle3(server.getMinecraftVersion(), ob.getVersion())){
// Read Manifest
for(let sub of ob.getSubModules()){
if(sub.getType() === DistroManager.Types.VersionManifest){
resolve(JSON.parse(fs.readFileSync(sub.getArtifact().getPath(), 'utf-8')))
return
}
}
reject('No forge version manifest found!')
return
} else {
let obArtifact = ob.getArtifact()
let obPath = obArtifact.getPath()
let asset = new DistroModule(ob.getIdentifier(), obArtifact.getHash(), obArtifact.getSize(), obArtifact.getURL(), obPath, type)
try {
let forgeData = await AssetGuard._finalizeForgeAsset(asset, self.commonPath)
resolve(forgeData)
} catch (err){
reject(err)
}
return
}
}
}
reject('No forge module found!')
})
}
_parseForgeLibraries(){
/* TODO
* Forge asset validations are already implemented. When there's nothing much
* to work on, implement forge downloads using forge's version.json. This is to
* have the code on standby if we ever need it (since it's half implemented already).
*/
}
// #endregion
// Java (Category=''') Validation (download) Functions
// #region
@ -1642,43 +1057,6 @@ class AssetGuard extends EventEmitter {
}
}
// _enqueueMojangJRE(dir){
// return new Promise((resolve, reject) => {
// // Mojang does not host the JRE for linux.
// if(process.platform === 'linux'){
// resolve(false)
// }
// AssetGuard.loadMojangLauncherData().then(data => {
// if(data != null) {
// try {
// const mJRE = data[Library.mojangFriendlyOS()]['64'].jre
// const url = mJRE.url
// request.head(url, (err, resp, body) => {
// if(err){
// resolve(false)
// } else {
// const name = url.substring(url.lastIndexOf('/')+1)
// const fDir = path.join(dir, name)
// const jre = new Asset('jre' + mJRE.version, mJRE.sha1, resp.headers['content-length'], url, fDir)
// this.java = new DLTracker([jre], jre.size, a => {
// fs.readFile(a.to, (err, data) => {
// // Data buffer needs to be decompressed from lzma,
// // not really possible using node.js
// })
// })
// }
// })
// } catch (err){
// resolve(false)
// }
// }
// })
// })
// }
// #endregion
@ -1838,57 +1216,11 @@ class AssetGuard extends EventEmitter {
})
}
async validateEverything(serverid, dev = false){
try {
if(!ConfigManager.isLoaded()){
ConfigManager.load()
}
DistroManager.setDevMode(dev)
const dI = await DistroManager.pullLocal()
const server = dI.getServer(serverid)
// Validate Everything
await this.validateDistribution(server)
this.emit('validate', 'distribution')
const versionData = await this.loadVersionData(server.getMinecraftVersion())
this.emit('validate', 'version')
await this.validateAssets(versionData)
this.emit('validate', 'assets')
await this.validateLibraries(versionData)
this.emit('validate', 'libraries')
await this.validateMiscellaneous(versionData)
this.emit('validate', 'files')
await this.processDlQueues()
//this.emit('complete', 'download')
const forgeData = await this.loadForgeData(server)
return {
versionData,
forgeData
}
} catch (err){
return {
versionData: null,
forgeData: null,
error: err
}
}
}
// #endregion
}
module.exports = {
Util,
AssetGuard,
JavaGuard,
Asset,
Library
}
JavaGuard
}

View File

@ -1,5 +1,6 @@
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const { mcVersionAtLeast } = require('helios-core/common')
const os = require('os')
const path = require('path')
@ -64,27 +65,6 @@ 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.

View File

@ -1,621 +1,15 @@
const fs = require('fs')
const path = require('path')
const request = require('request')
const { LoggerUtil } = require('helios-core')
const { DistributionAPI } = require('helios-core/common')
const ConfigManager = require('./configmanager')
const logger = LoggerUtil.getLogger('DistroManager')
exports.REMOTE_DISTRO_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
/**
* Represents the download information
* for a specific module.
*/
class Artifact {
/**
* Parse a JSON object into an Artifact.
*
* @param {Object} json A JSON object representing an Artifact.
*
* @returns {Artifact} The parsed Artifact.
*/
static fromJSON(json){
return Object.assign(new Artifact(), json)
}
const api = new DistributionAPI(
ConfigManager.getLauncherDirectory(),
null, // Injected forcefully by the preloader.
null, // Injected forcefully by the preloader.
exports.REMOTE_DISTRO_URL,
false
)
/**
* Get the MD5 hash of the artifact. This value may
* be undefined for artifacts which are not to be
* validated and updated.
*
* @returns {string} The MD5 hash of the Artifact or undefined.
*/
getHash(){
return this.MD5
}
/**
* @returns {number} The download size of the artifact.
*/
getSize(){
return this.size
}
/**
* @returns {string} The download url of the artifact.
*/
getURL(){
return this.url
}
/**
* @returns {string} The artifact's destination path.
*/
getPath(){
return this.path
}
}
exports.Artifact
/**
* Represents a the requirement status
* of a module.
*/
class Required {
/**
* Parse a JSON object into a Required object.
*
* @param {Object} json A JSON object representing a Required object.
*
* @returns {Required} The parsed Required object.
*/
static fromJSON(json){
if(json == null){
return new Required(true, true)
} else {
return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def)
}
}
constructor(value, def){
this.value = value
this.default = def
}
/**
* Get the default value for a required object. If a module
* is not required, this value determines whether or not
* it is enabled by default.
*
* @returns {boolean} The default enabled value.
*/
isDefault(){
return this.default
}
/**
* @returns {boolean} Whether or not the module is required.
*/
isRequired(){
return this.value
}
}
exports.Required
/**
* Represents a module.
*/
class Module {
/**
* Parse a JSON object into a Module.
*
* @param {Object} json A JSON object representing a Module.
* @param {string} serverid The ID of the server to which this module belongs.
*
* @returns {Module} The parsed Module.
*/
static fromJSON(json, serverid){
return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid)
}
/**
* Resolve the default extension for a specific module type.
*
* @param {string} type The type of the module.
*
* @return {string} The default extension for the given type.
*/
static _resolveDefaultExtension(type){
switch (type) {
case exports.Types.Library:
case exports.Types.ForgeHosted:
case exports.Types.LiteLoader:
case exports.Types.ForgeMod:
return 'jar'
case exports.Types.LiteMod:
return 'litemod'
case exports.Types.File:
default:
return 'jar' // There is no default extension really.
}
}
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)
this.artifact = Artifact.fromJSON(artifact)
this._resolveArtifactPath(artifact.path, serverid)
this._resolveSubModules(subModules, serverid)
}
_resolveMetaData(){
try {
const m0 = this.identifier.split('@')
this.artifactExt = m0[1] || Module._resolveDefaultExtension(this.type)
const m1 = m0[0].split(':')
this.artifactClassifier = m1[3] || undefined
this.artifactVersion = m1[2] || '???'
this.artifactID = m1[1] || '???'
this.artifactGroup = m1[0] || '???'
} catch (err) {
// Improper identifier
logger.error('Improper ID for module', this.identifier, err)
}
}
_resolveArtifactPath(artifactPath, serverid){
const pth = artifactPath == null ? path.join(...this.getGroup().split('.'), this.getID(), this.getVersion(), `${this.getID()}-${this.getVersion()}${this.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${this.getExtension()}`) : artifactPath
switch (this.type){
case exports.Types.Library:
case exports.Types.ForgeHosted:
case exports.Types.LiteLoader:
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'libraries', pth)
break
case exports.Types.ForgeMod:
case exports.Types.LiteMod:
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'modstore', pth)
break
case exports.Types.VersionManifest:
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'versions', this.getIdentifier(), `${this.getIdentifier()}.json`)
break
case exports.Types.File:
default:
this.artifact.path = path.join(ConfigManager.getInstanceDirectory(), serverid, pth)
break
}
}
_resolveSubModules(json, serverid){
const arr = []
if(json != null){
for(let sm of json){
arr.push(Module.fromJSON(sm, serverid))
}
}
this.subModules = arr.length > 0 ? arr : null
}
/**
* @returns {string} The full, unparsed module identifier.
*/
getIdentifier(){
return this.identifier
}
/**
* @returns {string} The name of the module.
*/
getName(){
return this.name
}
/**
* @returns {Required} The required object declared by this module.
*/
getRequired(){
return this.required
}
/**
* @returns {Artifact} The artifact declared by this module.
*/
getArtifact(){
return this.artifact
}
/**
* @returns {string} The maven identifier of this module's artifact.
*/
getID(){
return this.artifactID
}
/**
* @returns {string} The maven group of this module's artifact.
*/
getGroup(){
return this.artifactGroup
}
/**
* @returns {string} The identifier without he version or extension.
*/
getVersionlessID(){
return this.getGroup() + ':' + this.getID()
}
/**
* @returns {string} The identifier without the extension.
*/
getExtensionlessID(){
return this.getIdentifier().split('@')[0]
}
/**
* @returns {string} The version of this module's artifact.
*/
getVersion(){
return this.artifactVersion
}
/**
* @returns {string} The classifier of this module's artifact
*/
getClassifier(){
return this.artifactClassifier
}
/**
* @returns {string} The extension of this module's artifact.
*/
getExtension(){
return this.artifactExt
}
/**
* @returns {boolean} Whether or not this module has sub modules.
*/
hasSubModules(){
return this.subModules != null
}
/**
* @returns {Array.<Module>} An array of sub modules.
*/
getSubModules(){
return this.subModules
}
/**
* @returns {string} The type of the module.
*/
getType(){
return this.type
}
/**
* @returns {boolean} Whether or not this library should be on the classpath.
*/
getClasspath(){
return this.classpath ?? true
}
}
exports.Module
/**
* Represents a server configuration.
*/
class Server {
/**
* Parse a JSON object into a Server.
*
* @param {Object} json A JSON object representing a Server.
*
* @returns {Server} The parsed Server object.
*/
static fromJSON(json){
const mdls = json.modules
json.modules = []
const serv = Object.assign(new Server(), json)
serv._resolveModules(mdls)
return serv
}
_resolveModules(json){
const arr = []
for(let m of json){
arr.push(Module.fromJSON(m, this.getID()))
}
this.modules = arr
}
/**
* @returns {string} The ID of the server.
*/
getID(){
return this.id
}
/**
* @returns {string} The name of the server.
*/
getName(){
return this.name
}
/**
* @returns {string} The description of the server.
*/
getDescription(){
return this.description
}
/**
* @returns {string} The URL of the server's icon.
*/
getIcon(){
return this.icon
}
/**
* @returns {string} The version of the server configuration.
*/
getVersion(){
return this.version
}
/**
* @returns {string} The IP address of the server.
*/
getAddress(){
return this.address
}
/**
* @returns {string} The minecraft version of the server.
*/
getMinecraftVersion(){
return this.minecraftVersion
}
/**
* @returns {boolean} Whether or not this server is the main
* server. The main server is selected by the launcher when
* no valid server is selected.
*/
isMainServer(){
return this.mainServer
}
/**
* @returns {boolean} Whether or not the server is autoconnect.
* by default.
*/
isAutoConnect(){
return this.autoconnect
}
/**
* @returns {Array.<Module>} An array of modules for this server.
*/
getModules(){
return this.modules
}
}
exports.Server
/**
* Represents the Distribution Index.
*/
class DistroIndex {
/**
* Parse a JSON object into a DistroIndex.
*
* @param {Object} json A JSON object representing a DistroIndex.
*
* @returns {DistroIndex} The parsed Server object.
*/
static fromJSON(json){
const servers = json.servers
json.servers = []
const distro = Object.assign(new DistroIndex(), json)
distro._resolveServers(servers)
distro._resolveMainServer()
return distro
}
_resolveServers(json){
const arr = []
for(let s of json){
arr.push(Server.fromJSON(s))
}
this.servers = arr
}
_resolveMainServer(){
for(let serv of this.servers){
if(serv.mainServer){
this.mainServer = serv.id
return
}
}
// If no server declares default_selected, default to the first one declared.
this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null
}
/**
* @returns {string} The version of the distribution index.
*/
getVersion(){
return this.version
}
/**
* @returns {string} The URL to the news RSS feed.
*/
getRSS(){
return this.rss
}
/**
* @returns {Array.<Server>} An array of declared server configurations.
*/
getServers(){
return this.servers
}
/**
* Get a server configuration by its ID. If it does not
* exist, null will be returned.
*
* @param {string} id The ID of the server.
*
* @returns {Server} The server configuration with the given ID or null.
*/
getServer(id){
for(let serv of this.servers){
if(serv.id === id){
return serv
}
}
return null
}
/**
* Get the main server.
*
* @returns {Server} The main server.
*/
getMainServer(){
return this.mainServer != null ? this.getServer(this.mainServer) : null
}
}
exports.DistroIndex
exports.Types = {
Library: 'Library',
ForgeHosted: 'ForgeHosted',
Forge: 'Forge', // Unimplemented
LiteLoader: 'LiteLoader',
ForgeMod: 'ForgeMod',
LiteMod: 'LiteMod',
File: 'File',
VersionManifest: 'VersionManifest'
}
let DEV_MODE = false
const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
let data = null
/**
* @returns {Promise.<DistroIndex>}
*/
exports.pullRemote = function(){
if(DEV_MODE){
return exports.pullLocal()
}
return new Promise((resolve, reject) => {
const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
//const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
const opts = {
url: distroURL,
timeout: 2500
}
const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
request(opts, (error, resp, body) => {
if(!error){
try {
data = DistroIndex.fromJSON(JSON.parse(body))
} catch (e) {
reject(e)
return
}
fs.writeFile(distroDest, body, 'utf-8', (err) => {
if(!err){
resolve(data)
return
} else {
reject(err)
return
}
})
} else {
reject(error)
return
}
})
})
}
/**
* @returns {Promise.<DistroIndex>}
*/
exports.pullLocal = function(){
return new Promise((resolve, reject) => {
fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => {
if(!err){
data = DistroIndex.fromJSON(JSON.parse(d))
resolve(data)
return
} else {
reject(err)
return
}
})
})
}
exports.setDevMode = function(value){
if(value){
logger.info('Developer mode enabled.')
logger.info('If you don\'t know what that means, revert immediately.')
} else {
logger.info('Developer mode disabled.')
}
DEV_MODE = value
}
exports.isDevMode = function(){
return DEV_MODE
}
/**
* @returns {DistroIndex}
*/
exports.getDistribution = function(){
return data
}
exports.DistroAPI = api

View File

@ -4,9 +4,11 @@ const os = require('os')
const path = require('path')
const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
const { DistroAPI } = require('./distromanager')
const LangLoader = require('./langloader')
const { LoggerUtil } = require('helios-core')
// eslint-disable-next-line no-unused-vars
const { HeliosDistribution } = require('helios-core/common')
const logger = LoggerUtil.getLogger('Preloader')
@ -15,16 +17,25 @@ logger.info('Loading..')
// Load ConfigManager
ConfigManager.load()
// Yuck!
// TODO Fix this
DistroAPI['commonDir'] = ConfigManager.getCommonDirectory()
DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory()
// Load Strings
LangLoader.loadLanguage('en_US')
/**
*
* @param {HeliosDistribution} data
*/
function onDistroLoad(data){
if(data != null){
// 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.getServerById(ConfigManager.getSelectedServer()) == null){
logger.info('Determining default selected server..')
ConfigManager.setSelectedServer(data.getMainServer().getID())
ConfigManager.setSelectedServer(data.getMainServer().rawServer.id)
ConfigManager.save()
}
}
@ -32,35 +43,20 @@ function onDistroLoad(data){
}
// Ensure Distribution is downloaded and cached.
DistroManager.pullRemote().then((data) => {
logger.info('Loaded distribution index.')
onDistroLoad(data)
}).catch((err) => {
logger.info('Failed to load distribution index.')
logger.error(err)
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.info('Successfully loaded an older version of the distribution index.')
onDistroLoad(data)
}).catch((err) => {
DistroAPI.getDistribution()
.then(heliosDistro => {
logger.info('Loaded distribution index.')
onDistroLoad(heliosDistro)
})
.catch(err => {
logger.info('Failed to load an older version of the distribution index.')
logger.info('Application cannot run.')
logger.error(err)
onDistroLoad(null)
})
})
// Clean up temp dir incase previous launches ended unexpectedly.
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
if(err){

View File

@ -3,20 +3,19 @@ const child_process = require('child_process')
const crypto = require('crypto')
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const { getMojangOS, isLibraryCompatible, mcVersionAtLeast } = require('helios-core/common')
const { Type } = require('helios-distribution-types')
const os = require('os')
const path = require('path')
const { URL } = require('url')
const { Util, Library } = require('./assetguard')
const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
const logger = LoggerUtil.getLogger('ProcessBuilder')
class ProcessBuilder {
constructor(distroServer, versionData, forgeData, authUser, launcherVersion){
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.getID())
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id)
this.commonDir = ConfigManager.getCommonDirectory()
this.server = distroServer
this.versionData = versionData
@ -41,10 +40,10 @@ class ProcessBuilder {
process.throwDeprecation = true
this.setupLiteLoader()
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.rawServer.id).mods, this.server.modules)
// Mod list below 1.13
if(!Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
this.constructJSONModList('forge', modObj.fMods, true)
if(this.usingLiteLoader){
this.constructJSONModList('liteloader', modObj.lMods, true)
@ -54,14 +53,14 @@ class ProcessBuilder {
const uberModArr = modObj.fMods.concat(modObj.lMods)
let args = this.constructJVMArguments(uberModArr, tempNativePath)
if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
//args = args.concat(this.constructModArguments(modObj.fMods))
args = args.concat(this.constructModList(modObj.fMods))
}
logger.info('Launch Arguments:', args)
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.getID()), args, {
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
cwd: this.gameDir,
detached: ConfigManager.getLaunchDetached()
})
@ -122,7 +121,7 @@ class ProcessBuilder {
* @returns {boolean} True if the mod is enabled, false otherwise.
*/
static isModEnabled(modCfg, required = null){
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.def : true
}
/**
@ -132,20 +131,20 @@ class ProcessBuilder {
* mod. It must not be declared as a submodule.
*/
setupLiteLoader(){
for(let ll of this.server.getModules()){
if(ll.getType() === DistroManager.Types.LiteLoader){
if(!ll.getRequired().isRequired()){
const modCfg = ConfigManager.getModConfiguration(this.server.getID()).mods
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.getRequired())){
if(fs.existsSync(ll.getArtifact().getPath())){
for(let ll of this.server.modules){
if(ll.rawModule.type === Type.LiteLoader){
if(!ll.getRequired().value){
const modCfg = ConfigManager.getModConfiguration(this.server.rawServer.id).mods
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessMavenIdentifier()], ll.getRequired())){
if(fs.existsSync(ll.getPath())){
this.usingLiteLoader = true
this.llPath = ll.getArtifact().getPath()
this.llPath = ll.getPath()
}
}
} else {
if(fs.existsSync(ll.getArtifact().getPath())){
if(fs.existsSync(ll.getPath())){
this.usingLiteLoader = true
this.llPath = ll.getArtifact().getPath()
this.llPath = ll.getPath()
}
}
}
@ -166,20 +165,20 @@ class ProcessBuilder {
let lMods = []
for(let mdl of mdls){
const type = mdl.getType()
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
const o = !mdl.getRequired().isRequired()
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.getRequired())
const type = mdl.rawModule.type
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
const o = !mdl.getRequired().value
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired())
if(!o || (o && e)){
if(mdl.hasSubModules()){
const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessID()].mods, mdl.getSubModules())
if(mdl.subModules.length > 0){
const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessMavenIdentifier()].mods, mdl.subModules)
fMods = fMods.concat(v.fMods)
lMods = lMods.concat(v.lMods)
if(mdl.type === DistroManager.Types.LiteLoader){
if(mdl.type === Type.LiteLoader){
continue
}
}
if(mdl.type === DistroManager.Types.ForgeMod){
if(mdl.type === Type.ForgeMod){
fMods.push(mdl)
} else {
lMods.push(mdl)
@ -307,14 +306,11 @@ class ProcessBuilder {
}
_processAutoConnectArg(args){
if(ConfigManager.getAutoConnect() && this.server.isAutoConnect()){
const serverURL = new URL('my://' + this.server.getAddress())
if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
args.push('--server')
args.push(serverURL.hostname)
if(serverURL.port){
args.push('--port')
args.push(serverURL.port)
}
args.push(this.server.hostname)
args.push('--port')
args.push(this.server.port)
}
}
@ -326,7 +322,7 @@ class ProcessBuilder {
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
*/
constructJVMArguments(mods, tempNativePath){
if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
return this._constructJVMArguments113(mods, tempNativePath)
} else {
return this._constructJVMArguments112(mods, tempNativePath)
@ -354,9 +350,9 @@ class ProcessBuilder {
args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
}
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('-Xmx' + ConfigManager.getMaxRAM(this.server.rawServer.id))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.rawServer.id))
args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
args.push('-Djava.library.path=' + tempNativePath)
// Main Java Class
@ -405,9 +401,9 @@ class ProcessBuilder {
args.push('-Xdock:name=HeliosLauncher')
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
}
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('-Xmx' + ConfigManager.getMaxRAM(this.server.rawServer.id))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.rawServer.id))
args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
// Main Java Class
args.push(this.forgeData.mainClass)
@ -421,7 +417,7 @@ class ProcessBuilder {
let checksum = 0
for(let rule of args[i].rules){
if(rule.os != null){
if(rule.os.name === Library.mojangFriendlyOS()
if(rule.os.name === getMojangOS()
&& (rule.os.version == null || new RegExp(rule.os.version).test(os.release))){
if(rule.action === 'allow'){
checksum++
@ -471,7 +467,7 @@ class ProcessBuilder {
break
case 'version_name':
//val = versionData.id
val = this.server.getID()
val = this.server.rawServer.id
break
case 'game_directory':
val = this.gameDir
@ -523,7 +519,7 @@ class ProcessBuilder {
// Autoconnect
let isAutoconnectBroken
try {
isAutoconnectBroken = Util.isAutoconnectBroken(this.forgeData.id.split('-')[2])
isAutoconnectBroken = ProcessBuilder.isAutoconnectBroken(this.forgeData.id.split('-')[2])
} catch(err) {
logger.error(err)
logger.error('Forge version format changed.. assuming autoconnect works.')
@ -569,7 +565,7 @@ class ProcessBuilder {
break
case 'version_name':
//val = versionData.id
val = this.server.getID()
val = this.server.rawServer.id
break
case 'game_directory':
val = this.gameDir
@ -668,7 +664,7 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){
let cpArgs = []
if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) {
if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion)) {
// Add the version.jar to the classpath.
// Must not be added to the classpath for Forge 1.17+.
const version = this.versionData.id
@ -714,13 +710,13 @@ class ProcessBuilder {
fs.ensureDirSync(tempNativePath)
for(let i=0; i<libArr.length; i++){
const lib = libArr[i]
if(Library.validateRules(lib.rules, lib.natives)){
if(isLibraryCompatible(lib.rules, lib.natives)){
// 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', ''))]
const artifact = lib.downloads.classifiers[lib.natives[getMojangOS()].replace('${arch}', process.arch.replace('x', ''))]
// Location of native zip.
const to = path.join(this.libPath, artifact.path)
@ -826,15 +822,15 @@ class ProcessBuilder {
* @returns {{[id: string]: string}} An object containing the paths of each library this server requires.
*/
_resolveServerLibraries(mods){
const mdls = this.server.getModules()
const mdls = this.server.modules
let libs = {}
// Locate Forge/Libraries
for(let mdl of mdls){
const type = mdl.getType()
if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Library){
libs[mdl.getVersionlessID()] = mdl.getArtifact().getPath()
if(mdl.hasSubModules()){
const type = mdl.rawModule.type
if(type === Type.ForgeHosted || type === Type.Library){
libs[mdl.getVersionlessMavenIdentifier()] = mdl.getPath()
if(mdl.subModules.length > 0){
const res = this._resolveModuleLibraries(mdl)
if(res.length > 0){
libs = {...libs, ...res}
@ -863,20 +859,20 @@ class ProcessBuilder {
* @returns {Array.<string>} An array containing the paths of each library this module requires.
*/
_resolveModuleLibraries(mdl){
if(!mdl.hasSubModules()){
if(!mdl.subModules.length > 0){
return []
}
let libs = []
for(let sm of mdl.getSubModules()){
if(sm.getType() === DistroManager.Types.Library){
for(let sm of mdl.subModules){
if(sm.rawModule.type === Type.Library){
if(sm.getClasspath()) {
libs.push(sm.getArtifact().getPath())
if(sm.rawModule.classpath ?? true) {
libs.push(sm.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()){
if(mdl.subModules.length > 0){
const res = this._resolveModuleLibraries(sm)
if(res.length > 0){
libs = libs.concat(res)
@ -886,6 +882,24 @@ class ProcessBuilder {
return libs
}
static isAutoconnectBroken(forgeVersion) {
const minWorking = [31, 2, 15]
const verSplit = forgeVersion.split('.').map(v => Number(v))
if(verSplit[0] === 31) {
for(let i=0; i<minWorking.length; i++) {
if(verSplit[i] > minWorking[i]) {
return false
} else if(verSplit[i] < minWorking[i]) {
return true
}
}
}
return false
}
}
module.exports = ProcessBuilder

View File

@ -5,14 +5,24 @@
const cp = require('child_process')
const crypto = require('crypto')
const { URL } = require('url')
const { MojangRestAPI, getServerStatus } = require('helios-core/mojang')
const {
MojangRestAPI,
getServerStatus
} = require('helios-core/mojang')
const {
RestResponseStatus,
isDisplayableError,
mcVersionAtLeast
} = require('helios-core/common')
const {
FullRepair,
DistributionIndexProcessor,
MojangIndexProcessor
} = require('helios-core/dl')
// 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')
@ -54,26 +64,22 @@ function setLaunchDetails(details){
/**
* Set the value of the loading progress bar and display that value.
*
* @param {number} value The progress value.
* @param {number} max The total size.
* @param {number|string} percent Optional. The percentage to display on the progress label.
* @param {number} percent Percentage (0-100)
*/
function setLaunchPercentage(value, max, percent = ((value/max)*100)){
launch_progress.setAttribute('max', max)
launch_progress.setAttribute('value', value)
function setLaunchPercentage(percent){
launch_progress.setAttribute('max', 100)
launch_progress.setAttribute('value', percent)
launch_progress_label.innerHTML = percent + '%'
}
/**
* Set the value of the OS progress bar and display that on the UI.
*
* @param {number} value The progress value.
* @param {number} max The total download size.
* @param {number|string} percent Optional. The percentage to display on the progress label.
* @param {number} percent Percentage (0-100)
*/
function setDownloadPercentage(value, max, percent = ((value/max)*100)){
remote.getCurrentWindow().setProgressBar(value/max)
setLaunchPercentage(value, max, percent)
function setDownloadPercentage(percent){
remote.getCurrentWindow().setProgressBar(percent/100)
setLaunchPercentage(percent)
}
/**
@ -86,9 +92,9 @@ function setLaunchEnabled(val){
}
// Bind launch button
document.getElementById('launch_button').addEventListener('click', function(e){
document.getElementById('launch_button').addEventListener('click', async (e) => {
loggerLanding.info('Launching game..')
const mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()
const mcVersion = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()).rawServer.minecraftVersion
const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())
if(jExe == null){
asyncSystemScan(mcVersion)
@ -99,10 +105,10 @@ document.getElementById('launch_button').addEventListener('click', function(e){
setLaunchPercentage(0, 100)
const jg = new JavaGuard(mcVersion)
jg._validateJavaBinary(jExe).then((v) => {
jg._validateJavaBinary(jExe).then(async v => {
loggerLanding.info('Java version meta', v)
if(v.valid){
dlAsync()
await dlAsync()
} else {
asyncSystemScan(mcVersion)
}
@ -111,14 +117,14 @@ document.getElementById('launch_button').addEventListener('click', function(e){
})
// Bind settings button
document.getElementById('settingsMediaButton').onclick = (e) => {
prepareSettings()
document.getElementById('settingsMediaButton').onclick = async e => {
await prepareSettings()
switchView(getCurrentView(), VIEWS.settings)
}
// Bind avatar overlay button.
document.getElementById('avatarOverlay').onclick = (e) => {
prepareSettings()
document.getElementById('avatarOverlay').onclick = async e => {
await prepareSettings()
switchView(getCurrentView(), VIEWS.settings, 500, 500, () => {
settingsNavItemListener(document.getElementById('settingsNavAccount'), false)
})
@ -144,9 +150,9 @@ function updateSelectedServer(serv){
if(getCurrentView() === VIEWS.settings){
fullSettingsSave()
}
ConfigManager.setSelectedServer(serv != null ? serv.getID() : null)
ConfigManager.setSelectedServer(serv != null ? serv.rawServer.id : null)
ConfigManager.save()
server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected')
server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.rawServer.name : 'No Server Selected')
if(getCurrentView() === VIEWS.settings){
animateSettingsTabRefresh()
}
@ -154,9 +160,9 @@ function updateSelectedServer(serv){
}
// Real text is set in uibinder.js on distributionIndexDone.
server_selection_button.innerHTML = '\u2022 Loading..'
server_selection_button.onclick = (e) => {
server_selection_button.onclick = async e => {
e.target.blur()
toggleServerSelection(true)
await toggleServerSelection(true)
}
// Update Mojang Status Color
@ -220,17 +226,16 @@ const refreshMojangStatuses = async function(){
document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
}
const refreshServerStatus = async function(fade = false){
const refreshServerStatus = async (fade = false) => {
loggerLanding.info('Refreshing Server Status')
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
let pLabel = 'SERVER'
let pVal = 'OFFLINE'
try {
const serverURL = new URL('my://' + serv.getAddress())
const servStat = await getServerStatus(47, serverURL.hostname, Number(serverURL.port))
const servStat = await getServerStatus(47, serv.hostname, serv.port)
console.log(servStat)
pLabel = 'PLAYERS'
pVal = servStat.players.online + '/' + servStat.players.max
@ -318,9 +323,9 @@ function asyncSystemScan(mcVersion, launchAfter = true){
console.log(`\x1b[31m[SysAEx]\x1b[0m ${data}`)
})
const javaVer = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8'
const javaVer = mcVersionAtLeast('1.17', mcVersion) ? '17' : '8'
sysAEx.on('message', (m) => {
sysAEx.on('message', async (m) => {
if(m.context === 'validateJava'){
if(m.result == null){
@ -368,10 +373,10 @@ function asyncSystemScan(mcVersion, launchAfter = true){
// We need to make sure that the updated value is on the settings UI.
// Just incase the settings UI is already open.
settingsJavaExecVal.value = m.result
populateJavaExecDetails(settingsJavaExecVal.value)
await populateJavaExecDetails(settingsJavaExecVal.value)
if(launchAfter){
dlAsync()
await dlAsync()
}
sysAEx.disconnect()
}
@ -447,7 +452,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){
setLaunchDetails('Java Installed!')
if(launchAfter){
dlAsync()
await dlAsync()
}
sysAEx.disconnect()
@ -475,18 +480,28 @@ const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/
const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+)$/
const MIN_LINGER = 5000
let aEx
let serv
let versionData
let forgeData
let progressListener
function dlAsync(login = true){
async function dlAsync(login = true) {
// Login parameter is temporary for debug purposes. Allows testing the validation/downloads without
// launching the game.
const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite')
setLaunchDetails('Loading server information..')
let distro
try {
distro = await DistroAPI.refreshDistributionOrFallback()
onDistroRefresh(distro)
} catch(err) {
loggerLaunchSuite.error('Unable to refresh distribution index.', err)
showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.')
return
}
const serv = distro.getServerById(ConfigManager.getSelectedServer())
if(login) {
if(ConfigManager.getSelectedAccount() == null){
loggerLanding.error('You must be logged into an account.')
@ -498,272 +513,148 @@ function dlAsync(login = true){
toggleLaunchArea(true)
setLaunchPercentage(0, 100)
const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite')
const forkEnv = JSON.parse(JSON.stringify(process.env))
forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory()
// Start AssetExec to run validations and downloads in a forked process.
aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
'AssetGuard',
const fullRepairModule = new FullRepair(
ConfigManager.getCommonDirectory(),
ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())
], {
env: forkEnv,
stdio: 'pipe'
})
// Stdout
aEx.stdio[1].setEncoding('utf8')
aEx.stdio[1].on('data', (data) => {
console.log(`\x1b[32m[AEx]\x1b[0m ${data}`)
})
// Stderr
aEx.stdio[2].setEncoding('utf8')
aEx.stdio[2].on('data', (data) => {
console.log(`\x1b[31m[AEx]\x1b[0m ${data}`)
})
aEx.on('error', (err) => {
ConfigManager.getInstanceDirectory(),
ConfigManager.getLauncherDirectory(),
ConfigManager.getSelectedServer(),
DistroAPI.isDevMode()
)
fullRepairModule.spawnReceiver()
fullRepairModule.childProcess.on('error', (err) => {
loggerLaunchSuite.error('Error during launch', err)
showLaunchFailure('Error During Launch', err.message || 'See console (CTRL + Shift + i) for more details.')
})
aEx.on('close', (code, signal) => {
fullRepairModule.childProcess.on('close', (code, _signal) => {
if(code !== 0){
loggerLaunchSuite.error(`AssetExec exited with code ${code}, assuming error.`)
showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.')
}
})
// Establish communications between the AssetExec and current process.
aEx.on('message', (m) => {
loggerLaunchSuite.info('Validating files.')
setLaunchDetails('Validating file integrity..')
const invalidFileCount = await fullRepairModule.verifyFiles(percent => {
setLaunchPercentage(percent)
})
setLaunchPercentage(100)
if(m.context === 'validate'){
switch(m.data){
case 'distribution':
setLaunchPercentage(20, 100)
loggerLaunchSuite.info('Validated distibution index.')
setLaunchDetails('Loading version information..')
break
case 'version':
setLaunchPercentage(40, 100)
loggerLaunchSuite.info('Version data loaded.')
setLaunchDetails('Validating asset integrity..')
break
case 'assets':
setLaunchPercentage(60, 100)
loggerLaunchSuite.info('Asset Validation Complete')
setLaunchDetails('Validating library integrity..')
break
case 'libraries':
setLaunchPercentage(80, 100)
loggerLaunchSuite.info('Library validation complete.')
setLaunchDetails('Validating miscellaneous file integrity..')
break
case 'files':
setLaunchPercentage(100, 100)
loggerLaunchSuite.info('File validation complete.')
setLaunchDetails('Downloading files..')
break
if(invalidFileCount > 0) {
loggerLaunchSuite.info('Downloading files.')
setLaunchDetails('Downloading files..')
await fullRepairModule.download(percent => {
setDownloadPercentage(percent)
})
setDownloadPercentage(100)
} else {
loggerLaunchSuite.info('No invalid files, skipping download.')
}
// Remove download bar.
remote.getCurrentWindow().setProgressBar(-1)
fullRepairModule.destroyReceiver()
setLaunchDetails('Preparing to launch..')
const mojangIndexProcessor = new MojangIndexProcessor(
ConfigManager.getCommonDirectory(),
serv.rawServer.minecraftVersion)
const distributionIndexProcessor = new DistributionIndexProcessor(
ConfigManager.getCommonDirectory(),
distro,
serv.rawServer.id
)
// TODO need to load these.
const forgeData = await distributionIndexProcessor.loadForgeVersionJson(serv)
const versionData = await mojangIndexProcessor.getVersionJson()
if(login) {
const authUser = ConfigManager.getSelectedAccount()
loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`)
let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion())
setLaunchDetails('Launching game..')
// const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/
const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`)
const onLoadComplete = () => {
toggleLaunchArea(false)
if(hasRPC){
DiscordWrapper.updateDetails('Loading game..')
}
} else if(m.context === 'progress'){
switch(m.data){
case 'assets': {
const perc = (m.value/m.total)*20
setLaunchPercentage(40+perc, 100, parseInt(40+perc))
break
}
case 'download':
setDownloadPercentage(m.value, m.total, m.percent)
break
case 'extract': {
// Show installing progress bar.
remote.getCurrentWindow().setProgressBar(2)
proc.stdout.on('data', gameStateChange)
proc.stdout.removeListener('data', tempListener)
proc.stderr.removeListener('data', gameErrorListener)
}
const start = Date.now()
// Download done, extracting.
const eLStr = 'Extracting libraries'
let dotStr = ''
setLaunchDetails(eLStr)
progressListener = setInterval(() => {
if(dotStr.length >= 3){
dotStr = ''
} else {
dotStr += '.'
}
setLaunchDetails(eLStr + dotStr)
}, 750)
break
// Attach a temporary listener to the client output.
// Will wait for a certain bit of text meaning that
// the client application has started, and we can hide
// the progress bar stuff.
const tempListener = function(data){
if(GAME_LAUNCH_REGEX.test(data.trim())){
const diff = Date.now()-start
if(diff < MIN_LINGER) {
setTimeout(onLoadComplete, MIN_LINGER-diff)
} else {
onLoadComplete()
}
}
} else if(m.context === 'complete'){
switch(m.data){
case 'download':
// Download and extraction complete, remove the loading from the OS progress bar.
remote.getCurrentWindow().setProgressBar(-1)
if(progressListener != null){
clearInterval(progressListener)
progressListener = null
}
}
setLaunchDetails('Preparing to launch..')
break
// Listener for Discord RPC.
const gameStateChange = function(data){
data = data.trim()
if(SERVER_JOINED_REGEX.test(data)){
DiscordWrapper.updateDetails('Exploring the Realm!')
} else if(GAME_JOINED_REGEX.test(data)){
DiscordWrapper.updateDetails('Sailing to Westeros!')
}
} else if(m.context === 'error'){
switch(m.data){
case 'download':
loggerLaunchSuite.error('Error while downloading:', m.error)
if(m.error.code === 'ENOENT'){
showLaunchFailure(
'Download Error',
'Could not connect to the file server. Ensure that you are connected to the internet and try again.'
)
} else {
showLaunchFailure(
'Download Error',
'Check the console (CTRL + Shift + i) for more details. Please try again.'
)
}
}
remote.getCurrentWindow().setProgressBar(-1)
// Disconnect from AssetExec
aEx.disconnect()
break
const gameErrorListener = function(data){
data = data.trim()
if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){
loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.')
showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href="https://github.com/dscalzi/HeliosLauncher/issues">submit an issue</a> and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.')
}
} else if(m.context === 'validateEverything'){
}
let allGood = true
try {
// Build Minecraft process.
proc = pb.build()
// If these properties are not defined it's likely an error.
if(m.result.forgeData == null || m.result.versionData == null){
loggerLaunchSuite.error('Error during validation:', m.result)
// Bind listeners to stdout.
proc.stdout.on('data', tempListener)
proc.stderr.on('data', gameErrorListener)
loggerLaunchSuite.error('Error during launch', m.result.error)
showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.')
setLaunchDetails('Done. Enjoy the server!')
allGood = false
// Init Discord Hook
if(distro.rawDistribution.discord != null && serv.rawServerdiscord != null){
DiscordWrapper.initRPC(distro.rawDistribution.discord, serv.rawServer.discord)
hasRPC = true
proc.on('close', (code, signal) => {
loggerLaunchSuite.info('Shutting down Discord Rich Presence..')
DiscordWrapper.shutdownRPC()
hasRPC = false
proc = null
})
}
forgeData = m.result.forgeData
versionData = m.result.versionData
} catch(err) {
if(login && allGood) {
const authUser = ConfigManager.getSelectedAccount()
loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`)
let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion())
setLaunchDetails('Launching game..')
// const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/
const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`)
const onLoadComplete = () => {
toggleLaunchArea(false)
if(hasRPC){
DiscordWrapper.updateDetails('Loading game..')
}
proc.stdout.on('data', gameStateChange)
proc.stdout.removeListener('data', tempListener)
proc.stderr.removeListener('data', gameErrorListener)
}
const start = Date.now()
// Attach a temporary listener to the client output.
// Will wait for a certain bit of text meaning that
// the client application has started, and we can hide
// the progress bar stuff.
const tempListener = function(data){
if(GAME_LAUNCH_REGEX.test(data.trim())){
const diff = Date.now()-start
if(diff < MIN_LINGER) {
setTimeout(onLoadComplete, MIN_LINGER-diff)
} else {
onLoadComplete()
}
}
}
// Listener for Discord RPC.
const gameStateChange = function(data){
data = data.trim()
if(SERVER_JOINED_REGEX.test(data)){
DiscordWrapper.updateDetails('Exploring the Realm!')
} else if(GAME_JOINED_REGEX.test(data)){
DiscordWrapper.updateDetails('Sailing to Westeros!')
}
}
const gameErrorListener = function(data){
data = data.trim()
if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){
loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.')
showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href="https://github.com/dscalzi/HeliosLauncher/issues">submit an issue</a> and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.')
}
}
try {
// Build Minecraft process.
proc = pb.build()
// Bind listeners to stdout.
proc.stdout.on('data', tempListener)
proc.stderr.on('data', gameErrorListener)
setLaunchDetails('Done. Enjoy the server!')
// Init Discord Hook
const distro = DistroManager.getDistribution()
if(distro.discord != null && serv.discord != null){
DiscordWrapper.initRPC(distro.discord, serv.discord)
hasRPC = true
proc.on('close', (code, signal) => {
loggerLaunchSuite.info('Shutting down Discord Rich Presence..')
DiscordWrapper.shutdownRPC()
hasRPC = false
proc = null
})
}
} catch(err) {
loggerLaunchSuite.error('Error during launch', err)
showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.')
}
}
// Disconnect from AssetExec
aEx.disconnect()
loggerLaunchSuite.error('Error during launch', err)
showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.')
}
})
}
// Begin Validations
// Validate Forge files.
setLaunchDetails('Loading server information..')
refreshDistributionIndex(true, (data) => {
onDistroRefresh(data)
serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]})
}, (err) => {
loggerLaunchSuite.info('Error while fetching a fresh copy of the distribution index.', err)
refreshDistributionIndex(false, (data) => {
onDistroRefresh(data)
serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]})
}, (err) => {
loggerLaunchSuite.error('Unable to refresh distribution index.', err)
if(DistroManager.getDistribution() == null){
showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.')
// Disconnect from AssetExec
aEx.disconnect()
} else {
serv = data.getServer(ConfigManager.getSelectedServer())
aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]})
}
})
})
}
/**
@ -1089,10 +980,13 @@ function displayArticle(articleObject, index){
* Load news information from the RSS feed specified in the
* distribution index.
*/
function loadNews(){
return new Promise((resolve, reject) => {
const distroData = DistroManager.getDistribution()
const newsFeed = distroData.getRSS()
async function loadNews(){
const distroData = await DistroAPI.getDistribution()
const promise = new Promise((resolve, reject) => {
const newsFeed = distroData.rawDistribution.rss
const newsHost = new URL(newsFeed).origin + '/'
$.ajax({
url: newsFeed,
@ -1147,4 +1041,6 @@ function loadNews(){
})
})
})
return await promise
}

View File

@ -193,10 +193,10 @@ loginButton.addEventListener('click', () => {
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
setTimeout(() => {
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, async () => {
// Temporary workaround
if(loginViewOnSuccess === VIEWS.settings){
prepareSettings()
await prepareSettings()
}
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
loginCancelEnabled(false) // Reset this for good measure.

View File

@ -117,8 +117,8 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
}
}
function toggleServerSelection(toggleState){
prepareServerSelectionList()
async function toggleServerSelection(toggleState){
await prepareServerSelectionList()
toggleOverlay(toggleState, true, 'serverSelectContent')
}
@ -171,11 +171,11 @@ function setDismissHandler(handler){
/* Server Select View */
document.getElementById('serverSelectConfirm').addEventListener('click', () => {
document.getElementById('serverSelectConfirm').addEventListener('click', async () => {
const listings = document.getElementsByClassName('serverListing')
for(let i=0; i<listings.length; i++){
if(listings[i].hasAttribute('selected')){
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
updateSelectedServer(serv)
refreshServerStatus(true)
toggleOverlay(false)
@ -184,13 +184,13 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
}
// None are selected? Not possible right? Meh, handle it.
if(listings.length > 0){
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
updateSelectedServer(serv)
toggleOverlay(false)
}
})
document.getElementById('accountSelectConfirm').addEventListener('click', () => {
document.getElementById('accountSelectConfirm').addEventListener('click', async () => {
const listings = document.getElementsByClassName('accountListing')
for(let i=0; i<listings.length; i++){
if(listings[i].hasAttribute('selected')){
@ -198,7 +198,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
ConfigManager.save()
updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) {
prepareSettings()
await prepareSettings()
}
toggleOverlay(false)
validateSelectedAccount()
@ -211,7 +211,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
ConfigManager.save()
updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) {
prepareSettings()
await prepareSettings()
}
toggleOverlay(false)
validateSelectedAccount()
@ -267,21 +267,21 @@ function setAccountListingHandlers(){
})
}
function populateServerListings(){
const distro = DistroManager.getDistribution()
async function populateServerListings(){
const distro = await DistroAPI.getDistribution()
const giaSel = ConfigManager.getSelectedServer()
const servers = distro.getServers()
const servers = distro.servers
let htmlString = ''
for(const serv of servers){
htmlString += `<button class="serverListing" servid="${serv.getID()}" ${serv.getID() === giaSel ? 'selected' : ''}>
<img class="serverListingImg" src="${serv.getIcon()}"/>
htmlString += `<button class="serverListing" servid="${serv.rawServer.id}" ${serv.rawServer.id === giaSel ? 'selected' : ''}>
<img class="serverListingImg" src="${serv.rawServer.icon}"/>
<div class="serverListingDetails">
<span class="serverListingName">${serv.getName()}</span>
<span class="serverListingDescription">${serv.getDescription()}</span>
<span class="serverListingName">${serv.rawServer.name}</span>
<span class="serverListingDescription">${serv.rawServer.description}</span>
<div class="serverListingInfo">
<div class="serverListingVersion">${serv.getMinecraftVersion()}</div>
<div class="serverListingRevision">${serv.getVersion()}</div>
${serv.isMainServer() ? `<div class="serverListingStarWrapper">
<div class="serverListingVersion">${serv.rawServer.minecraftVersion}</div>
<div class="serverListingRevision">${serv.rawServer.version}</div>
${serv.rawServer.mainServer ? `<div class="serverListingStarWrapper">
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
<defs>
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
@ -313,8 +313,8 @@ function populateAccountListings(){
}
function prepareServerSelectionList(){
populateServerListings()
async function prepareServerSelectionList(){
await populateServerListings()
setServerListingHandlers()
}

View File

@ -69,7 +69,7 @@ function bindFileSelectors(){
if(!res.canceled) {
ele.previousElementSibling.value = res.filePaths[0]
if(isJavaExecSel) {
populateJavaExecDetails(ele.previousElementSibling.value)
await populateJavaExecDetails(ele.previousElementSibling.value)
}
}
}
@ -123,9 +123,10 @@ function initSettingsValidators(){
/**
* Load configuration values onto the UI. This is an automated process.
*/
function initSettingsValues(){
async function initSettingsValues(){
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => {
for(const v of sEls) {
const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const gFn = ConfigManager['get' + cVal]
@ -139,7 +140,7 @@ function initSettingsValues(){
// Special Conditions
if(cVal === 'JavaExecutable'){
v.value = gFn.apply(null, gFnOpts)
populateJavaExecDetails(v.value)
await populateJavaExecDetails(v.value)
} else if (cVal === 'DataDirectory'){
v.value = gFn.apply(null, gFnOpts)
} else if(cVal === 'JVMOptions'){
@ -168,8 +169,8 @@ function initSettingsValues(){
}
}
}
}
})
}
/**
@ -416,8 +417,8 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
const authCode = queryMap.code
AuthManager.addMicrosoftAccount(authCode).then(value => {
updateSelectedAccount(value)
switchView(getCurrentView(), viewOnClose, 500, 500, () => {
prepareSettings()
switchView(getCurrentView(), viewOnClose, 500, 500, async () => {
await prepareSettings()
})
})
.catch((displayableError) => {
@ -713,13 +714,13 @@ const settingsModsContainer = document.getElementById('settingsModsContainer')
/**
* Resolve and update the mods on the UI.
*/
function resolveModsForUI(){
async function resolveModsForUI(){
const serv = ConfigManager.getSelectedServer()
const distro = DistroManager.getDistribution()
const distro = await DistroAPI.getDistribution()
const servConf = ConfigManager.getModConfiguration(serv)
const modStr = parseModulesForUI(distro.getServer(serv).getModules(), false, servConf.mods)
const modStr = parseModulesForUI(distro.getServerById(serv).modules, false, servConf.mods)
document.getElementById('settingsReqModsContent').innerHTML = modStr.reqMods
document.getElementById('settingsOptModsContent').innerHTML = modStr.optMods
@ -739,17 +740,17 @@ function parseModulesForUI(mdls, submodules, servConf){
for(const mdl of mdls){
if(mdl.getType() === DistroManager.Types.ForgeMod || mdl.getType() === DistroManager.Types.LiteMod || mdl.getType() === DistroManager.Types.LiteLoader){
if(mdl.rawModule.type === Type.ForgeMod || mdl.rawModule.type === Type.LiteMod || mdl.rawModule.type === Type.LiteLoader){
if(mdl.getRequired().isRequired()){
if(mdl.getRequired().value){
reqMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled>
reqMods += `<div id="${mdl.getVersionlessMavenIdentifier()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled>
<div class="settingsModContent">
<div class="settingsModMainWrapper">
<div class="settingsModStatus"></div>
<div class="settingsModDetails">
<span class="settingsModName">${mdl.getName()}</span>
<span class="settingsModVersion">v${mdl.getVersion()}</span>
<span class="settingsModName">${mdl.rawModule.name}</span>
<span class="settingsModVersion">v${mdl.mavenComponents.version}</span>
</div>
</div>
<label class="toggleSwitch" reqmod>
@ -757,32 +758,32 @@ function parseModulesForUI(mdls, submodules, servConf){
<span class="toggleSwitchSlider"></span>
</label>
</div>
${mdl.hasSubModules() ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.getSubModules(), true, servConf[mdl.getVersionlessID()])).join('')}
${mdl.subModules.length > 0 ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.subModules, true, servConf[mdl.getVersionlessMavenIdentifier()])).join('')}
</div>` : ''}
</div>`
} else {
const conf = servConf[mdl.getVersionlessID()]
const conf = servConf[mdl.getVersionlessMavenIdentifier()]
const val = typeof conf === 'object' ? conf.value : conf
optMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}>
optMods += `<div id="${mdl.getVersionlessMavenIdentifier()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}>
<div class="settingsModContent">
<div class="settingsModMainWrapper">
<div class="settingsModStatus"></div>
<div class="settingsModDetails">
<span class="settingsModName">${mdl.getName()}</span>
<span class="settingsModVersion">v${mdl.getVersion()}</span>
<span class="settingsModName">${mdl.rawModule.name}</span>
<span class="settingsModVersion">v${mdl.mavenComponents.version}</span>
</div>
</div>
<label class="toggleSwitch">
<input type="checkbox" formod="${mdl.getVersionlessID()}" ${val ? 'checked' : ''}>
<input type="checkbox" formod="${mdl.getVersionlessMavenIdentifier()}" ${val ? 'checked' : ''}>
<span class="toggleSwitchSlider"></span>
</label>
</div>
${mdl.hasSubModules() ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.getSubModules(), true, conf.mods)).join('')}
${mdl.subModules.length > 0 ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.subModules, true, conf.mods)).join('')}
</div>` : ''}
</div>`
@ -858,10 +859,10 @@ let CACHE_DROPIN_MODS
* Resolve any located drop-in mods for this server and
* populate the results onto the UI.
*/
function resolveDropinModsForUI(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID(), 'mods')
CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.getMinecraftVersion())
async function resolveDropinModsForUI(){
const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.rawServer.id, 'mods')
CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.rawServer.minecraftVersion)
let dropinMods = ''
@ -934,12 +935,12 @@ function bindDropinModFileSystemButton(){
fsBtn.removeAttribute('drag')
}
fsBtn.ondrop = e => {
fsBtn.ondrop = async e => {
fsBtn.removeAttribute('drag')
e.preventDefault()
DropinModUtil.addDropinMods(e.dataTransfer.files, CACHE_SETTINGS_MODS_DIR)
reloadDropinMods()
await reloadDropinMods()
}
}
@ -971,18 +972,18 @@ function saveDropinModConfiguration(){
// Refresh the drop-in mods when F5 is pressed.
// Only active on the mods tab.
document.addEventListener('keydown', (e) => {
document.addEventListener('keydown', async (e) => {
if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){
if(e.key === 'F5'){
reloadDropinMods()
await reloadDropinMods()
saveShaderpackSettings()
resolveShaderpacksForUI()
await resolveShaderpacksForUI()
}
}
})
function reloadDropinMods(){
resolveDropinModsForUI()
async function reloadDropinMods(){
await resolveDropinModsForUI()
bindDropinModsRemoveButton()
bindDropinModFileSystemButton()
bindModsToggleSwitch()
@ -997,9 +998,9 @@ let CACHE_SELECTED_SHADERPACK
/**
* Load shaderpack information.
*/
function resolveShaderpacksForUI(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID())
async function resolveShaderpacksForUI(){
const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.rawServer.id)
CACHE_SHADERPACKS = DropinModUtil.scanForShaderpacks(CACHE_SETTINGS_INSTANCE_DIR)
CACHE_SELECTED_SHADERPACK = DropinModUtil.getEnabledShaderpack(CACHE_SETTINGS_INSTANCE_DIR)
@ -1058,13 +1059,13 @@ function bindShaderpackButton() {
spBtn.removeAttribute('drag')
}
spBtn.ondrop = e => {
spBtn.ondrop = async e => {
spBtn.removeAttribute('drag')
e.preventDefault()
DropinModUtil.addShaderpacks(e.dataTransfer.files, CACHE_SETTINGS_INSTANCE_DIR)
saveShaderpackSettings()
resolveShaderpacksForUI()
await resolveShaderpacksForUI()
}
}
@ -1073,19 +1074,19 @@ function bindShaderpackButton() {
/**
* Load the currently selected server information onto the mods tab.
*/
function loadSelectedServerOnModsTab(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
async function loadSelectedServerOnModsTab(){
const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
for(const el of document.getElementsByClassName('settingsSelServContent')) {
el.innerHTML = `
<img class="serverListingImg" src="${serv.getIcon()}"/>
<img class="serverListingImg" src="${serv.rawServer.icon}"/>
<div class="serverListingDetails">
<span class="serverListingName">${serv.getName()}</span>
<span class="serverListingDescription">${serv.getDescription()}</span>
<span class="serverListingName">${serv.rawServer.name}</span>
<span class="serverListingDescription">${serv.rawServer.description}</span>
<div class="serverListingInfo">
<div class="serverListingVersion">${serv.getMinecraftVersion()}</div>
<div class="serverListingRevision">${serv.getVersion()}</div>
${serv.isMainServer() ? `<div class="serverListingStarWrapper">
<div class="serverListingVersion">${serv.rawServer.minecraftVersion}</div>
<div class="serverListingRevision">${serv.rawServer.version}</div>
${serv.rawServer.mainServer ? `<div class="serverListingStarWrapper">
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
<defs>
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
@ -1103,9 +1104,9 @@ function loadSelectedServerOnModsTab(){
// Bind functionality to the server switch button.
Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => {
el.addEventListener('click', (e) => {
el.addEventListener('click', async e => {
e.target.blur()
toggleServerSelection(true)
await toggleServerSelection(true)
})
})
@ -1123,8 +1124,8 @@ function saveAllModConfigurations(){
* server is changed.
*/
function animateSettingsTabRefresh(){
$(`#${selectedSettingsTab}`).fadeOut(500, () => {
prepareSettings()
$(`#${selectedSettingsTab}`).fadeOut(500, async () => {
await prepareSettings()
$(`#${selectedSettingsTab}`).fadeIn(500)
})
}
@ -1132,15 +1133,15 @@ function animateSettingsTabRefresh(){
/**
* Prepare the Mods tab for display.
*/
function prepareModsTab(first){
resolveModsForUI()
resolveDropinModsForUI()
resolveShaderpacksForUI()
async function prepareModsTab(first){
await resolveModsForUI()
await resolveDropinModsForUI()
await resolveShaderpacksForUI()
bindDropinModsRemoveButton()
bindDropinModFileSystemButton()
bindShaderpackButton()
bindModsToggleSwitch()
loadSelectedServerOnModsTab()
await loadSelectedServerOnModsTab()
}
/**
@ -1348,8 +1349,8 @@ function populateMemoryStatus(){
*
* @param {string} execPath The executable path to populate against.
*/
function populateJavaExecDetails(execPath){
const jg = new JavaGuard(DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion())
async function populateJavaExecDetails(execPath){
const jg = new JavaGuard((await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()).rawServer.minecraftVersion)
jg._validateJavaBinary(execPath).then(v => {
if(v.valid){
const vendor = v.vendor != null ? ` (${v.vendor})` : ''
@ -1364,18 +1365,18 @@ function populateJavaExecDetails(execPath){
})
}
function populateJavaReqDesc() {
const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()
if(Util.mcVersionAtLeast('1.17', mcVer)) {
async function populateJavaReqDesc() {
const mcVer = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()).rawServer.minecraftVersion
if(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)) {
async function populateJvmOptsLink() {
const mcVer = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()).rawServer.minecraftVersion
if(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 {
@ -1387,11 +1388,11 @@ function populateJvmOptsLink() {
/**
* Prepare the Java tab for display.
*/
function prepareJavaTab(){
async function prepareJavaTab(){
bindRangeSlider()
populateMemoryStatus()
populateJavaReqDesc()
populateJvmOptsLink()
await populateJavaReqDesc()
await populateJvmOptsLink()
}
/**
@ -1567,17 +1568,17 @@ function prepareUpdateTab(data = null){
*
* @param {boolean} first Whether or not it is the first load.
*/
function prepareSettings(first = false) {
async function prepareSettings(first = false) {
if(first){
setupSettingsTabs()
initSettingsValidators()
prepareUpdateTab()
} else {
prepareModsTab()
await prepareModsTab()
}
initSettingsValues()
await initSettingsValues()
prepareAccountsTab()
prepareJavaTab()
await prepareJavaTab()
prepareAboutTab()
}

View File

@ -4,10 +4,11 @@
*/
// Requirements
const path = require('path')
const { Type } = require('helios-distribution-types')
const AuthManager = require('./assets/js/authmanager')
const ConfigManager = require('./assets/js/configmanager')
const DistroManager = require('./assets/js/distromanager')
const { DistroAPI } = require('./assets/js/distromanager')
const Lang = require('./assets/js/langloader')
let rscShouldLoad = false
@ -40,10 +41,10 @@ let currentView
*/
function switchView(current, next, currentFadeTime = 500, nextFadeTime = 500, onCurrentFade = () => {}, onNextFade = () => {}){
currentView = next
$(`${current}`).fadeOut(currentFadeTime, () => {
onCurrentFade()
$(`${next}`).fadeIn(nextFadeTime, () => {
onNextFade()
$(`${current}`).fadeOut(currentFadeTime, async () => {
await onCurrentFade()
$(`${next}`).fadeIn(nextFadeTime, async () => {
await onNextFade()
})
})
}
@ -57,15 +58,15 @@ function getCurrentView(){
return currentView
}
function showMainUI(data){
async function showMainUI(data){
if(!isDev){
loggerAutoUpdater.info('Initializing..')
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
}
prepareSettings(true)
updateSelectedServer(data.getServer(ConfigManager.getSelectedServer()))
await prepareSettings(true)
updateSelectedServer(data.getServerById(ConfigManager.getSelectedServer()))
refreshServerStatus()
setTimeout(() => {
document.getElementById('frameBar').style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
@ -133,7 +134,7 @@ function showFatalStartupError(){
* @param {Object} data The distro index object.
*/
function onDistroRefresh(data){
updateSelectedServer(data.getServer(ConfigManager.getSelectedServer()))
updateSelectedServer(data.getServerById(ConfigManager.getSelectedServer()))
refreshServerStatus()
initNews()
syncModConfigurations(data)
@ -149,10 +150,10 @@ function syncModConfigurations(data){
const syncedCfgs = []
for(let serv of data.getServers()){
for(let serv of data.servers){
const id = serv.getID()
const mdls = serv.getModules()
const id = serv.rawServer.id
const mdls = serv.modules
const cfg = ConfigManager.getModConfiguration(id)
if(cfg != null){
@ -161,20 +162,20 @@ function syncModConfigurations(data){
const mods = {}
for(let mdl of mdls){
const type = mdl.getType()
const type = mdl.rawModule.type
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
if(!mdl.getRequired().isRequired()){
const mdlID = mdl.getVersionlessID()
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
if(!mdl.getRequired().value){
const mdlID = mdl.getVersionlessMavenIdentifier()
if(modsOld[mdlID] == null){
mods[mdlID] = scanOptionalSubModules(mdl.getSubModules(), mdl)
mods[mdlID] = scanOptionalSubModules(mdl.subModules, mdl)
} else {
mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.getSubModules(), mdl), false)
mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.subModules, mdl), false)
}
} else {
if(mdl.hasSubModules()){
const mdlID = mdl.getVersionlessID()
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
if(mdl.subModules.length > 0){
const mdlID = mdl.getVersionlessMavenIdentifier()
const v = scanOptionalSubModules(mdl.subModules, mdl)
if(typeof v === 'object'){
if(modsOld[mdlID] == null){
mods[mdlID] = v
@ -197,15 +198,15 @@ function syncModConfigurations(data){
const mods = {}
for(let mdl of mdls){
const type = mdl.getType()
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
if(!mdl.getRequired().isRequired()){
mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl)
const type = mdl.rawModule.type
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
} else {
if(mdl.hasSubModules()){
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
if(mdl.subModules.length > 0){
const v = scanOptionalSubModules(mdl.subModules, mdl)
if(typeof v === 'object'){
mods[mdl.getVersionlessID()] = v
mods[mdl.getVersionlessMavenIdentifier()] = v
}
}
}
@ -232,8 +233,8 @@ function syncModConfigurations(data){
function ensureJavaSettings(data) {
// Nothing too fancy for now.
for(const serv of data.getServers()){
ConfigManager.ensureJavaConfig(serv.getID(), serv.getMinecraftVersion())
for(const serv of data.servers){
ConfigManager.ensureJavaConfig(serv.rawServer.id, serv.rawServer.minecraftVersion)
}
ConfigManager.save()
@ -251,17 +252,17 @@ function scanOptionalSubModules(mdls, origin){
const mods = {}
for(let mdl of mdls){
const type = mdl.getType()
const type = mdl.rawModule.type
// Optional types.
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
// It is optional.
if(!mdl.getRequired().isRequired()){
mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl)
if(!mdl.getRequired().value){
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
} else {
if(mdl.hasSubModules()){
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
const v = scanOptionalSubModules(mdl.subModules, mdl)
if(typeof v === 'object'){
mods[mdl.getVersionlessID()] = v
mods[mdl.getVersionlessMavenIdentifier()] = v
}
}
}
@ -272,13 +273,13 @@ function scanOptionalSubModules(mdls, origin){
const ret = {
mods
}
if(!origin.getRequired().isRequired()){
ret.value = origin.getRequired().isDefault()
if(!origin.getRequired().value){
ret.value = origin.getRequired().def
}
return ret
}
}
return origin.getRequired().isDefault()
return origin.getRequired().def
}
/**
@ -323,18 +324,6 @@ function mergeModConfiguration(o, n, nReq = false){
return n
}
function refreshDistributionIndex(remote, onSuccess, onError){
if(remote){
DistroManager.pullRemote()
.then(onSuccess)
.catch(onError)
} else {
DistroManager.pullLocal()
.then(onSuccess)
.catch(onError)
}
}
async function validateSelectedAccount(){
const selectedAcc = ConfigManager.getSelectedAccount()
if(selectedAcc != null){
@ -429,14 +418,14 @@ function setSelectedAccount(uuid){
}
// Synchronous Listener
document.addEventListener('readystatechange', function(){
document.addEventListener('readystatechange', async () => {
if (document.readyState === 'interactive' || document.readyState === 'complete'){
if(rscShouldLoad){
rscShouldLoad = false
if(!fatalStartupError){
const data = DistroManager.getDistribution()
showMainUI(data)
const data = await DistroAPI.getDistribution()
await showMainUI(data)
} else {
showFatalStartupError()
}
@ -446,13 +435,13 @@ document.addEventListener('readystatechange', function(){
}, false)
// Actions that must be performed after the distribution index is downloaded.
ipcRenderer.on('distributionIndexDone', (event, res) => {
ipcRenderer.on('distributionIndexDone', async (event, res) => {
if(res) {
const data = DistroManager.getDistribution()
const data = await DistroAPI.getDistribution()
syncModConfigurations(data)
ensureJavaSettings(data)
if(document.readyState === 'interactive' || document.readyState === 'complete'){
showMainUI(data)
await showMainUI(data)
} else {
rscShouldLoad = true
}
@ -467,11 +456,10 @@ 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)
})
async function devModeToggle() {
DistroAPI.toggleDevMode(true)
const data = await DistroAPI.getDistributionLocalLoadOnly()
ensureJavaSettings(data)
updateSelectedServer(data.getServers()[0])
syncModConfigurations(data)
}

226
package-lock.json generated
View File

@ -19,7 +19,7 @@
"fs-extra": "^11.1.0",
"github-syntax-dark": "^0.5.0",
"got": "^11.8.5",
"helios-core": "~0.1.2",
"helios-distribution-types": "^1.1.0",
"jquery": "^3.6.1",
"node-disk-info": "^1.3.0",
"node-stream-zip": "^1.15.0",
@ -37,24 +37,6 @@
"node": "18.x.x"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"dependencies": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@develar/schema-utils": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@ -439,11 +421,6 @@
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
},
"node_modules/@types/triple-beam": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz",
"integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g=="
},
"node_modules/@types/verror": {
"version": "1.10.6",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz",
@ -1037,15 +1014,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1062,28 +1030,6 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/colors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
@ -1093,15 +1039,6 @@
"node": ">=0.1.90"
}
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"dependencies": {
"color": "^3.1.3",
"text-hex": "1.0.x"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -1596,11 +1533,6 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -1873,11 +1805,6 @@
"pend": "~1.2.0"
}
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -1952,11 +1879,6 @@
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"dev": true
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@ -2268,30 +2190,10 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/helios-core": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/helios-core/-/helios-core-0.1.2.tgz",
"integrity": "sha512-xM3+nZcymy9iS36Z5odmuFZAsrSuyviQUh+dffXc+utXOP/Ox7m2HUo76t86cN8R1tLjJ/OzQJLzILACVaRryg==",
"dependencies": {
"fs-extra": "^10.1.0",
"got": "^11.8.5",
"luxon": "^3.1.0",
"triple-beam": "^1.3.0",
"winston": "^3.8.2"
}
},
"node_modules/helios-core/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
"node_modules/helios-distribution-types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/helios-distribution-types/-/helios-distribution-types-1.1.0.tgz",
"integrity": "sha512-mxZPvPIBTltWUGzMzj/8LuG/UPwIEf7ZCE9h9o7pj1QCsMsctQIDu8bv91HDUEH0MByghe+t5Pi5XCHTHhzayg=="
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
@ -2459,11 +2361,6 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/is-ci": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
@ -2515,17 +2412,6 @@
"node": ">=8"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -2686,11 +2572,6 @@
"json-buffer": "3.0.1"
}
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"node_modules/lazy-val": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
@ -2746,19 +2627,6 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/logform": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz",
"integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==",
"dependencies": {
"@colors/colors": "1.5.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
}
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@ -2778,14 +2646,6 @@
"node": ">=10"
}
},
"node_modules/luxon": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz",
"integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==",
"engines": {
"node": ">=12"
}
},
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@ -3003,14 +2863,6 @@
"wrappy": "1"
}
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -3418,14 +3270,6 @@
}
]
},
"node_modules/safe-stable-stringify": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz",
"integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -3513,14 +3357,6 @@
"node": ">=8"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-update-notifier": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
@ -3617,14 +3453,6 @@
"node": ">=0.10.0"
}
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"engines": {
"node": "*"
}
},
"node_modules/stat-mode": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
@ -3774,11 +3602,6 @@
"node": ">=12"
}
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -3823,11 +3646,6 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/triple-beam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@ -3976,40 +3794,6 @@
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
"integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA=="
},
"node_modules/winston": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz",
"integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==",
"dependencies": {
"@colors/colors": "1.5.0",
"@dabh/diagnostics": "^2.0.2",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.4.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz",
"integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==",
"dependencies": {
"logform": "^2.3.2",
"readable-stream": "^3.6.0",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 6.4.0"
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View File

@ -33,7 +33,8 @@
"fs-extra": "^11.1.0",
"github-syntax-dark": "^0.5.0",
"got": "^11.8.5",
"helios-core": "~0.1.2",
"helios-core": "~0.2.0-pre.1",
"helios-distribution-types": "^1.1.0",
"jquery": "^3.6.1",
"node-disk-info": "^1.3.0",
"node-stream-zip": "^1.15.0",