Added dynamic detection of default server.

This commit is contained in:
Daniel Scalzi 2017-12-02 02:59:25 -05:00
parent 52ab270ce3
commit 086bfc8593
8 changed files with 156 additions and 39 deletions

View File

@ -8,11 +8,20 @@ document.addEventListener('readystatechange', function(){
if (document.readyState === 'interactive'){ if (document.readyState === 'interactive'){
// Bind launch button // Bind launch button
document.getElementById("launch_button").addEventListener('click', function(e){ document.getElementById('launch_button').addEventListener('click', function(e){
console.log('Launching game..') console.log('Launching game..')
testdownloads() testdownloads()
}) })
if(DEFAULT_CONFIG.getSelectedServer() == null){
console.log('Determining default selected server..')
DEFAULT_CONFIG.setSelectedServer(AssetGuard.resolveSelectedServer())
}
// TODO convert this to dropdown menu.
// Bind selected server
document.getElementById('server_selection').innerHTML = '\u2022 ' + AssetGuard.getServerById(DEFAULT_CONFIG.getSelectedServer()).name
} }
}, false) }, false)
@ -20,11 +29,11 @@ document.addEventListener('readystatechange', function(){
let tracker; let tracker;
testdownloads = async function(){ testdownloads = async function(){
const content = document.getElementById("launch_content") const content = document.getElementById('launch_content')
const details = document.getElementById("launch_details") const details = document.getElementById('launch_details')
const progress = document.getElementById("launch_progress") const progress = document.getElementById('launch_progress')
const progress_text = document.getElementById("launch_progress_label") const progress_text = document.getElementById('launch_progress_label')
const det_text = document.getElementById("launch_details_text") const det_text = document.getElementById('launch_details_text')
det_text.innerHTML = 'Please wait..' det_text.innerHTML = 'Please wait..'
progress.setAttribute('max', '100') progress.setAttribute('max', '100')
@ -33,34 +42,34 @@ testdownloads = async function(){
tracker = new AssetGuard() tracker = new AssetGuard()
det_text.innerHTML = 'Loading version information..' det_text.innerHTML = 'Loading server information..'
const versionData = await tracker.loadVersionData('1.11.2', GAME_DIRECTORY) const serv = await tracker.validateDistribution(DEFAULT_CONFIG.getSelectedServer(), GAME_DIRECTORY)
progress.setAttribute('value', 20) progress.setAttribute('value', 20)
progress_text.innerHTML = '20%' progress_text.innerHTML = '20%'
console.log('forge stuff done')
det_text.innerHTML = 'Loading version information..'
const versionData = await tracker.loadVersionData(serv.mc_version, GAME_DIRECTORY)
progress.setAttribute('value', 40)
progress_text.innerHTML = '40%'
det_text.innerHTML = 'Validating asset integrity..' det_text.innerHTML = 'Validating asset integrity..'
await tracker.validateAssets(versionData, GAME_DIRECTORY) await tracker.validateAssets(versionData, GAME_DIRECTORY)
progress.setAttribute('value', 40) progress.setAttribute('value', 60)
progress_text.innerHTML = '40%' progress_text.innerHTML = '60%'
console.log('assets done') console.log('assets done')
det_text.innerHTML = 'Validating library integrity..' det_text.innerHTML = 'Validating library integrity..'
await tracker.validateLibraries(versionData, GAME_DIRECTORY) await tracker.validateLibraries(versionData, GAME_DIRECTORY)
progress.setAttribute('value', 60) progress.setAttribute('value', 80)
progress_text.innerHTML = '60%' progress_text.innerHTML = '80%'
console.log('libs done') console.log('libs done')
det_text.innerHTML = 'Validating miscellaneous file integrity..' det_text.innerHTML = 'Validating miscellaneous file integrity..'
await tracker.validateMiscellaneous(versionData, GAME_DIRECTORY) await tracker.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 tracker.validateDistribution('WesterosCraft-1.11.2', GAME_DIRECTORY)
progress.setAttribute('value', 100) progress.setAttribute('value', 100)
progress_text.innerHTML = '100%' progress_text.innerHTML = '100%'
console.log('forge stuff done') console.log('files done')
det_text.innerHTML = 'Downloading files..' det_text.innerHTML = 'Downloading files..'
tracker.on('totaldlprogress', function(data){ tracker.on('totaldlprogress', function(data){
@ -72,7 +81,7 @@ testdownloads = async function(){
tracker.on('dlcomplete', async function(){ tracker.on('dlcomplete', async function(){
det_text.innerHTML = 'Preparing to launch..' det_text.innerHTML = 'Preparing to launch..'
const forgeData = await tracker.loadForgeData('WesterosCraft-1.11.2', GAME_DIRECTORY) const forgeData = await tracker.loadForgeData(serv.id, GAME_DIRECTORY)
const authUser = await mojang.auth('EMAIL', 'PASS', DEFAULT_CONFIG.getClientToken(), { const authUser = await mojang.auth('EMAIL', 'PASS', DEFAULT_CONFIG.getClientToken(), {
name: 'Minecraft', name: 'Minecraft',
version: 1 version: 1

View File

@ -26,7 +26,7 @@ const AdmZip = require('adm-zip')
const async = require('async') const async = require('async')
const child_process = require('child_process') const child_process = require('child_process')
const crypto = require('crypto') const crypto = require('crypto')
const {DEFAULT_CONFIG} = require('./constants') const {DEFAULT_CONFIG, DISTRO_DIRECTORY} = require('./constants')
const EventEmitter = require('events') const EventEmitter = require('events')
const fs = require('fs') const fs = require('fs')
const mkpath = require('mkdirp'); const mkpath = require('mkdirp');
@ -152,6 +152,8 @@ class DLTracker {
} }
let distributionData = null
/** /**
* Central object class used for control flow. This object stores data about * Central object class used for control flow. This object stores data about
* categories of downloads. Each category is assigned an identifier with a * categories of downloads. Each category is assigned an identifier with a
@ -272,6 +274,85 @@ class AssetGuard extends EventEmitter {
return false; return false;
} }
/**
* Statically retrieve the distribution data.
*
* @param {Boolean} cached - optional. False if the distro should be freshly downloaded, else
* a cached copy will be returned.
* @returns {Promise.<Object>} - A promise which resolves to the distribution data object.
*/
static retrieveDistributionData(cached = true){
return new Promise(function(fulfill, reject){
if(!cached || distributionData == null){
// TODO Download file from upstream.
//const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/westeroscraft.json'
// TODO Save file to DISTRO_DIRECTORY
// TODO Fulfill with JSON.parse()
// Workaround while file is not hosted.
fs.readFile(path.join(__dirname, '..', 'westeroscraft.json'), 'utf-8', (err, data) => {
distributionData = JSON.parse(data)
fulfill(distributionData)
})
} else {
fulfill(distributionData)
}
})
}
/**
* Statically retrieve the distribution data.
*
* @param {Boolean} cached - optional. False if the distro should be freshly downloaded, else
* a cached copy will be returned.
* @returns {Object} - The distribution data object.
*/
static retrieveDistributionDataSync(cached = true){
if(!cached || distributionData == null){
distributionData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'westeroscraft.json'), 'utf-8'))
}
return distributionData
}
/**
* Resolve the default selected server from the distribution index.
*
* @returns {Object} - An object resolving to the default selected server.
*/
static resolveSelectedServer(){
const distro = AssetGuard.retrieveDistributionDataSync()
const servers = distro.servers
for(let i=0; i<servers.length; i++){
if(servers[i].default_selected){
return servers[i].id
}
}
// If no server declares default_selected, default to the first one declared.
return (servers.length > 0) ? servers[0].id : null
}
/**
* Gets a server from the distro index which maches the provided ID.
* Returns null if the ID could not be found or the distro index has
* not yet been loaded.
*
* @param {String} serverID - The id of the server to retrieve.
* @returns {Object} - The server object whose id matches the parameter.
*/
static getServerById(serverID){
if(distributionData == null){
AssetGuard.retrieveDistributionDataSync(false)
}
const servers = distributionData.servers
let serv = null
for(let i=0; i<servers.length; i++){
if(servers[i].id === serverID){
serv = servers[i]
}
}
return serv
}
/** /**
* Validates a file in the style used by forge's version index. * Validates a file in the style used by forge's version index.
* *
@ -718,15 +799,16 @@ class AssetGuard extends EventEmitter {
validateDistribution(serverpackid, basePath){ validateDistribution(serverpackid, basePath){
const self = this const self = this
return new Promise(function(fulfill, reject){ return new Promise(function(fulfill, reject){
self._chainValidateDistributionIndex(basePath).then((value) => { AssetGuard.retrieveDistributionData(false).then((value) => {
let servers = value.servers /*const servers = value.servers
let serv = null let serv = null
for(let i=0; i<servers.length; i++){ for(let i=0; i<servers.length; i++){
if(servers[i].id === serverpackid){ if(servers[i].id === serverpackid){
serv = servers[i] serv = servers[i]
break break
} }
} }*/
const serv = AssetGuard.getServerById(serverpackid)
self.forge = self._parseDistroModules(serv.modules, basePath, serv.mc_version) self.forge = self._parseDistroModules(serv.modules, basePath, serv.mc_version)
//Correct our workaround here. //Correct our workaround here.
@ -744,19 +826,18 @@ class AssetGuard extends EventEmitter {
}) })
} }
//TODO The distro index should be downloaded in the 'pre-loader'. This is because /*//TODO The file should be hosted, the following code is for local testing.
//we will eventually NEED the index to generate the server list on the ui.
_chainValidateDistributionIndex(basePath){ _chainValidateDistributionIndex(basePath){
return new Promise(function(fulfill, reject){ return new Promise(function(fulfill, reject){
//const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/westeroscraft.json' //const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/westeroscraft.json'
const targetFile = path.join(basePath, 'westeroscraft.json') //const targetFile = path.join(basePath, 'westeroscraft.json')
//TEMP WORKAROUND TO TEST WHILE THIS IS NOT HOSTED //TEMP WORKAROUND TO TEST WHILE THIS IS NOT HOSTED
fs.readFile(path.join(__dirname, '..', 'westeroscraft.json'), 'utf-8', (err, data) => { fs.readFile(path.join(__dirname, '..', 'westeroscraft.json'), 'utf-8', (err, data) => {
fulfill(JSON.parse(data)) fulfill(JSON.parse(data))
}) })
}) })
} }*/
_parseDistroModules(modules, basePath, version){ _parseDistroModules(modules, basePath, version){
let alist = [] let alist = []
@ -816,7 +897,7 @@ class AssetGuard extends EventEmitter {
loadForgeData(serverpack, basePath){ loadForgeData(serverpack, basePath){
const self = this const self = this
return new Promise(async function(fulfill, reject){ return new Promise(async function(fulfill, reject){
let distro = await self._chainValidateDistributionIndex(basePath) let distro = AssetGuard.retrieveDistributionDataSync()
const servers = distro.servers const servers = distro.servers
let serv = null let serv = null

View File

@ -2,5 +2,12 @@ const path = require('path')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
//TODO: Resolve game directory based on windows, linux, or mac.. //TODO: Resolve game directory based on windows, linux, or mac..
exports.GAME_DIRECTORY = path.join(__dirname, '..', '..', '..', 'target', 'test', 'mcfiles') const GAME_DIRECTORY = path.join(__dirname, '..', '..', '..', 'target', 'test', 'mcfiles')
exports.DEFAULT_CONFIG = new ConfigManager(path.join(exports.GAME_DIRECTORY, 'config.json')) const DISTRO_DIRECTORY = path.join(GAME_DIRECTORY, 'westeroscraft.json')
const DEFAULT_CONFIG = new ConfigManager(path.join(GAME_DIRECTORY, 'config.json'))
module.exports = {
GAME_DIRECTORY,
DISTRO_DIRECTORY,
DEFAULT_CONFIG
}

View File

@ -0,0 +1,6 @@
const {AssetGuard} = require('./assetguard.js')
console.log('Preloading')
// Ensure Distribution is downloaded and cached.
AssetGuard.retrieveDistributionDataSync(false)

View File

@ -9,6 +9,7 @@
"revision": "0.0.1", "revision": "0.0.1",
"server_ip": "mc.westeroscraft.com", "server_ip": "mc.westeroscraft.com",
"mc_version": "1.11.2", "mc_version": "1.11.2",
"default_selected": true,
"autoconnect": true, "autoconnect": true,
"modules": [ "modules": [
{ {

View File

@ -60,7 +60,7 @@
<button id="launch_button">PLAY</button> <button id="launch_button">PLAY</button>
<div class="bot_divider"></div> <div class="bot_divider"></div>
<!-- Span until we implement the real selection --> <!-- Span until we implement the real selection -->
<span class="bot_label" id="server_selection">&#8226; Westeroscraft Production Server</span> <span class="bot_label" id="server_selection">&#8226; No Server Selected</span>
</div> </div>
<div id="launch_details"> <div id="launch_details">
<div id="launch_details_left"> <div id="launch_details_left">

View File

@ -14,6 +14,7 @@ The distribution index is written in JSON. The general format of the index is as
"revision": "0.0.1", "revision": "0.0.1",
"server_ip": "mc.westeroscraft.com:1337", "server_ip": "mc.westeroscraft.com:1337",
"mc_version": "1.11.2", "mc_version": "1.11.2",
"default_selected": true,
"autoconnect": true, "autoconnect": true,
"modules": [ "modules": [
... ...
@ -25,6 +26,8 @@ The distribution index is written in JSON. The general format of the index is as
You can declare an unlimited number of servers, however you must provide valid values for the fields listed above. In addition to that, the server can declare modules. You can declare an unlimited number of servers, however you must provide valid values for the fields listed above. In addition to that, the server can declare modules.
Only one server in the array should have the `default_selected` property enabled. This will tell the launcher that this is the default server to select if either the previously selected server is invalid, or there is no previously selected server. This field is not defined by any server (avoid this), the first server will be selected as the default. If multiple servers have `default_selected` enabled, the first one the launcher finds will be the effective value. Servers which are not the default may omit this property rather than explicitly setting it to false.
## Modules ## Modules
A module is a generic representation of a file required to run the minecraft client. It takes the general form: A module is a generic representation of a file required to run the minecraft client. It takes the general form:
@ -35,8 +38,8 @@ A module is a generic representation of a file required to run the minecraft cli
"name": "Artifact {version}", "name": "Artifact {version}",
"type": "{a valid type}", "type": "{a valid type}",
"artifact": { "artifact": {
"size": {file size in bytes}, "size": "{file size in bytes}",
"MD5": {MD5 hash for the file, string}, "MD5": "{MD5 hash for the file, string}",
"extension": ".jar", "extension": ".jar",
"url": "http://files.site.com/maven/group/id/artifact/version/artifact-version.jar" "url": "http://files.site.com/maven/group/id/artifact/version/artifact-version.jar"
}, },
@ -46,8 +49,8 @@ A module is a generic representation of a file required to run the minecraft cli
"name": "Example File", "name": "Example File",
"type": "file", "type": "file",
"artifact": { "artifact": {
"size": {file size in bytes}, "size": "{file size in bytes}",
"MD5": {MD5 hash for the file, string}, "MD5": "{MD5 hash for the file, string}",
"path": "examplefile.txt", "path": "examplefile.txt",
"url": "http://files.site.com/examplefile.txt" "url": "http://files.site.com/examplefile.txt"
} }
@ -63,8 +66,8 @@ Modules may also declare a `required` object.
```json ```json
"required": { "required": {
"value": false, (if the module is required) "value": false, "(if the module is required)"
"def": false (if it's enabled by default, has no effect if value is true) "def": false "(if it's enabled by default, has no effect if value is true)"
} }
``` ```
@ -75,6 +78,8 @@ The format of the module's artifact depends on several things. The most importan
Other times, you may want to store the files maven-style, such as with libraries and mods. In this case you must declare the module as the example artifact above. The `id` becomes more important as it will be used to resolve the final path. The `id` must be provided in maven format, that is `group.id.maybemore:artifact:version`. From there, you need to declare the `extension` of the file in the artifact object. This effectively replaces the `path` option we used above. Other times, you may want to store the files maven-style, such as with libraries and mods. In this case you must declare the module as the example artifact above. The `id` becomes more important as it will be used to resolve the final path. The `id` must be provided in maven format, that is `group.id.maybemore:artifact:version`. From there, you need to declare the `extension` of the file in the artifact object. This effectively replaces the `path` option we used above.
**It is EXTREMELY IMPORTANT that the file size is CORRECT. The launcher's download queue will not function properly otherwise.**
Ex. Ex.
```SHELL ```SHELL

View File

@ -9,7 +9,15 @@ const ejse = require('ejs-electron')
let win let win
function createWindow() { function createWindow() {
win = new BrowserWindow({ width: 980, height: 552, icon: getPlatformIcon('WesterosSealSquare'), frame: false}) win = new BrowserWindow({
width: 980,
height: 552,
icon: getPlatformIcon('WesterosSealSquare'),
frame: false,
webPreferences: {
preload: path.join(__dirname, 'app', 'assets', 'js', 'preloader.js')
}
})
ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length))) ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)))