Beginning work on configuration management, updates to UI to prevent unresponsive behavior, bug fixes..

This commit is contained in:
Daniel Scalzi
2017-11-30 01:40:56 -05:00
parent aac789bf2c
commit 4c2c46f535
12 changed files with 482 additions and 198 deletions

View File

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

View File

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

View File

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

View File

@@ -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'))
exports.DEFAULT_CONFIG = new ConfigManager(path.join(exports.GAME_DIRECTORY, 'config.json'))

View File

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

View File

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

View File

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

93
app/assets/js/uicore.js Normal file
View File

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