diff --git a/app/assets/js/actionbinder.js b/app/assets/js/actionbinder.js new file mode 100644 index 00000000..75979857 --- /dev/null +++ b/app/assets/js/actionbinder.js @@ -0,0 +1,99 @@ +const mojang = require('mojang') +const path = require('path') +const AssetGuard = require(path.join(__dirname, 'assets', 'js', 'assetguard.js')) +const ProcessBuilder = require(path.join(__dirname, 'assets', 'js', 'processbuilder.js')) +const {GAME_DIRECTORY, DEFAULT_CONFIG} = require(path.join(__dirname, 'assets', 'js', 'constants.js')) + +document.onreadystatechange = function(){ + if (document.readyState === 'interactive'){ + + // Bind launch button + document.getElementById("launch_button").addEventListener('click', function(e){ + console.log('Launching game..') + testdownloads() + }) + + } +} + +testdownloads = async function(){ + const content = document.getElementById("launch_content") + const details = document.getElementById("launch_details") + const progress = document.getElementById("launch_progress") + const progress_text = document.getElementById("launch_progress_label") + const det_text = document.getElementById("launch_details_text") + + det_text.innerHTML = 'Please wait..' + progress.setAttribute('max', '100') + details.style.display = 'flex' + content.style.display = 'none' + + det_text.innerHTML = 'Loading version information..' + const versionData = await AssetGuard.loadVersionData('1.11.2', GAME_DIRECTORY) + progress.setAttribute('value', 20) + progress_text.innerHTML = '20%' + + det_text.innerHTML = 'Validating asset integrity..' + await AssetGuard.validateAssets(versionData, GAME_DIRECTORY) + progress.setAttribute('value', 40) + progress_text.innerHTML = '40%' + console.log('assets done') + + det_text.innerHTML = 'Validating library integrity..' + await AssetGuard.validateLibraries(versionData, GAME_DIRECTORY) + progress.setAttribute('value', 60) + progress_text.innerHTML = '60%' + console.log('libs done') + + det_text.innerHTML = 'Validating miscellaneous file integrity..' + await AssetGuard.validateMiscellaneous(versionData, GAME_DIRECTORY) + progress.setAttribute('value', 80) + progress_text.innerHTML = '80%' + console.log('files done') + + det_text.innerHTML = 'Validating server distribution files..' + const serv = await AssetGuard.validateDistribution('WesterosCraft-1.11.2', GAME_DIRECTORY) + progress.setAttribute('value', 100) + progress_text.innerHTML = '100%' + console.log('forge stuff done') + + det_text.innerHTML = 'Downloading files..' + AssetGuard.instance.on('totaldlprogress', function(data){ + progress.setAttribute('max', data.total) + progress.setAttribute('value', data.acc) + progress_text.innerHTML = parseInt((data.acc/data.total)*100) + '%' + }) + + AssetGuard.instance.on('dlcomplete', async function(){ + det_text.innerHTML = 'Preparing to launch..' + const forgeData = await AssetGuard.loadForgeData('WesterosCraft-1.11.2', GAME_DIRECTORY) + const authUser = await mojang.auth('EMAIL', 'PASS', DEFAULT_CONFIG.getClientToken(), { + name: 'Minecraft', + version: 1 + }) + let pb = new ProcessBuilder(GAME_DIRECTORY, serv, versionData, forgeData, authUser) + det_text.innerHTML = 'Launching game..' + let proc; + try{ + proc = pb.build() + det_text.innerHTML = 'Done. Enjoy the server!' + const tempListener = function(data){ + if(data.indexOf('[Client thread/INFO]: -- System Details --') > -1){ + details.style.display = 'none' + content.style.display = 'inline-flex' + proc.stdout.removeListener('data', tempListener) + } + } + proc.stdout.on('data', tempListener) + } catch(err) { + //det_text.innerHTML = 'Error: ' + err.message; + det_text.innerHTML = 'Error: See log for details..'; + console.log(err) + setTimeout(function(){ + details.style.display = 'none' + content.style.display = 'inline-flex' + }, 5000) + } + }) + AssetGuard.processDlQueues() +} \ No newline at end of file diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index f4631d87..437cd025 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -22,21 +22,22 @@ * @module assetguard */ // Requirements -const fs = require('fs') -const request = require('request') -const path = require('path') -const mkpath = require('mkdirp'); -const async = require('async') -const crypto = require('crypto') const AdmZip = require('adm-zip') +const async = require('async') const child_process = require('child_process') +const crypto = require('crypto') +const {DEFAULT_CONFIG} = require('./constants') const EventEmitter = require('events') +const fs = require('fs') +const mkpath = require('mkdirp'); +const path = require('path') +const request = require('request') const {remote} = require('electron') // Classes /** Class representing a base asset. */ -class Asset{ +class Asset { /** * Create an asset. * @@ -56,7 +57,7 @@ class Asset{ } /** Class representing a mojang library. */ -class Library extends Asset{ +class Library extends Asset { /** * Converts the process.platform OS names to match mojang's OS names. @@ -349,7 +350,7 @@ function _extractPackXZ(filePaths){ return new Promise(function(fulfill, reject){ const libPath = path.join(__dirname, '..', 'libraries', 'java', 'PackXZExtract.jar') const filePath = filePaths.join(',') - const child = child_process.spawn('C:\\Program Files\\Java\\jdk1.8.0_152\\bin\\javaw.exe', ['-jar', libPath, '-packxz', filePath]) + const child = child_process.spawn(DEFAULT_CONFIG.getJavaExecutable(), ['-jar', libPath, '-packxz', filePath]) child.stdout.on('data', (data) => { //console.log('PackXZExtract:', data.toString('utf8')) }) diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index fc32cb4c..fb39cd72 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -1,5 +1,6 @@ const fs = require('fs') const mkpath = require('mkdirp') +const os = require('os') const path = require('path') const uuidV4 = require('uuid/v4') @@ -11,6 +12,13 @@ class ConfigManager { this.load() } + /* Private functions to resolve default settings based on system specs. */ + + static _resolveMaxRAM(){ + const mem = os.totalmem() + return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G') + } + /** * Generates a default configuration object and saves it. * @@ -18,15 +26,45 @@ class ConfigManager { */ _generateDefault(save = true){ this.config = { - settings: {}, + settings: { + java: { + minRAM: '2G', + maxRAM: ConfigManager._resolveMaxRAM(), + executable: 'C:\\Program Files\\Java\\jdk1.8.0_152\\bin\\javaw.exe', //TODO Resolve + jvmOptions: [ + '-XX:+UseConcMarkSweepGC', + '-XX:+CMSIncrementalMode', + '-XX:-UseAdaptiveSizePolicy', + '-Xmn128M' + ], + }, + game: { + resWidth: 1280, + resHeight: 720, + fullscreen: false, + autoConnect: true + }, + launcher: { + + } + }, clientToken: uuidV4(), - authenticationDatabase: [] + selectedServer: null, + selectedAccount: null, + authenticationDatabase: [], + discord: { + clientID: 385581240906022916 + } } if(save){ this.save() } } + /** + * Load the launcher configuration into memory. If the specified file does + * not exist, a default configuration will be generated and saved. + */ load(){ if(!fs.existsSync(this.path)){ mkpath.sync(path.join(this.path, '..')) @@ -36,14 +74,101 @@ class ConfigManager { } } + /** + * Save the launcher configuration to the specified file. + */ save(){ fs.writeFileSync(this.path, JSON.stringify(this.config, null, 4), 'UTF-8') } + /** + * Retrieve the launcher's Client Token. + */ getClientToken(){ return this.config.clientToken } + /** + * Retrieve the selected server configuration value. + */ + getSelectedServer(){ + return this.config.selectedServer + } + + /** + * Set the selected server configuration value. + * + * @param {String} serverID - the id of the new selected server. + */ + setSelectedServer(serverID){ + this.config.selectedServer = serverID + this.save() + } + + /** + * Retrieve the launcher's Discord Client ID. + */ + getDiscordClientID(){ + return this.config.discord.clientID + } + + /** + * Retrieve the minimum amount of memory for JVM initialization. + */ + getMinRAM(){ + return this.config.settings.java.minRAM + } + + /** + * Retrieve the maximum amount of memory for JVM initialization. + */ + getMaxRAM(){ + return this.config.settings.java.maxRAM + } + + /** + * Retrieve the path of the java executable. + */ + getJavaExecutable(){ + return this.config.settings.java.executable + } + + /** + * Retrieve the additional arguments for JVM initialization. Required arguments, + * such as memory allocation, will be dynamically resolved. + */ + getJVMOptions(){ + return this.config.settings.java.jvmOptions + } + + /** + * Retrieve the width of the game window. + */ + getGameWidth(){ + return this.config.settings.game.resWidth + } + + /** + * Retrieve the height of the game window. + */ + getGameHeight(){ + return this.config.settings.game.resHeight + } + + /** + * Check if the game should be launched in fullscreen mode. + */ + isFullscreen(){ + return this.config.settings.game.fullscreen + } + + /** + * Check if auto connect is enabled. + */ + isAutoConnect(){ + return this.config.settings.game.autoConnect + } + } module.exports = ConfigManager \ No newline at end of file diff --git a/app/assets/js/constants.js b/app/assets/js/constants.js index ead2c105..3478b90b 100644 --- a/app/assets/js/constants.js +++ b/app/assets/js/constants.js @@ -3,4 +3,4 @@ const ConfigManager = require('./configmanager') //TODO: Resolve game directory based on windows, linux, or mac.. exports.GAME_DIRECTORY = path.join(__dirname, '..', '..', '..', 'target', 'test', 'mcfiles') -exports.DEFAULT_CONFIG = new ConfigManager(path.join(exports.GAME_DIRECTORY, 'config.yml')) \ No newline at end of file +exports.DEFAULT_CONFIG = new ConfigManager(path.join(exports.GAME_DIRECTORY, 'config.json')) \ No newline at end of file diff --git a/app/assets/js/discordwrapper.js b/app/assets/js/discordwrapper.js new file mode 100644 index 00000000..960d6d45 --- /dev/null +++ b/app/assets/js/discordwrapper.js @@ -0,0 +1,22 @@ +// Work in progress +const Client = require('discord-rpc') +const {DEFAULT_CONFIG} = require('./constants') + +let rpc + +function initRPC(){ + rpc = new Client({ transport: 'ipc' }); + + rpc.login(DEFAULT_CONFIG.getDiscordClientID()).catch(error => { + if(error.message.includes('ENOENT')) { + console.log('Unable to initialize Discord Rich Presence, no client detected.') + } else { + console.log('Unable to initialize Discord Rich Presence: ' + error.message) + } + }) +} + +function shutdownRPC(){ + rpc.destroy() + rpc = null +} \ No newline at end of file diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js index 697f8e95..f9214b3d 100644 --- a/app/assets/js/processbuilder.js +++ b/app/assets/js/processbuilder.js @@ -8,9 +8,11 @@ const AdmZip = require('adm-zip') const ag = require('./assetguard.js') const child_process = require('child_process') +const {DEFAULT_CONFIG} = require('./constants') const fs = require('fs') const mkpath = require('mkdirp') const path = require('path') +const {URL} = require('url') class ProcessBuilder { @@ -33,13 +35,14 @@ class ProcessBuilder { * Convienence method to run the functions typically used to build a process. */ build(){ + process.throwDeprecation = true const mods = this.resolveDefaultMods() this.constructFMLModList(mods, true) const args = this.constructJVMArguments(mods) - //console.log(args) + console.log(args) - const child = child_process.spawn('C:\\Program Files\\Java\\jdk1.8.0_152\\bin\\javaw.exe', args) + const child = child_process.spawn(DEFAULT_CONFIG.getJavaExecutable(), args) child.stdout.on('data', (data) => { console.log('Minecraft:', data.toString('utf8')) @@ -95,16 +98,18 @@ class ProcessBuilder { */ constructJVMArguments(mods){ - let args = ['-Xmx4G', - '-XX:+UseConcMarkSweepGC', - '-XX:+CMSIncrementalMode', - '-XX:-UseAdaptiveSizePolicy', - '-Xmn128M', + let args = ['-Xmx' + DEFAULT_CONFIG.getMaxRAM(), + '-Xms' + DEFAULT_CONFIG.getMinRAM(),, '-Djava.library.path=' + path.join(this.dir, 'natives'), '-cp', this.classpathArg(mods).join(';'), this.forgeData.mainClass] + // For some reason this will add an undefined value unless + // the delete count is 1. I suspect this is unintended behavior + // by the function.. need to keep an eye on this. + args.splice(2, 1, ...DEFAULT_CONFIG.getJVMOptions()) + args = args.concat(this._resolveForgeArgs()) return args @@ -161,6 +166,28 @@ class ProcessBuilder { } mcArgs.push('--modListFile') mcArgs.push('absolute:' + this.fmlDir) + + // Prepare game resolution + if(DEFAULT_CONFIG.isFullscreen()){ + mcArgs.unshift('--fullscreen') + } else { + mcArgs.unshift(DEFAULT_CONFIG.getGameWidth()) + mcArgs.unshift('--width') + mcArgs.unshift(DEFAULT_CONFIG.getGameHeight()) + mcArgs.unshift('--height') + } + + // Prepare autoconnect + if(DEFAULT_CONFIG.isAutoConnect() && this.server.autoconnect){ + const serverURL = new URL('my://' + this.server.server_ip) + mcArgs.unshift(serverURL.hostname) + mcArgs.unshift('--server') + if(serverURL.port){ + mcArgs.unshift(serverURL.port) + mcArgs.unshift('--port') + } + } + return mcArgs } @@ -244,7 +271,11 @@ class ProcessBuilder { // Extract the file. if(!shouldExclude){ mkpath.sync(path.join(nativePath, fileName, '..')) - fs.writeFile(path.join(nativePath, fileName), zipEntries[i].getData()) + fs.writeFile(path.join(nativePath, fileName), zipEntries[i].getData(), (err) => { + if(err){ + console.error('Error while extracting native library:', err) + } + }) } } diff --git a/app/assets/js/script.js b/app/assets/js/script.js deleted file mode 100644 index d02ee168..00000000 --- a/app/assets/js/script.js +++ /dev/null @@ -1,170 +0,0 @@ -const $ = require('jquery'); -const remote = require('electron').remote -const shell = require('electron').shell -const path = require('path') -const os = require('os'); -const ag = require(path.join(__dirname, 'assets', 'js', 'assetguard.js')) -const ProcessBuilder = require(path.join(__dirname, 'assets', 'js', 'processbuilder.js')) -const mojang = require('mojang') -const {GAME_DIRECTORY, DEFAULT_CONFIG} = require(path.join(__dirname, 'assets', 'js', 'constants.js')) - -$(document).on('ready', function(){ - console.log('okay'); -}) - -document.onreadystatechange = function () { - if (document.readyState == "complete") { - - // Bind close button. - document.getElementById("frame_btn_close").addEventListener("click", function (e) { - const window = remote.getCurrentWindow() - window.close() - }) - - // Bind restore down button. - document.getElementById("frame_btn_restoredown").addEventListener("click", function (e) { - const window = remote.getCurrentWindow() - if(window.isMaximized()){ - window.unmaximize(); - } else { - window.maximize() - } - }) - - // Bind minimize button. - document.getElementById("frame_btn_minimize").addEventListener("click", function (e) { - const window = remote.getCurrentWindow() - window.minimize() - }) - - // Bind launch button - document.getElementById("launch_button").addEventListener('click', function(e){ - console.log('Launching game..') - testdownloads() - }) - - // 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 - } -} - -// Open web links in the user's default browser. -$(document).on('click', 'a[href^="http"]', function(event) { - event.preventDefault(); - //console.log(os.homedir()) - shell.openExternal(this.href) -}) - -testdownloads = async function(){ - const content = document.getElementById("launch_content") - const details = document.getElementById("launch_details") - const progress = document.getElementById("launch_progress") - const progress_text = document.getElementById("launch_progress_label") - const det_text = document.getElementById("launch_details_text") - - det_text.innerHTML = 'Please wait..' - progress.setAttribute('max', '100') - details.style.display = 'flex' - content.style.display = 'none' - - det_text.innerHTML = 'Loading version information..' - const versionData = await ag.loadVersionData('1.11.2', GAME_DIRECTORY) - progress.setAttribute('value', 20) - progress_text.innerHTML = '20%' - - det_text.innerHTML = 'Validating asset integrity..' - await ag.validateAssets(versionData, GAME_DIRECTORY) - progress.setAttribute('value', 40) - progress_text.innerHTML = '40%' - console.log('assets done') - - det_text.innerHTML = 'Validating library integrity..' - await ag.validateLibraries(versionData, GAME_DIRECTORY) - progress.setAttribute('value', 60) - progress_text.innerHTML = '60%' - console.log('libs done') - - det_text.innerHTML = 'Validating miscellaneous file integrity..' - await ag.validateMiscellaneous(versionData, GAME_DIRECTORY) - progress.setAttribute('value', 80) - progress_text.innerHTML = '80%' - console.log('files done') - - det_text.innerHTML = 'Validating server distribution files..' - const serv = await ag.validateDistribution('WesterosCraft-1.11.2', GAME_DIRECTORY) - progress.setAttribute('value', 100) - progress_text.innerHTML = '100%' - console.log('forge stuff done') - - det_text.innerHTML = 'Downloading files..' - ag.instance.on('totaldlprogress', function(data){ - progress.setAttribute('max', data.total) - progress.setAttribute('value', data.acc) - progress_text.innerHTML = parseInt((data.acc/data.total)*100) + '%' - }) - - ag.instance.on('dlcomplete', async function(){ - det_text.innerHTML = 'Preparing to launch..' - const forgeData = await ag.loadForgeData('WesterosCraft-1.11.2', GAME_DIRECTORY) - const authUser = await mojang.auth('EMAIL', 'PASS', DEFAULT_CONFIG.getClientToken(), { - name: 'Minecraft', - version: 1 - }) - let pb = new ProcessBuilder(GAME_DIRECTORY, serv, versionData, forgeData, authUser) - det_text.innerHTML = 'Launching game..' - let proc; - try{ - proc = pb.build() - det_text.innerHTML = 'Done. Enjoy the server!' - } catch(err) { - //det_text.innerHTML = 'Error: ' + err.message; - det_text.innerHTML = 'Error: See log for details..'; - } - setTimeout(function(){ - details.style.display = 'none' - content.style.display = 'inline-flex' - }, 5000) - }) - ag.processDlQueues() -} - -/** - * Opens DevTools window if you type "wcdev" in sequence. - * This will crash the program if you are using multiple - * DevTools, for example the chrome debugger in VS Code. - */ -const match = [87, 67, 68, 69, 86] -let at = 0; - -document.addEventListener('keydown', function (e) { - switch(e.keyCode){ - case match[0]: - if(at === 0) ++at - break - case match[1]: - if(at === 1) ++at - break - case match[2]: - if(at === 2) ++at - break - case match[3]: - if(at === 3) ++at - break - case match[4]: - if(at === 4) ++at - break - default: - at = 0 - } - if(at === 5) { - var window = remote.getCurrentWindow() - window.toggleDevTools() - at = 0 - } -}) \ No newline at end of file diff --git a/app/assets/js/uicore.js b/app/assets/js/uicore.js new file mode 100644 index 00000000..fad32dbd --- /dev/null +++ b/app/assets/js/uicore.js @@ -0,0 +1,93 @@ +/** + * Core UI functions are initialized in this file. This prevents + * unexpected errors from breaking the core features. + */ +const $ = require('jquery'); +const {remote, shell} = require('electron') + +/* jQuery Example +$(function(){ + console.log('UICore Initialized'); +})*/ + +document.onreadystatechange = function () { + if (document.readyState === "interactive") { + + console.log('UICore Initializing..'); + + // Bind close button. + document.getElementById("frame_btn_close").addEventListener("click", function (e) { + const window = remote.getCurrentWindow() + window.close() + }) + + // Bind restore down button. + document.getElementById("frame_btn_restoredown").addEventListener("click", function (e) { + const window = remote.getCurrentWindow() + if(window.isMaximized()){ + window.unmaximize(); + } else { + window.maximize() + } + }) + + // Bind minimize button. + document.getElementById("frame_btn_minimize").addEventListener("click", function (e) { + const window = remote.getCurrentWindow() + window.minimize() + }) + + // 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 + } +} + +/** + * Open web links in the user's default browser. + */ +$(document).on('click', 'a[href^="http"]', function(event) { + event.preventDefault(); + //console.log(os.homedir()) + shell.openExternal(this.href) +}) + +/** + * Opens DevTools window if you type "wcdev" in sequence. + * This will crash the program if you are using multiple + * DevTools, for example the chrome debugger in VS Code. + */ +const match = [87, 67, 68, 69, 86] +let at = 0; + +document.addEventListener('keydown', function (e) { + switch(e.keyCode){ + case match[0]: + if(at === 0) ++at + break + case match[1]: + if(at === 1) ++at + break + case match[2]: + if(at === 2) ++at + break + case match[3]: + if(at === 3) ++at + break + case match[4]: + if(at === 4) ++at + break + default: + at = 0 + } + if(at === 5) { + var window = remote.getCurrentWindow() + window.toggleDevTools() + at = 0 + } +}) \ No newline at end of file diff --git a/app/assets/westeroscraft.json b/app/assets/westeroscraft.json index 752e9a14..4037407c 100644 --- a/app/assets/westeroscraft.json +++ b/app/assets/westeroscraft.json @@ -7,7 +7,7 @@ "news_feed": "http://www.westeroscraft.com/api/rss.php?preset_id=12700544", "icon_url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-prod.png", "revision": "0.0.1", - "server_ip": "mc.westeroscraft.com:4444", + "server_ip": "mc.westeroscraft.com", "mc_version": "1.11.2", "autoconnect": true, "modules": [ diff --git a/app/index.ejs b/app/index.ejs index 51098b3f..0c9ca749 100644 --- a/app/index.ejs +++ b/app/index.ejs @@ -2,7 +2,8 @@ Westeroscraft Launcher - + +