diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index eb13a9a5..4f51f569 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -6,8 +6,6 @@ const crypto = require('crypto') const EventEmitter = require('events') const fs = require('fs-extra') const { LoggerUtil } = require('helios-core') -const { DistributionAPI } = require('helios-core/common') -const { Type } = require('helios-distribution-types') const nodeDiskInfo = require('node-disk-info') const StreamZip = require('node-stream-zip') const path = require('path') @@ -16,8 +14,6 @@ const request = require('request') const tar = require('tar-fs') const zlib = require('zlib') -const ConfigManager = require('./configmanager') -const { REMOTE_DISTRO_URL } = require('./distromanager') const isDev = require('./isdev') // Classes @@ -42,86 +38,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.} 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. @@ -1152,416 +1068,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.} 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} 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.} 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.} 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.} 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.} 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.} 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.} 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.} 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.} 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.} 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.} 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 === Type.ForgeHosted || type === Type.Forge){ - if(Util.isForgeGradle3(server.getMinecraftVersion(), ob.getVersion())){ - // Read Manifest - for(let sub of ob.getSubModules()){ - if(sub.getType() === Type.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 @@ -1803,55 +1313,6 @@ class AssetGuard extends EventEmitter { }) } - async validateEverything(serverid, dev = false){ - - try { - if(!ConfigManager.isLoaded()){ - ConfigManager.load() - } - const api = new DistributionAPI( - ConfigManager.getLauncherDirectory(), - REMOTE_DISTRO_URL, - dev - ) - - const dI = await api.getDistributionLocalLoadOnly() - - // TODO replace all - 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 }