diff --git a/README.md b/README.md index 9e8eab4f..161b2ae0 100644 --- a/README.md +++ b/README.md @@ -104,4 +104,6 @@ Run either of the build scrips noted below. Note that each platform can only be If you run into any issue which cannot be resolved via a quick google search, create an issue using the tab above. -Much of the discussion regarding this launcher is done on Discord, feel free to join us there [![Discord](https://discordapp.com/api/guilds/98469309352775680/widget.png)](https://discord.gg/hqdjs3m) \ No newline at end of file +Much of the discussion regarding this launcher is done on #launcherdev in Discord, feel free to join us there. + +[![Discord](https://discordapp.com/api/guilds/98469309352775680/embed.png?style=banner2)](https://discord.gg/hqdjs3m) \ No newline at end of file diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index 4f0a97af..246c3731 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -1210,15 +1210,38 @@ p { display: flex; flex-direction: column; align-items: center; - justify-content: space-between; + /*justify-content: space-between;*/ width: 300px; - height: 35%; + /*height: 35%;*/ box-sizing: border-box; padding: 15px 0px; /* background-color: #424242; */ text-align: center; } +#overlayContent a { + color: rgba(202, 202, 202, 0.75); + transition: 0.25s ease; +} +#overlayContent a:hover { + color: rgba(255, 255, 255, 0.75); +} +#overlayContent a:active { + color: rgba(165, 165, 165, 0.75); +} + +#overlayContent > *:first-child { + margin-top: 0px !important; +} + +#overlayContent > *:last-child { + margin-bottom: 0px !important; +} + +#overlayContent > * { + margin: 8px 0px; +} + #overlayTitle { font-family: 'Avenir Medium'; font-size: 20px; diff --git a/app/assets/js/actionbinder.js b/app/assets/js/actionbinder.js index 5872813c..22dffe25 100644 --- a/app/assets/js/actionbinder.js +++ b/app/assets/js/actionbinder.js @@ -39,6 +39,11 @@ document.addEventListener('readystatechange', function(){ if(jExe == null){ asyncSystemScan() } else { + + setLaunchDetails('Please wait..') + toggleLaunchArea(true) + setLaunchPercentage(0, 100) + AssetGuard._validateJavaBinary(jExe).then((v) => { if(v){ dlAsync() @@ -194,12 +199,13 @@ function setDownloadPercentage(value, max, percent = ((value/max)*100)){ let sysAEx let scanAt -function asyncSystemScan(){ +function asyncSystemScan(launchAfter = true){ setLaunchDetails('Please wait..') toggleLaunchArea(true) setLaunchPercentage(0, 100) + // Fork a process to run validations. sysAEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ ConfigManager.getGameDirectory(), ConfigManager.getJavaExecutable() @@ -207,21 +213,96 @@ function asyncSystemScan(){ sysAEx.on('message', (m) => { if(m.content === 'validateJava'){ - jPath = m.result - console.log(m.result) - sysAEx.disconnect() + + //m.result = null + + if(m.result == null){ + // If the result is null, no valid Java installation was found. + // Show this information to the user. + setOverlayContent( + 'No Compatible
Java Installation Found..', + 'In order to join WesterosCraft, you need a 64-bit installation of Java 8. Would you like us to install a copy? By installing, you accept Oracle\'s license agreement.', + 'Install Java' + ) + setOverlayHandler(() => { + setLaunchDetails('Preparing Java Download..') + sysAEx.send({task: 0, content: '_enqueueOracleJRE', argsArr: [ConfigManager.getLauncherDirectory()]}) + toggleOverlay(false) + }) + toggleOverlay(true) + + // TODO Add option to not install Java x64. + + } else { + // Java installation found, use this to launch the game. + ConfigManager.setJavaExecutable(m.result) + ConfigManager.save() + if(launchAfter){ + dlAsync() + } + sysAEx.disconnect() + } + + } else if(m.content === '_enqueueOracleJRE'){ + + if(m.result === true){ + + // Oracle JRE enqueued successfully, begin download. + setLaunchDetails('Downloading Java..') + sysAEx.send({task: 0, content: 'processDlQueues', argsArr: [[{id:'java', limit:1}]]}) + + } else { + + // Oracle JRE enqueue failed. Probably due to a change in their website format. + // User will have to follow the guide to install Java. + setOverlayContent( + 'Yikes!
Java download failed.', + 'Unfortunately we\'ve encountered an issue while attempting to install Java. You will need to install a copy yourself. Please check out this guide for more details and instructions.', + 'Got it' + ) + setOverlayHandler(null) + toggleOverlay(true) + sysAEx.disconnect() + + } + + } else if(m.content === 'dl'){ + + if(m.task === 0){ + // Downloading.. + setDownloadPercentage(m.value, m.total, m.percent) + } else if(m.task === 1){ + // Download will be at 100%, remove the loading from the OS progress bar. + remote.getCurrentWindow().setProgressBar(-1) + + // Wait for extration to complete. + setLaunchDetails('Extracting..') + + } else if(m.task === 2){ + + // Extraction completed successfully. + ConfigManager.setJavaExecutable(m.jPath) + ConfigManager.save() + + setLaunchDetails('Java Installed!') + + if(launchAfter){ + dlAsync() + } + + sysAEx.disconnect() + } else { + console.error('Unknown download data type.', m) + } } }) + // Begin system Java scan. setLaunchDetails('Checking system info..') sysAEx.send({task: 0, content: 'validateJava', argsArr: [ConfigManager.getLauncherDirectory()]}) } -function overlayError(){ - -} - // Keep reference to Minecraft Process let proc // Is DiscordRPC enabled @@ -283,12 +364,18 @@ function dlAsync(login = true){ } else if(m.content === 'validateAssets'){ - setLaunchPercentage(60, 100) - console.log('Asset Validation Complete') + // Asset validation can *potentially* take longer, so let's track progress. + if(m.task === 0){ + const perc = (m.value/m.total)*20 + setLaunchPercentage(40+perc, 100, parseInt(40+perc)) + } else { + setLaunchPercentage(60, 100) + console.log('Asset Validation Complete') - // Begin library validation. - setLaunchDetails('Validating library integrity..') - aEx.send({task: 0, content: 'validateLibraries', argsArr: [versionData]}) + // Begin library validation. + setLaunchDetails('Validating library integrity..') + aEx.send({task: 0, content: 'validateLibraries', argsArr: [versionData]}) + } } else if(m.content === 'validateLibraries'){ diff --git a/app/assets/js/assetexec.js b/app/assets/js/assetexec.js index 5d41fed9..7a75d90b 100644 --- a/app/assets/js/assetexec.js +++ b/app/assets/js/assetexec.js @@ -6,6 +6,10 @@ console.log('AssetExec Started') // Temporary for debug purposes. process.on('unhandledRejection', r => console.log(r)) +tracker.on('assetVal', (data) => { + process.send({task: 0, total: data.total, value: data.acc, content: 'validateAssets'}) +}) + tracker.on('totaldlprogress', (data) => { process.send({task: 0, total: data.total, value: data.acc, percent: parseInt((data.acc/data.total)*100), content: 'dl'}) }) @@ -14,6 +18,10 @@ tracker.on('dlcomplete', () => { process.send({task: 1, content: 'dl'}) }) +tracker.on('jExtracted', (jPath) => { + process.send({task: 2, content: 'dl', jPath}) +}) + process.on('message', (msg) => { if(msg.task === 0){ const func = msg.content diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index 5a713eb9..f8c50633 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -32,7 +32,8 @@ const mkpath = require('mkdirp'); const path = require('path') const Registry = require('winreg') const request = require('request') -const targz = require('targz') +const tar = require('tar-fs') +const zlib = require('zlib') // Constants const PLATFORM_MAP = { @@ -558,6 +559,23 @@ class AssetGuard extends EventEmitter { }) } + /** + * 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. + */ + static javaExecFromRoot(rootDir){ + if(process.platform === 'win32'){ + return path.join(rootDir, 'bin', 'javaw.exe') + } else if(process.platform === 'darwin'){ + return path.join(rootDir, 'Contents', 'Home', 'bin', 'java') + } else if(process.platform === 'linux'){ + return path.join(rootDir, 'bin', 'java') + } + return rootDir + } + /** * Load Mojang's launcher.json file. * @@ -583,20 +601,16 @@ class AssetGuard extends EventEmitter { * the function's code throws errors. That would indicate that the option is changed or * removed. * - * @param {string} binaryPath Path to the root of the java binary we wish to validate. + * @param {string} binaryExecPath Path to the java executable we wish to validate. * * @returns {Promise.} Resolves to false only if the test is successful and the result * is less than 64. */ - static _validateJavaBinary(binaryPath){ + static _validateJavaBinary(binaryExecPath){ return new Promise((resolve, reject) => { - let fBp = binaryPath - if(!fBp.endsWith('.exe')){ - fBp = path.join(binaryPath, 'bin', 'java.exe') - } - if(fs.existsSync(fBp)){ - child_process.exec('"' + fBp + '" -XshowSettings:properties', (err, stdout, stderr) => { + if(fs.existsSync(binaryExecPath)){ + child_process.exec('"' + binaryExecPath + '" -XshowSettings:properties', (err, stdout, stderr) => { try { // Output is stored in stderr? @@ -605,7 +619,7 @@ class AssetGuard extends EventEmitter { for(let i=0; i -1){ let arch = props[i].split('=')[1].trim() - console.log(props[i].trim() + ' for ' + binaryPath) + console.log(props[i].trim() + ' for ' + binaryExecPath) resolve(parseInt(arch) >= 64) } } @@ -770,7 +784,7 @@ class AssetGuard extends EventEmitter { * If versions are equal, JRE > JDK. * * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the root path of a valid + * @returns {Promise.} A Promise which resolves to the executable path of a valid * x64 Java installation. If none are found, null is returned. */ static async _win32JavaValidate(dataDir){ @@ -813,9 +827,10 @@ class AssetGuard extends EventEmitter { // Validate that the binary is actually x64. for(let i=0; i { AssetGuard._latestJREOracle().then(verData => { if(verData != null){ @@ -1269,24 +1288,37 @@ class AssetGuard extends EventEmitter { if(err){ resolve(false) } else { + dataDir = path.join(dataDir, 'runtime', 'x64') const name = combined.substring(combined.lastIndexOf('/')+1) - const fDir = path.join(dir, name) + const fDir = path.join(dataDir, name) const jre = new Asset(name, null, resp.headers['content-length'], opts, fDir) - this.java = new DLTracker([jre], jre.size, a => { - targz.decompress({ - src: a.to, - dest: dir - }, err => { - if(err){ - console.log(err) - } else { + 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('jExtracted', AssetGuard.javaExecFromRoot(pos)) }) - } - }) + }) + }) resolve(true) } @@ -1371,7 +1403,7 @@ class AssetGuard extends EventEmitter { writeStream.on('close', () => { //console.log('DLResults ' + asset.size + ' ' + count + ' ', asset.size === count) if(concurrentDlTracker.callback != null){ - concurrentDlTracker.callback.apply(concurrentDlTracker, [asset]) + concurrentDlTracker.callback.apply(concurrentDlTracker, [asset, self]) } cb() }) diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index 8899d134..4ce6dc8f 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -25,7 +25,7 @@ const DEFAULT_CONFIG = { java: { minRAM: '2G', maxRAM: resolveMaxRAM(), // Dynamic - executable: 'C:\\Program Files\\Java\\jdk1.8.0_152\\bin\\javaw.exe', // TODO Resolve + executable: null, jvmOptions: [ '-XX:+UseConcMarkSweepGC', '-XX:+CMSIncrementalMode', diff --git a/package-lock.json b/package-lock.json index 407b9951..54267aee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -246,7 +246,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "safe-buffer": "5.1.1" }, "dependencies": { @@ -256,23 +256,23 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", - "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "5.1.1" } @@ -2750,7 +2750,7 @@ "requires": { "bl": "1.2.2", "end-of-stream": "1.4.1", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -2760,23 +2760,23 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", - "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "5.1.1" } @@ -2788,14 +2788,6 @@ } } }, - "targz": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", - "integrity": "sha1-j3alI2lM3t+7XWCkB2/27uzFOY8=", - "requires": { - "tar-fs": "1.16.0" - } - }, "temp-file": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.1.1.tgz", diff --git a/package.json b/package.json index 646ea0cd..03183698 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ejs-electron": "^2.0.1", "jquery": "^3.3.1", "request-promise-native": "^1.0.5", - "targz": "^1.0.1", + "tar-fs": "^1.16.0", "uuid": "^3.2.1", "winreg": "^1.2.4" },