diff --git a/app/assets/js/actionbinder.js b/app/assets/js/actionbinder.js index 2dfb43d4..ace4ff57 100644 --- a/app/assets/js/actionbinder.js +++ b/app/assets/js/actionbinder.js @@ -35,8 +35,18 @@ document.addEventListener('readystatechange', function(){ // Bind launch button document.getElementById('launch_button').addEventListener('click', function(e){ console.log('Launching game..') - //testdownloads() - dlAsync() + const jExe = ConfigManager.getJavaExecutable() + if(jExe == null){ + asyncSystemScan() + } else { + AssetGuard._validateJavaBinary(jExe).then((v) => { + if(v){ + dlAsync() + } else { + asyncSystemScan() + } + }) + } }) // TODO convert this to dropdown menu. @@ -79,6 +89,64 @@ document.addEventListener('readystatechange', function(){ } }, false) +/* Launch Progress Wrapper Functions */ + +function toggleLaunchArea(loading){ + if(loading){ + launch_details.style.display = 'flex' + launch_content.style.display = 'none' + } else { + launch_details.style.display = 'none' + launch_content.style.display = 'inline-flex' + } +} + +function setLaunchDetails(details){ + launch_details_text.innerHTML = details +} + +function setLaunchPercentage(value, max, percent = ((value/max)*100)){ + launch_progress.setAttribute('max', max) + launch_progress.setAttribute('value', value) + launch_progress_label.innerHTML = percent + '%' +} + +function setDownloadPercentage(value, max, percent = ((value/max)*100)){ + remote.getCurrentWindow().setProgressBar(value/max) + setLaunchPercentage(value, max, percent) +} + +let sysAEx +let scanAt + +function asyncSystemScan(){ + + setLaunchDetails('Please wait..') + toggleLaunchArea(true) + setLaunchPercentage(0, 100) + + sysAEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ + ConfigManager.getGameDirectory(), + ConfigManager.getJavaExecutable() + ]) + + sysAEx.on('message', (m) => { + if(m.content === 'validateJava'){ + jPath = m.result + console.log(m.result) + sysAEx.disconnect() + } + }) + + 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 @@ -89,7 +157,6 @@ const gameJoined = /\[[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\] \[Client thread\/WARN\] const gameJoined2 = /\[[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\] \[Client thread\/INFO\]: Created: \d+x\d+ textures-atlas/g let aEx -let currentProc let serv let versionData let forgeData @@ -107,89 +174,86 @@ function dlAsync(login = true){ } } - launch_details_text.innerHTML = 'Please wait..' - launch_progress.setAttribute('max', '100') - launch_details.style.display = 'flex' - launch_content.style.display = 'none' + setLaunchDetails('Please wait..') + toggleLaunchArea(true) + setLaunchPercentage(0, 100) + // Start AssetExec to run validations and downloads in a forked process. aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ ConfigManager.getGameDirectory(), ConfigManager.getJavaExecutable() ]) + // Establish communications between the AssetExec and current process. aEx.on('message', (m) => { - if(currentProc === 'validateDistribution'){ + if(m.content === 'validateDistribution'){ - launch_progress.setAttribute('value', 20) - launch_progress_label.innerHTML = '20%' + setLaunchPercentage(20, 100) serv = m.result - console.log('forge stuff done') + console.log('Forge Validation Complete.') // Begin version load. - launch_details_text.innerHTML = 'Loading version information..' - currentProc = 'loadVersionData' - aEx.send({task: 0, content: currentProc, argsArr: [serv.mc_version]}) + setLaunchDetails('Loading version information..') + aEx.send({task: 0, content: 'loadVersionData', argsArr: [serv.mc_version]}) - } else if(currentProc === 'loadVersionData'){ + } else if(m.content === 'loadVersionData'){ - launch_progress.setAttribute('value', 40) - launch_progress_label.innerHTML = '40%' + setLaunchPercentage(40, 100) versionData = m.result + console.log('Version data loaded.') // Begin asset validation. - launch_details_text.innerHTML = 'Validating asset integrity..' - currentProc = 'validateAssets' - aEx.send({task: 0, content: currentProc, argsArr: [versionData]}) + setLaunchDetails('Validating asset integrity..') + aEx.send({task: 0, content: 'validateAssets', argsArr: [versionData]}) - } else if(currentProc === 'validateAssets'){ + } else if(m.content === 'validateAssets'){ - launch_progress.setAttribute('value', 60) - launch_progress_label.innerHTML = '60%' - console.log('assets done') + setLaunchPercentage(60, 100) + console.log('Asset Validation Complete') // Begin library validation. - launch_details_text.innerHTML = 'Validating library integrity..' - currentProc = 'validateLibraries' - aEx.send({task: 0, content: currentProc, argsArr: [versionData]}) + setLaunchDetails('Validating library integrity..') + aEx.send({task: 0, content: 'validateLibraries', argsArr: [versionData]}) - } else if(currentProc === 'validateLibraries'){ + } else if(m.content === 'validateLibraries'){ - launch_progress.setAttribute('value', 80) - launch_progress_label.innerHTML = '80%' - console.log('libs done') + setLaunchPercentage(80, 100) + console.log('Library validation complete.') // Begin miscellaneous validation. - launch_details_text.innerHTML = 'Validating miscellaneous file integrity..' - currentProc = 'validateMiscellaneous' - aEx.send({task: 0, content: currentProc, argsArr: [versionData]}) + setLaunchDetails('Validating miscellaneous file integrity..') + aEx.send({task: 0, content: 'validateMiscellaneous', argsArr: [versionData]}) - } else if(currentProc === 'validateMiscellaneous'){ + } else if(m.content === 'validateMiscellaneous'){ - launch_progress.setAttribute('value', 100) - launch_progress_label.innerHTML = '100%' - console.log('files done') + setLaunchPercentage(100, 100) + console.log('File validation complete.') - launch_details_text.innerHTML = 'Downloading files..' - currentProc = 'processDlQueues' - aEx.send({task: 0, content: currentProc}) + // Download queued files. + setLaunchDetails('Downloading files..') + aEx.send({task: 0, content: 'processDlQueues'}) + + } else if(m.content === 'dl'){ - } else if(currentProc === 'processDlQueues'){ if(m.task === 0){ - remote.getCurrentWindow().setProgressBar(m.value/m.total) - launch_progress.setAttribute('max', m.total) - launch_progress.setAttribute('value', m.value) - launch_progress_label.innerHTML = m.percent + '%' + + 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) - launch_details_text.innerHTML = 'Preparing to launch..' - currentProc = 'loadForgeData' - aEx.send({task: 0, content: currentProc, argsArr: [serv.id]}) + setLaunchDetails('Preparing to launch..') + aEx.send({task: 0, content: 'loadForgeData', argsArr: [serv.id]}) } else { + console.error('Unknown download data type.', m) + } - } else if(currentProc === 'loadForgeData'){ + + } else if(m.content === 'loadForgeData'){ forgeData = m.result @@ -200,20 +264,27 @@ function dlAsync(login = true){ const authUser = ConfigManager.getSelectedAccount(); console.log('authu', authUser) let pb = new ProcessBuilder(ConfigManager.getGameDirectory(), serv, versionData, forgeData, authUser) - launch_details_text.innerHTML = 'Launching game..' - try{ + setLaunchDetails('Launching game..') + try { + // Build Minecraft process. proc = pb.build() - launch_details_text.innerHTML = 'Done. Enjoy the server!' + setLaunchDetails('Done. Enjoy the server!') + + // Attach a temporary listener to the client output. + // Will wait for a certain bit of text meaning that + // the client application has started, and we can hide + // the progress bar stuff. const tempListener = function(data){ if(data.indexOf('[Client thread/INFO]: -- System Details --') > -1){ - launch_details.style.display = 'none' - launch_content.style.display = 'inline-flex' + toggleLaunchArea(false) if(hasRPC){ DiscordWrapper.updateDetails('Loading game..') } proc.stdout.removeListener('data', tempListener) } } + + // Listener for Discord RPC. const gameStateChange = function(data){ if(servJoined.test(data)){ DiscordWrapper.updateDetails('Exploring the Realm!') @@ -221,9 +292,12 @@ function dlAsync(login = true){ DiscordWrapper.updateDetails('Idling on Main Menu') } } + + // Bind listeners to stdout. proc.stdout.on('data', tempListener) proc.stdout.on('data', gameStateChange) - // Init Discord Hook (Untested) + + // Init Discord Hook const distro = AssetGuard.retrieveDistributionDataSync(ConfigManager.getGameDirectory) if(distro.discord != null && serv.discord != null){ DiscordWrapper.initRPC(distro.discord, serv.discord) @@ -235,14 +309,18 @@ function dlAsync(login = true){ proc = null }) } + } catch(err) { - //launch_details_text.innerHTML = 'Error: ' + err.message; - launch_details_text.innerHTML = 'Error: See log for details..'; + + // Show that there was an error then hide the + // progress area. Maybe switch this to an error + // alert in the future. TODO + setLaunchDetails('Error: See log for details..') console.log(err) setTimeout(function(){ - launch_details.style.display = 'none' - launch_content.style.display = 'inline-flex' + toggleLaunchArea(false) }, 5000) + } } @@ -252,7 +330,9 @@ function dlAsync(login = true){ } }) - launch_details_text.innerHTML = 'Loading server information..' - currentProc = 'validateDistribution' - aEx.send({task: 0, content: currentProc, argsArr: [ConfigManager.getSelectedServer()]}) + // Begin Validations + + // Validate Forge files. + setLaunchDetails('Loading server information..') + aEx.send({task: 0, content: 'validateDistribution', argsArr: [ConfigManager.getSelectedServer()]}) } \ No newline at end of file diff --git a/app/assets/js/assetexec.js b/app/assets/js/assetexec.js index f3f44788..5d41fed9 100644 --- a/app/assets/js/assetexec.js +++ b/app/assets/js/assetexec.js @@ -7,25 +7,27 @@ console.log('AssetExec Started') process.on('unhandledRejection', r => console.log(r)) tracker.on('totaldlprogress', (data) => { - process.send({task: 0, total: data.total, value: data.acc, percent: parseInt((data.acc/data.total)*100)}) + process.send({task: 0, total: data.total, value: data.acc, percent: parseInt((data.acc/data.total)*100), content: 'dl'}) }) tracker.on('dlcomplete', () => { - process.send({task: 1}) + process.send({task: 1, content: 'dl'}) }) process.on('message', (msg) => { if(msg.task === 0){ const func = msg.content - if(typeof tracker[func] === 'function'){ - const f = tracker[func] - const res = f.apply(tracker, msg.argsArr) + let nS = tracker[func] + let iS = AssetGuard[func] + 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}) + process.send({result: v, content: msg.content}) }) } else { - process.send({result: res}) + process.send({result: res, content: msg.content}) } } } diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index 5f44af9f..5a713eb9 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -591,7 +591,10 @@ class AssetGuard extends EventEmitter { static _validateJavaBinary(binaryPath){ return new Promise((resolve, reject) => { - const fBp = path.join(binaryPath, 'bin', 'java.exe') + 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) => { @@ -642,6 +645,37 @@ class AssetGuard extends EventEmitter { } } + /** + * Scans the data folder's runtime directory for suitable JRE candidates. + * + * @param {string} dataDir The base launcher directory. + * @returns {Promise.>} A set containing suitable JRE candidates found + * in the runtime directory. + */ + static _scanDataFolder(dataDir){ + return new Promise((resolve, reject) => { + const x64RuntimeDir = path.join(dataDir, 'runtime', 'x64') + fs.exists(x64RuntimeDir, (e) => { + let res = new Set() + if(e){ + fs.readdir(x64RuntimeDir, (err, files) => { + if(err){ + resolve(res) + console.log(err) + } else { + for(let i=0; i Lower versions * 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 * x64 Java installation. If none are found, null is returned. */ - static async _win32JavaValidate(){ + static async _win32JavaValidate(dataDir){ // Get possible paths from the registry. const pathSet = await AssetGuard._scanRegistry() //console.log(Array.from(pathSet)) // DEBUGGING + // Get possible paths from the data directory. + const pathSet2 = await AssetGuard._scanDataFolder(dataDir) + // Validate JAVA_HOME const jHome = AssetGuard._scanJavaHome() if(jHome != null && jHome.indexOf('(x86)') === -1){ pathSet.add(jHome) } + const mergedSet = new Set([...pathSet, ...pathSet2]) + // Convert path set to an array for processing. - let pathArr = Array.from(pathSet) + let pathArr = Array.from(mergedSet) //console.log(pathArr) // DEBUGGING @@ -787,24 +827,25 @@ class AssetGuard extends EventEmitter { /** * WIP -> get a valid x64 Java path on macOS. */ - static async _darwinJavaValidate(){ + static async _darwinJavaValidate(dataDir){ return null } /** * WIP -> get a valid x64 Java path on linux. */ - static async _linuxJavaValidate(){ + static async _linuxJavaValidate(dataDir){ 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. */ - static async validateJava(){ - return await AssetGuard['_' + process.platform + 'JavaValidate']() + static async validateJava(dataDir){ + return await AssetGuard['_' + process.platform + 'JavaValidate'](dataDir) } // #endregion diff --git a/app/assets/js/uicore.js b/app/assets/js/uicore.js index 4d67b93d..fdbf2053 100644 --- a/app/assets/js/uicore.js +++ b/app/assets/js/uicore.js @@ -40,14 +40,18 @@ document.addEventListener('readystatechange', function () { } else if(document.readyState === 'complete'){ + //266.01 + //170.8 + //53.21 // Bind progress bar length to length of bot wrapper - const targetWidth = document.getElementById("launch_content").getBoundingClientRect().width - const targetWidth2 = document.getElementById("server_selection").getBoundingClientRect().width - const targetWidth3 = document.getElementById("launch_button").getBoundingClientRect().width - document.getElementById("launch_details").style.maxWidth = targetWidth - document.getElementById("launch_progress").style.width = targetWidth2 - document.getElementById("launch_details_right").style.maxWidth = targetWidth2 - document.getElementById("launch_progress_label").style.width = targetWidth3 + //const targetWidth = document.getElementById("launch_content").getBoundingClientRect().width + //const targetWidth2 = document.getElementById("server_selection").getBoundingClientRect().width + //const targetWidth3 = document.getElementById("launch_button").getBoundingClientRect().width + + document.getElementById("launch_details").style.maxWidth = 266.01 + document.getElementById("launch_progress").style.width = 170.8 + document.getElementById("launch_details_right").style.maxWidth = 170.8 + document.getElementById("launch_progress_label").style.width = 53.21 }