Implemented Java validations within the UI.

When a user attemps to launch, the configured Java executable will be validated. If it is invalid, we will look for a valid installation. If no valid installation is found, the user will be prompted with an option to install Java. An option to decline needs to be added. If they choose to install, it will download, extract, and update the executable in the config. The game will then be launched.

Also added progress tracking for asset validations, as they can potentially take a bit longer. Showing progress assures the user that the program isn't stuck or broken.
pull/1/head
Daniel Scalzi 2018-04-07 18:06:49 -04:00
parent 9b63d9bb58
commit 0a79634b8a
No known key found for this signature in database
GPG Key ID: 5CA2F145B63535F9
8 changed files with 214 additions and 70 deletions

View File

@ -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)
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)

View File

@ -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;

View File

@ -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<br>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 <a href="http://www.oracle.com/technetwork/java/javase/terms/license/index.html">Oracle\'s license agreement</a>.',
'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!<br>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 <a href="http://westeroscraft.wikia.com/wiki/Troubleshooting_Guide">this guide</a> 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'){

View File

@ -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

View File

@ -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.<boolean>} 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<props.length; i++){
if(props[i].indexOf('sun.arch.data.model') > -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.<string>} A Promise which resolves to the root path of a valid
* @returns {Promise.<string>} 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<pathArr.length; i++) {
let res = await AssetGuard._validateJavaBinary(pathArr[i])
const execPath = AssetGuard.javaExecFromRoot(pathArr[i])
let res = await AssetGuard._validateJavaBinary(execPath)
if(res){
return pathArr[i]
return execPath
}
}
@ -961,9 +976,13 @@ class AssetGuard extends EventEmitter {
const objectPath = path.join(localPath, 'objects')
const assetDlQueue = []
let dlSize = 0;
let dlSize = 0
let acc = 0
const total = Object.keys(indexData.objects).length
//const objKeys = Object.keys(data.objects)
async.forEachOfLimit(indexData.objects, 10, function(value, key, cb){
acc++
self.emit('assetVal', {acc, total})
const hash = value.hash
const assetName = path.join(hash.substring(0, 2), hash)
const urlName = hash.substring(0, 2) + "/" + hash
@ -1128,7 +1147,7 @@ class AssetGuard extends EventEmitter {
self.forge = self._parseDistroModules(serv.modules, serv.mc_version)
//Correct our workaround here.
let decompressqueue = self.forge.callback
self.forge.callback = function(asset){
self.forge.callback = function(asset, self){
if(asset.to.toLowerCase().endsWith('.pack.xz')){
AssetGuard._extractPackXZ([asset.to], self.javaexec)
}
@ -1251,7 +1270,7 @@ class AssetGuard extends EventEmitter {
// Java (Category=''') Validation (download) Functions
// #region
_enqueueOracleJRE(dir){
_enqueueOracleJRE(dataDir){
return new Promise((resolve, reject) => {
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()
})

View File

@ -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',

40
package-lock.json generated
View File

@ -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",

View File

@ -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"
},