소스 검색

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 년 전
부모
커밋
5c7e0c3c8a
4개의 변경된 파일211개의 추가작업 그리고 84개의 파일을 삭제
  1. 143 63
      app/assets/js/actionbinder.js
  2. 9 7
      app/assets/js/assetexec.js
  3. 48 7
      app/assets/js/assetguard.js
  4. 11 7
      app/assets/js/uicore.js

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