Przeglądaj źródła

Various additions and fixes to the launch controller.
Fixed an issue with concurrency when there are no downloads queued on launch. Added support for static function execution in AssetExec. Fixed a binding issue in uicore.js caused by delayed div loading. For now the solution is just hard coding in the value, will probably switch these two a css file later on. Included the launcher's x64 runtime directory in Java scans.

Daniel Scalzi 7 lat temu
rodzic
commit
5c7e0c3c8a

+ 143 - 63
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()]})
 }

+ 9 - 7
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})
             }
         }
     }

+ 48 - 7
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.<Set.<string>>} 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<files.length; i++){
+                               res.add(path.join(x64RuntimeDir, files[i]))
+                            }
+                            resolve(res)
+                        }
+                    })
+                } else {
+                    resolve(res)
+                }
+            })
+        })
+    }
+
     /**
      * 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.
@@ -735,24 +769,30 @@ class AssetGuard extends EventEmitter {
      * Higher versions > Lower versions
      * 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 
      * 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

+ 11 - 7
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
         
     }