Large update to AssetGuard to make the module purely object based. This fixed several issues which were present with the static implementation and seems to have increased performance. Several other bugs related to the front-end scripts have been fixed.

This commit is contained in:
Daniel Scalzi 2017-11-30 03:00:06 -05:00
parent 4c2c46f535
commit 52ab270ce3
4 changed files with 703 additions and 691 deletions

View File

@ -1,10 +1,10 @@
const mojang = require('mojang')
const path = require('path')
const AssetGuard = require(path.join(__dirname, 'assets', 'js', 'assetguard.js'))
const {AssetGuard} = require(path.join(__dirname, 'assets', 'js', 'assetguard.js'))
const ProcessBuilder = require(path.join(__dirname, 'assets', 'js', 'processbuilder.js'))
const {GAME_DIRECTORY, DEFAULT_CONFIG} = require(path.join(__dirname, 'assets', 'js', 'constants.js'))
document.onreadystatechange = function(){
document.addEventListener('readystatechange', function(){
if (document.readyState === 'interactive'){
// Bind launch button
@ -14,7 +14,10 @@ document.onreadystatechange = function(){
})
}
}
}, false)
// Keep reference to AssetGuard object temporarily
let tracker;
testdownloads = async function(){
const content = document.getElementById("launch_content")
@ -28,45 +31,48 @@ testdownloads = async function(){
details.style.display = 'flex'
content.style.display = 'none'
tracker = new AssetGuard()
det_text.innerHTML = 'Loading version information..'
const versionData = await AssetGuard.loadVersionData('1.11.2', GAME_DIRECTORY)
const versionData = await tracker.loadVersionData('1.11.2', GAME_DIRECTORY)
progress.setAttribute('value', 20)
progress_text.innerHTML = '20%'
det_text.innerHTML = 'Validating asset integrity..'
await AssetGuard.validateAssets(versionData, GAME_DIRECTORY)
await tracker.validateAssets(versionData, GAME_DIRECTORY)
progress.setAttribute('value', 40)
progress_text.innerHTML = '40%'
console.log('assets done')
det_text.innerHTML = 'Validating library integrity..'
await AssetGuard.validateLibraries(versionData, GAME_DIRECTORY)
await tracker.validateLibraries(versionData, GAME_DIRECTORY)
progress.setAttribute('value', 60)
progress_text.innerHTML = '60%'
console.log('libs done')
det_text.innerHTML = 'Validating miscellaneous file integrity..'
await AssetGuard.validateMiscellaneous(versionData, GAME_DIRECTORY)
await tracker.validateMiscellaneous(versionData, GAME_DIRECTORY)
progress.setAttribute('value', 80)
progress_text.innerHTML = '80%'
console.log('files done')
det_text.innerHTML = 'Validating server distribution files..'
const serv = await AssetGuard.validateDistribution('WesterosCraft-1.11.2', GAME_DIRECTORY)
const serv = await tracker.validateDistribution('WesterosCraft-1.11.2', GAME_DIRECTORY)
progress.setAttribute('value', 100)
progress_text.innerHTML = '100%'
console.log('forge stuff done')
det_text.innerHTML = 'Downloading files..'
AssetGuard.instance.on('totaldlprogress', function(data){
tracker.on('totaldlprogress', function(data){
progress.setAttribute('max', data.total)
progress.setAttribute('value', data.acc)
progress_text.innerHTML = parseInt((data.acc/data.total)*100) + '%'
})
AssetGuard.instance.on('dlcomplete', async function(){
tracker.on('dlcomplete', async function(){
det_text.innerHTML = 'Preparing to launch..'
const forgeData = await AssetGuard.loadForgeData('WesterosCraft-1.11.2', GAME_DIRECTORY)
const forgeData = await tracker.loadForgeData('WesterosCraft-1.11.2', GAME_DIRECTORY)
const authUser = await mojang.auth('EMAIL', 'PASS', DEFAULT_CONFIG.getClientToken(), {
name: 'Minecraft',
version: 1
@ -94,6 +100,8 @@ testdownloads = async function(){
content.style.display = 'inline-flex'
}, 5000)
}
// Remove reference to tracker.
tracker = null
})
AssetGuard.processDlQueues()
tracker.processDlQueues()
}

View File

@ -2,21 +2,21 @@
* AssetGuard
*
* This module aims to provide a comprehensive and stable method for processing
* and downloading game assets for the WesterosCraft server. A central object
* stores download meta for several identifiers (categories). This meta data
* is initially empty until one of the module's processing functions are called.
* That function will process the corresponding asset index and validate any exisitng
* local files. If a file is missing or fails validation, it will be placed into an
* array which acts as a queue. This queue is wrapped in a download tracker object
* and downloading game assets for the WesterosCraft server. Download meta is
* for several identifiers (categories) is stored inside of an AssetGuard object.
* This meta data is initially empty until one of the module's processing functions
* are called. That function will process the corresponding asset index and validate
* any exisitng local files. If a file is missing or fails validation, it will be
* placed into a download queue (array). This queue is wrapped in a download tracker object
* so that essential information can be cached. The download tracker object is then
* assigned as the value of the identifier in the central object. These download
* assigned as the value of the identifier in the AssetGuard object. These download
* trackers will remain idle until an async process is started to process them.
*
* Once the async process is started, any enqueued assets will be downloaded. The central
* Once the async process is started, any enqueued assets will be downloaded. The AssetGuard
* object will emit events throughout the download whose name correspond to the identifier
* being processed. For example, if the 'assets' identifier was being processed, whenever
* the download stream recieves data, the event 'assetsdlprogress' will be emitted off of
* the central object instance. This can be listened to by external modules allowing for
* the AssetGuard instance. This can be listened to by external modules allowing for
* categorical tracking of the downloading process.
*
* @module assetguard
@ -136,6 +136,7 @@ class DistroModule extends Asset {
* about a download queue, including the queue itself.
*/
class DLTracker {
/**
* Create a DLTracker
*
@ -148,6 +149,7 @@ class DLTracker {
this.dlsize = dlsize
this.callback = callback
}
}
/**
@ -158,7 +160,8 @@ class DLTracker {
* to emit events so that external modules can listen into processing done in
* this module.
*/
class AssetGuard extends EventEmitter{
class AssetGuard extends EventEmitter {
/**
* AssetGuard class should only ever have one instance which is defined in
* this module. On creation the object's properties are never-null default
@ -173,16 +176,10 @@ class AssetGuard extends EventEmitter{
this.files = new DLTracker([], 0)
this.forge = new DLTracker([], 0)
}
}
/**
* Global static final instance of AssetGuard
*/
const instance = new AssetGuard()
// Static Utility Functions
// Utility Functions
/**
/**
* Resolve an artifact id into a path. For example, on windows
* 'net.minecraftforge:forge:1.11.2-13.20.0.2282', '.jar' becomes
* net\minecraftforge\forge\1.11.2-13.20.0.2282\forge-1.11.2-13.20.0.2282.jar
@ -191,7 +188,7 @@ const instance = new AssetGuard()
* @param {String} extension - the extension of the file at the resolved path.
* @returns {String} - the resolved relative path from the artifact id.
*/
function _resolvePath(artifactid, extension){
static _resolvePath(artifactid, extension){
let ps = artifactid.split(':')
let cs = ps[0].split('.')
@ -200,9 +197,9 @@ function _resolvePath(artifactid, extension){
cs.push(ps[1].concat('-').concat(ps[2]).concat(extension))
return path.join.apply(path, cs)
}
}
/**
/**
* Resolve an artifact id into a URL. For example,
* 'net.minecraftforge:forge:1.11.2-13.20.0.2282', '.jar' becomes
* net/minecraftforge/forge/1.11.2-13.20.0.2282/forge-1.11.2-13.20.0.2282.jar
@ -211,7 +208,7 @@ function _resolvePath(artifactid, extension){
* @param {String} extension - the extension of the file at the resolved url.
* @returns {String} - the resolved relative URL from the artifact id.
*/
function _resolveURL(artifactid, extension){
static _resolveURL(artifactid, extension){
let ps = artifactid.split(':')
let cs = ps[0].split('.')
@ -220,27 +217,27 @@ function _resolveURL(artifactid, extension){
cs.push(ps[1].concat('-').concat(ps[2]).concat(extension))
return cs.join('/')
}
}
/**
/**
* Calculates the hash for a file using the specified algorithm.
*
* @param {Buffer} buf - the buffer containing file data.
* @param {String} algo - the hash algorithm.
* @returns {String} - the calculated hash in hex.
*/
function _calculateHash(buf, algo){
static _calculateHash(buf, algo){
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.
*/
function _parseChecksumsFile(content){
static _parseChecksumsFile(content){
let finalContent = {}
let lines = content.split('\n')
for(let i=0; i<lines.length; i++){
@ -251,9 +248,9 @@ function _parseChecksumsFile(content){
finalContent[bits[1]] = bits[0]
}
return finalContent
}
}
/**
/**
* Validate that a file exists and matches a given hash value.
*
* @param {String} filePath - the path of the file to validate.
@ -261,7 +258,7 @@ function _parseChecksumsFile(content){
* @param {String} hash - the existing hash to check against.
* @returns {Boolean} - true if the file exists and calculated hash matches the given hash, otherwise false.
*/
function _validateLocal(filePath, algo, hash){
static _validateLocal(filePath, algo, hash){
if(fs.existsSync(filePath)){
//No hash provided, have to assume it's good.
if(hash == null){
@ -269,36 +266,36 @@ function _validateLocal(filePath, algo, hash){
}
let fileName = path.basename(filePath)
let buf = fs.readFileSync(filePath)
let calcdhash = _calculateHash(buf, algo)
let calcdhash = AssetGuard._calculateHash(buf, algo)
return calcdhash === hash
}
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.
*/
function _validateForgeChecksum(filePath, checksums){
static _validateForgeChecksum(filePath, checksums){
if(fs.existsSync(filePath)){
if(checksums == null || checksums.length === 0){
return true
}
let buf = fs.readFileSync(filePath)
let calcdhash = _calculateHash(buf, 'sha1')
let calcdhash = AssetGuard._calculateHash(buf, 'sha1')
let valid = checksums.includes(calcdhash)
if(!valid && filePath.endsWith('.jar')){
valid = _validateForgeJar(filePath, checksums)
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.
@ -307,7 +304,7 @@ function _validateForgeChecksum(filePath, checksums){
* @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.
*/
function _validateForgeJar(buf, checksums){
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.
@ -321,9 +318,9 @@ function _validateForgeJar(buf, checksums){
for(let i=0; i<zipEntries.length; i++){
let entry = zipEntries[i]
if(entry.entryName === 'checksums.sha1'){
expected = _parseChecksumsFile(zip.readAsText(entry))
expected = AssetGuard._parseChecksumsFile(zip.readAsText(entry))
}
hashes[entry.entryName] = _calculateHash(entry.getData(), 'sha1')
hashes[entry.entryName] = AssetGuard._calculateHash(entry.getData(), 'sha1')
}
if(!checksums.includes(hashes['checksums.sha1'])){
@ -338,15 +335,15 @@ function _validateForgeJar(buf, checksums){
}
}
return true
}
}
/**
/**
* Extracts and unpacks a file from .pack.xz format.
*
* @param {Array.<String>} filePaths - The paths of the files to be extracted and unpacked.
* @returns {Promise.<Void>} - An empty promise to indicate the extraction has completed.
*/
function _extractPackXZ(filePaths){
static _extractPackXZ(filePaths){
return new Promise(function(fulfill, reject){
const libPath = path.join(__dirname, '..', 'libraries', 'java', 'PackXZExtract.jar')
const filePath = filePaths.join(',')
@ -362,9 +359,9 @@ function _extractPackXZ(filePaths){
fulfill()
})
})
}
}
/**
/**
* 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
@ -374,7 +371,7 @@ function _extractPackXZ(filePaths){
* @param {String} basePath
* @returns {Promise.<Object>} - A promise which resolves to the contents of forge's version.json.
*/
function _finalizeForgeAsset(asset, basePath){
static _finalizeForgeAsset(asset, basePath){
return new Promise(function(fulfill, reject){
fs.readFile(asset.to, (err, data) => {
const zip = new AdmZip(data)
@ -400,19 +397,20 @@ function _finalizeForgeAsset(asset, basePath){
reject('Unable to finalize Forge processing, version.json not found! Has forge changed their format?')
})
})
}
}
/**
/**
* Initiate an async download process for an AssetGuard DLTracker.
*
* @param {String} identifier - the identifier of the AssetGuard DLTracker.
* @param {Number} limit - optional. The number of async processes to run in parallel.
* @returns {Boolean} - true if the process began, otherwise false.
*/
function startAsyncProcess(identifier, limit = 5){
startAsyncProcess(identifier, limit = 5){
const self = this
let win = remote.getCurrentWindow()
let acc = 0
const concurrentDlTracker = instance[identifier]
const concurrentDlTracker = this[identifier]
const concurrentDlQueue = concurrentDlTracker.dlqueue.slice(0)
if(concurrentDlQueue.length === 0){
return false
@ -437,44 +435,44 @@ function startAsyncProcess(identifier, limit = 5){
} else {
req.abort()
console.log('Failed to download ' + asset.from + '. Response code', resp.statusCode)
instance.progress += asset.size*1
win.setProgressBar(instance.progress/instance.totaldlsize)
instance.emit('totaldlprogress', {acc: instance.progress, total: instance.totaldlsize})
self.progress += asset.size*1
win.setProgressBar(self.progress/self.totaldlsize)
self.emit('totaldlprogress', {acc: self.progress, total: self.totaldlsize})
cb()
}
})
req.on('data', function(chunk){
count += chunk.length
instance.progress += chunk.length
self.progress += chunk.length
acc += chunk.length
instance.emit(identifier + 'dlprogress', acc)
//console.log(identifier + ' Progress', acc/instance[identifier].dlsize)
win.setProgressBar(instance.progress/instance.totaldlsize)
instance.emit('totaldlprogress', {acc: instance.progress, total: instance.totaldlsize})
self.emit(identifier + 'dlprogress', acc)
//console.log(identifier + ' Progress', acc/this[identifier].dlsize)
win.setProgressBar(self.progress/self.totaldlsize)
self.emit('totaldlprogress', {acc: self.progress, total: self.totaldlsize})
})
}, function(err){
if(err){
instance.emit(identifier + 'dlerror')
self.emit(identifier + 'dlerror')
console.log('An item in ' + identifier + ' failed to process');
} else {
instance.emit(identifier + 'dlcomplete')
self.emit(identifier + 'dlcomplete')
console.log('All ' + identifier + ' have been processed successfully')
}
instance.totaldlsize -= instance[identifier].dlsize
instance.progress -= instance[identifier].dlsize
instance[identifier] = new DLTracker([], 0)
if(instance.totaldlsize === 0) {
self.totaldlsize -= self[identifier].dlsize
self.progress -= self[identifier].dlsize
self[identifier] = new DLTracker([], 0)
if(self.totaldlsize === 0) {
win.setProgressBar(-1)
instance.emit('dlcomplete')
self.emit('dlcomplete')
}
})
return true
}
}
}
// Validation Functions
// Validation Functions
/**
/**
* Loads the version data for a given minecraft version.
*
* @param {String} version - the game version for which to load the index data.
@ -482,7 +480,7 @@ function startAsyncProcess(identifier, limit = 5){
* @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.
*/
function loadVersionData(version, basePath, force = false){
loadVersionData(version, basePath, force = false){
return new Promise(function(fulfill, reject){
const name = version + '.json'
const url = 'https://s3.amazonaws.com/Minecraft.Download/versions/' + version + '/' + name
@ -502,9 +500,9 @@ function loadVersionData(version, basePath, force = false){
fulfill(JSON.parse(fs.readFileSync(versionFile)))
}
})
}
}
/**
/**
* 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.
@ -515,16 +513,17 @@ function loadVersionData(version, basePath, force = false){
* @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.
*/
function validateAssets(versionData, basePath, force = false){
validateAssets(versionData, basePath, force = false){
const self = this
return new Promise(function(fulfill, reject){
_assetChainIndexData(versionData, basePath, force).then(() => {
self._assetChainIndexData(versionData, basePath, force).then(() => {
fulfill()
})
})
}
}
//Chain the asset tasks to provide full async. The below functions are private.
/**
//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
@ -532,7 +531,8 @@ function validateAssets(versionData, basePath, force = false){
* @param {Boolean} force
* @returns {Promise.<Void>} - An empty promise to indicate the async processing has completed.
*/
function _assetChainIndexData(versionData, basePath, force = false){
_assetChainIndexData(versionData, basePath, force = false){
const self = this
return new Promise(function(fulfill, reject){
//Asset index constants.
const assetIndex = versionData.assetIndex
@ -547,20 +547,20 @@ function _assetChainIndexData(versionData, basePath, force = false){
const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc))
stream.on('finish', function() {
data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
_assetChainValidateAssets(versionData, basePath, data).then(() => {
self._assetChainValidateAssets(versionData, basePath, data).then(() => {
fulfill()
})
})
} else {
data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
_assetChainValidateAssets(versionData, basePath, data).then(() => {
self._assetChainValidateAssets(versionData, basePath, data).then(() => {
fulfill()
})
}
})
}
}
/**
/**
* Private function used to chain the asset validation process. This function processes
* the assets and enqueues missing or invalid files.
* @param {Object} versionData
@ -568,7 +568,8 @@ function _assetChainIndexData(versionData, basePath, force = false){
* @param {Boolean} force
* @returns {Promise.<Void>} - An empty promise to indicate the async processing has completed.
*/
function _assetChainValidateAssets(versionData, basePath, indexData){
_assetChainValidateAssets(versionData, basePath, indexData){
const self = this
return new Promise(function(fulfill, reject){
//Asset constants
@ -585,19 +586,19 @@ function _assetChainValidateAssets(versionData, basePath, indexData){
const assetName = path.join(hash.substring(0, 2), hash)
const urlName = hash.substring(0, 2) + "/" + hash
const ast = new Asset(key, hash, String(value.size), resourceURL + urlName, path.join(objectPath, assetName))
if(!_validateLocal(ast.to, 'sha1', ast.hash)){
if(!AssetGuard._validateLocal(ast.to, 'sha1', ast.hash)){
dlSize += (ast.size*1)
assetDlQueue.push(ast)
}
cb()
}, function(err){
instance.assets = new DLTracker(assetDlQueue, dlSize)
self.assets = new DLTracker(assetDlQueue, dlSize)
fulfill()
})
})
}
}
/**
/**
* 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
@ -607,7 +608,8 @@ function _assetChainValidateAssets(versionData, basePath, indexData){
* @param {String} basePath - the absolute file path which will be prepended to the given relative paths.
* @returns {Promise.<Void>} - An empty promise to indicate the async processing has completed.
*/
function validateLibraries(versionData, basePath){
validateLibraries(versionData, basePath){
const self = this
return new Promise(function(fulfill, reject){
const libArr = versionData.libraries
@ -621,20 +623,20 @@ function validateLibraries(versionData, basePath){
if(Library.validateRules(lib.rules)){
let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()]]
const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
if(!_validateLocal(libItm.to, 'sha1', libItm.hash)){
if(!AssetGuard._validateLocal(libItm.to, 'sha1', libItm.hash)){
dlSize += (libItm.size*1)
libDlQueue.push(libItm)
}
}
cb()
}, function(err){
instance.libraries = new DLTracker(libDlQueue, dlSize)
self.libraries = new DLTracker(libDlQueue, dlSize)
fulfill()
})
})
}
}
/**
/**
* Public miscellaneous mojang file validation function. These files will be enqueued under
* the 'files' identifier.
*
@ -642,15 +644,16 @@ function validateLibraries(versionData, basePath){
* @param {String} basePath - the absolute file path which will be prepended to the given relative paths.
* @returns {Promise.<Void>} - An empty promise to indicate the async processing has completed.
*/
function validateMiscellaneous(versionData, basePath){
validateMiscellaneous(versionData, basePath){
const self = this
return new Promise(async function(fulfill, reject){
await validateClient(versionData, basePath)
await validateLogConfig(versionData, basePath)
await self.validateClient(versionData, basePath)
await self.validateLogConfig(versionData, basePath)
fulfill()
})
}
}
/**
/**
* Validate client file - artifact renamed from client.jar to '{version}'.jar.
*
* @param {Object} versionData - the version data for the assets.
@ -658,7 +661,8 @@ function validateMiscellaneous(versionData, basePath){
* @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.
*/
function validateClient(versionData, basePath, force = false){
validateClient(versionData, basePath, force = false){
const self = this
return new Promise(function(fulfill, reject){
const clientData = versionData.downloads.client
const version = versionData.id
@ -667,17 +671,17 @@ function validateClient(versionData, basePath, force = false){
let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, path.join(targetPath, targetFile))
if(!_validateLocal(client.to, 'sha1', client.hash) || force){
instance.files.dlqueue.push(client)
instance.files.dlsize += client.size*1
if(!AssetGuard._validateLocal(client.to, 'sha1', client.hash) || force){
self.files.dlqueue.push(client)
self.files.dlsize += client.size*1
fulfill()
} else {
fulfill()
}
})
}
}
/**
/**
* Validate log config.
*
* @param {Object} versionData - the version data for the assets.
@ -685,7 +689,8 @@ function validateClient(versionData, basePath, force = false){
* @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.
*/
function validateLogConfig(versionData, basePath){
validateLogConfig(versionData, basePath){
const self = this
return new Promise(function(fulfill, reject){
const client = versionData.logging.client
const file = client.file
@ -693,26 +698,27 @@ function validateLogConfig(versionData, basePath){
let logConfig = new Asset(file.id, file.sha1, file.size, file.url, path.join(targetPath, file.id))
if(!_validateLocal(logConfig.to, 'sha1', logConfig.hash)){
instance.files.dlqueue.push(logConfig)
instance.files.dlsize += logConfig.size*1
if(!AssetGuard._validateLocal(logConfig.to, 'sha1', logConfig.hash)){
self.files.dlqueue.push(logConfig)
self.files.dlsize += logConfig.size*1
fulfill()
} else {
fulfill()
}
})
}
}
/**
/**
* Validate the distribution.
*
* @param {String} serverpackid - The id of the server to validate.
* @param {String} basePath - the absolute file path which will be prepended to the given relative paths.
* @returns {Promise.<Object>} - A promise which resolves to the server distribution object.
*/
function validateDistribution(serverpackid, basePath){
validateDistribution(serverpackid, basePath){
const self = this
return new Promise(function(fulfill, reject){
_chainValidateDistributionIndex(basePath).then((value) => {
self._chainValidateDistributionIndex(basePath).then((value) => {
let servers = value.servers
let serv = null
for(let i=0; i<servers.length; i++){
@ -722,25 +728,25 @@ function validateDistribution(serverpackid, basePath){
}
}
instance.forge = _parseDistroModules(serv.modules, basePath, serv.mc_version)
self.forge = self._parseDistroModules(serv.modules, basePath, serv.mc_version)
//Correct our workaround here.
let decompressqueue = instance.forge.callback
instance.forge.callback = function(asset){
let decompressqueue = self.forge.callback
self.forge.callback = function(asset){
if(asset.to.toLowerCase().endsWith('.pack.xz')){
_extractPackXZ([asset.to])
AssetGuard._extractPackXZ([asset.to])
}
if(asset.type === 'forge-hosted' || asset.type === 'forge'){
_finalizeForgeAsset(asset, basePath)
AssetGuard._finalizeForgeAsset(asset, basePath)
}
}
fulfill(serv)
})
})
}
}
//TODO The distro index should be downloaded in the 'pre-loader'. This is because
//we will eventually NEED the index to generate the server list on the ui.
function _chainValidateDistributionIndex(basePath){
//TODO The distro index should be downloaded in the 'pre-loader'. This is because
//we will eventually NEED the index to generate the server list on the ui.
_chainValidateDistributionIndex(basePath){
return new Promise(function(fulfill, reject){
//const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/westeroscraft.json'
const targetFile = path.join(basePath, 'westeroscraft.json')
@ -750,9 +756,9 @@ function _chainValidateDistributionIndex(basePath){
fulfill(JSON.parse(data))
})
})
}
}
function _parseDistroModules(modules, basePath, version){
_parseDistroModules(modules, basePath, version){
let alist = []
let asize = 0;
//This may be removed soon, considering the most efficient way to extract.
@ -761,7 +767,7 @@ function _parseDistroModules(modules, basePath, version){
let ob = modules[i]
let obType = ob.type
let obArtifact = ob.artifact
let obPath = obArtifact.path == null ? _resolvePath(ob.id, obArtifact.extension) : obArtifact.path
let obPath = obArtifact.path == null ? AssetGuard._resolvePath(ob.id, obArtifact.extension) : obArtifact.path
switch(obType){
case 'forge-hosted':
case 'forge':
@ -782,14 +788,14 @@ function _parseDistroModules(modules, basePath, version){
}
let artifact = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, obType)
const validationPath = obPath.toLowerCase().endsWith('.pack.xz') ? obPath.substring(0, obPath.toLowerCase().lastIndexOf('.pack.xz')) : obPath
if(!_validateLocal(validationPath, 'MD5', artifact.hash)){
if(!AssetGuard._validateLocal(validationPath, 'MD5', artifact.hash)){
asize += artifact.size*1
alist.push(artifact)
if(validationPath !== obPath) decompressqueue.push(obPath)
}
//Recursively process the submodules then combine the results.
if(ob.sub_modules != null){
let dltrack = _parseDistroModules(ob.sub_modules, basePath, version)
let dltrack = this._parseDistroModules(ob.sub_modules, basePath, version)
asize += dltrack.dlsize*1
alist = alist.concat(dltrack.dlqueue)
decompressqueue = decompressqueue.concat(dltrack.callback)
@ -798,18 +804,19 @@ function _parseDistroModules(modules, basePath, version){
//Since we have no callback at this point, we use this value to store the decompressqueue.
return new DLTracker(alist, asize, decompressqueue)
}
}
/**
/**
* Loads Forge's version.json data into memory for the specified server id.
*
* @param {String} serverpack - The id of the server to load Forge data for.
* @param {String} basePath
* @returns {Promise.<Object>} - A promise which resolves to Forge's version.json data.
*/
function loadForgeData(serverpack, basePath){
loadForgeData(serverpack, basePath){
const self = this
return new Promise(async function(fulfill, reject){
let distro = await _chainValidateDistributionIndex(basePath)
let distro = await self._chainValidateDistributionIndex(basePath)
const servers = distro.servers
let serv = null
@ -825,69 +832,63 @@ function loadForgeData(serverpack, basePath){
const ob = modules[i]
if(ob.type === 'forge-hosted' || ob.type === 'forge'){
let obArtifact = ob.artifact
let obPath = obArtifact.path == null ? path.join(basePath, 'libraries', _resolvePath(ob.id, obArtifact.extension)) : obArtifact.path
let obPath = obArtifact.path == null ? path.join(basePath, 'libraries', AssetGuard._resolvePath(ob.id, obArtifact.extension)) : obArtifact.path
let asset = new DistroModule(ob.id, obArtifact.MD5, obArtifact.size, obArtifact.url, obPath, ob.type)
let forgeData = await _finalizeForgeAsset(asset, basePath)
let forgeData = await AssetGuard._finalizeForgeAsset(asset, basePath)
fulfill(forgeData)
return
}
}
reject('No forge module found!')
})
}
}
function _parseForgeLibraries(){
_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).
*/
}
}
/**
/**
* This function will initiate the download processed for the specified identifiers. If no argument is
* given, all identifiers will be initiated. Note that in order for files to be processed you need to run
* the processing function corresponding to that identifier. If you run this function without processing
* the files, it is likely nothing will be enqueued in the global object and processing will complete
* the files, it is likely nothing will be enqueued in the object and processing will complete
* immediately. Once all downloads are complete, this function will fire the 'dlcomplete' event on the
* global object instance.
*
* @param {Array.<{id: string, limit: number}>} identifiers - optional. The identifiers to process and corresponding parallel async task limit.
*/
function processDlQueues(identifiers = [{id:'assets', limit:20}, {id:'libraries', limit:5}, {id:'files', limit:5}, {id:'forge', limit:5}]){
processDlQueues(identifiers = [{id:'assets', limit:20}, {id:'libraries', limit:5}, {id:'files', limit:5}, {id:'forge', limit:5}]){
this.progress = 0;
let win = remote.getCurrentWindow()
let shouldFire = true
// Assign global dltracking variables.
instance.totaldlsize = 0
instance.progress = 0
// Assign dltracking variables.
this.totaldlsize = 0
this.progress = 0
for(let i=0; i<identifiers.length; i++){
instance.totaldlsize += instance[identifiers[i].id].dlsize
this.totaldlsize += this[identifiers[i].id].dlsize
}
for(let i=0; i<identifiers.length; i++){
let iden = identifiers[i]
let r = startAsyncProcess(iden.id, iden.limit)
let r = this.startAsyncProcess(iden.id, iden.limit)
if(r) shouldFire = false
}
if(shouldFire){
instance.emit('dlcomplete')
this.emit('dlcomplete')
}
}
}
module.exports = {
loadVersionData,
loadForgeData,
validateAssets,
validateLibraries,
validateMiscellaneous,
validateDistribution,
processDlQueues,
instance,
AssetGuard,
Asset,
Library,
_resolvePath
Library
}

View File

@ -6,7 +6,7 @@
* TODO why are logs not working??????
*/
const AdmZip = require('adm-zip')
const ag = require('./assetguard.js')
const {AssetGuard, Library} = require('./assetguard.js')
const child_process = require('child_process')
const {DEFAULT_CONFIG} = require('./constants')
const fs = require('fs')
@ -232,7 +232,7 @@ class ProcessBuilder {
const nativePath = path.join(this.dir, 'natives')
for(let i=0; i<libArr.length; i++){
const lib = libArr[i]
if(ag.Library.validateRules(lib.rules)){
if(Library.validateRules(lib.rules)){
if(lib.natives == null){
const dlInfo = lib.downloads
const artifact = dlInfo.artifact
@ -243,7 +243,7 @@ class ProcessBuilder {
const natives = lib.natives
const extractInst = lib.extract
const exclusionArr = extractInst.exclude
const opSys = ag.Library.mojangFriendlyOS()
const opSys = Library.mojangFriendlyOS()
const indexId = natives[opSys]
const dlInfo = lib.downloads
const classifiers = dlInfo.classifiers
@ -304,7 +304,7 @@ class ProcessBuilder {
for(let i=0; i<mdles.length; i++){
if(mdles[i].type != null && (mdles[i].type === 'forge-hosted' || mdles[i].type === 'library')){
let lib = mdles[i]
libs.push(path.join(this.libPath, lib.artifact.path == null ? ag._resolvePath(lib.id, lib.artifact.extension) : lib.artifact.path))
libs.push(path.join(this.libPath, lib.artifact.path == null ? AssetGuard._resolvePath(lib.id, lib.artifact.extension) : lib.artifact.path))
if(lib.sub_modules != null){
const res = this._resolveModuleLibraries(lib)
if(res.length > 0){
@ -341,7 +341,7 @@ class ProcessBuilder {
for(let i=0; i<mdle.sub_modules.length; i++){
const sm = mdle.sub_modules[i]
if(sm.type != null && sm.type == 'library'){
libs.push(path.join(this.libPath, sm.artifact.path == null ? ag._resolvePath(sm.id, sm.artifact.extension) : sm.artifact.path))
libs.push(path.join(this.libPath, sm.artifact.path == null ? AssetGuard._resolvePath(sm.id, sm.artifact.extension) : sm.artifact.path))
}
// If this module has submodules, we need to resolve the libraries for those.
// To avoid unnecessary recursive calls, base case is checked here.

View File

@ -10,9 +10,8 @@ $(function(){
console.log('UICore Initialized');
})*/
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
document.addEventListener('readystatechange', function () {
if (document.readyState === 'interactive'){
console.log('UICore Initializing..');
// Bind close button.
@ -37,6 +36,8 @@ document.onreadystatechange = function () {
window.minimize()
})
} else if(document.readyState === 'complete'){
// Bind progress bar length to length of bot wrapper
const targetWidth = document.getElementById("launch_content").getBoundingClientRect().width
const targetWidth2 = document.getElementById("server_selection").getBoundingClientRect().width
@ -45,8 +46,10 @@ document.onreadystatechange = function () {
document.getElementById("launch_progress").style.width = targetWidth2
document.getElementById("launch_details_right").style.maxWidth = targetWidth2
document.getElementById("launch_progress_label").style.width = targetWidth3
}
}
}, false)
/**
* Open web links in the user's default browser.