mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-22 19:52:14 -08:00
Compare commits
6 Commits
e314599d99
...
15b2efefde
Author | SHA1 | Date | |
---|---|---|---|
|
15b2efefde | ||
|
4273be5a4c | ||
|
8d26db70dc | ||
|
13f86339bb | ||
|
d9291d82e5 | ||
|
b9b67b7824 |
@ -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,8 +13,6 @@ 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')
|
||||
|
||||
// Classes
|
||||
@ -40,86 +37,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 +79,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]
|
||||
@ -1002,26 +891,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 +912,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 +954,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 +1040,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 +1199,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
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
@ -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){
|
||||
|
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
226
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user