diff --git a/package-lock.json b/package-lock.json index b4efa74..641b478 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5290,9 +5290,9 @@ "dev": true }, "eslint": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.0.0.tgz", - "integrity": "sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.1.0.tgz", + "integrity": "sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -6990,9 +6990,9 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -8862,9 +8862,9 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", "dev": true, "requires": { "ansi-colors": "3.2.3", diff --git a/package.json b/package.json index 90c33c9..ad3f67a 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "electron-builder": "^22.6.1", "electron-webpack": "^2.8.2", "electron-webpack-ts": "^4.0.1", - "eslint": "^7.0.0", + "eslint": "^7.1.0", "helios-distribution-types": "1.0.0-pre.1", - "mocha": "^7.1.2", + "mocha": "^7.2.0", "nock": "^12.0.3", "react": "^16.13.0", "react-dom": "^16.13.0", diff --git a/src/common/util/MojangUtils.ts b/src/common/util/MojangUtils.ts index f8664e4..c9c619c 100644 --- a/src/common/util/MojangUtils.ts +++ b/src/common/util/MojangUtils.ts @@ -1,4 +1,4 @@ -import { Rule, Natives } from "../../main/asset/model/mojang/VersionJson" +import { Rule, Natives } from "../asset/model/mojang/VersionJson" export function getMojangOS(): string { const opSys = process.platform diff --git a/src/main/index.ts b/src/main/index.ts index 1a561fc..7c7b16b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -230,16 +230,19 @@ function createMenu() { } function getPlatformIcon(filename: string){ - const opSys = process.platform - if (opSys === 'darwin') { - filename = filename + '.icns' - } else if (opSys === 'win32') { - filename = filename + '.ico' - } else { - filename = filename + '.png' + let ext + switch(process.platform) { + case 'win32': + ext = 'ico' + break + case 'darwin': + case 'linux': + default: + ext = 'png' + break } - return join(__dirname, '..', 'assets', 'images', filename) + return join(__dirname, '..', 'assets', 'images', `${filename}.${ext}`) } app.on('ready', createWindow) diff --git a/src/main/old/assetexec.ts b/src/main/old/assetexec.ts deleted file mode 100644 index 0dc3a71..0000000 --- a/src/main/old/assetexec.ts +++ /dev/null @@ -1,69 +0,0 @@ -let target = require('./assetguard')[process.argv[2]] - -if(target == null){ - process.send!({context: 'error', data: null, error: 'Invalid class name'}) - console.error('Invalid class name passed to argv[2], cannot continue.') - process.exit(1) -} -let tracker = new target(...(process.argv.splice(3))) - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - -//const tracker = new AssetGuard(process.argv[2], process.argv[3]) -console.log('AssetExec Started') - -// Temporary for debug purposes. -process.on('unhandledRejection', r => console.log(r)) - -function assignListeners(){ - tracker.on('validate', (data: any) => { - process.send!({context: 'validate', data}) - }) - tracker.on('progress', (data: any, acc: number, total: number) => { - process.send!({context: 'progress', data, value: acc, total, percent: parseInt(((acc/total)*100) as unknown as string)}) - }) - tracker.on('complete', (data: any, ...args: any[]) => { - process.send!({context: 'complete', data, args}) - }) - tracker.on('error', (data: any, error: any) => { - process.send!({context: 'error', data, error}) - }) -} - -assignListeners() - -process.on('message', (msg) => { - if(msg.task === 'execute'){ - const func = msg.function - let nS = tracker[func] // Nonstatic context - let iS = target[func] // Static context - if(typeof nS === 'function' || typeof iS === 'function'){ - const f = typeof nS === 'function' ? nS : iS - const res = f.apply(f === nS ? tracker : null, msg.argsArr) - if(res instanceof Promise){ - res.then((v) => { - process.send!({result: v, context: func}) - }).catch((err) => { - process.send!({result: err.message || err, context: func}) - }) - } else { - process.send!({result: res, context: func}) - } - } else { - process.send!({context: 'error', data: null, error: `Function ${func} not found on ${process.argv[2]}`}) - } - } else if(msg.task === 'changeContext'){ - target = require('./assetguard')[msg.class] - if(target == null){ - process.send!({context: 'error', data: null, error: `Invalid class ${msg.class}`}) - } else { - tracker = new target(...(msg.args)) - assignListeners() - } - } -}) - -process.on('disconnect', () => { - console.log('AssetExec Disconnected') - process.exit(0) -}) \ No newline at end of file diff --git a/src/main/old/assetguard.ts b/src/main/old/assetguard.ts deleted file mode 100644 index 2b3456a..0000000 --- a/src/main/old/assetguard.ts +++ /dev/null @@ -1,1916 +0,0 @@ -import { EventEmitter } from 'events' -import request from 'request' -import { join } from 'path' -import { pathExistsSync, pathExists, readdir, exists, readFileSync, createWriteStream, ensureDirSync, readFile, writeFileSync, unlink, createReadStream, readJsonSync } from 'fs-extra' -import Registry from 'winreg' -import { exec, spawn } from 'child_process' -import { LauncherJson } from 'common/asset/model/mojang/LauncherJson' -import { createHash } from 'crypto' -import AdmZip from 'adm-zip' -import { forEachOfLimit, eachLimit } from 'async' -import { extract } from 'tar-fs' -import { createGunzip } from 'zlib' -import { VersionJson, AssetIndex, Rule, Natives, Library } from 'common/asset/model/mojang/VersionJson' - -import { ConfigManager } from 'common/config/configmanager' -import isDev from 'common/util/isdev' -const DistroManager = require('./distromanager') - -// Constants -// const PLATFORM_MAP = { -// win32: '-windows-x64.tar.gz', -// darwin: '-macosx-x64.tar.gz', -// linux: '-linux-x64.tar.gz' -// } - -// Classes - -/** Class representing a base asset. */ -export class Asset { - /** - * Create an asset. - * - * @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. - */ - constructor( - public id: any, - public hash: string, - public size: number, - public from: string, - public to: string - ) {} -} - -/** Class representing a mojang library. */ -export class LibraryInternal extends Asset { - - /** - * Converts the process.platform OS names to match mojang's OS names. - */ - public 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 null - } - } - - // TODO types - /** - * 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. - */ - public static validateRules(rules: Rule[] | null | undefined, natives: Natives | null | undefined){ - if(rules == null) { - if(natives == null) { - return true - } else { - return natives[LibraryInternal.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 = LibraryInternal.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: any, - hash: string, - size: number, - from: string, - to: string, - public type: string - ) { - 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. - */ -class DLTracker { - - /** - * Create a DLTracker - * - * @param {Array.} dlqueue An array containing assets queued for download. - * @param {number} dlsize The combined size of each asset in the download queue array. - * @param {function(Asset)} callback Optional callback which is called when an asset finishes downloading. - */ - constructor( - public dlqueue: Asset[], - public dlsize: number, - public callback?: (asset: Asset, ...args: any[]) => void - ) {} - -} - -export class Util { - - /** - * 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. - */ - public static mcVersionAtLeast(desired: string, actual: string){ - const des = desired.split('.') - const act = actual.split('.') - - for(let i=0; i= parseInt(des[i]))){ - return false - } - } - return true - } - -} - -interface OpenJDKData { - uri: string - size: number - name: string -} - -interface JDK8Version { - major: number - update: number - build: number -} - -interface JDK9Version { - major: number - minor: number - revision: number - build: number -} - -interface JVMMeta { - arch?: number - version?: JDK8Version | JDK9Version - execPath?: string - valid: boolean // Above properties are present if valid. -} - -export class JavaGuard extends EventEmitter { - - constructor( - public mcVersion: string - ) { - super() - } - - // /** - // * @typedef OracleJREData - // * @property {string} uri The base uri of the JRE. - // * @property {{major: string, update: string, build: string}} version Object containing version information. - // */ - - // /** - // * Resolves the latest version of Oracle's JRE and parses its download link. - // * - // * @returns {Promise.} Promise which resolved to an object containing the JRE download data. - // */ - // static _latestJREOracle(){ - - // const url = 'https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html' - // const regex = /https:\/\/.+?(?=\/java)\/java\/jdk\/([0-9]+u[0-9]+)-(b[0-9]+)\/([a-f0-9]{32})?\/jre-\1/ - - // return new Promise((resolve, reject) => { - // request(url, (err, resp, body) => { - // if(!err){ - // const arr = body.match(regex) - // const verSplit = arr[1].split('u') - // resolve({ - // uri: arr[0], - // version: { - // major: verSplit[0], - // update: verSplit[1], - // build: arr[2] - // } - // }) - // } else { - // resolve(null) - // } - // }) - // }) - // } - - /** - * Fetch the last open JDK binary. Uses https://api.adoptopenjdk.net/ - * - * @param {string} major The major version of Java to fetch. - * - * @returns {Promise.} Promise which resolved to an object containing the JRE download data. - */ - // TODO reject not null use try catch in caller - public static latestOpenJDK(major = '8'): Promise { - - const sanitizedOS = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'mac' : process.platform) - - const url = `https://api.adoptopenjdk.net/v2/latestAssets/nightly/openjdk${major}?os=${sanitizedOS}&arch=x64&heap_size=normal&openjdk_impl=hotspot&type=jre` - - return new Promise((resolve, reject) => { - request({url, json: true}, (err, resp, body) => { - if(!err && body.length > 0){ - resolve({ - uri: body[0].binary_link, - size: body[0].binary_size, - name: body[0].binary_name - }) - } else { - resolve(null) - } - }) - }) - } - - /** - * Returns the path of the OS-specific executable for the given Java - * installation. Supported OS's are win32, darwin, linux. - * - * @param {string} rootDir The root directory of the Java installation. - * @returns {string} The path to the Java executable. - */ - public static javaExecFromRoot(rootDir: string){ - if(process.platform === 'win32'){ - return join(rootDir, 'bin', 'javaw.exe') - } else if(process.platform === 'darwin'){ - return join(rootDir, 'Contents', 'Home', 'bin', 'java') - } else if(process.platform === 'linux'){ - return join(rootDir, 'bin', 'java') - } - return rootDir - } - - /** - * Check to see if the given path points to a Java executable. - * - * @param {string} pth The path to check against. - * @returns {boolean} True if the path points to a Java executable, otherwise false. - */ - public static isJavaExecPath(pth: string){ - if(process.platform === 'win32'){ - return pth.endsWith(join('bin', 'javaw.exe')) - } else if(process.platform === 'darwin'){ - return pth.endsWith(join('bin', 'java')) - } else if(process.platform === 'linux'){ - return pth.endsWith(join('bin', 'java')) - } - return false - } - - /** - * Load Mojang's launcher.json file. - * - * @returns {Promise.} Promise which resolves to Mojang's launcher.json object. - */ - // TODO reject and have caller try catch - public static loadMojangLauncherData(): Promise { - return new Promise((resolve, reject) => { - request.get('https://launchermeta.mojang.com/mc/launcher.json', (err, resp, body) => { - if(err){ - resolve(null) - } else { - resolve(JSON.parse(body)) - } - }) - }) - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Dynamically detects the formatting - * to use. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - public static parseJavaRuntimeVersion(verString: string){ - const major = Number(verString.split('.')[0]) - if(major === 1){ - return JavaGuard._parseJavaRuntimeVersion_8(verString) - } else { - return JavaGuard._parseJavaRuntimeVersion_9(verString) - } - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 8 formatting. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - public static _parseJavaRuntimeVersion_8(verString: string): JDK8Version { - // 1.{major}.0_{update}-b{build} - // ex. 1.8.0_152-b16 - const ptsOne = verString.split('-') - const ptsTwo = ptsOne[0].split('_') - return { - major: parseInt(ptsTwo[0].split('.')[1]), - update: parseInt(ptsTwo[1]), - build: parseInt(ptsOne[1].substring(1)) - } - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 9+ formatting. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - public static _parseJavaRuntimeVersion_9(verString: string): JDK9Version { - // {major}.{minor}.{revision}+{build} - // ex. 10.0.2+13 - const ptsOne = verString.split('+') - const ptsTwo = ptsOne[0].split('.') - return { - major: parseInt(ptsTwo[0]), - minor: parseInt(ptsTwo[1]), - revision: parseInt(ptsTwo[2]), - build: parseInt(ptsOne[1]) - } - } - - /** - * Validates the output of a JVM's properties. Currently validates that a JRE is x64 - * and that the major = 8, update > 52. - * - * @param {string} stderr The output to validate. - * - * @returns {JVMMeta} A meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - private _validateJVMProperties(stderr: string) { - const res = stderr - const props = res.split('\n') - - const goal = 2 - let checksum = 0 - - const meta: any = {} - - for(let i=0; i -1){ - const arch = parseInt(props[i].split('=')[1].trim()) - console.log(props[i].trim()) - if(arch === 64){ - meta.arch = arch - ++checksum - if(checksum === goal){ - break - } - } - } else if(props[i].indexOf('java.runtime.version') > -1){ - let verString = props[i].split('=')[1].trim() - console.log(props[i].trim()) - const verOb = JavaGuard.parseJavaRuntimeVersion(verString) - if(verOb.major < 9){ - // Java 8 - if(verOb.major === 8 && (verOb as JDK8Version).update > 52){ - meta.version = verOb - ++checksum - if(checksum === goal){ - break - } - } - } else { - // Java 9+ - if(Util.mcVersionAtLeast('1.13', this.mcVersion)){ - console.log('Java 9+ not yet tested.') - /* meta.version = verOb - ++checksum - if(checksum === goal){ - break - } */ - } - } - } - } - - meta.valid = checksum === goal - - return meta as JVMMeta - } - - /** - * Validates that a Java binary is at least 64 bit. This makes use of the non-standard - * command line option -XshowSettings:properties. The output of this contains a property, - * sun.arch.data.model = ARCH, in which ARCH is either 32 or 64. This option is supported - * in Java 8 and 9. Since this is a non-standard option. This will resolve to true if - * the function's code throws errors. That would indicate that the option is changed or - * removed. - * - * @param {string} binaryExecPath Path to the java executable we wish to validate. - * - * @returns {Promise.} A promise which resolves to a meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - private _validateJavaBinary(binaryExecPath: string): Promise { - - return new Promise((resolve, reject) => { - if(!JavaGuard.isJavaExecPath(binaryExecPath)){ - resolve({valid: false}) - } else if(pathExistsSync(binaryExecPath)){ - // Workaround (javaw.exe no longer outputs this information.) - console.log(typeof binaryExecPath) - if(binaryExecPath.indexOf('javaw.exe') > -1) { - binaryExecPath.replace('javaw.exe', 'java.exe') - } - exec('"' + binaryExecPath + '" -XshowSettings:properties', (err, stdout, stderr) => { - try { - // Output is stored in stderr? - resolve(this._validateJVMProperties(stderr)) - } catch (err){ - // Output format might have changed, validation cannot be completed. - resolve({valid: false}) - } - }) - } else { - resolve({valid: false}) - } - }) - - } - - /** - * Checks for the presence of the environment variable JAVA_HOME. If it exits, we will check - * to see if the value points to a path which exists. If the path exits, the path is returned. - * - * @returns {string} The path defined by JAVA_HOME, if it exists. Otherwise null. - */ - private static _scanJavaHome(){ - const jHome = process.env.JAVA_HOME as string - try { - let res = pathExistsSync(jHome) - return res ? jHome : null - } catch (err) { - // Malformed JAVA_HOME property. - return null - } - } - - /** - * Scans the registry for 64-bit Java entries. The paths of each entry are added to - * a set and returned. Currently, only Java 8 (1.8) is supported. - * - * @returns {Promise.>} A promise which resolves to a set of 64-bit Java root - * paths found in the registry. - */ - private static _scanRegistry(): Promise> { - - return new Promise((resolve, reject) => { - // Keys for Java v9.0.0 and later: - // 'SOFTWARE\\JavaSoft\\JRE' - // 'SOFTWARE\\JavaSoft\\JDK' - // Forge does not yet support Java 9, therefore we do not. - - // Keys for Java 1.8 and prior: - const regKeys = [ - '\\SOFTWARE\\JavaSoft\\Java Runtime Environment', - '\\SOFTWARE\\JavaSoft\\Java Development Kit' - ] - - let keysDone = 0 - - const candidates = new Set() - - for(let i=0; i { - if(exists) { - key.keys((err, javaVers) => { - if(err){ - keysDone++ - console.error(err) - - // REG KEY DONE - // DUE TO ERROR - if(keysDone === regKeys.length){ - resolve(candidates) - } - } else { - if(javaVers.length === 0){ - // REG KEY DONE - // NO SUBKEYS - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } else { - - let numDone = 0 - - for(let j=0; j { - const jHome = res.value - if(jHome.indexOf('(x86)') === -1){ - candidates.add(jHome) - } - - // SUBKEY DONE - - numDone++ - if(numDone === javaVers.length){ - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - }) - } else { - - // SUBKEY DONE - // NOT JAVA 8 - - numDone++ - if(numDone === javaVers.length){ - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - } - } - } - } - }) - } else { - - // REG KEY DONE - // DUE TO NON-EXISTANCE - - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - }) - } - - }) - - } - - /** - * See if JRE exists in the Internet Plug-Ins folder. - * - * @returns {string} The path of the JRE if found, otherwise null. - */ - private static _scanInternetPlugins(){ - // /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java - const pth = '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin' - const res = pathExistsSync(JavaGuard.javaExecFromRoot(pth)) - return res ? pth : null - } - - /** - * Scan a directory for root JVM folders. - * - * @param {string} scanDir The directory to scan. - * @returns {Promise.>} A promise which resolves to a set of the discovered - * root JVM folders. - */ - private static _scanFileSystem(scanDir: string): Promise> { - return new Promise((resolve, reject) => { - - pathExists(scanDir, (e) => { - - let res = new Set() - - if(e){ - readdir(scanDir, (err, files) => { - if(err){ - resolve(res) - console.log(err) - } else { - let pathsDone = 0 - - for(let i=0; i { - - if(v){ - res.add(combinedPath) - } - - ++pathsDone - - if(pathsDone === files.length){ - resolve(res) - } - - }) - } - if(pathsDone === files.length){ - resolve(res) - } - } - }) - } else { - resolve(res) - } - }) - - }) - } - - /** - * - * @param {Set.} rootSet A set of JVM root strings to validate. - * @returns {Promise.} A promise which resolves to an array of meta objects - * for each valid JVM root directory. - */ - private async _validateJavaRootSet(rootSet: Set): Promise{ - - const rootArr = Array.from(rootSet) - const validArr: JVMMeta[] = [] - - for(let i=0; i { - - if(a.version.major === b.version.major){ - - if(a.version.major < 9){ - // Java 8 - if(a.version.update === b.version.update){ - if(a.version.build === b.version.build){ - - // Same version, give priority to JRE. - if(a.execPath.toLowerCase().indexOf('jdk') > -1){ - return b.execPath.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.build > b.version.build ? -1 : 1 - } - } else { - return a.version.update > b.version.update ? -1 : 1 - } - } else { - // Java 9+ - if(a.version.minor === b.version.minor){ - if(a.version.revision === b.version.revision){ - - // Same version, give priority to JRE. - if(a.execPath.toLowerCase().indexOf('jdk') > -1){ - return b.execPath.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.revision > b.version.revision ? -1 : 1 - } - } else { - return a.version.minor > b.version.minor ? -1 : 1 - } - } - - } else { - return a.version.major > b.version.major ? -1 : 1 - } - }) - - return retArr - } - - /** - * Attempts to find a valid x64 installation of Java on Windows machines. - * Possible paths will be pulled from the registry and the JAVA_HOME environment - * variable. The paths will be sorted with higher versions preceeding lower, and - * JREs preceeding JDKs. The binaries at the sorted paths will then be validated. - * The first validated is returned. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - private async _win32JavaValidate(dataDir: string){ - - // Get possible paths from the registry. - let pathSet1 = await JavaGuard._scanRegistry() - if(pathSet1.size === 0){ - // Do a manual file system scan of program files. - pathSet1 = await JavaGuard._scanFileSystem('C:\\Program Files\\Java') - } - - // Get possible paths from the data directory. - const pathSet2 = await JavaGuard._scanFileSystem(join(dataDir, 'runtime', 'x64')) - - // Merge the results. - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME. - const jHome = JavaGuard._scanJavaHome() - if(jHome != null && jHome.indexOf('(x86)') === -1){ - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - return pathArr[0].execPath - } else { - return null - } - - } - - /** - * Attempts to find a valid x64 installation of Java on MacOS. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable and internet plugins directory - * are also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - async _darwinJavaValidate(dataDir: string){ - - const pathSet1 = await JavaGuard._scanFileSystem('/Library/Java/JavaVirtualMachines') - const pathSet2 = await JavaGuard._scanFileSystem(join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Check Internet Plugins folder. - const iPPath = JavaGuard._scanInternetPlugins() - if(iPPath != null){ - uberSet.add(iPPath) - } - - // Check the JAVA_HOME environment variable. - let jHome = JavaGuard._scanJavaHome() - if(jHome != null){ - // Ensure we are at the absolute root. - if(jHome.includes('/Contents/Home')){ - jHome = jHome.substring(0, jHome.indexOf('/Contents/Home')) - } - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - return pathArr[0].execPath - } else { - return null - } - } - - /** - * Attempts to find a valid x64 installation of Java on Linux. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable is also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - async _linuxJavaValidate(dataDir: string){ - - const pathSet1 = await JavaGuard._scanFileSystem('/usr/lib/jvm') - const pathSet2 = await JavaGuard._scanFileSystem(join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME - const jHome = JavaGuard._scanJavaHome() - if(jHome != null){ - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - return pathArr[0].execPath - } else { - return null - } - } - - /** - * Retrieve the path of a valid x64 Java installation. - * - * @param {string} dataDir The base launcher directory. - * @returns {string} A path to a valid x64 Java installation, null if none found. - */ - async validateJava(dataDir: string){ - return await (this as any)[`_${process.platform}JavaValidate`](dataDir) - } - -} - - - - -/** - * Central object class used for control flow. This object stores data about - * categories of downloads. Each category is assigned an identifier with a - * DLTracker object as its value. Combined information is also stored, such as - * the total size of all the queued files in each category. This event is used - * to emit events so that external modules can listen into processing done in - * this module. - */ -class AssetGuard extends EventEmitter { - - protected totaldlsize: number - protected progress: number - protected assets: DLTracker - protected libraries: DLTracker - protected files: DLTracker - protected forge: DLTracker - protected java: DLTracker - protected extractQueue: any[] - - /** - * Create an instance of AssetGuard. - * On creation the object's properties are never-null default - * values. Each identifier is resolved to an empty DLTracker. - * - * @param {string} commonPath The common path for shared game files. - * @param {string} javaexec The path to a java executable which will be used - * to finalize installation. - */ - constructor( - public commonPath: string, - public javaexec: string - ) { - super() - this.totaldlsize = 0 - this.progress = 0 - this.assets = new DLTracker([], 0) - this.libraries = new DLTracker([], 0) - this.files = new DLTracker([], 0) - this.forge = new DLTracker([], 0) - this.java = new DLTracker([], 0) - this.extractQueue = [] - this.commonPath = commonPath - this.javaexec = javaexec - } - - // Static Utility Functions - // #region - - // Static Hash Validation Functions - // #region - - /** - * 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. - */ - private static _calculateHash(buf: Buffer, algo: string){ - return 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. - */ - private static _parseChecksumsFile(content: string) { - let finalContent: {[fileName: string]: string} = {} - let lines = content.split('\n') - for(let i=0; i} checksums The checksums listed in the forge version index. - * @returns {boolean} True if the file exists and the hashes match, otherwise false. - */ - private static _validateForgeChecksum(filePath: string, checksums: string[]){ - if(pathExistsSync(filePath)){ - if(checksums == null || checksums.length === 0){ - return true - } - let buf = readFileSync(filePath) - let calcdhash = AssetGuard._calculateHash(buf, 'sha1') - let valid = checksums.includes(calcdhash) - if(!valid && filePath.endsWith('.jar')){ - valid = AssetGuard._validateForgeJar(buf, 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.} 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. - */ - private static _validateForgeJar(buf: Buffer, checksums: string[]){ - // 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: {[fileName: string]: string} = {} - let expected: {[fileName: string]: string} = {} - - const zip = new AdmZip(buf) - const zipEntries = zip.getEntries() - - //First pass - for(let i=0; i} filePaths The paths of the files to be extracted and unpacked. - * @returns {Promise.} An empty promise to indicate the extraction has completed. - */ - private static _extractPackXZ(filePaths: string[], javaExecutable: string){ - console.log('[PackXZExtract] Starting') - return new Promise((resolve, reject) => { - - let libPath - if(isDev){ - libPath = join(process.cwd(), 'libraries', 'java', 'PackXZExtract.jar') - } else { - if(process.platform === 'darwin'){ - libPath = join(process.cwd(),'Contents', 'Resources', 'libraries', 'java', 'PackXZExtract.jar') - } else { - libPath = join(process.cwd(), 'resources', 'libraries', 'java', 'PackXZExtract.jar') - } - } - - const filePath = filePaths.join(',') - const child = spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath]) - child.stdout.on('data', (data) => { - console.log('[PackXZExtract]', data.toString('utf8').trim()) - }) - child.stderr.on('data', (data) => { - console.log('[PackXZExtract]', data.toString('utf8').trim()) - }) - child.on('close', (code, signal) => { - console.log('[PackXZExtract]', 'Exited with code', code) - resolve() - }) - }) - } - - /** - * 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. - */ - // TODO type def - private static _finalizeForgeAsset(asset: Asset, commonPath: string){ - return new Promise((resolve, reject) => { - 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. - */ - public loadVersionData(version: string, force = false): Promise{ - const self = this - return new Promise(async (resolve, reject) => { - const versionPath = join(self.commonPath, 'versions', version) - const versionFile = join(versionPath, version + '.json') - if(!pathExistsSync(versionFile) || force){ - const url = await self._getVersionDataUrl(version) - if (url == null) { - console.error(`No version index found for ${version}.`) - reject() - return - } - //This download will never be tracked as it's essential and trivial. - console.log('Preparing download of ' + version + ' assets.') - ensureDirSync(versionPath) - const stream = request(url).pipe(createWriteStream(versionFile)) - stream.on('finish', () => { - resolve(readJsonSync(versionFile) as VersionJson) - }) - } else { - resolve(readJsonSync(versionFile) as VersionJson) - } - }) - } - - /** - * 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. - */ - private _getVersionDataUrl(version: string): Promise { - 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 {VersionJson} 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. - */ - public validateAssets(versionData: VersionJson, force = false): Promise { - 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 {VersionJson} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - private _assetChainIndexData(versionData: VersionJson, force = false): Promise { - const self = this - return new Promise((resolve, reject) => { - //Asset index constants. - const assetIndex = versionData.assetIndex - const name = assetIndex.id + '.json' - const indexPath = join(self.commonPath, 'assets', 'indexes') - const assetIndexLoc = join(indexPath, name) - - let data = null - if(!pathExistsSync(assetIndexLoc) || force){ - console.log('Downloading ' + versionData.id + ' asset index.') - ensureDirSync(indexPath) - const stream = request(assetIndex.url).pipe(createWriteStream(assetIndexLoc)) - stream.on('finish', () => { - data = JSON.parse(readFileSync(assetIndexLoc, 'utf-8')) - self._assetChainValidateAssets(versionData, data).then(() => { - resolve() - }) - }) - } else { - data = JSON.parse(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 {VersionJson} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - private _assetChainValidateAssets(versionData: VersionJson, indexData: AssetIndex): Promise { - const self = this - return new Promise((resolve, reject) => { - - //Asset constants - const resourceURL = 'http://resources.download.minecraft.net/' - const localPath = join(self.commonPath, 'assets') - const objectPath = join(localPath, 'objects') - - const assetDlQueue: Asset[] = [] - let dlSize = 0 - let acc = 0 - const total = Object.keys(indexData.objects).length - //const objKeys = Object.keys(data.objects) - forEachOfLimit(indexData.objects, 10, (value, key, cb) => { - acc++ - self.emit('progress', 'assets', acc, total) - const hash = value.hash - const assetName = join(hash.substring(0, 2), hash) - const urlName = hash.substring(0, 2) + '/' + hash - const ast = new Asset(key, hash, value.size, resourceURL + urlName, 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 {VersionJson} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public validateLibraries(versionData: VersionJson){ - const self = this - return new Promise((resolve, reject) => { - - const libArr = versionData.libraries - const libPath = join(self.commonPath, 'libraries') - - const libDlQueue: LibraryInternal[] = [] - let dlSize = 0 - - //Check validity of each library. If the hashs don't match, download the library. - eachLimit(libArr, 5, (lib: Library, cb) => { - if(LibraryInternal.validateRules(lib.rules, lib.natives)){ - // @ts-ignore - let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[LibraryInternal.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] - const libItm = new LibraryInternal(lib.name, artifact.sha1, artifact.size, artifact.url, 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 {VersionJson} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public validateMiscellaneous(versionData: VersionJson){ - 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 {VersionJson} 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. - */ - public validateClient(versionData: VersionJson, force = false){ - const self = this - return new Promise((resolve, reject) => { - const clientData = versionData.downloads.client - const version = versionData.id - const targetPath = join(self.commonPath, 'versions', version) - const targetFile = version + '.jar' - - let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, 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 {VersionJson} 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. - */ - public validateLogConfig(versionData: VersionJson){ - const self = this - return new Promise((resolve, reject) => { - const client = versionData.logging.client - const file = client.file - const targetPath = join(self.commonPath, 'assets', 'log_configs') - - let logConfig = new Asset(file.id, file.sha1, file.size, file.url, 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. - */ - public validateDistribution(server: any){ - const self = this - return new Promise((resolve, reject) => { - self.forge = self._parseDistroModules(server.getModules(), server.getMinecraftVersion(), server.getID()) - resolve(server) - }) - } - - private _parseDistroModules(modules: any[], version: string, servid: string){ - let alist: any[] = [] - 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) - // @ts-ignore - // TODO revisit - 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 {any} server The Server to load Forge data for. - * @returns {Promise.} A promise which resolves to Forge's version.json data. - */ - public loadForgeData(server: any){ - 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.mcVersionAtLeast('1.13', server.getMinecraftVersion())){ - // Read Manifest - for(let sub of ob.getSubModules()){ - if(sub.getType() === DistroManager.Types.VersionManifest){ - resolve(JSON.parse(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!') - }) - } - - private _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 - - private _enqueueOpenJDK(dataDir: string){ - return new Promise((resolve, reject) => { - JavaGuard.latestOpenJDK('8').then(verData => { - if(verData != null){ - - dataDir = join(dataDir, 'runtime', 'x64') - const fDir = join(dataDir, verData.name) - // @ts-ignore - // TODO revisit - const jre = new Asset(verData.name, null, verData.size, verData.uri, fDir) - this.java = new DLTracker([jre], jre.size, (a: Asset, self: this) => { - if(verData.name.endsWith('zip')){ - - const zip = new AdmZip(a.to) - const pos = join(dataDir, zip.getEntries()[0].entryName) - zip.extractAllToAsync(dataDir, true, (err) => { - if(err){ - console.log(err) - self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - } else { - unlink(a.to, err => { - if(err){ - console.log(err) - } - self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - }) - } - }) - - } else { - // Tar.gz - let h: string - createReadStream(a.to) - .on('error', err => console.log(err)) - .pipe(createGunzip()) - .on('error', err => console.log(err)) - .pipe(extract(dataDir, { - map: (header) => { - if(h == null){ - h = header.name - } - return header - } - })) - .on('error', err => console.log(err)) - .on('finish', () => { - unlink(a.to, err => { - if(err){ - console.log(err) - } - if(h.indexOf('/') > -1){ - h = h.substring(0, h.indexOf('/')) - } - const pos = join(dataDir, h) - self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - }) - }) - } - }) - resolve(true) - - } else { - resolve(false) - } - }) - }) - - } - - // _enqueueOracleJRE(dataDir){ - // return new Promise((resolve, reject) => { - // JavaGuard._latestJREOracle().then(verData => { - // if(verData != null){ - - // const combined = verData.uri + PLATFORM_MAP[process.platform] - - // const opts = { - // url: combined, - // headers: { - // 'Cookie': 'oraclelicense=accept-securebackup-cookie' - // } - // } - - // request.head(opts, (err, resp, body) => { - // if(err){ - // resolve(false) - // } else { - // dataDir = path.join(dataDir, 'runtime', 'x64') - // const name = combined.substring(combined.lastIndexOf('/')+1) - // const fDir = path.join(dataDir, name) - // const jre = new Asset(name, null, parseInt(resp.headers['content-length']), opts, fDir) - // this.java = new DLTracker([jre], jre.size, (a, self) => { - // let h = null - // fs.createReadStream(a.to) - // .on('error', err => console.log(err)) - // .pipe(zlib.createGunzip()) - // .on('error', err => console.log(err)) - // .pipe(tar.extract(dataDir, { - // map: (header) => { - // if(h == null){ - // h = header.name - // } - // } - // })) - // .on('error', err => console.log(err)) - // .on('finish', () => { - // fs.unlink(a.to, err => { - // if(err){ - // console.log(err) - // } - // if(h.indexOf('/') > -1){ - // h = h.substring(0, h.indexOf('/')) - // } - // const pos = path.join(dataDir, h) - // self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - // }) - // }) - - // }) - // resolve(true) - // } - // }) - - // } else { - // resolve(false) - // } - // }) - // }) - - // } - - // _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 - - // #endregion - - // Control Flow Functions - // #region - - /** - * 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. - */ - public startAsyncProcess(identifier: string, limit = 5){ - - const self = this - const dlTracker = (this as any)[identifier] - const dlQueue = dlTracker.dlqueue - - if(dlQueue.length > 0){ - console.log('DLQueue', dlQueue) - - eachLimit(dlQueue, limit, (asset: Asset, cb) => { - - ensureDirSync(join(asset.to, '..')) - - let req = request(asset.from) - req.pause() - - req.on('response', (resp) => { - - if(resp.statusCode === 200){ - - let doHashCheck = false - const contentLength = parseInt(resp.headers['content-length'] as string) - - if(contentLength !== asset.size){ - console.log(`WARN: Got ${contentLength} bytes for ${asset.id}: Expected ${asset.size}`) - doHashCheck = true - - // Adjust download - this.totaldlsize -= asset.size - this.totaldlsize += contentLength - } - - let writeStream = createWriteStream(asset.to) - writeStream.on('close', () => { - if(dlTracker.callback != null){ - dlTracker.callback.apply(dlTracker, [asset, self]) - } - - if(doHashCheck){ - // @ts-ignore - // TODO revisit - const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash) - if(v){ - console.log(`Hashes match for ${asset.id}, byte mismatch is an issue in the distro index.`) - } else { - console.error(`Hashes do not match, ${asset.id} may be corrupted.`) - } - } - - cb() - }) - req.pipe(writeStream) - req.resume() - - } else { - - req.abort() - // @ts-ignore - // TODO revisit - console.log(`Failed to download ${asset.id}(${typeof asset.from === 'object' ? asset.from.url : asset.from}). Response code ${resp.statusCode}`) - self.progress += asset.size*1 - self.emit('progress', 'download', self.progress, self.totaldlsize) - cb() - - } - - }) - - req.on('error', (err) => { - self.emit('error', 'download', err) - }) - - req.on('data', (chunk) => { - self.progress += chunk.length - self.emit('progress', 'download', self.progress, self.totaldlsize) - }) - - }, (err) => { - - if(err){ - console.log('An item in ' + identifier + ' failed to process') - } else { - console.log('All ' + identifier + ' have been processed successfully') - } - - //self.totaldlsize -= dlTracker.dlsize - //self.progress -= dlTracker.dlsize - (self as any)[identifier] = new DLTracker([], 0) - - if(self.progress >= self.totaldlsize) { - if(self.extractQueue.length > 0){ - self.emit('progress', 'extract', 1, 1) - //self.emit('extracting') - AssetGuard._extractPackXZ(self.extractQueue, self.javaexec).then(() => { - self.extractQueue = [] - self.emit('complete', 'download') - }) - } else { - self.emit('complete', 'download') - } - } - - }) - - return true - - } else { - return false - } - } - - /** - * 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 object and processing will complete - * immediately. Once all downloads are complete, this function will fire the 'complete' event on the - * global object instance. - * - * @param {Array.<{id: string, limit: number}>} identifiers Optional. The identifiers to process and corresponding parallel async task limit. - */ - public processDlQueues(identifiers = [{id:'assets', limit:20}, {id:'libraries', limit:5}, {id:'files', limit:5}, {id:'forge', limit:5}]){ - return new Promise((resolve, reject) => { - let shouldFire = true - - // Assign dltracking variables. - this.totaldlsize = 0 - this.progress = 0 - - for(let iden of identifiers){ - this.totaldlsize += (this as any)[iden.id].dlsize - } - - this.once('complete', (data) => { - resolve() - }) - - for(let iden of identifiers){ - let r = this.startAsyncProcess(iden.id, iden.limit) - if(r) shouldFire = false - } - - if(shouldFire){ - this.emit('complete', 'download') - } - }) - } - - public async validateEverything(serverid: string, 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 - -} diff --git a/src/main/old/authmanager.ts b/src/main/old/authmanager.ts deleted file mode 100644 index de3b6d9..0000000 --- a/src/main/old/authmanager.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { LoggerUtil } from './loggerutil' -import { ConfigManager } from '../../common/config/configmanager' -import { Mojang } from '../../common/mojang/mojang' -import { SavedAccount } from '../../common/config/model/SavedAccount' - -/** - * AuthManager - * - * This module aims to abstract login procedures. Results from Mojang's REST api - * are retrieved through our Mojang module. These results are processed and stored, - * if applicable, in the config using the ConfigManager. All login procedures should - * be made through this module. - * - * @module authmanager - */ - -const logger = new LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold') -const loggerSuccess = new LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold') - -// Functions - -/** - * Add an account. This will authenticate the given credentials with Mojang's - * authserver. The resultant data will be stored as an auth account in the - * configuration database. - * - * @param {string} username The account username (email if migrated). - * @param {string} password The account password. - * @returns {Promise.} Promise which resolves the resolved authenticated account object. - */ -exports.addAccount = async function(username: string, password: string){ - try { - const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken()) - if(session.selectedProfile != null){ - const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name) - if(ConfigManager.getClientToken() == null){ - ConfigManager.setClientToken(session.clientToken) - } - ConfigManager.save() - return ret - } else { - throw new Error('NotPaidAccount') - } - - } catch (err){ - return Promise.reject(err) - } -} - -/** - * Remove an account. This will invalidate the access token associated - * with the account and then remove it from the database. - * - * @param {string} uuid The UUID of the account to be removed. - * @returns {Promise.} Promise which resolves to void when the action is complete. - */ -exports.removeAccount = async function(uuid: string){ - try { - const authAcc = ConfigManager.getAuthAccount(uuid) - await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken() as string) - ConfigManager.removeAuthAccount(uuid) - ConfigManager.save() - return Promise.resolve() - } catch (err){ - return Promise.reject(err) - } -} - -/** - * Validate the selected account with Mojang's authserver. If the account is not valid, - * we will attempt to refresh the access token and update that value. If that fails, a - * new login will be required. - * - * **Function is WIP** - * - * @returns {Promise.} Promise which resolves to true if the access token is valid, - * otherwise false. - */ -exports.validateSelected = async function(){ - const current = ConfigManager.getSelectedAccount() as SavedAccount - const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken() as string) - if(!isValid){ - try { - const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken() as string) - ConfigManager.updateAuthAccount(current.uuid, session.accessToken) - ConfigManager.save() - } catch(err) { - logger.debug('Error while validating selected profile:', err) - if(err && err.error === 'ForbiddenOperationException'){ - // What do we do? - } - logger.log('Account access token is invalid.') - return false - } - loggerSuccess.log('Account access token validated.') - return true - } else { - loggerSuccess.log('Account access token validated.') - return true - } -} \ No newline at end of file diff --git a/src/main/old/discordwrapper.ts b/src/main/old/discordwrapper.ts deleted file mode 100644 index 02edff6..0000000 --- a/src/main/old/discordwrapper.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { LoggerUtil } from './loggerutil' -import { Client, Presence } from 'discord-rpc' - -// Work in progress -const logger = new LoggerUtil('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold') - -let client: Client -let activity: Presence - -// TODO types for these settings -export function initRPC(genSettings: any, servSettings: any, initialDetails = 'Waiting for Client..'){ - client = new Client({ transport: 'ipc' }) - - activity = { - details: initialDetails, - state: 'Server: ' + servSettings.shortId, - largeImageKey: servSettings.largeImageKey, - largeImageText: servSettings.largeImageText, - smallImageKey: genSettings.smallImageKey, - smallImageText: genSettings.smallImageText, - startTimestamp: new Date().getTime(), - instance: false - } - - client.on('ready', () => { - logger.log('Discord RPC Connected') - client.setActivity(activity) - }) - - client.login({clientId: genSettings.clientId}).catch(error => { - if(error.message.includes('ENOENT')) { - logger.log('Unable to initialize Discord Rich Presence, no client detected.') - } else { - logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error) - } - }) -} - -export function updateDetails(details: string){ - activity.details = details - client.setActivity(activity) -} - -export function shutdownRPC(){ - if(!client) return - client.clearActivity() - client.destroy() - client = null as unknown as Client // TODO cleanup - activity = null as unknown as Presence // TODO cleanup -} \ No newline at end of file diff --git a/src/main/old/distromanager.ts b/src/main/old/distromanager.ts deleted file mode 100644 index 1c972b0..0000000 --- a/src/main/old/distromanager.ts +++ /dev/null @@ -1,296 +0,0 @@ -import request from 'request' -import { Distribution, Module, Type, TypeMetadata, Server } from 'helios-distribution-types' -import { readJson, writeJson } from 'fs-extra' -import { join } from 'path' -import { LoggerUtil } from './loggerutil' -import { ConfigManager } from '../../common/config/configmanager' - -const logger = new LoggerUtil('%c[DistroManager]', 'color: #a02d2a; font-weight: bold') - -interface ArtifactMeta { - group: string - artifact: string - version: string - classifier?: string - extension: string -} - -export class ModuleWrapper { - - private artifactMeta: ArtifactMeta - private subModules: ModuleWrapper[] = [] - - constructor(public module: Module, private serverId: string) { - this.artifactMeta = this.resolveMetaData() - this.resolveArtifactPath() - this.resolveRequired() - if (this.module.subModules != null) { - this.subModules = this.module.subModules.map(mdl => new ModuleWrapper(mdl, serverId)) - } - } - - private resolveMetaData(): ArtifactMeta { - try { - - const m0 = this.module.id.split('@') - const m1 = m0[0].split(':') - - return { - group: m1[0] || '???', - artifact: m1[1] || '???', - version: m1[2] || '???', - classifier: m1[3] || undefined, - extension: m0[1] || TypeMetadata[this.module.type].defaultExtension || 'undefined' - } - - } catch (err) { - logger.error('Improper ID for module', this.module.id, err) - return { - group: '???', - artifact: '???', - version: '???', - classifier: undefined, - extension: '???' - } - } - } - - private resolveArtifactPath(): void { - const relativePath = this.module.artifact.path == null ? join( - ...this.artifactMeta.group.split('.'), - this.artifactMeta.artifact, - this.artifactMeta.version, - `${this.artifactMeta.artifact}-${this.artifactMeta.version}${this.artifactMeta.classifier != undefined ? `-${this.artifactMeta.classifier}` : ''}.${this.artifactMeta.extension}` - ) : this.module.artifact.path - - switch (this.module.type){ - case Type.Library: - case Type.ForgeHosted: - case Type.LiteLoader: - this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'libraries', relativePath) - break - case Type.ForgeMod: - case Type.LiteMod: - this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'modstore', relativePath) - break - case Type.VersionManifest: - this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'versions', this.module.id, `${this.module.id}.json`) - break - case Type.File: - default: - this.module.artifact.path = join(ConfigManager.getInstanceDirectory(), this.serverId, relativePath) - break - } - - } - - private resolveRequired(): void { - if (this.module.required == null) { - this.module.required = { - value: true, - def: true - } - } else { - if (this.module.required.value == null) { - this.module.required.value = true - } - if (this.module.required.def == null) { - this.module.required.def = true - } - } - } - - /** - * @returns {string} The maven identifier of this module's artifact. - */ - public getArtifact(): string { - return this.artifactMeta.artifact - } - - /** - * @returns {string} The maven group of this module's artifact. - */ - public getGroup(): string { - return this.artifactMeta.group - } - - /** - * @returns {string} The version of this module's artifact. - */ - public getVersion(): string { - return this.artifactMeta.version - } - - /** - * @returns {string | undefined} The classifier of this module's artifact - */ - public getClassifier(): string | undefined { - return this.artifactMeta.classifier - } - - /** - * @returns {string} The extension of this module's artifact. - */ - public getExtension(): string { - return this.artifactMeta.extension - } - - /** - * @returns {string} The identifier without he version or extension. - */ - public getVersionlessID(): string { - return this.artifactMeta.group + ':' + this.artifactMeta.artifact - } - - /** - * @returns {string} The identifier without the extension. - */ - public getExtensionlessID(): string { - return this.module.id.split('@')[0] - } - - /** - * @returns {boolean} Whether or not this module has sub modules. - */ - public hasSubModules(): boolean { - return this.module.subModules != null - } - - public getWrappedSubmodules(): ModuleWrapper[] { - return this.subModules - } - -} - -export class ServerWrapper { - - private modules: ModuleWrapper[] = [] - - constructor(public server: Server) { - this.server.modules.map(mdl => new ModuleWrapper(mdl, server.id)) - } - - public getWrappedModules() { - return this.modules - } - -} - -export class DistributionWrapper { - - private mainServer: ServerWrapper | null = null - private servers: ServerWrapper[] - - constructor(public distro: Distribution) { - this.servers = this.distro.servers.map(serv => new ServerWrapper(serv)) - this.resolveMainServer() - } - - private resolveMainServer(): void { - - for(const serv of this.servers){ - if(serv.server.mainServer){ - this.mainServer = serv - return - } - } - - // If no server declares default_selected, default to the first one declared. - this.mainServer = (this.servers.length > 0) ? this.servers[0] : null - } - - public getServer(id: string): ServerWrapper | null { - for(const serv of this.servers){ - if(serv.server.id === id){ - return serv - } - } - return null - } - - public getMainServer(): ServerWrapper | null { - return this.mainServer - } - -} - - -export class DistroManager { - - private static readonly DISTRO_PATH = join(ConfigManager.getLauncherDirectory(), 'distribution.json') - private static readonly DEV_PATH = join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json') - - private static readonly DISTRIBUTION_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json' - // private static readonly DISTRIBUTION_URL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/' - - private static DEV_MODE = false - - private static distro: DistributionWrapper - - public static isDevMode() { - return DistroManager.DEV_MODE - } - - public static setDevMode(value: boolean) { - if(value){ - logger.log('Developer mode enabled.') - logger.log('If you don\'t know what that means, revert immediately.') - } else { - logger.log('Developer mode disabled.') - } - DistroManager.DEV_MODE = value - } - - - public static pullRemote(): Promise { - if(DistroManager.DEV_MODE){ - return DistroManager.pullLocal() - } - return new Promise((resolve, reject) => { - const opts = { - url: DistroManager.DISTRIBUTION_URL, - timeout: 2500 - } - const distroDest = join(ConfigManager.getLauncherDirectory(), 'distribution.json') - request(opts, async (error, resp, body) => { - if(!error){ - - let data: Distribution - - try { - data = JSON.parse(body) as Distribution - - DistroManager.distro = new DistributionWrapper(data) - } catch (e) { - reject(e) - return; - } - - try { - await writeJson(distroDest, DistroManager.distro) - resolve(DistroManager.distro) - } catch (err) { - reject(err) - } - - } else { - reject(error) - } - }) - }) - } - - public static async pullLocal(): Promise { - const data = await readJson(DistroManager.DEV_MODE ? DistroManager.DEV_PATH : DistroManager.DISTRO_PATH) as Distribution - - DistroManager.distro = new DistributionWrapper(data) - - return DistroManager.distro - } - - public static getDistribution(): DistributionWrapper { - return DistroManager.distro - } - -} - diff --git a/src/main/old/dropinmodutil.ts b/src/main/old/dropinmodutil.ts deleted file mode 100644 index 7719f86..0000000 --- a/src/main/old/dropinmodutil.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { ensureDirSync, pathExistsSync, readdirSync, moveSync, readFileSync, writeFileSync, rename } from 'fs-extra' -import { join } from 'path' -import { shell } from 'electron' - -// Group #1: File Name (without .disabled, if any) -// Group #2: File Extension (jar, zip, or litemod) -// Group #3: If it is disabled (if string 'disabled' is present) -const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/ -const DISABLED_EXT = '.disabled' - -const SHADER_REGEX = /^(.+)\.zip$/ -const SHADER_OPTION = /shaderPack=(.+)/ -const SHADER_DIR = 'shaderpacks' -const SHADER_CONFIG = 'optionsshaders.txt' - -/** - * Validate that the given directory exists. If not, it is - * created. - * - * @param {string} modsDir The path to the mods directory. - */ -export function validateDir(dir: string) { - ensureDirSync(dir) -} - -/** - * Scan for drop-in mods in both the mods folder and version - * safe mods folder. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} version The minecraft version of the server configuration. - * - * @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]} - * An array of objects storing metadata about each discovered mod. - */ -export function scanForDropinMods(modsDir: string, version: string) { - const modsDiscovered = [] - if(pathExistsSync(modsDir)){ - let modCandidates = readdirSync(modsDir) - let verCandidates: string[] = [] - const versionDir = join(modsDir, version) - if(pathExistsSync(versionDir)){ - verCandidates = readdirSync(versionDir) - } - for(let file of modCandidates){ - const match = MOD_REGEX.exec(file) - if(match != null){ - modsDiscovered.push({ - fullName: match[0], - name: match[1], - ext: match[2], - disabled: match[3] != null - }) - } - } - for(let file of verCandidates){ - const match = MOD_REGEX.exec(file) - if(match != null){ - modsDiscovered.push({ - fullName: join(version, match[0]), - name: match[1], - ext: match[2], - disabled: match[3] != null - }) - } - } - } - return modsDiscovered -} - -/** - * Add dropin mods. - * - * @param {FileList} files The files to add. - * @param {string} modsDir The path to the mods directory. - */ -export function addDropinMods(files: any, modsdir: string) { - - exports.validateDir(modsdir) - - for(let f of files) { - if(MOD_REGEX.exec(f.name) != null) { - moveSync(f.path, join(modsdir, f.name)) - } - } - -} - -/** - * Delete a drop-in mod from the file system. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} fullName The fullName of the discovered mod to delete. - * - * @returns {boolean} True if the mod was deleted, otherwise false. - */ -export function deleteDropinMod(modsDir: string, fullName: string){ - const res = shell.moveItemToTrash(join(modsDir, fullName)) - if(!res){ - shell.beep() - } - return res -} - -/** - * Toggle a discovered mod on or off. This is achieved by either - * adding or disabling the .disabled extension to the local file. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} fullName The fullName of the discovered mod to toggle. - * @param {boolean} enable Whether to toggle on or off the mod. - * - * @returns {Promise.} A promise which resolves when the mod has - * been toggled. If an IO error occurs the promise will be rejected. - */ -export function toggleDropinMod(modsDir: string, fullName: string, enable: boolean){ - return new Promise((resolve, reject) => { - const oldPath = join(modsDir, fullName) - const newPath = join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT) - - rename(oldPath, newPath, (err) => { - if(err){ - reject(err) - } else { - resolve() - } - }) - }) -} - -/** - * Check if a drop-in mod is enabled. - * - * @param {string} fullName The fullName of the discovered mod to toggle. - * @returns {boolean} True if the mod is enabled, otherwise false. - */ -export function isDropinModEnabled(fullName: string){ - return !fullName.endsWith(DISABLED_EXT) -} - -/** - * Scan for shaderpacks inside the shaderpacks folder. - * - * @param {string} instanceDir The path to the server instance directory. - * - * @returns {{fullName: string, name: string}[]} - * An array of objects storing metadata about each discovered shaderpack. - */ -export function scanForShaderpacks(instanceDir: string){ - const shaderDir = join(instanceDir, SHADER_DIR) - const packsDiscovered = [{ - fullName: 'OFF', - name: 'Off (Default)' - }] - if(pathExistsSync(shaderDir)){ - let modCandidates = readdirSync(shaderDir) - for(let file of modCandidates){ - const match = SHADER_REGEX.exec(file) - if(match != null){ - packsDiscovered.push({ - fullName: match[0], - name: match[1] - }) - } - } - } - return packsDiscovered -} - -/** - * Read the optionsshaders.txt file to locate the current - * enabled pack. If the file does not exist, OFF is returned. - * - * @param {string} instanceDir The path to the server instance directory. - * - * @returns {string} The file name of the enabled shaderpack. - */ -export function getEnabledShaderpack(instanceDir: string){ - validateDir(instanceDir) - - const optionsShaders = join(instanceDir, SHADER_CONFIG) - if(pathExistsSync(optionsShaders)){ - const buf = readFileSync(optionsShaders, {encoding: 'utf-8'}) - const match = SHADER_OPTION.exec(buf) - if(match != null){ - return match[1] - } else { - console.warn('WARNING: Shaderpack regex failed.') - } - } - return 'OFF' -} - -/** - * Set the enabled shaderpack. - * - * @param {string} instanceDir The path to the server instance directory. - * @param {string} pack the file name of the shaderpack. - */ -export function setEnabledShaderpack(instanceDir: string, pack: string){ - validateDir(instanceDir) - - const optionsShaders = join(instanceDir, SHADER_CONFIG) - let buf - if(pathExistsSync(optionsShaders)){ - buf = readFileSync(optionsShaders, {encoding: 'utf-8'}) - buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`) - } else { - buf = `shaderPack=${pack}` - } - writeFileSync(optionsShaders, buf, {encoding: 'utf-8'}) -} - -/** - * Add shaderpacks. - * - * @param {FileList} files The files to add. - * @param {string} instanceDir The path to the server instance directory. - */ -export function addShaderpacks(files: any, instanceDir: string) { - - const p = join(instanceDir, SHADER_DIR) - - exports.validateDir(p) - - for(let f of files) { - if(SHADER_REGEX.exec(f.name) != null) { - moveSync(f.path, join(p, f.name)) - } - } - -} \ No newline at end of file diff --git a/src/main/old/langloader.ts b/src/main/old/langloader.ts deleted file mode 100644 index a4986ab..0000000 --- a/src/main/old/langloader.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readJSONSync } from 'fs-extra' -import { join } from 'path' - -// TODO revisit - -let lang: any - -export function loadLanguage(id: string){ - lang = readJSONSync(join(__dirname, '..', 'assets', 'lang', `${id}.json`)) || {} -} - -export function query(id: string){ - let query = id.split('.') - let res = lang - for(let q of query){ - res = res[q] - } - return res === lang ? {} : res -} - -export function queryJS(id: string){ - return exports.query(`js.${id}`) -} \ No newline at end of file diff --git a/src/main/old/loggerutil.ts b/src/main/old/loggerutil.ts deleted file mode 100644 index 95b3f0b..0000000 --- a/src/main/old/loggerutil.ts +++ /dev/null @@ -1,28 +0,0 @@ -export class LoggerUtil { - - constructor( - protected prefix: string, - protected style: string - ){} - - public log(...args: any[]){ - console.log(this.prefix, this.style, ...args) - } - - public info(...args: any[]){ - console.info(this.prefix, this.style, ...args) - } - - public warn(...args: any[]){ - console.warn(this.prefix, this.style, ...args) - } - - public debug(...args: any[]){ - console.debug(this.prefix, this.style, ...args) - } - - public error(...args: any[]){ - console.error(this.prefix, this.style, ...args) - } - -} diff --git a/src/main/old/preloader.ts b/src/main/old/preloader.ts deleted file mode 100644 index a7bd2dd..0000000 --- a/src/main/old/preloader.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ConfigManager } from '../../common/config/configmanager' -import { DistroManager, DistributionWrapper } from './distromanager' -import { join } from 'path' -import { remove } from 'fs-extra' -import { loadLanguage } from './langloader' -import { LoggerUtil } from './loggerutil' -import { tmpdir } from 'os' -import { ipcRenderer } from 'electron' - -const logger = new LoggerUtil('%c[Preloader]', 'color: #a02d2a; font-weight: bold') - -logger.log('Loading..') - -// Load ConfigManager -ConfigManager.load() - -// Load Strings -loadLanguage('en_US') - -function onDistroLoad(data: DistributionWrapper | null){ - if(data != null){ - - // Resolve the selected server if its value has yet to be set. - if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()!) == null){ - logger.log('Determining default selected server..') - // TODO what if undefined - ConfigManager.setSelectedServer(data.getMainServer()!.server.id as string) - ConfigManager.save() - } - } - ipcRenderer.send('distributionIndexDone', data != null) -} - -// Ensure Distribution is downloaded and cached. -DistroManager.pullRemote().then((data) => { - logger.log('Loaded distribution index.') - - onDistroLoad(data) - -}).catch((err) => { - logger.log('Failed to load distribution index.') - logger.error(err) - - logger.log('Attempting to load an older version of the distribution index.') - // Try getting a local copy, better than nothing. - DistroManager.pullLocal().then((data) => { - logger.log('Successfully loaded an older version of the distribution index.') - - onDistroLoad(data) - - - }).catch((err) => { - - logger.log('Failed to load an older version of the distribution index.') - logger.log('Application cannot run.') - logger.error(err) - - onDistroLoad(null) - - }) - -}) - -// Clean up temp dir incase previous launches ended unexpectedly. -remove(join(tmpdir(), ConfigManager.getTempNativeFolder()), (err) => { - if(err){ - logger.warn('Error while cleaning natives directory', err) - } else { - logger.log('Cleaned natives directory.') - } -}) diff --git a/src/main/old/processbuilder.ts b/src/main/old/processbuilder.ts deleted file mode 100644 index f37b7b1..0000000 --- a/src/main/old/processbuilder.ts +++ /dev/null @@ -1,755 +0,0 @@ -import AdmZip from 'adm-zip' -import { pathExistsSync, writeFile, ensureDirSync, writeFileSync, remove } from 'fs-extra' -import { join, basename } from 'path' -import { ModuleWrapper, ServerWrapper } from './distromanager' -import { Type, Required } from 'helios-distribution-types' -import { LoggerUtil } from './loggerutil' -import { ConfigManager } from '../../common/config/configmanager' -import { spawn } from 'child_process' -import { SavedAccount } from '../../common/config/model/SavedAccount' -import { tmpdir, release } from 'os' -import { SubModConfig } from '../../common/config/model/ModConfig' -import { pseudoRandomBytes } from 'crypto' -import { Util, LibraryInternal } from './assetguard' -import { VersionJson, Rule } from '../asset/model/mojang/VersionJson' -import { URL } from 'url' - -const logger = new LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold') - -export class ProcessBuilder { - - private gameDir: string - private commonDir: string - private fmlDir: string - private llDir: string - private libPath: string - - private usingLiteLoader: boolean - private llPath: string | null - - constructor( - private wrappedServer: ServerWrapper, - private versionData: VersionJson, - private forgeData: any, // TODO type - private authUser: SavedAccount, - private launcherVersion: string - ){ - this.gameDir = join(ConfigManager.getInstanceDirectory(), wrappedServer.server.id) - this.commonDir = ConfigManager.getCommonDirectory() - this.authUser = authUser - this.launcherVersion = launcherVersion - this.fmlDir = join(this.gameDir, 'forgeModList.json') - this.llDir = join(this.gameDir, 'liteloaderModList.json') - this.libPath = join(this.commonDir, 'libraries') - - this.usingLiteLoader = false - this.llPath = null - } - - /** - * Convienence method to run the functions typically used to build a process. - */ - build(){ - ensureDirSync(this.gameDir) - const tempNativePath = join(tmpdir(), ConfigManager.getTempNativeFolder(), pseudoRandomBytes(16).toString('hex')) - process.throwDeprecation = true - this.setupLiteLoader() - logger.log('Using liteloader:', this.usingLiteLoader) - const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.wrappedServer.server.id)!.mods, this.wrappedServer.getWrappedModules()) - - // Mod list below 1.13 - if(!Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){ - this.constructModList('forge', modObj.fMods, true) - if(this.usingLiteLoader){ - this.constructModList('liteloader', modObj.lMods, true) - } - } - - const uberModArr = modObj.fMods.concat(modObj.lMods) - let args = this.constructJVMArguments(uberModArr, tempNativePath) - - if(Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){ - args = args.concat(this.constructModArguments(modObj.fMods)) - } - - logger.log('Launch Arguments:', args) - - const child = spawn(ConfigManager.getJavaExecutable()!, args, { - cwd: this.gameDir, - detached: ConfigManager.getLaunchDetached() - }) - - if(ConfigManager.getLaunchDetached()){ - child.unref() - } - - child.stdout.setEncoding('utf8') - child.stderr.setEncoding('utf8') - - const loggerMCstdout = new LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold') - const loggerMCstderr = new LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold') - - child.stdout.on('data', (data) => { - loggerMCstdout.log(data) - }) - child.stderr.on('data', (data) => { - loggerMCstderr.log(data) - }) - child.on('close', (code, signal) => { - logger.log('Exited with code', code) - remove(tempNativePath, (err) => { - if(err){ - logger.warn('Error while deleting temp dir', err) - } else { - logger.log('Temp dir deleted successfully.') - } - }) - }) - - return child - } - - /** - * Determine if an optional mod is enabled from its configuration value. If the - * configuration value is null, the required object will be used to - * determine if it is enabled. - * - * A mod is enabled if: - * * The configuration is not null and one of the following: - * * The configuration is a boolean and true. - * * The configuration is an object and its 'value' property is true. - * * The configuration is null and one of the following: - * * The required object is null. - * * The required object's 'def' property is null or true. - * - * @param {SubModConfig | boolean} modCfg The mod configuration object. - * @param {Required | undefined} required Optional. The required object from the mod's distro declaration. - * @returns {boolean} True if the mod is enabled, false otherwise. - */ - public static isModEnabled(modCfg: SubModConfig | boolean, required?: Required){ - return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.def : true - } - - /** - * Function which performs a preliminary scan of the top level - * mods. If liteloader is present here, we setup the special liteloader - * launch options. Note that liteloader is only allowed as a top level - * mod. It must not be declared as a submodule. - */ - private setupLiteLoader(): void { - for(const ll of this.wrappedServer.getWrappedModules()){ - if(ll.module.type === Type.LiteLoader){ - if(!ll.module.required!.value!){ - const modCfg = ConfigManager.getModConfiguration(this.wrappedServer.server.id)!.mods - if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.module.required)){ - if(pathExistsSync(ll.module.artifact.path!)){ - this.usingLiteLoader = true - this.llPath = ll.module.artifact.path! - } - } - } else { - if(pathExistsSync(ll.module.artifact.path!)){ - this.usingLiteLoader = true - this.llPath = ll.module.artifact.path! - } - } - } - } - } - - /** - * Resolve an array of all enabled mods. These mods will be constructed into - * a mod list format and enabled at launch. - * - * @param {{[id: string]: boolean | SubModConfig}} modCfg The mod configuration object. - * @param {Array.} mdls An array of modules to parse. - * @returns {{fMods: Array., lMods: Array.}} An object which contains - * a list of enabled forge mods and litemods. - */ - resolveModConfiguration(modCfg: {[id: string]: boolean | SubModConfig}, mdls: ModuleWrapper[]): {fMods: ModuleWrapper[], lMods: ModuleWrapper[]}{ - let fMods: ModuleWrapper[] = [] - let lMods: ModuleWrapper[] = [] - - for(const mdl of mdls){ - const type = mdl.module.type - if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){ - const o = !mdl.module.required!.value! - const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.module.required) - if(!o || (o && e)){ - if(mdl.hasSubModules()){ - const v = this.resolveModConfiguration((modCfg[mdl.getVersionlessID()] as SubModConfig).mods, mdl.getWrappedSubmodules()) - fMods = fMods.concat(v.fMods) - lMods = lMods.concat(v.lMods) - if(mdl.module.type === Type.LiteLoader){ - continue - } - } - if(mdl.module.type === Type.ForgeMod){ - fMods.push(mdl) - } else { - lMods.push(mdl) - } - } - } - } - - return { - fMods, - lMods - } - } - - _isBelowOneDotSeven() { - return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= 7 - } - - /** - * Test to see if this version of forge requires the absolute: prefix - * on the modListFile repository field. - */ - _requiresAbsolute(){ - try { - if(this._isBelowOneDotSeven()) { - return false - } - const ver = this.forgeData.id.split('-')[2] - const pts = ver.split('.') - const min = [14, 23, 3, 2655] - for(let i=0; i min[i]){ - return true - } - } - } catch (err) { - // We know old forge versions follow this format. - // Error must be caused by newer version. - } - - // Equal or errored - return true - } - - /** - * Construct a mod list json object. - * - * @param {'forge' | 'liteloader'} type The mod list type to construct. - * @param {Array.} mods An array of mods to add to the mod list. - * @param {boolean} save Optional. Whether or not we should save the mod list file. - */ - constructModList(type: 'forge' | 'liteloader', mods: ModuleWrapper[], save = false){ - const modList = { - repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + join(this.commonDir, 'modstore'), - modRef: [] as string[] - } - - const ids = [] - if(type === 'forge'){ - for(let mod of mods){ - ids.push(mod.getExtensionlessID()) - } - } else { - for(let mod of mods){ - ids.push(mod.getExtensionlessID() + '@' + mod.getExtension()) - } - } - modList.modRef = ids - - if(save){ - const json = JSON.stringify(modList, null, 4) - writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, 'UTF-8') - } - - return modList - } - - /** - * Construct the mod argument list for forge 1.13 - * - * @param {Array.} mods An array of mods to add to the mod list. - */ - constructModArguments(mods: ModuleWrapper[]){ - const argStr = mods.map(mod => { - return mod.getExtensionlessID() - }).join(',') - - if(argStr){ - return [ - '--fml.mavenRoots', - join('..', '..', 'common', 'modstore'), - '--fml.mods', - argStr - ] - } else { - return [] - } - - } - - /** - * Construct the argument array that will be passed to the JVM process. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - constructJVMArguments(mods: ModuleWrapper[], tempNativePath: string): string[] { - if(Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){ - return this._constructJVMArguments113(mods, tempNativePath) - } else { - return this._constructJVMArguments112(mods, tempNativePath) - } - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.12 and below. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments112(mods: ModuleWrapper[], tempNativePath: string): string[] { - - let args = [] - - // Classpath Argument - args.push('-cp') - args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')) - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) - args.push('-Djava.library.path=' + tempNativePath) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Forge Arguments - args = args.concat(this._resolveForgeArgs()) - - return args - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.13+ - * - * Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82 - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments113(mods: ModuleWrapper[], tempNativePath: string): string[] { - - const argDiscovery = /\${*(.*)}/ - - // JVM Arguments First - let args: (string | { rules: Rule[], value: string[] })[] = this.versionData.arguments.jvm - - //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml') - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Vanilla Arguments - args = args.concat(this.versionData.arguments.game) - - for(let i=0; i { - return arg != null - }) - - return args as string[] - } - - /** - * Resolve the arguments required by forge. - * - * @returns {Array.} An array containing the arguments required by forge. - */ - _resolveForgeArgs(): string[] { - const mcArgs: string[] = this.forgeData.minecraftArguments.split(' ') - const argDiscovery = /\${*(.*)}/ - - // Replace the declared variables with their proper values. - for(let i=0; i} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the paths of each library required by this process. - */ - classpathArg(mods: ModuleWrapper[], tempNativePath: string): string[] { - let cpArgs: string[] = [] - - // Add the version.jar to the classpath. - const version = this.versionData.id - cpArgs.push(join(this.commonDir, 'versions', version, version + '.jar')) - - if(this.usingLiteLoader){ - cpArgs.push(this.llPath!) - } - - // Resolve the Mojang declared libraries. - const mojangLibs = this._resolveMojangLibraries(tempNativePath) - - // Resolve the server declared libraries. - const servLibs = this._resolveServerLibraries(mods) - - // Merge libraries, server libs with the same - // maven identifier will override the mojang ones. - // Ex. 1.7.10 forge overrides mojang's guava with newer version. - const finalLibs = {...mojangLibs, ...servLibs} - cpArgs = cpArgs.concat(Object.values(finalLibs)) - - return cpArgs - } - - /** - * Resolve the libraries defined by Mojang's version data. This method will also extract - * native libraries and point to the correct location for its classpath. - * - * TODO - clean up function - * - * @param {string} tempNativePath The path to store the native libraries. - * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. - */ - _resolveMojangLibraries(tempNativePath: string): {[id: string]: string} { - const libs: {[id: string]: string} = {} - - const libArr = this.versionData.libraries - ensureDirSync(tempNativePath) - for(let i=0; i { - if(fileName.indexOf(exclusion) > -1){ - shouldExclude = true - } - }) - - // Extract the file. - if(!shouldExclude){ - writeFile(join(tempNativePath, fileName), zipEntries[i].getData(), (err) => { - if(err){ - logger.error('Error while extracting native library:', err) - } - }) - } - - } - } - } - } - - return libs - } - - /** - * Resolve the libraries declared by this server in order to add them to the classpath. - * This method will also check each enabled mod for libraries, as mods are permitted to - * declare libraries. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @returns {{[id: string]: string}} An object containing the paths of each library this server requires. - */ - _resolveServerLibraries(mods: ModuleWrapper[]): {[id: string]: string} { - const mdls: ModuleWrapper[] = this.wrappedServer.getWrappedModules() - let libs: {[id: string]: string} = {} - - // Locate Forge/Libraries - for(let mdl of mdls){ - const type = mdl.module.type - if(type === Type.ForgeHosted || type === Type.Library){ - libs[mdl.getVersionlessID()] = mdl.module.artifact.path as string - if(mdl.hasSubModules()){ - const res = this._resolveModuleLibraries(mdl) - if(Object.keys(res).length > 0){ - libs = {...libs, ...res} - } - } - } - } - - //Check for any libraries in our mod list. - for(let i=0; i 0){ - libs = {...libs, ...res} - } - } - } - - return libs - } - - /** - * Recursively resolve the path of each library required by this module. - * - * @param {ModuleWrapper} mdl A module object from the server distro index. - * @returns {Array.} An array containing the paths of each library this module requires. - */ - _resolveModuleLibraries(mdl: ModuleWrapper): {[id: string]: string} { - if(!mdl.hasSubModules()){ - return {} - } - let libs: {[id: string]: string} = {} - for(const sm of mdl.getWrappedSubmodules()){ - if(sm.module.type === Type.Library){ - libs[sm.getVersionlessID()] = sm.module.artifact.path as string - } - // 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()){ - const res = this._resolveModuleLibraries(sm) - if(Object.keys(res).length > 0){ - libs = {...libs, ...res} - } - } - } - return libs - } -} \ No newline at end of file diff --git a/src/main/old/processbuilderold.js b/src/main/old/processbuilderold.js deleted file mode 100644 index 433d8c7..0000000 --- a/src/main/old/processbuilderold.js +++ /dev/null @@ -1,738 +0,0 @@ -const AdmZip = require('adm-zip') -const child_process = require('child_process') -const crypto = require('crypto') -const fs = require('fs-extra') -const os = require('os') -const path = require('path') -const { URL } = require('url') - -const { Util, Library: LibraryInternal } = require('./assetguard') -const ConfigManager = require('./configmanager') -const DistroManager = require('./distromanager') -const LoggerUtil = require('./loggerutil') - -const logger = LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold') - -class ProcessBuilder { - - constructor(distroServer, versionData, forgeData, authUser, launcherVersion){ - this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.getID()) - this.commonDir = ConfigManager.getCommonDirectory() - this.server = distroServer - this.versionData = versionData - this.forgeData = forgeData - this.authUser = authUser - this.launcherVersion = launcherVersion - this.fmlDir = path.join(this.gameDir, 'forgeModList.json') - this.llDir = path.join(this.gameDir, 'liteloaderModList.json') - this.libPath = path.join(this.commonDir, 'libraries') - - this.usingLiteLoader = false - this.llPath = null - } - - /** - * Convienence method to run the functions typically used to build a process. - */ - build(){ - fs.ensureDirSync(this.gameDir) - const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex')) - process.throwDeprecation = true - this.setupLiteLoader() - logger.log('Using liteloader:', this.usingLiteLoader) - const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules()) - - // Mod list below 1.13 - if(!Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - this.constructModList('forge', modObj.fMods, true) - if(this.usingLiteLoader){ - this.constructModList('liteloader', modObj.lMods, true) - } - } - - const uberModArr = modObj.fMods.concat(modObj.lMods) - let args = this.constructJVMArguments(uberModArr, tempNativePath) - - if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - args = args.concat(this.constructModArguments(modObj.fMods)) - } - - logger.log('Launch Arguments:', args) - - const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, { - cwd: this.gameDir, - detached: ConfigManager.getLaunchDetached() - }) - - if(ConfigManager.getLaunchDetached()){ - child.unref() - } - - child.stdout.setEncoding('utf8') - child.stderr.setEncoding('utf8') - - const loggerMCstdout = LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold') - const loggerMCstderr = LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold') - - child.stdout.on('data', (data) => { - loggerMCstdout.log(data) - }) - child.stderr.on('data', (data) => { - loggerMCstderr.log(data) - }) - child.on('close', (code, signal) => { - logger.log('Exited with code', code) - fs.remove(tempNativePath, (err) => { - if(err){ - logger.warn('Error while deleting temp dir', err) - } else { - logger.log('Temp dir deleted successfully.') - } - }) - }) - - return child - } - - /** - * Determine if an optional mod is enabled from its configuration value. If the - * configuration value is null, the required object will be used to - * determine if it is enabled. - * - * A mod is enabled if: - * * The configuration is not null and one of the following: - * * The configuration is a boolean and true. - * * The configuration is an object and its 'value' property is true. - * * The configuration is null and one of the following: - * * The required object is null. - * * The required object's 'def' property is null or true. - * - * @param {Object | boolean} modCfg The mod configuration object. - * @param {Object} required Optional. The required object from the mod's distro declaration. - * @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 - } - - /** - * Function which performs a preliminary scan of the top level - * mods. If liteloader is present here, we setup the special liteloader - * launch options. Note that liteloader is only allowed as a top level - * 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())){ - this.usingLiteLoader = true - this.llPath = ll.getArtifact().getPath() - } - } - } else { - if(fs.existsSync(ll.getArtifact().getPath())){ - this.usingLiteLoader = true - this.llPath = ll.getArtifact().getPath() - } - } - } - } - } - - /** - * Resolve an array of all enabled mods. These mods will be constructed into - * a mod list format and enabled at launch. - * - * @param {Object} modCfg The mod configuration object. - * @param {Array.} mdls An array of modules to parse. - * @returns {{fMods: Array., lMods: Array.}} An object which contains - * a list of enabled forge mods and litemods. - */ - resolveModConfiguration(modCfg, mdls){ - let fMods = [] - 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()) - if(!o || (o && e)){ - if(mdl.hasSubModules()){ - const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessID()].mods, mdl.getSubModules()) - fMods = fMods.concat(v.fMods) - lMods = lMods.concat(v.lMods) - if(mdl.type === DistroManager.Types.LiteLoader){ - continue - } - } - if(mdl.type === DistroManager.Types.ForgeMod){ - fMods.push(mdl) - } else { - lMods.push(mdl) - } - } - } - } - - return { - fMods, - lMods - } - } - - _isBelowOneDotSeven() { - return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= 7 - } - - /** - * Test to see if this version of forge requires the absolute: prefix - * on the modListFile repository field. - */ - _requiresAbsolute(){ - try { - if(this._isBelowOneDotSeven()) { - return false - } - const ver = this.forgeData.id.split('-')[2] - const pts = ver.split('.') - const min = [14, 23, 3, 2655] - for(let i=0; i min[i]){ - return true - } - } - } catch (err) { - // We know old forge versions follow this format. - // Error must be caused by newer version. - } - - // Equal or errored - return true - } - - /** - * Construct a mod list json object. - * - * @param {'forge' | 'liteloader'} type The mod list type to construct. - * @param {Array.} mods An array of mods to add to the mod list. - * @param {boolean} save Optional. Whether or not we should save the mod list file. - */ - constructModList(type, mods, save = false){ - const modList = { - repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + path.join(this.commonDir, 'modstore') - } - - const ids = [] - if(type === 'forge'){ - for(let mod of mods){ - ids.push(mod.getExtensionlessID()) - } - } else { - for(let mod of mods){ - ids.push(mod.getExtensionlessID() + '@' + mod.getExtension()) - } - } - modList.modRef = ids - - if(save){ - const json = JSON.stringify(modList, null, 4) - fs.writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, 'UTF-8') - } - - return modList - } - - /** - * Construct the mod argument list for forge 1.13 - * - * @param {Array.} mods An array of mods to add to the mod list. - */ - constructModArguments(mods){ - const argStr = mods.map(mod => { - return mod.getExtensionlessID() - }).join(',') - - if(argStr){ - return [ - '--fml.mavenRoots', - path.join('..', '..', 'common', 'modstore'), - '--fml.mods', - argStr - ] - } else { - return [] - } - - } - - /** - * Construct the argument array that will be passed to the JVM process. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - constructJVMArguments(mods, tempNativePath){ - if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - return this._constructJVMArguments113(mods, tempNativePath) - } else { - return this._constructJVMArguments112(mods, tempNativePath) - } - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.12 and below. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments112(mods, tempNativePath){ - - let args = [] - - // Classpath Argument - args.push('-cp') - args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')) - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) - args.push('-Djava.library.path=' + tempNativePath) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Forge Arguments - args = args.concat(this._resolveForgeArgs()) - - return args - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.13+ - * - * Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82 - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments113(mods, tempNativePath){ - - const argDiscovery = /\${*(.*)}/ - - // JVM Arguments First - let args = this.versionData.arguments.jvm - - //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml') - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM()) - args.push('-Xms' + ConfigManager.getMinRAM()) - args = args.concat(ConfigManager.getJVMOptions()) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Vanilla Arguments - args = args.concat(this.versionData.arguments.game) - - for(let i=0; i { - return arg != null - }) - - return args - } - - /** - * Resolve the arguments required by forge. - * - * @returns {Array.} An array containing the arguments required by forge. - */ - _resolveForgeArgs(){ - const mcArgs = this.forgeData.minecraftArguments.split(' ') - const argDiscovery = /\${*(.*)}/ - - // Replace the declared variables with their proper values. - for(let i=0; i} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the paths of each library required by this process. - */ - classpathArg(mods, tempNativePath){ - let cpArgs = [] - - // Add the version.jar to the classpath. - const version = this.versionData.id - cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) - - if(this.usingLiteLoader){ - cpArgs.push(this.llPath) - } - - // Resolve the Mojang declared libraries. - const mojangLibs = this._resolveMojangLibraries(tempNativePath) - - // Resolve the server declared libraries. - const servLibs = this._resolveServerLibraries(mods) - - // Merge libraries, server libs with the same - // maven identifier will override the mojang ones. - // Ex. 1.7.10 forge overrides mojang's guava with newer version. - const finalLibs = {...mojangLibs, ...servLibs} - cpArgs = cpArgs.concat(Object.values(finalLibs)) - - return cpArgs - } - - /** - * Resolve the libraries defined by Mojang's version data. This method will also extract - * native libraries and point to the correct location for its classpath. - * - * TODO - clean up function - * - * @param {string} tempNativePath The path to store the native libraries. - * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. - */ - _resolveMojangLibraries(tempNativePath){ - const libs = {} - - const libArr = this.versionData.libraries - fs.ensureDirSync(tempNativePath) - for(let i=0; i -1){ - shouldExclude = true - } - }) - - // Extract the file. - if(!shouldExclude){ - fs.writeFile(path.join(tempNativePath, fileName), zipEntries[i].getData(), (err) => { - if(err){ - logger.error('Error while extracting native library:', err) - } - }) - } - - } - } - } - } - - return libs - } - - /** - * Resolve the libraries declared by this server in order to add them to the classpath. - * This method will also check each enabled mod for libraries, as mods are permitted to - * declare libraries. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @returns {{[id: string]: string}} An object containing the paths of each library this server requires. - */ - _resolveServerLibraries(mods){ - const mdls = this.server.getModules() - 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 res = this._resolveModuleLibraries(mdl) - if(res.length > 0){ - libs = {...libs, ...res} - } - } - } - } - - //Check for any libraries in our mod list. - for(let i=0; i 0){ - libs = {...libs, ...res} - } - } - } - - return libs - } - - /** - * Recursively resolve the path of each library required by this module. - * - * @param {Object} mdl A module object from the server distro index. - * @returns {Array.} An array containing the paths of each library this module requires. - */ - _resolveModuleLibraries(mdl){ - if(!mdl.hasSubModules()){ - return [] - } - let libs = [] - for(let sm of mdl.getSubModules()){ - if(sm.getType() === DistroManager.Types.Library){ - libs.push(sm.getArtifact().getPath()) - } - // If this module has submodules, we need to resolve the libraries for those. - // To avoid unnecessary recursive calls, base case is checked here. - if(mdl.hasSubModules()){ - const res = this._resolveModuleLibraries(sm) - if(res.length > 0){ - libs = libs.concat(res) - } - } - } - return libs - } -} - -module.exports = ProcessBuilder \ No newline at end of file diff --git a/src/main/old/serverstatus.ts b/src/main/old/serverstatus.ts deleted file mode 100644 index 63c6c6e..0000000 --- a/src/main/old/serverstatus.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { connect } from 'net' - -/** - * Retrieves the status of a minecraft server. - * - * @param {string} address The server address. - * @param {number} port Optional. The port of the server. Defaults to 25565. - * @returns {Promise.} A promise which resolves to an object containing - * status information. - */ -export function getStatus(address: string, port: number | string = 25565){ - - let sanitizedPort: number - - if(port == null || port == ''){ - sanitizedPort = 25565 - } - if(typeof port === 'string'){ - sanitizedPort = parseInt(port) - } - - return new Promise((resolve, reject) => { - const socket = connect(sanitizedPort, address, () => { - let buff = Buffer.from([0xFE, 0x01]) - socket.write(buff) - }) - - socket.setTimeout(2500, () => { - socket.end() - reject({ - code: 'ETIMEDOUT', - errno: 'ETIMEDOUT', - address, - sanitizedPort - }) - }) - - socket.on('data', (data) => { - if(data != null){ - let server_info = data.toString().split('\x00\x00\x00') - const NUM_FIELDS = 6 - if(server_info != null && server_info.length >= NUM_FIELDS){ - resolve({ - online: true, - version: server_info[2].replace(/\u0000/g, ''), - motd: server_info[3].replace(/\u0000/g, ''), - onlinePlayers: server_info[4].replace(/\u0000/g, ''), - maxPlayers: server_info[5].replace(/\u0000/g,'') - }) - } else { - resolve({ - online: false - }) - } - } - socket.end() - }) - - socket.on('error', (err) => { - socket.destroy() - reject(err) - // ENOTFOUND = Unable to resolve. - // ECONNREFUSED = Unable to connect to port. - }) - }) - -} \ No newline at end of file