Add files via upload
This commit is contained in:
parent
d84b162867
commit
6c0e851f2b
@ -1,73 +1,73 @@
|
||||
let target = require('./assetguard')[process.argv[2]]
|
||||
if(target == null){
|
||||
process.send({context: 'error', data: null, error: 'Invalid class name'})
|
||||
console.error('Invalid class name passed to argv[2], cannot continue.')
|
||||
process.exit(1)
|
||||
}
|
||||
let tracker = new target(...(process.argv.splice(3)))
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
//const tracker = new AssetGuard(process.argv[2], process.argv[3])
|
||||
console.log('AssetExec Started')
|
||||
|
||||
// Temporary for debug purposes.
|
||||
process.on('unhandledRejection', r => console.log(r))
|
||||
|
||||
let percent = 0
|
||||
function assignListeners(){
|
||||
tracker.on('validate', (data) => {
|
||||
process.send({context: 'validate', data})
|
||||
})
|
||||
tracker.on('progress', (data, acc, total) => {
|
||||
const currPercent = parseInt((acc/total) * 100)
|
||||
if (currPercent !== percent) {
|
||||
percent = currPercent
|
||||
process.send({context: 'progress', data, value: acc, total, percent})
|
||||
}
|
||||
})
|
||||
tracker.on('complete', (data, ...args) => {
|
||||
process.send({context: 'complete', data, args})
|
||||
})
|
||||
tracker.on('error', (data, error) => {
|
||||
process.send({context: 'error', data, error})
|
||||
})
|
||||
}
|
||||
|
||||
assignListeners()
|
||||
|
||||
process.on('message', (msg) => {
|
||||
if(msg.task === 'execute'){
|
||||
const func = msg.function
|
||||
let nS = tracker[func] // Nonstatic context
|
||||
let iS = target[func] // Static context
|
||||
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, context: func})
|
||||
}).catch((err) => {
|
||||
process.send({result: err.message || err, context: func})
|
||||
})
|
||||
} else {
|
||||
process.send({result: res, context: func})
|
||||
}
|
||||
} else {
|
||||
process.send({context: 'error', data: null, error: `Function ${func} not found on ${process.argv[2]}`})
|
||||
}
|
||||
} else if(msg.task === 'changeContext'){
|
||||
target = require('./assetguard')[msg.class]
|
||||
if(target == null){
|
||||
process.send({context: 'error', data: null, error: `Invalid class ${msg.class}`})
|
||||
} else {
|
||||
tracker = new target(...(msg.args))
|
||||
assignListeners()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
process.on('disconnect', () => {
|
||||
console.log('AssetExec Disconnected')
|
||||
process.exit(0)
|
||||
let target = require('./assetguard')[process.argv[2]]
|
||||
if(target == null){
|
||||
process.send({context: 'error', data: null, error: 'Invalid class name'})
|
||||
console.error('Invalid class name passed to argv[2], cannot continue.')
|
||||
process.exit(1)
|
||||
}
|
||||
let tracker = new target(...(process.argv.splice(3)))
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
//const tracker = new AssetGuard(process.argv[2], process.argv[3])
|
||||
console.log('AssetExec Started')
|
||||
|
||||
// Temporary for debug purposes.
|
||||
process.on('unhandledRejection', r => console.log(r))
|
||||
|
||||
let percent = 0
|
||||
function assignListeners(){
|
||||
tracker.on('validate', (data) => {
|
||||
process.send({context: 'validate', data})
|
||||
})
|
||||
tracker.on('progress', (data, acc, total) => {
|
||||
const currPercent = parseInt((acc/total) * 100)
|
||||
if (currPercent !== percent) {
|
||||
percent = currPercent
|
||||
process.send({context: 'progress', data, value: acc, total, percent})
|
||||
}
|
||||
})
|
||||
tracker.on('complete', (data, ...args) => {
|
||||
process.send({context: 'complete', data, args})
|
||||
})
|
||||
tracker.on('error', (data, error) => {
|
||||
process.send({context: 'error', data, error})
|
||||
})
|
||||
}
|
||||
|
||||
assignListeners()
|
||||
|
||||
process.on('message', (msg) => {
|
||||
if(msg.task === 'execute'){
|
||||
const func = msg.function
|
||||
let nS = tracker[func] // Nonstatic context
|
||||
let iS = target[func] // Static context
|
||||
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, context: func})
|
||||
}).catch((err) => {
|
||||
process.send({result: err.message || err, context: func})
|
||||
})
|
||||
} else {
|
||||
process.send({result: res, context: func})
|
||||
}
|
||||
} else {
|
||||
process.send({context: 'error', data: null, error: `Function ${func} not found on ${process.argv[2]}`})
|
||||
}
|
||||
} else if(msg.task === 'changeContext'){
|
||||
target = require('./assetguard')[msg.class]
|
||||
if(target == null){
|
||||
process.send({context: 'error', data: null, error: `Invalid class ${msg.class}`})
|
||||
} else {
|
||||
tracker = new target(...(msg.args))
|
||||
assignListeners()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
process.on('disconnect', () => {
|
||||
console.log('AssetExec Disconnected')
|
||||
process.exit(0)
|
||||
})
|
File diff suppressed because it is too large
Load Diff
@ -1,99 +1,99 @@
|
||||
/**
|
||||
* AuthManager
|
||||
*
|
||||
* This module aims to abstract login procedures. Results from Mojang's REST api
|
||||
* are retrieved through our Mojang module. These results are processed and stored,
|
||||
* if applicable, in the config using the ConfigManager. All login procedures should
|
||||
* be made through this module.
|
||||
*
|
||||
* @module authmanager
|
||||
*/
|
||||
// Requirements
|
||||
const ConfigManager = require('./configmanager')
|
||||
const LoggerUtil = require('./loggerutil')
|
||||
const Mojang = require('./mojang')
|
||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
||||
|
||||
// Functions
|
||||
|
||||
/**
|
||||
* Add an account. This will authenticate the given credentials with Mojang's
|
||||
* authserver. The resultant data will be stored as an auth account in the
|
||||
* configuration database.
|
||||
*
|
||||
* @param {string} username The account username (email if migrated).
|
||||
* @param {string} password The account password.
|
||||
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
|
||||
*/
|
||||
exports.addAccount = async function(username, password){
|
||||
try {
|
||||
const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken())
|
||||
if(session.selectedProfile != null){
|
||||
const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
|
||||
if(ConfigManager.getClientToken() == null){
|
||||
ConfigManager.setClientToken(session.clientToken)
|
||||
}
|
||||
ConfigManager.save()
|
||||
return ret
|
||||
} else {
|
||||
throw new Error('NotPaidAccount')
|
||||
}
|
||||
|
||||
} catch (err){
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an account. This will invalidate the access token associated
|
||||
* with the account and then remove it from the database.
|
||||
*
|
||||
* @param {string} uuid The UUID of the account to be removed.
|
||||
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
|
||||
*/
|
||||
exports.removeAccount = async function(uuid){
|
||||
try {
|
||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
return Promise.resolve()
|
||||
} catch (err){
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the selected account with Mojang's authserver. If the account is not valid,
|
||||
* we will attempt to refresh the access token and update that value. If that fails, a
|
||||
* new login will be required.
|
||||
*
|
||||
* **Function is WIP**
|
||||
*
|
||||
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
|
||||
* otherwise false.
|
||||
*/
|
||||
exports.validateSelected = async function(){
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
}
|
||||
/**
|
||||
* AuthManager
|
||||
*
|
||||
* This module aims to abstract login procedures. Results from Mojang's REST api
|
||||
* are retrieved through our Mojang module. These results are processed and stored,
|
||||
* if applicable, in the config using the ConfigManager. All login procedures should
|
||||
* be made through this module.
|
||||
*
|
||||
* @module authmanager
|
||||
*/
|
||||
// Requirements
|
||||
const ConfigManager = require('./configmanager')
|
||||
const LoggerUtil = require('./loggerutil')
|
||||
const Mojang = require('./mojang')
|
||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
||||
|
||||
// Functions
|
||||
|
||||
/**
|
||||
* Add an account. This will authenticate the given credentials with Mojang's
|
||||
* authserver. The resultant data will be stored as an auth account in the
|
||||
* configuration database.
|
||||
*
|
||||
* @param {string} username The account username (email if migrated).
|
||||
* @param {string} password The account password.
|
||||
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
|
||||
*/
|
||||
exports.addAccount = async function(username, password){
|
||||
try {
|
||||
const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken())
|
||||
if(session.selectedProfile != null){
|
||||
const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
|
||||
if(ConfigManager.getClientToken() == null){
|
||||
ConfigManager.setClientToken(session.clientToken)
|
||||
}
|
||||
ConfigManager.save()
|
||||
return ret
|
||||
} else {
|
||||
throw new Error('NotPaidAccount')
|
||||
}
|
||||
|
||||
} catch (err){
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an account. This will invalidate the access token associated
|
||||
* with the account and then remove it from the database.
|
||||
*
|
||||
* @param {string} uuid The UUID of the account to be removed.
|
||||
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
|
||||
*/
|
||||
exports.removeAccount = async function(uuid){
|
||||
try {
|
||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.removeAuthAccount(uuid)
|
||||
ConfigManager.save()
|
||||
return Promise.resolve()
|
||||
} catch (err){
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the selected account with Mojang's authserver. If the account is not valid,
|
||||
* we will attempt to refresh the access token and update that value. If that fails, a
|
||||
* new login will be required.
|
||||
*
|
||||
* **Function is WIP**
|
||||
*
|
||||
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
|
||||
* otherwise false.
|
||||
*/
|
||||
exports.validateSelected = async function(){
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
||||
if(!isValid){
|
||||
try {
|
||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
||||
ConfigManager.save()
|
||||
} catch(err) {
|
||||
logger.debug('Error while validating selected profile:', err)
|
||||
if(err && err.error === 'ForbiddenOperationException'){
|
||||
// What do we do?
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,48 +1,48 @@
|
||||
// Work in progress
|
||||
const logger = require('./loggerutil')('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold')
|
||||
|
||||
const {Client} = require('discord-rpc')
|
||||
|
||||
let client
|
||||
let activity
|
||||
|
||||
exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){
|
||||
client = new Client({ transport: 'ipc' })
|
||||
|
||||
activity = {
|
||||
details: initialDetails,
|
||||
state: 'Server: ' + servSettings.shortId,
|
||||
largeImageKey: servSettings.largeImageKey,
|
||||
largeImageText: servSettings.largeImageText,
|
||||
smallImageKey: genSettings.smallImageKey,
|
||||
smallImageText: genSettings.smallImageText,
|
||||
startTimestamp: new Date().getTime(),
|
||||
instance: false
|
||||
}
|
||||
|
||||
client.on('ready', () => {
|
||||
logger.log('Discord RPC Connected')
|
||||
client.setActivity(activity)
|
||||
})
|
||||
|
||||
client.login({clientId: genSettings.clientId}).catch(error => {
|
||||
if(error.message.includes('ENOENT')) {
|
||||
logger.log('Unable to initialize Discord Rich Presence, no client detected.')
|
||||
} else {
|
||||
logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.updateDetails = function(details){
|
||||
activity.details = details
|
||||
client.setActivity(activity)
|
||||
}
|
||||
|
||||
exports.shutdownRPC = function(){
|
||||
if(!client) return
|
||||
client.clearActivity()
|
||||
client.destroy()
|
||||
client = null
|
||||
activity = null
|
||||
// Work in progress
|
||||
const logger = require('./loggerutil')('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold')
|
||||
|
||||
const {Client} = require('discord-rpc')
|
||||
|
||||
let client
|
||||
let activity
|
||||
|
||||
exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){
|
||||
client = new Client({ transport: 'ipc' })
|
||||
|
||||
activity = {
|
||||
details: initialDetails,
|
||||
state: 'Server: ' + servSettings.shortId,
|
||||
largeImageKey: servSettings.largeImageKey,
|
||||
largeImageText: servSettings.largeImageText,
|
||||
smallImageKey: genSettings.smallImageKey,
|
||||
smallImageText: genSettings.smallImageText,
|
||||
startTimestamp: new Date().getTime(),
|
||||
instance: false
|
||||
}
|
||||
|
||||
client.on('ready', () => {
|
||||
logger.log('Discord RPC Connected')
|
||||
client.setActivity(activity)
|
||||
})
|
||||
|
||||
client.login({clientId: genSettings.clientId}).catch(error => {
|
||||
if(error.message.includes('ENOENT')) {
|
||||
logger.log('Unable to initialize Discord Rich Presence, no client detected.')
|
||||
} else {
|
||||
logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.updateDetails = function(details){
|
||||
activity.details = details
|
||||
client.setActivity(activity)
|
||||
}
|
||||
|
||||
exports.shutdownRPC = function(){
|
||||
if(!client) return
|
||||
client.clearActivity()
|
||||
client.destroy()
|
||||
client = null
|
||||
activity = null
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,232 +1,232 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { shell } = require('electron')
|
||||
|
||||
// Group #1: File Name (without .disabled, if any)
|
||||
// Group #2: File Extension (jar, zip, or litemod)
|
||||
// Group #3: If it is disabled (if string 'disabled' is present)
|
||||
const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/
|
||||
const DISABLED_EXT = '.disabled'
|
||||
|
||||
const SHADER_REGEX = /^(.+)\.zip$/
|
||||
const SHADER_OPTION = /shaderPack=(.+)/
|
||||
const SHADER_DIR = 'shaderpacks'
|
||||
const SHADER_CONFIG = 'optionsshaders.txt'
|
||||
|
||||
/**
|
||||
* Validate that the given directory exists. If not, it is
|
||||
* created.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
*/
|
||||
exports.validateDir = function(dir) {
|
||||
fs.ensureDirSync(dir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for drop-in mods in both the mods folder and version
|
||||
* safe mods folder.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} version The minecraft version of the server configuration.
|
||||
*
|
||||
* @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]}
|
||||
* An array of objects storing metadata about each discovered mod.
|
||||
*/
|
||||
exports.scanForDropinMods = function(modsDir, version) {
|
||||
const modsDiscovered = []
|
||||
if(fs.existsSync(modsDir)){
|
||||
let modCandidates = fs.readdirSync(modsDir)
|
||||
let verCandidates = []
|
||||
const versionDir = path.join(modsDir, version)
|
||||
if(fs.existsSync(versionDir)){
|
||||
verCandidates = fs.readdirSync(versionDir)
|
||||
}
|
||||
for(let file of modCandidates){
|
||||
const match = MOD_REGEX.exec(file)
|
||||
if(match != null){
|
||||
modsDiscovered.push({
|
||||
fullName: match[0],
|
||||
name: match[1],
|
||||
ext: match[2],
|
||||
disabled: match[3] != null
|
||||
})
|
||||
}
|
||||
}
|
||||
for(let file of verCandidates){
|
||||
const match = MOD_REGEX.exec(file)
|
||||
if(match != null){
|
||||
modsDiscovered.push({
|
||||
fullName: path.join(version, match[0]),
|
||||
name: match[1],
|
||||
ext: match[2],
|
||||
disabled: match[3] != null
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return modsDiscovered
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dropin mods.
|
||||
*
|
||||
* @param {FileList} files The files to add.
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
*/
|
||||
exports.addDropinMods = function(files, modsdir) {
|
||||
|
||||
exports.validateDir(modsdir)
|
||||
|
||||
for(let f of files) {
|
||||
if(MOD_REGEX.exec(f.name) != null) {
|
||||
fs.moveSync(f.path, path.join(modsdir, f.name))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drop-in mod from the file system.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} fullName The fullName of the discovered mod to delete.
|
||||
*
|
||||
* @returns {boolean} True if the mod was deleted, otherwise false.
|
||||
*/
|
||||
exports.deleteDropinMod = function(modsDir, fullName){
|
||||
const res = shell.moveItemToTrash(path.join(modsDir, fullName))
|
||||
if(!res){
|
||||
shell.beep()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a discovered mod on or off. This is achieved by either
|
||||
* adding or disabling the .disabled extension to the local file.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} fullName The fullName of the discovered mod to toggle.
|
||||
* @param {boolean} enable Whether to toggle on or off the mod.
|
||||
*
|
||||
* @returns {Promise.<void>} A promise which resolves when the mod has
|
||||
* been toggled. If an IO error occurs the promise will be rejected.
|
||||
*/
|
||||
exports.toggleDropinMod = function(modsDir, fullName, enable){
|
||||
return new Promise((resolve, reject) => {
|
||||
const oldPath = path.join(modsDir, fullName)
|
||||
const newPath = path.join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT)
|
||||
|
||||
fs.rename(oldPath, newPath, (err) => {
|
||||
if(err){
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a drop-in mod is enabled.
|
||||
*
|
||||
* @param {string} fullName The fullName of the discovered mod to toggle.
|
||||
* @returns {boolean} True if the mod is enabled, otherwise false.
|
||||
*/
|
||||
exports.isDropinModEnabled = function(fullName){
|
||||
return !fullName.endsWith(DISABLED_EXT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for shaderpacks inside the shaderpacks folder.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*
|
||||
* @returns {{fullName: string, name: string}[]}
|
||||
* An array of objects storing metadata about each discovered shaderpack.
|
||||
*/
|
||||
exports.scanForShaderpacks = function(instanceDir){
|
||||
const shaderDir = path.join(instanceDir, SHADER_DIR)
|
||||
const packsDiscovered = [{
|
||||
fullName: 'OFF',
|
||||
name: 'Off (Default)'
|
||||
}]
|
||||
if(fs.existsSync(shaderDir)){
|
||||
let modCandidates = fs.readdirSync(shaderDir)
|
||||
for(let file of modCandidates){
|
||||
const match = SHADER_REGEX.exec(file)
|
||||
if(match != null){
|
||||
packsDiscovered.push({
|
||||
fullName: match[0],
|
||||
name: match[1]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return packsDiscovered
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the optionsshaders.txt file to locate the current
|
||||
* enabled pack. If the file does not exist, OFF is returned.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*
|
||||
* @returns {string} The file name of the enabled shaderpack.
|
||||
*/
|
||||
exports.getEnabledShaderpack = function(instanceDir){
|
||||
exports.validateDir(instanceDir)
|
||||
|
||||
const optionsShaders = path.join(instanceDir, SHADER_CONFIG)
|
||||
if(fs.existsSync(optionsShaders)){
|
||||
const buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'})
|
||||
const match = SHADER_OPTION.exec(buf)
|
||||
if(match != null){
|
||||
return match[1]
|
||||
} else {
|
||||
console.warn('WARNING: Shaderpack regex failed.')
|
||||
}
|
||||
}
|
||||
return 'OFF'
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enabled shaderpack.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
* @param {string} pack the file name of the shaderpack.
|
||||
*/
|
||||
exports.setEnabledShaderpack = function(instanceDir, pack){
|
||||
exports.validateDir(instanceDir)
|
||||
|
||||
const optionsShaders = path.join(instanceDir, SHADER_CONFIG)
|
||||
let buf
|
||||
if(fs.existsSync(optionsShaders)){
|
||||
buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'})
|
||||
buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`)
|
||||
} else {
|
||||
buf = `shaderPack=${pack}`
|
||||
}
|
||||
fs.writeFileSync(optionsShaders, buf, {encoding: 'utf-8'})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shaderpacks.
|
||||
*
|
||||
* @param {FileList} files The files to add.
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*/
|
||||
exports.addShaderpacks = function(files, instanceDir) {
|
||||
|
||||
const p = path.join(instanceDir, SHADER_DIR)
|
||||
|
||||
exports.validateDir(p)
|
||||
|
||||
for(let f of files) {
|
||||
if(SHADER_REGEX.exec(f.name) != null) {
|
||||
fs.moveSync(f.path, path.join(p, f.name))
|
||||
}
|
||||
}
|
||||
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { shell } = require('electron')
|
||||
|
||||
// Group #1: File Name (without .disabled, if any)
|
||||
// Group #2: File Extension (jar, zip, or litemod)
|
||||
// Group #3: If it is disabled (if string 'disabled' is present)
|
||||
const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/
|
||||
const DISABLED_EXT = '.disabled'
|
||||
|
||||
const SHADER_REGEX = /^(.+)\.zip$/
|
||||
const SHADER_OPTION = /shaderPack=(.+)/
|
||||
const SHADER_DIR = 'shaderpacks'
|
||||
const SHADER_CONFIG = 'optionsshaders.txt'
|
||||
|
||||
/**
|
||||
* Validate that the given directory exists. If not, it is
|
||||
* created.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
*/
|
||||
exports.validateDir = function(dir) {
|
||||
fs.ensureDirSync(dir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for drop-in mods in both the mods folder and version
|
||||
* safe mods folder.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} version The minecraft version of the server configuration.
|
||||
*
|
||||
* @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]}
|
||||
* An array of objects storing metadata about each discovered mod.
|
||||
*/
|
||||
exports.scanForDropinMods = function(modsDir, version) {
|
||||
const modsDiscovered = []
|
||||
if(fs.existsSync(modsDir)){
|
||||
let modCandidates = fs.readdirSync(modsDir)
|
||||
let verCandidates = []
|
||||
const versionDir = path.join(modsDir, version)
|
||||
if(fs.existsSync(versionDir)){
|
||||
verCandidates = fs.readdirSync(versionDir)
|
||||
}
|
||||
for(let file of modCandidates){
|
||||
const match = MOD_REGEX.exec(file)
|
||||
if(match != null){
|
||||
modsDiscovered.push({
|
||||
fullName: match[0],
|
||||
name: match[1],
|
||||
ext: match[2],
|
||||
disabled: match[3] != null
|
||||
})
|
||||
}
|
||||
}
|
||||
for(let file of verCandidates){
|
||||
const match = MOD_REGEX.exec(file)
|
||||
if(match != null){
|
||||
modsDiscovered.push({
|
||||
fullName: path.join(version, match[0]),
|
||||
name: match[1],
|
||||
ext: match[2],
|
||||
disabled: match[3] != null
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return modsDiscovered
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dropin mods.
|
||||
*
|
||||
* @param {FileList} files The files to add.
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
*/
|
||||
exports.addDropinMods = function(files, modsdir) {
|
||||
|
||||
exports.validateDir(modsdir)
|
||||
|
||||
for(let f of files) {
|
||||
if(MOD_REGEX.exec(f.name) != null) {
|
||||
fs.moveSync(f.path, path.join(modsdir, f.name))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drop-in mod from the file system.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} fullName The fullName of the discovered mod to delete.
|
||||
*
|
||||
* @returns {boolean} True if the mod was deleted, otherwise false.
|
||||
*/
|
||||
exports.deleteDropinMod = function(modsDir, fullName){
|
||||
const res = shell.moveItemToTrash(path.join(modsDir, fullName))
|
||||
if(!res){
|
||||
shell.beep()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a discovered mod on or off. This is achieved by either
|
||||
* adding or disabling the .disabled extension to the local file.
|
||||
*
|
||||
* @param {string} modsDir The path to the mods directory.
|
||||
* @param {string} fullName The fullName of the discovered mod to toggle.
|
||||
* @param {boolean} enable Whether to toggle on or off the mod.
|
||||
*
|
||||
* @returns {Promise.<void>} A promise which resolves when the mod has
|
||||
* been toggled. If an IO error occurs the promise will be rejected.
|
||||
*/
|
||||
exports.toggleDropinMod = function(modsDir, fullName, enable){
|
||||
return new Promise((resolve, reject) => {
|
||||
const oldPath = path.join(modsDir, fullName)
|
||||
const newPath = path.join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT)
|
||||
|
||||
fs.rename(oldPath, newPath, (err) => {
|
||||
if(err){
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a drop-in mod is enabled.
|
||||
*
|
||||
* @param {string} fullName The fullName of the discovered mod to toggle.
|
||||
* @returns {boolean} True if the mod is enabled, otherwise false.
|
||||
*/
|
||||
exports.isDropinModEnabled = function(fullName){
|
||||
return !fullName.endsWith(DISABLED_EXT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for shaderpacks inside the shaderpacks folder.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*
|
||||
* @returns {{fullName: string, name: string}[]}
|
||||
* An array of objects storing metadata about each discovered shaderpack.
|
||||
*/
|
||||
exports.scanForShaderpacks = function(instanceDir){
|
||||
const shaderDir = path.join(instanceDir, SHADER_DIR)
|
||||
const packsDiscovered = [{
|
||||
fullName: 'OFF',
|
||||
name: 'Off (Default)'
|
||||
}]
|
||||
if(fs.existsSync(shaderDir)){
|
||||
let modCandidates = fs.readdirSync(shaderDir)
|
||||
for(let file of modCandidates){
|
||||
const match = SHADER_REGEX.exec(file)
|
||||
if(match != null){
|
||||
packsDiscovered.push({
|
||||
fullName: match[0],
|
||||
name: match[1]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return packsDiscovered
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the optionsshaders.txt file to locate the current
|
||||
* enabled pack. If the file does not exist, OFF is returned.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*
|
||||
* @returns {string} The file name of the enabled shaderpack.
|
||||
*/
|
||||
exports.getEnabledShaderpack = function(instanceDir){
|
||||
exports.validateDir(instanceDir)
|
||||
|
||||
const optionsShaders = path.join(instanceDir, SHADER_CONFIG)
|
||||
if(fs.existsSync(optionsShaders)){
|
||||
const buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'})
|
||||
const match = SHADER_OPTION.exec(buf)
|
||||
if(match != null){
|
||||
return match[1]
|
||||
} else {
|
||||
console.warn('WARNING: Shaderpack regex failed.')
|
||||
}
|
||||
}
|
||||
return 'OFF'
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enabled shaderpack.
|
||||
*
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
* @param {string} pack the file name of the shaderpack.
|
||||
*/
|
||||
exports.setEnabledShaderpack = function(instanceDir, pack){
|
||||
exports.validateDir(instanceDir)
|
||||
|
||||
const optionsShaders = path.join(instanceDir, SHADER_CONFIG)
|
||||
let buf
|
||||
if(fs.existsSync(optionsShaders)){
|
||||
buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'})
|
||||
buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`)
|
||||
} else {
|
||||
buf = `shaderPack=${pack}`
|
||||
}
|
||||
fs.writeFileSync(optionsShaders, buf, {encoding: 'utf-8'})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shaderpacks.
|
||||
*
|
||||
* @param {FileList} files The files to add.
|
||||
* @param {string} instanceDir The path to the server instance directory.
|
||||
*/
|
||||
exports.addShaderpacks = function(files, instanceDir) {
|
||||
|
||||
const p = path.join(instanceDir, SHADER_DIR)
|
||||
|
||||
exports.validateDir(p)
|
||||
|
||||
for(let f of files) {
|
||||
if(SHADER_REGEX.exec(f.name) != null) {
|
||||
fs.moveSync(f.path, path.join(p, f.name))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
'use strict'
|
||||
const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
|
||||
const isEnvSet = 'ELECTRON_IS_DEV' in process.env
|
||||
|
||||
'use strict'
|
||||
const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
|
||||
const isEnvSet = 'ELECTRON_IS_DEV' in process.env
|
||||
|
||||
module.exports = isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath))
|
@ -1,21 +1,21 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
let lang
|
||||
|
||||
exports.loadLanguage = function(id){
|
||||
lang = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.json`))) || {}
|
||||
}
|
||||
|
||||
exports.query = function(id){
|
||||
let query = id.split('.')
|
||||
let res = lang
|
||||
for(let q of query){
|
||||
res = res[q]
|
||||
}
|
||||
return res === lang ? {} : res
|
||||
}
|
||||
|
||||
exports.queryJS = function(id){
|
||||
return exports.query(`js.${id}`)
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
let lang
|
||||
|
||||
exports.loadLanguage = function(id){
|
||||
lang = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.json`))) || {}
|
||||
}
|
||||
|
||||
exports.query = function(id){
|
||||
let query = id.split('.')
|
||||
let res = lang
|
||||
for(let q of query){
|
||||
res = res[q]
|
||||
}
|
||||
return res === lang ? {} : res
|
||||
}
|
||||
|
||||
exports.queryJS = function(id){
|
||||
return exports.query(`js.${id}`)
|
||||
}
|
@ -1,32 +1,32 @@
|
||||
class LoggerUtil {
|
||||
|
||||
constructor(prefix, style){
|
||||
this.prefix = prefix
|
||||
this.style = style
|
||||
}
|
||||
|
||||
log(){
|
||||
console.log.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
info(){
|
||||
console.info.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
warn(){
|
||||
console.warn.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
debug(){
|
||||
console.debug.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
error(){
|
||||
console.error.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = function (prefix, style){
|
||||
return new LoggerUtil(prefix, style)
|
||||
class LoggerUtil {
|
||||
|
||||
constructor(prefix, style){
|
||||
this.prefix = prefix
|
||||
this.style = style
|
||||
}
|
||||
|
||||
log(){
|
||||
console.log.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
info(){
|
||||
console.info.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
warn(){
|
||||
console.warn.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
debug(){
|
||||
console.debug.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
error(){
|
||||
console.error.apply(null, [this.prefix, this.style, ...arguments])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = function (prefix, style){
|
||||
return new LoggerUtil(prefix, style)
|
||||
}
|
@ -1,271 +1,271 @@
|
||||
/**
|
||||
* Mojang
|
||||
*
|
||||
* This module serves as a minimal wrapper for Mojang's REST api.
|
||||
*
|
||||
* @module mojang
|
||||
*/
|
||||
// Requirements
|
||||
const request = require('request')
|
||||
const logger = require('./loggerutil')('%c[Mojang]', 'color: #a02d2a; font-weight: bold')
|
||||
|
||||
// Constants
|
||||
const minecraftAgent = {
|
||||
name: 'Minecraft',
|
||||
version: 1
|
||||
}
|
||||
const authpath = 'https://authserver.mojang.com'
|
||||
const statuses = [
|
||||
{
|
||||
service: 'sessionserver.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Multiplayer Session Service',
|
||||
essential: true
|
||||
},
|
||||
{
|
||||
service: 'authserver.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Authentication Service',
|
||||
essential: true
|
||||
},
|
||||
{
|
||||
service: 'textures.minecraft.net',
|
||||
status: 'grey',
|
||||
name: 'Minecraft Skins',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'api.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Public API',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'minecraft.net',
|
||||
status: 'grey',
|
||||
name: 'Minecraft.net',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'account.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Mojang Accounts Website',
|
||||
essential: false
|
||||
}
|
||||
]
|
||||
|
||||
// Functions
|
||||
|
||||
/**
|
||||
* Converts a Mojang status color to a hex value. Valid statuses
|
||||
* are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
|
||||
* to our project which represents an unknown status.
|
||||
*
|
||||
* @param {string} status A valid status code.
|
||||
* @returns {string} The hex color of the status code.
|
||||
*/
|
||||
exports.statusToHex = function(status){
|
||||
switch(status.toLowerCase()){
|
||||
case 'green':
|
||||
return '#a5c325'
|
||||
case 'yellow':
|
||||
return '#eac918'
|
||||
case 'red':
|
||||
return '#c32625'
|
||||
case 'grey':
|
||||
default:
|
||||
return '#848484'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status of Mojang's services.
|
||||
* The response is condensed into a single object. Each service is
|
||||
* a key, where the value is an object containing a status and name
|
||||
* property.
|
||||
*
|
||||
* @see http://wiki.vg/Mojang_API#API_Status
|
||||
*/
|
||||
exports.status = function(){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get('https://status.mojang.com/check',
|
||||
{
|
||||
json: true,
|
||||
timeout: 2500
|
||||
},
|
||||
function(error, response, body){
|
||||
|
||||
if(error || response.statusCode !== 200){
|
||||
logger.warn('Unable to retrieve Mojang status.')
|
||||
logger.debug('Error while retrieving Mojang statuses:', error)
|
||||
//reject(error || response.statusCode)
|
||||
for(let i=0; i<statuses.length; i++){
|
||||
statuses[i].status = 'grey'
|
||||
}
|
||||
resolve(statuses)
|
||||
} else {
|
||||
for(let i=0; i<body.length; i++){
|
||||
const key = Object.keys(body[i])[0]
|
||||
inner:
|
||||
for(let j=0; j<statuses.length; j++){
|
||||
if(statuses[j].service === key) {
|
||||
statuses[j].status = body[i][key]
|
||||
break inner
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(statuses)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user with their Mojang credentials.
|
||||
*
|
||||
* @param {string} username The user's username, this is often an email.
|
||||
* @param {string} password The user's password.
|
||||
* @param {string} clientToken The launcher's Client Token.
|
||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
||||
* @param {Object} agent Optional. Provided by default. Adds user info to the response.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Authenticate
|
||||
*/
|
||||
exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const body = {
|
||||
agent,
|
||||
username,
|
||||
password,
|
||||
requestUser
|
||||
}
|
||||
if(clientToken != null){
|
||||
body.clientToken = clientToken
|
||||
}
|
||||
|
||||
request.post(authpath + '/authenticate',
|
||||
{
|
||||
json: true,
|
||||
body
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during authentication.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 200){
|
||||
resolve(body)
|
||||
} else {
|
||||
reject(body || {code: 'ENOTFOUND'})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an access token. This should always be done before launching.
|
||||
* The client token should match the one used to create the access token.
|
||||
*
|
||||
* @param {string} accessToken The access token to validate.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Validate
|
||||
*/
|
||||
exports.validate = function(accessToken, clientToken){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/validate',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during validation.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 403){
|
||||
resolve(false)
|
||||
} else {
|
||||
// 204 if valid
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates an access token. The clientToken must match the
|
||||
* token used to create the provided accessToken.
|
||||
*
|
||||
* @param {string} accessToken The access token to invalidate.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Invalidate
|
||||
*/
|
||||
exports.invalidate = function(accessToken, clientToken){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/invalidate',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during invalidation.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 204){
|
||||
resolve()
|
||||
} else {
|
||||
reject(body)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a user's authentication. This should be used to keep a user logged
|
||||
* in without asking them for their credentials again. A new access token will
|
||||
* be generated using a recent invalid access token. See Wiki for more info.
|
||||
*
|
||||
* @param {string} accessToken The old access token.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Refresh
|
||||
*/
|
||||
exports.refresh = function(accessToken, clientToken, requestUser = true){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/refresh',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken,
|
||||
requestUser
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during refresh.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 200){
|
||||
resolve(body)
|
||||
} else {
|
||||
reject(body)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
/**
|
||||
* Mojang
|
||||
*
|
||||
* This module serves as a minimal wrapper for Mojang's REST api.
|
||||
*
|
||||
* @module mojang
|
||||
*/
|
||||
// Requirements
|
||||
const request = require('request')
|
||||
const logger = require('./loggerutil')('%c[Mojang]', 'color: #a02d2a; font-weight: bold')
|
||||
|
||||
// Constants
|
||||
const minecraftAgent = {
|
||||
name: 'Minecraft',
|
||||
version: 1
|
||||
}
|
||||
const authpath = 'https://authserver.mojang.com'
|
||||
const statuses = [
|
||||
{
|
||||
service: 'sessionserver.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Multiplayer Session Service',
|
||||
essential: true
|
||||
},
|
||||
{
|
||||
service: 'authserver.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Authentication Service',
|
||||
essential: true
|
||||
},
|
||||
{
|
||||
service: 'textures.minecraft.net',
|
||||
status: 'grey',
|
||||
name: 'Minecraft Skins',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'api.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Public API',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'minecraft.net',
|
||||
status: 'grey',
|
||||
name: 'Minecraft.net',
|
||||
essential: false
|
||||
},
|
||||
{
|
||||
service: 'account.mojang.com',
|
||||
status: 'grey',
|
||||
name: 'Mojang Accounts Website',
|
||||
essential: false
|
||||
}
|
||||
]
|
||||
|
||||
// Functions
|
||||
|
||||
/**
|
||||
* Converts a Mojang status color to a hex value. Valid statuses
|
||||
* are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
|
||||
* to our project which represents an unknown status.
|
||||
*
|
||||
* @param {string} status A valid status code.
|
||||
* @returns {string} The hex color of the status code.
|
||||
*/
|
||||
exports.statusToHex = function(status){
|
||||
switch(status.toLowerCase()){
|
||||
case 'green':
|
||||
return '#a5c325'
|
||||
case 'yellow':
|
||||
return '#eac918'
|
||||
case 'red':
|
||||
return '#c32625'
|
||||
case 'grey':
|
||||
default:
|
||||
return '#848484'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status of Mojang's services.
|
||||
* The response is condensed into a single object. Each service is
|
||||
* a key, where the value is an object containing a status and name
|
||||
* property.
|
||||
*
|
||||
* @see http://wiki.vg/Mojang_API#API_Status
|
||||
*/
|
||||
exports.status = function(){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get('https://status.mojang.com/check',
|
||||
{
|
||||
json: true,
|
||||
timeout: 2500
|
||||
},
|
||||
function(error, response, body){
|
||||
|
||||
if(error || response.statusCode !== 200){
|
||||
logger.warn('Unable to retrieve Mojang status.')
|
||||
logger.debug('Error while retrieving Mojang statuses:', error)
|
||||
//reject(error || response.statusCode)
|
||||
for(let i=0; i<statuses.length; i++){
|
||||
statuses[i].status = 'grey'
|
||||
}
|
||||
resolve(statuses)
|
||||
} else {
|
||||
for(let i=0; i<body.length; i++){
|
||||
const key = Object.keys(body[i])[0]
|
||||
inner:
|
||||
for(let j=0; j<statuses.length; j++){
|
||||
if(statuses[j].service === key) {
|
||||
statuses[j].status = body[i][key]
|
||||
break inner
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(statuses)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user with their Mojang credentials.
|
||||
*
|
||||
* @param {string} username The user's username, this is often an email.
|
||||
* @param {string} password The user's password.
|
||||
* @param {string} clientToken The launcher's Client Token.
|
||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
||||
* @param {Object} agent Optional. Provided by default. Adds user info to the response.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Authenticate
|
||||
*/
|
||||
exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const body = {
|
||||
agent,
|
||||
username,
|
||||
password,
|
||||
requestUser
|
||||
}
|
||||
if(clientToken != null){
|
||||
body.clientToken = clientToken
|
||||
}
|
||||
|
||||
request.post(authpath + '/authenticate',
|
||||
{
|
||||
json: true,
|
||||
body
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during authentication.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 200){
|
||||
resolve(body)
|
||||
} else {
|
||||
reject(body || {code: 'ENOTFOUND'})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an access token. This should always be done before launching.
|
||||
* The client token should match the one used to create the access token.
|
||||
*
|
||||
* @param {string} accessToken The access token to validate.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Validate
|
||||
*/
|
||||
exports.validate = function(accessToken, clientToken){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/validate',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during validation.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 403){
|
||||
resolve(false)
|
||||
} else {
|
||||
// 204 if valid
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates an access token. The clientToken must match the
|
||||
* token used to create the provided accessToken.
|
||||
*
|
||||
* @param {string} accessToken The access token to invalidate.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Invalidate
|
||||
*/
|
||||
exports.invalidate = function(accessToken, clientToken){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/invalidate',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during invalidation.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 204){
|
||||
resolve()
|
||||
} else {
|
||||
reject(body)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a user's authentication. This should be used to keep a user logged
|
||||
* in without asking them for their credentials again. A new access token will
|
||||
* be generated using a recent invalid access token. See Wiki for more info.
|
||||
*
|
||||
* @param {string} accessToken The old access token.
|
||||
* @param {string} clientToken The launcher's client token.
|
||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
||||
*
|
||||
* @see http://wiki.vg/Authentication#Refresh
|
||||
*/
|
||||
exports.refresh = function(accessToken, clientToken, requestUser = true){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(authpath + '/refresh',
|
||||
{
|
||||
json: true,
|
||||
body: {
|
||||
accessToken,
|
||||
clientToken,
|
||||
requestUser
|
||||
}
|
||||
},
|
||||
function(error, response, body){
|
||||
if(error){
|
||||
logger.error('Error during refresh.', error)
|
||||
reject(error)
|
||||
} else {
|
||||
if(response.statusCode === 200){
|
||||
resolve(body)
|
||||
} else {
|
||||
reject(body)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
@ -1,69 +1,69 @@
|
||||
const {ipcRenderer} = require('electron')
|
||||
const fs = require('fs-extra')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
|
||||
const ConfigManager = require('./configmanager')
|
||||
const DistroManager = require('./distromanager')
|
||||
const LangLoader = require('./langloader')
|
||||
const logger = require('./loggerutil')('%c[Preloader]', 'color: #a02d2a; font-weight: bold')
|
||||
|
||||
logger.log('Loading..')
|
||||
|
||||
// Load ConfigManager
|
||||
ConfigManager.load()
|
||||
|
||||
// Load Strings
|
||||
LangLoader.loadLanguage('en_US')
|
||||
|
||||
function onDistroLoad(data){
|
||||
if(data != null){
|
||||
|
||||
// Resolve the selected server if its value has yet to be set.
|
||||
if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){
|
||||
logger.log('Determining default selected server..')
|
||||
ConfigManager.setSelectedServer(data.getMainServer().getID())
|
||||
ConfigManager.save()
|
||||
}
|
||||
}
|
||||
ipcRenderer.send('distributionIndexDone', data != null)
|
||||
}
|
||||
|
||||
// Ensure Distribution is downloaded and cached.
|
||||
DistroManager.pullRemote().then((data) => {
|
||||
logger.log('Loaded distribution index.')
|
||||
|
||||
onDistroLoad(data)
|
||||
|
||||
}).catch((err) => {
|
||||
logger.log('Failed to load distribution index.')
|
||||
logger.error(err)
|
||||
|
||||
logger.log('Attempting to load an older version of the distribution index.')
|
||||
// Try getting a local copy, better than nothing.
|
||||
DistroManager.pullLocal().then((data) => {
|
||||
logger.log('Successfully loaded an older version of the distribution index.')
|
||||
|
||||
onDistroLoad(data)
|
||||
|
||||
|
||||
}).catch((err) => {
|
||||
|
||||
logger.log('Failed to load an older version of the distribution index.')
|
||||
logger.log('Application cannot run.')
|
||||
logger.error(err)
|
||||
|
||||
onDistroLoad(null)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// Clean up temp dir incase previous launches ended unexpectedly.
|
||||
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
||||
if(err){
|
||||
logger.warn('Error while cleaning natives directory', err)
|
||||
} else {
|
||||
logger.log('Cleaned natives directory.')
|
||||
}
|
||||
const {ipcRenderer} = require('electron')
|
||||
const fs = require('fs-extra')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
|
||||
const ConfigManager = require('./configmanager')
|
||||
const DistroManager = require('./distromanager')
|
||||
const LangLoader = require('./langloader')
|
||||
const logger = require('./loggerutil')('%c[Preloader]', 'color: #a02d2a; font-weight: bold')
|
||||
|
||||
logger.log('Loading..')
|
||||
|
||||
// Load ConfigManager
|
||||
ConfigManager.load()
|
||||
|
||||
// Load Strings
|
||||
LangLoader.loadLanguage('en_US')
|
||||
|
||||
function onDistroLoad(data){
|
||||
if(data != null){
|
||||
|
||||
// Resolve the selected server if its value has yet to be set.
|
||||
if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){
|
||||
logger.log('Determining default selected server..')
|
||||
ConfigManager.setSelectedServer(data.getMainServer().getID())
|
||||
ConfigManager.save()
|
||||
}
|
||||
}
|
||||
ipcRenderer.send('distributionIndexDone', data != null)
|
||||
}
|
||||
|
||||
// Ensure Distribution is downloaded and cached.
|
||||
DistroManager.pullRemote().then((data) => {
|
||||
logger.log('Loaded distribution index.')
|
||||
|
||||
onDistroLoad(data)
|
||||
|
||||
}).catch((err) => {
|
||||
logger.log('Failed to load distribution index.')
|
||||
logger.error(err)
|
||||
|
||||
logger.log('Attempting to load an older version of the distribution index.')
|
||||
// Try getting a local copy, better than nothing.
|
||||
DistroManager.pullLocal().then((data) => {
|
||||
logger.log('Successfully loaded an older version of the distribution index.')
|
||||
|
||||
onDistroLoad(data)
|
||||
|
||||
|
||||
}).catch((err) => {
|
||||
|
||||
logger.log('Failed to load an older version of the distribution index.')
|
||||
logger.log('Application cannot run.')
|
||||
logger.error(err)
|
||||
|
||||
onDistroLoad(null)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// Clean up temp dir incase previous launches ended unexpectedly.
|
||||
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
||||
if(err){
|
||||
logger.warn('Error while cleaning natives directory', err)
|
||||
} else {
|
||||
logger.log('Cleaned natives directory.')
|
||||
}
|
||||
})
|
@ -317,7 +317,7 @@ class ProcessBuilder {
|
||||
|
||||
// Java Arguments
|
||||
if(process.platform === 'darwin'){
|
||||
args.push('-Xdock:name=HeliosLauncher')
|
||||
args.push('-Xdock:name=NemesisMCLauncher')
|
||||
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||
}
|
||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
||||
@ -355,7 +355,7 @@ class ProcessBuilder {
|
||||
|
||||
// Java Arguments
|
||||
if(process.platform === 'darwin'){
|
||||
args.push('-Xdock:name=HeliosLauncher')
|
||||
args.push('-Xdock:name=NemesisMCLauncher')
|
||||
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||
}
|
||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
||||
@ -457,7 +457,7 @@ class ProcessBuilder {
|
||||
val = args[i].replace(argDiscovery, tempNativePath)
|
||||
break
|
||||
case 'launcher_name':
|
||||
val = args[i].replace(argDiscovery, 'Helios-Launcher')
|
||||
val = args[i].replace(argDiscovery, 'NemesisMC-Launcher')
|
||||
break
|
||||
case 'launcher_version':
|
||||
val = args[i].replace(argDiscovery, this.launcherVersion)
|
||||
|
@ -1,65 +1,65 @@
|
||||
const net = require('net')
|
||||
|
||||
/**
|
||||
* Retrieves the status of a minecraft server.
|
||||
*
|
||||
* @param {string} address The server address.
|
||||
* @param {number} port Optional. The port of the server. Defaults to 25565.
|
||||
* @returns {Promise.<Object>} A promise which resolves to an object containing
|
||||
* status information.
|
||||
*/
|
||||
exports.getStatus = function(address, port = 25565){
|
||||
|
||||
if(port == null || port == ''){
|
||||
port = 25565
|
||||
}
|
||||
if(typeof port === 'string'){
|
||||
port = parseInt(port)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.connect(port, address, () => {
|
||||
let buff = Buffer.from([0xFE, 0x01])
|
||||
socket.write(buff)
|
||||
})
|
||||
|
||||
socket.setTimeout(2500, () => {
|
||||
socket.end()
|
||||
reject({
|
||||
code: 'ETIMEDOUT',
|
||||
errno: 'ETIMEDOUT',
|
||||
address,
|
||||
port
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('data', (data) => {
|
||||
if(data != null && data != ''){
|
||||
let server_info = data.toString().split('\x00\x00\x00')
|
||||
const NUM_FIELDS = 6
|
||||
if(server_info != null && server_info.length >= NUM_FIELDS){
|
||||
resolve({
|
||||
online: true,
|
||||
version: server_info[2].replace(/\u0000/g, ''),
|
||||
motd: server_info[3].replace(/\u0000/g, ''),
|
||||
onlinePlayers: server_info[4].replace(/\u0000/g, ''),
|
||||
maxPlayers: server_info[5].replace(/\u0000/g,'')
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
online: false
|
||||
})
|
||||
}
|
||||
}
|
||||
socket.end()
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
socket.destroy()
|
||||
reject(err)
|
||||
// ENOTFOUND = Unable to resolve.
|
||||
// ECONNREFUSED = Unable to connect to port.
|
||||
})
|
||||
})
|
||||
|
||||
const net = require('net')
|
||||
|
||||
/**
|
||||
* Retrieves the status of a minecraft server.
|
||||
*
|
||||
* @param {string} address The server address.
|
||||
* @param {number} port Optional. The port of the server. Defaults to 25565.
|
||||
* @returns {Promise.<Object>} A promise which resolves to an object containing
|
||||
* status information.
|
||||
*/
|
||||
exports.getStatus = function(address, port = 25565){
|
||||
|
||||
if(port == null || port == ''){
|
||||
port = 25565
|
||||
}
|
||||
if(typeof port === 'string'){
|
||||
port = parseInt(port)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.connect(port, address, () => {
|
||||
let buff = Buffer.from([0xFE, 0x01])
|
||||
socket.write(buff)
|
||||
})
|
||||
|
||||
socket.setTimeout(2500, () => {
|
||||
socket.end()
|
||||
reject({
|
||||
code: 'ETIMEDOUT',
|
||||
errno: 'ETIMEDOUT',
|
||||
address,
|
||||
port
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('data', (data) => {
|
||||
if(data != null && data != ''){
|
||||
let server_info = data.toString().split('\x00\x00\x00')
|
||||
const NUM_FIELDS = 6
|
||||
if(server_info != null && server_info.length >= NUM_FIELDS){
|
||||
resolve({
|
||||
online: true,
|
||||
version: server_info[2].replace(/\u0000/g, ''),
|
||||
motd: server_info[3].replace(/\u0000/g, ''),
|
||||
onlinePlayers: server_info[4].replace(/\u0000/g, ''),
|
||||
maxPlayers: server_info[5].replace(/\u0000/g,'')
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
online: false
|
||||
})
|
||||
}
|
||||
}
|
||||
socket.end()
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
socket.destroy()
|
||||
reject(err)
|
||||
// ENOTFOUND = Unable to resolve.
|
||||
// ECONNREFUSED = Unable to connect to port.
|
||||
})
|
||||
})
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user