mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-22 11:42:14 -08:00
Remove some old code, apply fix from master.
Reference to the old code will be made from the master branch.
This commit is contained in:
parent
9a67087766
commit
dc7386f19d
18
package-lock.json
generated
18
package-lock.json
generated
@ -5290,9 +5290,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "7.0.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.1.0.tgz",
|
||||||
"integrity": "sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg==",
|
"integrity": "sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
@ -6990,9 +6990,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||||
"integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
|
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@ -8862,9 +8862,9 @@
|
|||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"version": "7.1.2",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz",
|
||||||
"integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==",
|
"integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-colors": "3.2.3",
|
"ansi-colors": "3.2.3",
|
||||||
|
@ -68,9 +68,9 @@
|
|||||||
"electron-builder": "^22.6.1",
|
"electron-builder": "^22.6.1",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"electron-webpack-ts": "^4.0.1",
|
"electron-webpack-ts": "^4.0.1",
|
||||||
"eslint": "^7.0.0",
|
"eslint": "^7.1.0",
|
||||||
"helios-distribution-types": "1.0.0-pre.1",
|
"helios-distribution-types": "1.0.0-pre.1",
|
||||||
"mocha": "^7.1.2",
|
"mocha": "^7.2.0",
|
||||||
"nock": "^12.0.3",
|
"nock": "^12.0.3",
|
||||||
"react": "^16.13.0",
|
"react": "^16.13.0",
|
||||||
"react-dom": "^16.13.0",
|
"react-dom": "^16.13.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Rule, Natives } from "../../main/asset/model/mojang/VersionJson"
|
import { Rule, Natives } from "../asset/model/mojang/VersionJson"
|
||||||
|
|
||||||
export function getMojangOS(): string {
|
export function getMojangOS(): string {
|
||||||
const opSys = process.platform
|
const opSys = process.platform
|
||||||
|
@ -230,16 +230,19 @@ function createMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPlatformIcon(filename: string){
|
function getPlatformIcon(filename: string){
|
||||||
const opSys = process.platform
|
let ext
|
||||||
if (opSys === 'darwin') {
|
switch(process.platform) {
|
||||||
filename = filename + '.icns'
|
case 'win32':
|
||||||
} else if (opSys === 'win32') {
|
ext = 'ico'
|
||||||
filename = filename + '.ico'
|
break
|
||||||
} else {
|
case 'darwin':
|
||||||
filename = filename + '.png'
|
case 'linux':
|
||||||
|
default:
|
||||||
|
ext = 'png'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return join(__dirname, '..', 'assets', 'images', filename)
|
return join(__dirname, '..', 'assets', 'images', `${filename}.${ext}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', createWindow)
|
app.on('ready', createWindow)
|
||||||
|
@ -1,69 +0,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))
|
|
||||||
|
|
||||||
function assignListeners(){
|
|
||||||
tracker.on('validate', (data: any) => {
|
|
||||||
process.send!({context: 'validate', data})
|
|
||||||
})
|
|
||||||
tracker.on('progress', (data: any, acc: number, total: number) => {
|
|
||||||
process.send!({context: 'progress', data, value: acc, total, percent: parseInt(((acc/total)*100) as unknown as string)})
|
|
||||||
})
|
|
||||||
tracker.on('complete', (data: any, ...args: any[]) => {
|
|
||||||
process.send!({context: 'complete', data, args})
|
|
||||||
})
|
|
||||||
tracker.on('error', (data: any, error: any) => {
|
|
||||||
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,101 +0,0 @@
|
|||||||
import { LoggerUtil } from './loggerutil'
|
|
||||||
import { ConfigManager } from '../../common/config/configmanager'
|
|
||||||
import { Mojang } from '../../common/mojang/mojang'
|
|
||||||
import { SavedAccount } from '../../common/config/model/SavedAccount'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
const logger = new LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
|
||||||
const loggerSuccess = new 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: string, password: string){
|
|
||||||
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: string){
|
|
||||||
try {
|
|
||||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
|
||||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken() as string)
|
|
||||||
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() as SavedAccount
|
|
||||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken() as string)
|
|
||||||
if(!isValid){
|
|
||||||
try {
|
|
||||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken() as string)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import { LoggerUtil } from './loggerutil'
|
|
||||||
import { Client, Presence } from 'discord-rpc'
|
|
||||||
|
|
||||||
// Work in progress
|
|
||||||
const logger = new LoggerUtil('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold')
|
|
||||||
|
|
||||||
let client: Client
|
|
||||||
let activity: Presence
|
|
||||||
|
|
||||||
// TODO types for these settings
|
|
||||||
export function initRPC(genSettings: any, servSettings: any, 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDetails(details: string){
|
|
||||||
activity.details = details
|
|
||||||
client.setActivity(activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shutdownRPC(){
|
|
||||||
if(!client) return
|
|
||||||
client.clearActivity()
|
|
||||||
client.destroy()
|
|
||||||
client = null as unknown as Client // TODO cleanup
|
|
||||||
activity = null as unknown as Presence // TODO cleanup
|
|
||||||
}
|
|
@ -1,296 +0,0 @@
|
|||||||
import request from 'request'
|
|
||||||
import { Distribution, Module, Type, TypeMetadata, Server } from 'helios-distribution-types'
|
|
||||||
import { readJson, writeJson } from 'fs-extra'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { LoggerUtil } from './loggerutil'
|
|
||||||
import { ConfigManager } from '../../common/config/configmanager'
|
|
||||||
|
|
||||||
const logger = new LoggerUtil('%c[DistroManager]', 'color: #a02d2a; font-weight: bold')
|
|
||||||
|
|
||||||
interface ArtifactMeta {
|
|
||||||
group: string
|
|
||||||
artifact: string
|
|
||||||
version: string
|
|
||||||
classifier?: string
|
|
||||||
extension: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModuleWrapper {
|
|
||||||
|
|
||||||
private artifactMeta: ArtifactMeta
|
|
||||||
private subModules: ModuleWrapper[] = []
|
|
||||||
|
|
||||||
constructor(public module: Module, private serverId: string) {
|
|
||||||
this.artifactMeta = this.resolveMetaData()
|
|
||||||
this.resolveArtifactPath()
|
|
||||||
this.resolveRequired()
|
|
||||||
if (this.module.subModules != null) {
|
|
||||||
this.subModules = this.module.subModules.map(mdl => new ModuleWrapper(mdl, serverId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveMetaData(): ArtifactMeta {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const m0 = this.module.id.split('@')
|
|
||||||
const m1 = m0[0].split(':')
|
|
||||||
|
|
||||||
return {
|
|
||||||
group: m1[0] || '???',
|
|
||||||
artifact: m1[1] || '???',
|
|
||||||
version: m1[2] || '???',
|
|
||||||
classifier: m1[3] || undefined,
|
|
||||||
extension: m0[1] || TypeMetadata[this.module.type].defaultExtension || 'undefined'
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Improper ID for module', this.module.id, err)
|
|
||||||
return {
|
|
||||||
group: '???',
|
|
||||||
artifact: '???',
|
|
||||||
version: '???',
|
|
||||||
classifier: undefined,
|
|
||||||
extension: '???'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveArtifactPath(): void {
|
|
||||||
const relativePath = this.module.artifact.path == null ? join(
|
|
||||||
...this.artifactMeta.group.split('.'),
|
|
||||||
this.artifactMeta.artifact,
|
|
||||||
this.artifactMeta.version,
|
|
||||||
`${this.artifactMeta.artifact}-${this.artifactMeta.version}${this.artifactMeta.classifier != undefined ? `-${this.artifactMeta.classifier}` : ''}.${this.artifactMeta.extension}`
|
|
||||||
) : this.module.artifact.path
|
|
||||||
|
|
||||||
switch (this.module.type){
|
|
||||||
case Type.Library:
|
|
||||||
case Type.ForgeHosted:
|
|
||||||
case Type.LiteLoader:
|
|
||||||
this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'libraries', relativePath)
|
|
||||||
break
|
|
||||||
case Type.ForgeMod:
|
|
||||||
case Type.LiteMod:
|
|
||||||
this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'modstore', relativePath)
|
|
||||||
break
|
|
||||||
case Type.VersionManifest:
|
|
||||||
this.module.artifact.path = join(ConfigManager.getCommonDirectory(), 'versions', this.module.id, `${this.module.id}.json`)
|
|
||||||
break
|
|
||||||
case Type.File:
|
|
||||||
default:
|
|
||||||
this.module.artifact.path = join(ConfigManager.getInstanceDirectory(), this.serverId, relativePath)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveRequired(): void {
|
|
||||||
if (this.module.required == null) {
|
|
||||||
this.module.required = {
|
|
||||||
value: true,
|
|
||||||
def: true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.module.required.value == null) {
|
|
||||||
this.module.required.value = true
|
|
||||||
}
|
|
||||||
if (this.module.required.def == null) {
|
|
||||||
this.module.required.def = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The maven identifier of this module's artifact.
|
|
||||||
*/
|
|
||||||
public getArtifact(): string {
|
|
||||||
return this.artifactMeta.artifact
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The maven group of this module's artifact.
|
|
||||||
*/
|
|
||||||
public getGroup(): string {
|
|
||||||
return this.artifactMeta.group
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The version of this module's artifact.
|
|
||||||
*/
|
|
||||||
public getVersion(): string {
|
|
||||||
return this.artifactMeta.version
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string | undefined} The classifier of this module's artifact
|
|
||||||
*/
|
|
||||||
public getClassifier(): string | undefined {
|
|
||||||
return this.artifactMeta.classifier
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The extension of this module's artifact.
|
|
||||||
*/
|
|
||||||
public getExtension(): string {
|
|
||||||
return this.artifactMeta.extension
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The identifier without he version or extension.
|
|
||||||
*/
|
|
||||||
public getVersionlessID(): string {
|
|
||||||
return this.artifactMeta.group + ':' + this.artifactMeta.artifact
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The identifier without the extension.
|
|
||||||
*/
|
|
||||||
public getExtensionlessID(): string {
|
|
||||||
return this.module.id.split('@')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Whether or not this module has sub modules.
|
|
||||||
*/
|
|
||||||
public hasSubModules(): boolean {
|
|
||||||
return this.module.subModules != null
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWrappedSubmodules(): ModuleWrapper[] {
|
|
||||||
return this.subModules
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerWrapper {
|
|
||||||
|
|
||||||
private modules: ModuleWrapper[] = []
|
|
||||||
|
|
||||||
constructor(public server: Server) {
|
|
||||||
this.server.modules.map(mdl => new ModuleWrapper(mdl, server.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWrappedModules() {
|
|
||||||
return this.modules
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DistributionWrapper {
|
|
||||||
|
|
||||||
private mainServer: ServerWrapper | null = null
|
|
||||||
private servers: ServerWrapper[]
|
|
||||||
|
|
||||||
constructor(public distro: Distribution) {
|
|
||||||
this.servers = this.distro.servers.map(serv => new ServerWrapper(serv))
|
|
||||||
this.resolveMainServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveMainServer(): void {
|
|
||||||
|
|
||||||
for(const serv of this.servers){
|
|
||||||
if(serv.server.mainServer){
|
|
||||||
this.mainServer = serv
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no server declares default_selected, default to the first one declared.
|
|
||||||
this.mainServer = (this.servers.length > 0) ? this.servers[0] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
public getServer(id: string): ServerWrapper | null {
|
|
||||||
for(const serv of this.servers){
|
|
||||||
if(serv.server.id === id){
|
|
||||||
return serv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
public getMainServer(): ServerWrapper | null {
|
|
||||||
return this.mainServer
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class DistroManager {
|
|
||||||
|
|
||||||
private static readonly DISTRO_PATH = join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
|
||||||
private static readonly DEV_PATH = join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
|
|
||||||
|
|
||||||
private static readonly DISTRIBUTION_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
|
|
||||||
// private static readonly DISTRIBUTION_URL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
|
|
||||||
|
|
||||||
private static DEV_MODE = false
|
|
||||||
|
|
||||||
private static distro: DistributionWrapper
|
|
||||||
|
|
||||||
public static isDevMode() {
|
|
||||||
return DistroManager.DEV_MODE
|
|
||||||
}
|
|
||||||
|
|
||||||
public static setDevMode(value: boolean) {
|
|
||||||
if(value){
|
|
||||||
logger.log('Developer mode enabled.')
|
|
||||||
logger.log('If you don\'t know what that means, revert immediately.')
|
|
||||||
} else {
|
|
||||||
logger.log('Developer mode disabled.')
|
|
||||||
}
|
|
||||||
DistroManager.DEV_MODE = value
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static pullRemote(): Promise<DistributionWrapper> {
|
|
||||||
if(DistroManager.DEV_MODE){
|
|
||||||
return DistroManager.pullLocal()
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const opts = {
|
|
||||||
url: DistroManager.DISTRIBUTION_URL,
|
|
||||||
timeout: 2500
|
|
||||||
}
|
|
||||||
const distroDest = join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
|
||||||
request(opts, async (error, resp, body) => {
|
|
||||||
if(!error){
|
|
||||||
|
|
||||||
let data: Distribution
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(body) as Distribution
|
|
||||||
|
|
||||||
DistroManager.distro = new DistributionWrapper(data)
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await writeJson(distroDest, DistroManager.distro)
|
|
||||||
resolve(DistroManager.distro)
|
|
||||||
} catch (err) {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async pullLocal(): Promise<DistributionWrapper> {
|
|
||||||
const data = await readJson(DistroManager.DEV_MODE ? DistroManager.DEV_PATH : DistroManager.DISTRO_PATH) as Distribution
|
|
||||||
|
|
||||||
DistroManager.distro = new DistributionWrapper(data)
|
|
||||||
|
|
||||||
return DistroManager.distro
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getDistribution(): DistributionWrapper {
|
|
||||||
return DistroManager.distro
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
|||||||
import { ensureDirSync, pathExistsSync, readdirSync, moveSync, readFileSync, writeFileSync, rename } from 'fs-extra'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { shell } from '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.
|
|
||||||
*/
|
|
||||||
export function validateDir(dir: string) {
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
export function scanForDropinMods(modsDir: string, version: string) {
|
|
||||||
const modsDiscovered = []
|
|
||||||
if(pathExistsSync(modsDir)){
|
|
||||||
let modCandidates = readdirSync(modsDir)
|
|
||||||
let verCandidates: string[] = []
|
|
||||||
const versionDir = join(modsDir, version)
|
|
||||||
if(pathExistsSync(versionDir)){
|
|
||||||
verCandidates = 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: 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.
|
|
||||||
*/
|
|
||||||
export function addDropinMods(files: any, modsdir: string) {
|
|
||||||
|
|
||||||
exports.validateDir(modsdir)
|
|
||||||
|
|
||||||
for(let f of files) {
|
|
||||||
if(MOD_REGEX.exec(f.name) != null) {
|
|
||||||
moveSync(f.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.
|
|
||||||
*/
|
|
||||||
export function deleteDropinMod(modsDir: string, fullName: string){
|
|
||||||
const res = shell.moveItemToTrash(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.
|
|
||||||
*/
|
|
||||||
export function toggleDropinMod(modsDir: string, fullName: string, enable: boolean){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const oldPath = join(modsDir, fullName)
|
|
||||||
const newPath = join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT)
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
export function isDropinModEnabled(fullName: string){
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
export function scanForShaderpacks(instanceDir: string){
|
|
||||||
const shaderDir = join(instanceDir, SHADER_DIR)
|
|
||||||
const packsDiscovered = [{
|
|
||||||
fullName: 'OFF',
|
|
||||||
name: 'Off (Default)'
|
|
||||||
}]
|
|
||||||
if(pathExistsSync(shaderDir)){
|
|
||||||
let modCandidates = 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.
|
|
||||||
*/
|
|
||||||
export function getEnabledShaderpack(instanceDir: string){
|
|
||||||
validateDir(instanceDir)
|
|
||||||
|
|
||||||
const optionsShaders = join(instanceDir, SHADER_CONFIG)
|
|
||||||
if(pathExistsSync(optionsShaders)){
|
|
||||||
const buf = 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.
|
|
||||||
*/
|
|
||||||
export function setEnabledShaderpack(instanceDir: string, pack: string){
|
|
||||||
validateDir(instanceDir)
|
|
||||||
|
|
||||||
const optionsShaders = join(instanceDir, SHADER_CONFIG)
|
|
||||||
let buf
|
|
||||||
if(pathExistsSync(optionsShaders)){
|
|
||||||
buf = readFileSync(optionsShaders, {encoding: 'utf-8'})
|
|
||||||
buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`)
|
|
||||||
} else {
|
|
||||||
buf = `shaderPack=${pack}`
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
export function addShaderpacks(files: any, instanceDir: string) {
|
|
||||||
|
|
||||||
const p = join(instanceDir, SHADER_DIR)
|
|
||||||
|
|
||||||
exports.validateDir(p)
|
|
||||||
|
|
||||||
for(let f of files) {
|
|
||||||
if(SHADER_REGEX.exec(f.name) != null) {
|
|
||||||
moveSync(f.path, join(p, f.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { readJSONSync } from 'fs-extra'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
// TODO revisit
|
|
||||||
|
|
||||||
let lang: any
|
|
||||||
|
|
||||||
export function loadLanguage(id: string){
|
|
||||||
lang = readJSONSync(join(__dirname, '..', 'assets', 'lang', `${id}.json`)) || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function query(id: string){
|
|
||||||
let query = id.split('.')
|
|
||||||
let res = lang
|
|
||||||
for(let q of query){
|
|
||||||
res = res[q]
|
|
||||||
}
|
|
||||||
return res === lang ? {} : res
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryJS(id: string){
|
|
||||||
return exports.query(`js.${id}`)
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
export class LoggerUtil {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected prefix: string,
|
|
||||||
protected style: string
|
|
||||||
){}
|
|
||||||
|
|
||||||
public log(...args: any[]){
|
|
||||||
console.log(this.prefix, this.style, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public info(...args: any[]){
|
|
||||||
console.info(this.prefix, this.style, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public warn(...args: any[]){
|
|
||||||
console.warn(this.prefix, this.style, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public debug(...args: any[]){
|
|
||||||
console.debug(this.prefix, this.style, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public error(...args: any[]){
|
|
||||||
console.error(this.prefix, this.style, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import { ConfigManager } from '../../common/config/configmanager'
|
|
||||||
import { DistroManager, DistributionWrapper } from './distromanager'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { remove } from 'fs-extra'
|
|
||||||
import { loadLanguage } from './langloader'
|
|
||||||
import { LoggerUtil } from './loggerutil'
|
|
||||||
import { tmpdir } from 'os'
|
|
||||||
import { ipcRenderer } from 'electron'
|
|
||||||
|
|
||||||
const logger = new LoggerUtil('%c[Preloader]', 'color: #a02d2a; font-weight: bold')
|
|
||||||
|
|
||||||
logger.log('Loading..')
|
|
||||||
|
|
||||||
// Load ConfigManager
|
|
||||||
ConfigManager.load()
|
|
||||||
|
|
||||||
// Load Strings
|
|
||||||
loadLanguage('en_US')
|
|
||||||
|
|
||||||
function onDistroLoad(data: DistributionWrapper | null){
|
|
||||||
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..')
|
|
||||||
// TODO what if undefined
|
|
||||||
ConfigManager.setSelectedServer(data.getMainServer()!.server.id as string)
|
|
||||||
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.
|
|
||||||
remove(join(tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
|
||||||
if(err){
|
|
||||||
logger.warn('Error while cleaning natives directory', err)
|
|
||||||
} else {
|
|
||||||
logger.log('Cleaned natives directory.')
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,755 +0,0 @@
|
|||||||
import AdmZip from 'adm-zip'
|
|
||||||
import { pathExistsSync, writeFile, ensureDirSync, writeFileSync, remove } from 'fs-extra'
|
|
||||||
import { join, basename } from 'path'
|
|
||||||
import { ModuleWrapper, ServerWrapper } from './distromanager'
|
|
||||||
import { Type, Required } from 'helios-distribution-types'
|
|
||||||
import { LoggerUtil } from './loggerutil'
|
|
||||||
import { ConfigManager } from '../../common/config/configmanager'
|
|
||||||
import { spawn } from 'child_process'
|
|
||||||
import { SavedAccount } from '../../common/config/model/SavedAccount'
|
|
||||||
import { tmpdir, release } from 'os'
|
|
||||||
import { SubModConfig } from '../../common/config/model/ModConfig'
|
|
||||||
import { pseudoRandomBytes } from 'crypto'
|
|
||||||
import { Util, LibraryInternal } from './assetguard'
|
|
||||||
import { VersionJson, Rule } from '../asset/model/mojang/VersionJson'
|
|
||||||
import { URL } from 'url'
|
|
||||||
|
|
||||||
const logger = new LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold')
|
|
||||||
|
|
||||||
export class ProcessBuilder {
|
|
||||||
|
|
||||||
private gameDir: string
|
|
||||||
private commonDir: string
|
|
||||||
private fmlDir: string
|
|
||||||
private llDir: string
|
|
||||||
private libPath: string
|
|
||||||
|
|
||||||
private usingLiteLoader: boolean
|
|
||||||
private llPath: string | null
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private wrappedServer: ServerWrapper,
|
|
||||||
private versionData: VersionJson,
|
|
||||||
private forgeData: any, // TODO type
|
|
||||||
private authUser: SavedAccount,
|
|
||||||
private launcherVersion: string
|
|
||||||
){
|
|
||||||
this.gameDir = join(ConfigManager.getInstanceDirectory(), wrappedServer.server.id)
|
|
||||||
this.commonDir = ConfigManager.getCommonDirectory()
|
|
||||||
this.authUser = authUser
|
|
||||||
this.launcherVersion = launcherVersion
|
|
||||||
this.fmlDir = join(this.gameDir, 'forgeModList.json')
|
|
||||||
this.llDir = join(this.gameDir, 'liteloaderModList.json')
|
|
||||||
this.libPath = join(this.commonDir, 'libraries')
|
|
||||||
|
|
||||||
this.usingLiteLoader = false
|
|
||||||
this.llPath = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convienence method to run the functions typically used to build a process.
|
|
||||||
*/
|
|
||||||
build(){
|
|
||||||
ensureDirSync(this.gameDir)
|
|
||||||
const tempNativePath = join(tmpdir(), ConfigManager.getTempNativeFolder(), pseudoRandomBytes(16).toString('hex'))
|
|
||||||
process.throwDeprecation = true
|
|
||||||
this.setupLiteLoader()
|
|
||||||
logger.log('Using liteloader:', this.usingLiteLoader)
|
|
||||||
const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.wrappedServer.server.id)!.mods, this.wrappedServer.getWrappedModules())
|
|
||||||
|
|
||||||
// Mod list below 1.13
|
|
||||||
if(!Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){
|
|
||||||
this.constructModList('forge', modObj.fMods, true)
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
this.constructModList('liteloader', modObj.lMods, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uberModArr = modObj.fMods.concat(modObj.lMods)
|
|
||||||
let args = this.constructJVMArguments(uberModArr, tempNativePath)
|
|
||||||
|
|
||||||
if(Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){
|
|
||||||
args = args.concat(this.constructModArguments(modObj.fMods))
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log('Launch Arguments:', args)
|
|
||||||
|
|
||||||
const child = spawn(ConfigManager.getJavaExecutable()!, args, {
|
|
||||||
cwd: this.gameDir,
|
|
||||||
detached: ConfigManager.getLaunchDetached()
|
|
||||||
})
|
|
||||||
|
|
||||||
if(ConfigManager.getLaunchDetached()){
|
|
||||||
child.unref()
|
|
||||||
}
|
|
||||||
|
|
||||||
child.stdout.setEncoding('utf8')
|
|
||||||
child.stderr.setEncoding('utf8')
|
|
||||||
|
|
||||||
const loggerMCstdout = new LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold')
|
|
||||||
const loggerMCstderr = new LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold')
|
|
||||||
|
|
||||||
child.stdout.on('data', (data) => {
|
|
||||||
loggerMCstdout.log(data)
|
|
||||||
})
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
loggerMCstderr.log(data)
|
|
||||||
})
|
|
||||||
child.on('close', (code, signal) => {
|
|
||||||
logger.log('Exited with code', code)
|
|
||||||
remove(tempNativePath, (err) => {
|
|
||||||
if(err){
|
|
||||||
logger.warn('Error while deleting temp dir', err)
|
|
||||||
} else {
|
|
||||||
logger.log('Temp dir deleted successfully.')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if an optional mod is enabled from its configuration value. If the
|
|
||||||
* configuration value is null, the required object will be used to
|
|
||||||
* determine if it is enabled.
|
|
||||||
*
|
|
||||||
* A mod is enabled if:
|
|
||||||
* * The configuration is not null and one of the following:
|
|
||||||
* * The configuration is a boolean and true.
|
|
||||||
* * The configuration is an object and its 'value' property is true.
|
|
||||||
* * The configuration is null and one of the following:
|
|
||||||
* * The required object is null.
|
|
||||||
* * The required object's 'def' property is null or true.
|
|
||||||
*
|
|
||||||
* @param {SubModConfig | boolean} modCfg The mod configuration object.
|
|
||||||
* @param {Required | undefined} required Optional. The required object from the mod's distro declaration.
|
|
||||||
* @returns {boolean} True if the mod is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
public static isModEnabled(modCfg: SubModConfig | boolean, required?: Required){
|
|
||||||
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.def : true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function which performs a preliminary scan of the top level
|
|
||||||
* mods. If liteloader is present here, we setup the special liteloader
|
|
||||||
* launch options. Note that liteloader is only allowed as a top level
|
|
||||||
* mod. It must not be declared as a submodule.
|
|
||||||
*/
|
|
||||||
private setupLiteLoader(): void {
|
|
||||||
for(const ll of this.wrappedServer.getWrappedModules()){
|
|
||||||
if(ll.module.type === Type.LiteLoader){
|
|
||||||
if(!ll.module.required!.value!){
|
|
||||||
const modCfg = ConfigManager.getModConfiguration(this.wrappedServer.server.id)!.mods
|
|
||||||
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.module.required)){
|
|
||||||
if(pathExistsSync(ll.module.artifact.path!)){
|
|
||||||
this.usingLiteLoader = true
|
|
||||||
this.llPath = ll.module.artifact.path!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(pathExistsSync(ll.module.artifact.path!)){
|
|
||||||
this.usingLiteLoader = true
|
|
||||||
this.llPath = ll.module.artifact.path!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve an array of all enabled mods. These mods will be constructed into
|
|
||||||
* a mod list format and enabled at launch.
|
|
||||||
*
|
|
||||||
* @param {{[id: string]: boolean | SubModConfig}} modCfg The mod configuration object.
|
|
||||||
* @param {Array.<ModuleWrapper>} mdls An array of modules to parse.
|
|
||||||
* @returns {{fMods: Array.<ModuleWrapper>, lMods: Array.<ModuleWrapper>}} An object which contains
|
|
||||||
* a list of enabled forge mods and litemods.
|
|
||||||
*/
|
|
||||||
resolveModConfiguration(modCfg: {[id: string]: boolean | SubModConfig}, mdls: ModuleWrapper[]): {fMods: ModuleWrapper[], lMods: ModuleWrapper[]}{
|
|
||||||
let fMods: ModuleWrapper[] = []
|
|
||||||
let lMods: ModuleWrapper[] = []
|
|
||||||
|
|
||||||
for(const mdl of mdls){
|
|
||||||
const type = mdl.module.type
|
|
||||||
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader){
|
|
||||||
const o = !mdl.module.required!.value!
|
|
||||||
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.module.required)
|
|
||||||
if(!o || (o && e)){
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const v = this.resolveModConfiguration((modCfg[mdl.getVersionlessID()] as SubModConfig).mods, mdl.getWrappedSubmodules())
|
|
||||||
fMods = fMods.concat(v.fMods)
|
|
||||||
lMods = lMods.concat(v.lMods)
|
|
||||||
if(mdl.module.type === Type.LiteLoader){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(mdl.module.type === Type.ForgeMod){
|
|
||||||
fMods.push(mdl)
|
|
||||||
} else {
|
|
||||||
lMods.push(mdl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fMods,
|
|
||||||
lMods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isBelowOneDotSeven() {
|
|
||||||
return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= 7
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test to see if this version of forge requires the absolute: prefix
|
|
||||||
* on the modListFile repository field.
|
|
||||||
*/
|
|
||||||
_requiresAbsolute(){
|
|
||||||
try {
|
|
||||||
if(this._isBelowOneDotSeven()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const ver = this.forgeData.id.split('-')[2]
|
|
||||||
const pts = ver.split('.')
|
|
||||||
const min = [14, 23, 3, 2655]
|
|
||||||
for(let i=0; i<pts.length; i++){
|
|
||||||
const parsed = Number.parseInt(pts[i])
|
|
||||||
if(parsed < min[i]){
|
|
||||||
return false
|
|
||||||
} else if(parsed > min[i]){
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// We know old forge versions follow this format.
|
|
||||||
// Error must be caused by newer version.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal or errored
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a mod list json object.
|
|
||||||
*
|
|
||||||
* @param {'forge' | 'liteloader'} type The mod list type to construct.
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of mods to add to the mod list.
|
|
||||||
* @param {boolean} save Optional. Whether or not we should save the mod list file.
|
|
||||||
*/
|
|
||||||
constructModList(type: 'forge' | 'liteloader', mods: ModuleWrapper[], save = false){
|
|
||||||
const modList = {
|
|
||||||
repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + join(this.commonDir, 'modstore'),
|
|
||||||
modRef: [] as string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids = []
|
|
||||||
if(type === 'forge'){
|
|
||||||
for(let mod of mods){
|
|
||||||
ids.push(mod.getExtensionlessID())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(let mod of mods){
|
|
||||||
ids.push(mod.getExtensionlessID() + '@' + mod.getExtension())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modList.modRef = ids
|
|
||||||
|
|
||||||
if(save){
|
|
||||||
const json = JSON.stringify(modList, null, 4)
|
|
||||||
writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, 'UTF-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
return modList
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the mod argument list for forge 1.13
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of mods to add to the mod list.
|
|
||||||
*/
|
|
||||||
constructModArguments(mods: ModuleWrapper[]){
|
|
||||||
const argStr = mods.map(mod => {
|
|
||||||
return mod.getExtensionlessID()
|
|
||||||
}).join(',')
|
|
||||||
|
|
||||||
if(argStr){
|
|
||||||
return [
|
|
||||||
'--fml.mavenRoots',
|
|
||||||
join('..', '..', 'common', 'modstore'),
|
|
||||||
'--fml.mods',
|
|
||||||
argStr
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string| number>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
constructJVMArguments(mods: ModuleWrapper[], tempNativePath: string): string[] {
|
|
||||||
if(Util.mcVersionAtLeast('1.13', this.wrappedServer.server.minecraftVersion)){
|
|
||||||
return this._constructJVMArguments113(mods, tempNativePath)
|
|
||||||
} else {
|
|
||||||
return this._constructJVMArguments112(mods, tempNativePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
* This function is for 1.12 and below.
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
_constructJVMArguments112(mods: ModuleWrapper[], tempNativePath: string): string[] {
|
|
||||||
|
|
||||||
let args = []
|
|
||||||
|
|
||||||
// Classpath Argument
|
|
||||||
args.push('-cp')
|
|
||||||
args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':'))
|
|
||||||
|
|
||||||
// Java Arguments
|
|
||||||
if(process.platform === 'darwin'){
|
|
||||||
args.push('-Xdock:name=HeliosLauncher')
|
|
||||||
args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns'))
|
|
||||||
}
|
|
||||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
|
||||||
args.push('-Xms' + ConfigManager.getMinRAM())
|
|
||||||
args = args.concat(ConfigManager.getJVMOptions())
|
|
||||||
args.push('-Djava.library.path=' + tempNativePath)
|
|
||||||
|
|
||||||
// Main Java Class
|
|
||||||
args.push(this.forgeData.mainClass)
|
|
||||||
|
|
||||||
// Forge Arguments
|
|
||||||
args = args.concat(this._resolveForgeArgs())
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
* This function is for 1.13+
|
|
||||||
*
|
|
||||||
* Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
_constructJVMArguments113(mods: ModuleWrapper[], tempNativePath: string): string[] {
|
|
||||||
|
|
||||||
const argDiscovery = /\${*(.*)}/
|
|
||||||
|
|
||||||
// JVM Arguments First
|
|
||||||
let args: (string | { rules: Rule[], value: string[] })[] = this.versionData.arguments.jvm
|
|
||||||
|
|
||||||
//args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
|
|
||||||
|
|
||||||
// Java Arguments
|
|
||||||
if(process.platform === 'darwin'){
|
|
||||||
args.push('-Xdock:name=HeliosLauncher')
|
|
||||||
args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns'))
|
|
||||||
}
|
|
||||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
|
||||||
args.push('-Xms' + ConfigManager.getMinRAM())
|
|
||||||
args = args.concat(ConfigManager.getJVMOptions())
|
|
||||||
|
|
||||||
// Main Java Class
|
|
||||||
args.push(this.forgeData.mainClass)
|
|
||||||
|
|
||||||
// Vanilla Arguments
|
|
||||||
args = args.concat(this.versionData.arguments.game)
|
|
||||||
|
|
||||||
for(let i=0; i<args.length; i++){
|
|
||||||
if(typeof args[i] === 'object' && (args[i] as any).rules != null){
|
|
||||||
const arg = args[i] as { rules: Rule[], value: string[] }
|
|
||||||
|
|
||||||
let checksum = 0
|
|
||||||
for(let rule of arg.rules){
|
|
||||||
if(rule.os != null){
|
|
||||||
if(rule.os.name === LibraryInternal.mojangFriendlyOS()
|
|
||||||
&& (rule.os.version == null || new RegExp(rule.os.version).test(release()))){
|
|
||||||
if(rule.action === 'allow'){
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(rule.action === 'disallow'){
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(rule.features != null){
|
|
||||||
// We don't have many 'features' in the index at the moment.
|
|
||||||
// This should be fine for a while.
|
|
||||||
if(rule.features.has_custom_resolution != null && rule.features.has_custom_resolution === true){
|
|
||||||
if(ConfigManager.getFullscreen()){
|
|
||||||
arg.value = [
|
|
||||||
'--fullscreen',
|
|
||||||
'true'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO splice not push
|
|
||||||
if(checksum === arg.rules.length){
|
|
||||||
if(typeof arg.value === 'string'){
|
|
||||||
args[i] = arg.value
|
|
||||||
} else if(typeof arg.value === 'object'){
|
|
||||||
//args = args.concat(args[i].value)
|
|
||||||
args.splice(i, 1, ...arg.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrement i to reprocess the resolved value
|
|
||||||
i--
|
|
||||||
} else {
|
|
||||||
args[i] = null! // TODO lol
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(typeof args[i] === 'string'){
|
|
||||||
const arg = args[i] as string
|
|
||||||
if(argDiscovery.test(arg)){
|
|
||||||
const identifier = arg.match(argDiscovery)![1]
|
|
||||||
let val = null
|
|
||||||
switch(identifier){
|
|
||||||
case 'auth_player_name':
|
|
||||||
val = this.authUser.displayName.trim()
|
|
||||||
break
|
|
||||||
case 'version_name':
|
|
||||||
//val = versionData.id
|
|
||||||
val = this.wrappedServer.server.id
|
|
||||||
break
|
|
||||||
case 'game_directory':
|
|
||||||
val = this.gameDir
|
|
||||||
break
|
|
||||||
case 'assets_root':
|
|
||||||
val = join(this.commonDir, 'assets')
|
|
||||||
break
|
|
||||||
case 'assets_index_name':
|
|
||||||
val = this.versionData.assets
|
|
||||||
break
|
|
||||||
case 'auth_uuid':
|
|
||||||
val = this.authUser.uuid.trim()
|
|
||||||
break
|
|
||||||
case 'auth_access_token':
|
|
||||||
val = this.authUser.accessToken
|
|
||||||
break
|
|
||||||
case 'user_type':
|
|
||||||
val = 'mojang'
|
|
||||||
break
|
|
||||||
case 'version_type':
|
|
||||||
val = this.versionData.type
|
|
||||||
break
|
|
||||||
case 'resolution_width':
|
|
||||||
val = ConfigManager.getGameWidth()
|
|
||||||
break
|
|
||||||
case 'resolution_height':
|
|
||||||
val = ConfigManager.getGameHeight()
|
|
||||||
break
|
|
||||||
case 'natives_directory':
|
|
||||||
val = arg.replace(argDiscovery, tempNativePath)
|
|
||||||
break
|
|
||||||
case 'launcher_name':
|
|
||||||
val = arg.replace(argDiscovery, 'Helios-Launcher')
|
|
||||||
break
|
|
||||||
case 'launcher_version':
|
|
||||||
val = arg.replace(argDiscovery, this.launcherVersion)
|
|
||||||
break
|
|
||||||
case 'classpath':
|
|
||||||
val = this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if(val != null){
|
|
||||||
args[i] = val.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forge Specific Arguments
|
|
||||||
args = args.concat(this.forgeData.arguments.game)
|
|
||||||
|
|
||||||
// Filter null values
|
|
||||||
args = args.filter(arg => {
|
|
||||||
return arg != null
|
|
||||||
})
|
|
||||||
|
|
||||||
return args as string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the arguments required by forge.
|
|
||||||
*
|
|
||||||
* @returns {Array.<string>} An array containing the arguments required by forge.
|
|
||||||
*/
|
|
||||||
_resolveForgeArgs(): string[] {
|
|
||||||
const mcArgs: string[] = this.forgeData.minecraftArguments.split(' ')
|
|
||||||
const argDiscovery = /\${*(.*)}/
|
|
||||||
|
|
||||||
// Replace the declared variables with their proper values.
|
|
||||||
for(let i=0; i<mcArgs.length; ++i){
|
|
||||||
if(argDiscovery.test(mcArgs[i])){
|
|
||||||
const identifier = mcArgs[i].match(argDiscovery)![1]
|
|
||||||
let val = null
|
|
||||||
switch(identifier){
|
|
||||||
case 'auth_player_name':
|
|
||||||
val = this.authUser.displayName.trim()
|
|
||||||
break
|
|
||||||
case 'version_name':
|
|
||||||
//val = versionData.id
|
|
||||||
val = this.wrappedServer.server.id
|
|
||||||
break
|
|
||||||
case 'game_directory':
|
|
||||||
val = this.gameDir
|
|
||||||
break
|
|
||||||
case 'assets_root':
|
|
||||||
val = join(this.commonDir, 'assets')
|
|
||||||
break
|
|
||||||
case 'assets_index_name':
|
|
||||||
val = this.versionData.assets
|
|
||||||
break
|
|
||||||
case 'auth_uuid':
|
|
||||||
val = this.authUser.uuid.trim()
|
|
||||||
break
|
|
||||||
case 'auth_access_token':
|
|
||||||
val = this.authUser.accessToken
|
|
||||||
break
|
|
||||||
case 'user_type':
|
|
||||||
val = 'mojang'
|
|
||||||
break
|
|
||||||
case 'user_properties': // 1.8.9 and below.
|
|
||||||
val = '{}'
|
|
||||||
break
|
|
||||||
case 'version_type':
|
|
||||||
val = this.versionData.type
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if(val != null){
|
|
||||||
mcArgs[i] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Autoconnect to the selected server.
|
|
||||||
if(ConfigManager.getAutoConnect() && this.wrappedServer.server.autoconnect){
|
|
||||||
const serverURL = new URL('my://' + this.wrappedServer.server.address)
|
|
||||||
mcArgs.push('--server')
|
|
||||||
mcArgs.push(serverURL.hostname)
|
|
||||||
if(serverURL.port){
|
|
||||||
mcArgs.push('--port')
|
|
||||||
mcArgs.push(serverURL.port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare game resolution
|
|
||||||
if(ConfigManager.getFullscreen()){
|
|
||||||
mcArgs.push('--fullscreen')
|
|
||||||
mcArgs.push('true')
|
|
||||||
} else {
|
|
||||||
mcArgs.push('--width')
|
|
||||||
mcArgs.push(ConfigManager.getGameWidth().toString())
|
|
||||||
mcArgs.push('--height')
|
|
||||||
mcArgs.push(ConfigManager.getGameHeight().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mod List File Argument
|
|
||||||
mcArgs.push('--modListFile')
|
|
||||||
if(this._isBelowOneDotSeven()) {
|
|
||||||
mcArgs.push(basename(this.fmlDir))
|
|
||||||
} else {
|
|
||||||
mcArgs.push('absolute:' + this.fmlDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// LiteLoader
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
mcArgs.push('--modRepo')
|
|
||||||
mcArgs.push(this.llDir)
|
|
||||||
|
|
||||||
// Set first arg to liteloader tweak class
|
|
||||||
mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker')
|
|
||||||
mcArgs.unshift('--tweakClass')
|
|
||||||
}
|
|
||||||
|
|
||||||
return mcArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the full classpath argument list for this process. This method will resolve all Mojang-declared
|
|
||||||
* libraries as well as the libraries declared by the server. Since mods are permitted to declare libraries,
|
|
||||||
* this method requires all enabled mods as an input
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the paths of each library required by this process.
|
|
||||||
*/
|
|
||||||
classpathArg(mods: ModuleWrapper[], tempNativePath: string): string[] {
|
|
||||||
let cpArgs: string[] = []
|
|
||||||
|
|
||||||
// Add the version.jar to the classpath.
|
|
||||||
const version = this.versionData.id
|
|
||||||
cpArgs.push(join(this.commonDir, 'versions', version, version + '.jar'))
|
|
||||||
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
cpArgs.push(this.llPath!)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the Mojang declared libraries.
|
|
||||||
const mojangLibs = this._resolveMojangLibraries(tempNativePath)
|
|
||||||
|
|
||||||
// Resolve the server declared libraries.
|
|
||||||
const servLibs = this._resolveServerLibraries(mods)
|
|
||||||
|
|
||||||
// Merge libraries, server libs with the same
|
|
||||||
// maven identifier will override the mojang ones.
|
|
||||||
// Ex. 1.7.10 forge overrides mojang's guava with newer version.
|
|
||||||
const finalLibs = {...mojangLibs, ...servLibs}
|
|
||||||
cpArgs = cpArgs.concat(Object.values(finalLibs))
|
|
||||||
|
|
||||||
return cpArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the libraries defined by Mojang's version data. This method will also extract
|
|
||||||
* native libraries and point to the correct location for its classpath.
|
|
||||||
*
|
|
||||||
* TODO - clean up function
|
|
||||||
*
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
|
|
||||||
*/
|
|
||||||
_resolveMojangLibraries(tempNativePath: string): {[id: string]: string} {
|
|
||||||
const libs: {[id: string]: string} = {}
|
|
||||||
|
|
||||||
const libArr = this.versionData.libraries
|
|
||||||
ensureDirSync(tempNativePath)
|
|
||||||
for(let i=0; i<libArr.length; i++){
|
|
||||||
const lib = libArr[i]
|
|
||||||
if(LibraryInternal.validateRules(lib.rules, lib.natives)){
|
|
||||||
if(lib.natives == null){
|
|
||||||
const dlInfo = lib.downloads
|
|
||||||
const artifact = dlInfo.artifact
|
|
||||||
const to = join(this.libPath, artifact.path)
|
|
||||||
const versionIndependentId: string = lib.name.substring(0, lib.name.lastIndexOf(':'))
|
|
||||||
libs[versionIndependentId] = to
|
|
||||||
} else {
|
|
||||||
// Extract the native library.
|
|
||||||
const exclusionArr: string[] = lib.extract != null ? lib.extract.exclude : ['META-INF/']
|
|
||||||
// @ts-ignore
|
|
||||||
const artifact = lib.downloads.classifiers[lib.natives[LibraryInternal.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
|
|
||||||
|
|
||||||
// Location of native zip.
|
|
||||||
const to = join(this.libPath, artifact.path)
|
|
||||||
|
|
||||||
let zip = new AdmZip(to)
|
|
||||||
let zipEntries = zip.getEntries()
|
|
||||||
|
|
||||||
// Unzip the native zip.
|
|
||||||
for(let i=0; i<zipEntries.length; i++){
|
|
||||||
const fileName = zipEntries[i].entryName
|
|
||||||
|
|
||||||
let shouldExclude = false
|
|
||||||
|
|
||||||
// Exclude noted files.
|
|
||||||
exclusionArr.forEach((exclusion: string) => {
|
|
||||||
if(fileName.indexOf(exclusion) > -1){
|
|
||||||
shouldExclude = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Extract the file.
|
|
||||||
if(!shouldExclude){
|
|
||||||
writeFile(join(tempNativePath, fileName), zipEntries[i].getData(), (err) => {
|
|
||||||
if(err){
|
|
||||||
logger.error('Error while extracting native library:', err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the libraries declared by this server in order to add them to the classpath.
|
|
||||||
* This method will also check each enabled mod for libraries, as mods are permitted to
|
|
||||||
* declare libraries.
|
|
||||||
*
|
|
||||||
* @param {Array.<ModuleWrapper>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @returns {{[id: string]: string}} An object containing the paths of each library this server requires.
|
|
||||||
*/
|
|
||||||
_resolveServerLibraries(mods: ModuleWrapper[]): {[id: string]: string} {
|
|
||||||
const mdls: ModuleWrapper[] = this.wrappedServer.getWrappedModules()
|
|
||||||
let libs: {[id: string]: string} = {}
|
|
||||||
|
|
||||||
// Locate Forge/Libraries
|
|
||||||
for(let mdl of mdls){
|
|
||||||
const type = mdl.module.type
|
|
||||||
if(type === Type.ForgeHosted || type === Type.Library){
|
|
||||||
libs[mdl.getVersionlessID()] = mdl.module.artifact.path as string
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const res = this._resolveModuleLibraries(mdl)
|
|
||||||
if(Object.keys(res).length > 0){
|
|
||||||
libs = {...libs, ...res}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for any libraries in our mod list.
|
|
||||||
for(let i=0; i<mods.length; i++){
|
|
||||||
if(mods[i].hasSubModules()){
|
|
||||||
const res = this._resolveModuleLibraries(mods[i])
|
|
||||||
if(Object.keys(res).length > 0){
|
|
||||||
libs = {...libs, ...res}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively resolve the path of each library required by this module.
|
|
||||||
*
|
|
||||||
* @param {ModuleWrapper} mdl A module object from the server distro index.
|
|
||||||
* @returns {Array.<string>} An array containing the paths of each library this module requires.
|
|
||||||
*/
|
|
||||||
_resolveModuleLibraries(mdl: ModuleWrapper): {[id: string]: string} {
|
|
||||||
if(!mdl.hasSubModules()){
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
let libs: {[id: string]: string} = {}
|
|
||||||
for(const sm of mdl.getWrappedSubmodules()){
|
|
||||||
if(sm.module.type === Type.Library){
|
|
||||||
libs[sm.getVersionlessID()] = sm.module.artifact.path as string
|
|
||||||
}
|
|
||||||
// If this module has submodules, we need to resolve the libraries for those.
|
|
||||||
// To avoid unnecessary recursive calls, base case is checked here.
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const res = this._resolveModuleLibraries(sm)
|
|
||||||
if(Object.keys(res).length > 0){
|
|
||||||
libs = {...libs, ...res}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,738 +0,0 @@
|
|||||||
const AdmZip = require('adm-zip')
|
|
||||||
const child_process = require('child_process')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const fs = require('fs-extra')
|
|
||||||
const os = require('os')
|
|
||||||
const path = require('path')
|
|
||||||
const { URL } = require('url')
|
|
||||||
|
|
||||||
const { Util, Library: LibraryInternal } = require('./assetguard')
|
|
||||||
const ConfigManager = require('./configmanager')
|
|
||||||
const DistroManager = require('./distromanager')
|
|
||||||
const LoggerUtil = require('./loggerutil')
|
|
||||||
|
|
||||||
const logger = LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold')
|
|
||||||
|
|
||||||
class ProcessBuilder {
|
|
||||||
|
|
||||||
constructor(distroServer, versionData, forgeData, authUser, launcherVersion){
|
|
||||||
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.getID())
|
|
||||||
this.commonDir = ConfigManager.getCommonDirectory()
|
|
||||||
this.server = distroServer
|
|
||||||
this.versionData = versionData
|
|
||||||
this.forgeData = forgeData
|
|
||||||
this.authUser = authUser
|
|
||||||
this.launcherVersion = launcherVersion
|
|
||||||
this.fmlDir = path.join(this.gameDir, 'forgeModList.json')
|
|
||||||
this.llDir = path.join(this.gameDir, 'liteloaderModList.json')
|
|
||||||
this.libPath = path.join(this.commonDir, 'libraries')
|
|
||||||
|
|
||||||
this.usingLiteLoader = false
|
|
||||||
this.llPath = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convienence method to run the functions typically used to build a process.
|
|
||||||
*/
|
|
||||||
build(){
|
|
||||||
fs.ensureDirSync(this.gameDir)
|
|
||||||
const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
|
|
||||||
process.throwDeprecation = true
|
|
||||||
this.setupLiteLoader()
|
|
||||||
logger.log('Using liteloader:', this.usingLiteLoader)
|
|
||||||
const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules())
|
|
||||||
|
|
||||||
// Mod list below 1.13
|
|
||||||
if(!Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
|
|
||||||
this.constructModList('forge', modObj.fMods, true)
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
this.constructModList('liteloader', modObj.lMods, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uberModArr = modObj.fMods.concat(modObj.lMods)
|
|
||||||
let args = this.constructJVMArguments(uberModArr, tempNativePath)
|
|
||||||
|
|
||||||
if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
|
|
||||||
args = args.concat(this.constructModArguments(modObj.fMods))
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log('Launch Arguments:', args)
|
|
||||||
|
|
||||||
const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, {
|
|
||||||
cwd: this.gameDir,
|
|
||||||
detached: ConfigManager.getLaunchDetached()
|
|
||||||
})
|
|
||||||
|
|
||||||
if(ConfigManager.getLaunchDetached()){
|
|
||||||
child.unref()
|
|
||||||
}
|
|
||||||
|
|
||||||
child.stdout.setEncoding('utf8')
|
|
||||||
child.stderr.setEncoding('utf8')
|
|
||||||
|
|
||||||
const loggerMCstdout = LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold')
|
|
||||||
const loggerMCstderr = LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold')
|
|
||||||
|
|
||||||
child.stdout.on('data', (data) => {
|
|
||||||
loggerMCstdout.log(data)
|
|
||||||
})
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
loggerMCstderr.log(data)
|
|
||||||
})
|
|
||||||
child.on('close', (code, signal) => {
|
|
||||||
logger.log('Exited with code', code)
|
|
||||||
fs.remove(tempNativePath, (err) => {
|
|
||||||
if(err){
|
|
||||||
logger.warn('Error while deleting temp dir', err)
|
|
||||||
} else {
|
|
||||||
logger.log('Temp dir deleted successfully.')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if an optional mod is enabled from its configuration value. If the
|
|
||||||
* configuration value is null, the required object will be used to
|
|
||||||
* determine if it is enabled.
|
|
||||||
*
|
|
||||||
* A mod is enabled if:
|
|
||||||
* * The configuration is not null and one of the following:
|
|
||||||
* * The configuration is a boolean and true.
|
|
||||||
* * The configuration is an object and its 'value' property is true.
|
|
||||||
* * The configuration is null and one of the following:
|
|
||||||
* * The required object is null.
|
|
||||||
* * The required object's 'def' property is null or true.
|
|
||||||
*
|
|
||||||
* @param {Object | boolean} modCfg The mod configuration object.
|
|
||||||
* @param {Object} required Optional. The required object from the mod's distro declaration.
|
|
||||||
* @returns {boolean} True if the mod is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
static isModEnabled(modCfg, required = null){
|
|
||||||
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function which performs a preliminary scan of the top level
|
|
||||||
* mods. If liteloader is present here, we setup the special liteloader
|
|
||||||
* launch options. Note that liteloader is only allowed as a top level
|
|
||||||
* mod. It must not be declared as a submodule.
|
|
||||||
*/
|
|
||||||
setupLiteLoader(){
|
|
||||||
for(let ll of this.server.getModules()){
|
|
||||||
if(ll.getType() === DistroManager.Types.LiteLoader){
|
|
||||||
if(!ll.getRequired().isRequired()){
|
|
||||||
const modCfg = ConfigManager.getModConfiguration(this.server.getID()).mods
|
|
||||||
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.getRequired())){
|
|
||||||
if(fs.existsSync(ll.getArtifact().getPath())){
|
|
||||||
this.usingLiteLoader = true
|
|
||||||
this.llPath = ll.getArtifact().getPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(fs.existsSync(ll.getArtifact().getPath())){
|
|
||||||
this.usingLiteLoader = true
|
|
||||||
this.llPath = ll.getArtifact().getPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve an array of all enabled mods. These mods will be constructed into
|
|
||||||
* a mod list format and enabled at launch.
|
|
||||||
*
|
|
||||||
* @param {Object} modCfg The mod configuration object.
|
|
||||||
* @param {Array.<Object>} mdls An array of modules to parse.
|
|
||||||
* @returns {{fMods: Array.<Object>, lMods: Array.<Object>}} An object which contains
|
|
||||||
* a list of enabled forge mods and litemods.
|
|
||||||
*/
|
|
||||||
resolveModConfiguration(modCfg, mdls){
|
|
||||||
let fMods = []
|
|
||||||
let lMods = []
|
|
||||||
|
|
||||||
for(let mdl of mdls){
|
|
||||||
const type = mdl.getType()
|
|
||||||
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
|
|
||||||
const o = !mdl.getRequired().isRequired()
|
|
||||||
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.getRequired())
|
|
||||||
if(!o || (o && e)){
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessID()].mods, mdl.getSubModules())
|
|
||||||
fMods = fMods.concat(v.fMods)
|
|
||||||
lMods = lMods.concat(v.lMods)
|
|
||||||
if(mdl.type === DistroManager.Types.LiteLoader){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(mdl.type === DistroManager.Types.ForgeMod){
|
|
||||||
fMods.push(mdl)
|
|
||||||
} else {
|
|
||||||
lMods.push(mdl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fMods,
|
|
||||||
lMods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isBelowOneDotSeven() {
|
|
||||||
return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= 7
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test to see if this version of forge requires the absolute: prefix
|
|
||||||
* on the modListFile repository field.
|
|
||||||
*/
|
|
||||||
_requiresAbsolute(){
|
|
||||||
try {
|
|
||||||
if(this._isBelowOneDotSeven()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const ver = this.forgeData.id.split('-')[2]
|
|
||||||
const pts = ver.split('.')
|
|
||||||
const min = [14, 23, 3, 2655]
|
|
||||||
for(let i=0; i<pts.length; i++){
|
|
||||||
const parsed = Number.parseInt(pts[i])
|
|
||||||
if(parsed < min[i]){
|
|
||||||
return false
|
|
||||||
} else if(parsed > min[i]){
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// We know old forge versions follow this format.
|
|
||||||
// Error must be caused by newer version.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal or errored
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a mod list json object.
|
|
||||||
*
|
|
||||||
* @param {'forge' | 'liteloader'} type The mod list type to construct.
|
|
||||||
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
|
||||||
* @param {boolean} save Optional. Whether or not we should save the mod list file.
|
|
||||||
*/
|
|
||||||
constructModList(type, mods, save = false){
|
|
||||||
const modList = {
|
|
||||||
repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + path.join(this.commonDir, 'modstore')
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids = []
|
|
||||||
if(type === 'forge'){
|
|
||||||
for(let mod of mods){
|
|
||||||
ids.push(mod.getExtensionlessID())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(let mod of mods){
|
|
||||||
ids.push(mod.getExtensionlessID() + '@' + mod.getExtension())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modList.modRef = ids
|
|
||||||
|
|
||||||
if(save){
|
|
||||||
const json = JSON.stringify(modList, null, 4)
|
|
||||||
fs.writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, 'UTF-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
return modList
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the mod argument list for forge 1.13
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
|
||||||
*/
|
|
||||||
constructModArguments(mods){
|
|
||||||
const argStr = mods.map(mod => {
|
|
||||||
return mod.getExtensionlessID()
|
|
||||||
}).join(',')
|
|
||||||
|
|
||||||
if(argStr){
|
|
||||||
return [
|
|
||||||
'--fml.mavenRoots',
|
|
||||||
path.join('..', '..', 'common', 'modstore'),
|
|
||||||
'--fml.mods',
|
|
||||||
argStr
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
constructJVMArguments(mods, tempNativePath){
|
|
||||||
if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){
|
|
||||||
return this._constructJVMArguments113(mods, tempNativePath)
|
|
||||||
} else {
|
|
||||||
return this._constructJVMArguments112(mods, tempNativePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
* This function is for 1.12 and below.
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
_constructJVMArguments112(mods, tempNativePath){
|
|
||||||
|
|
||||||
let args = []
|
|
||||||
|
|
||||||
// Classpath Argument
|
|
||||||
args.push('-cp')
|
|
||||||
args.push(this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':'))
|
|
||||||
|
|
||||||
// Java Arguments
|
|
||||||
if(process.platform === 'darwin'){
|
|
||||||
args.push('-Xdock:name=HeliosLauncher')
|
|
||||||
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
|
||||||
}
|
|
||||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
|
||||||
args.push('-Xms' + ConfigManager.getMinRAM())
|
|
||||||
args = args.concat(ConfigManager.getJVMOptions())
|
|
||||||
args.push('-Djava.library.path=' + tempNativePath)
|
|
||||||
|
|
||||||
// Main Java Class
|
|
||||||
args.push(this.forgeData.mainClass)
|
|
||||||
|
|
||||||
// Forge Arguments
|
|
||||||
args = args.concat(this._resolveForgeArgs())
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
|
||||||
* This function is for 1.13+
|
|
||||||
*
|
|
||||||
* Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
|
||||||
*/
|
|
||||||
_constructJVMArguments113(mods, tempNativePath){
|
|
||||||
|
|
||||||
const argDiscovery = /\${*(.*)}/
|
|
||||||
|
|
||||||
// JVM Arguments First
|
|
||||||
let args = this.versionData.arguments.jvm
|
|
||||||
|
|
||||||
//args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
|
|
||||||
|
|
||||||
// Java Arguments
|
|
||||||
if(process.platform === 'darwin'){
|
|
||||||
args.push('-Xdock:name=HeliosLauncher')
|
|
||||||
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
|
||||||
}
|
|
||||||
args.push('-Xmx' + ConfigManager.getMaxRAM())
|
|
||||||
args.push('-Xms' + ConfigManager.getMinRAM())
|
|
||||||
args = args.concat(ConfigManager.getJVMOptions())
|
|
||||||
|
|
||||||
// Main Java Class
|
|
||||||
args.push(this.forgeData.mainClass)
|
|
||||||
|
|
||||||
// Vanilla Arguments
|
|
||||||
args = args.concat(this.versionData.arguments.game)
|
|
||||||
|
|
||||||
for(let i=0; i<args.length; i++){
|
|
||||||
if(typeof args[i] === 'object' && args[i].rules != null){
|
|
||||||
|
|
||||||
let checksum = 0
|
|
||||||
for(let rule of args[i].rules){
|
|
||||||
if(rule.os != null){
|
|
||||||
if(rule.os.name === LibraryInternal.mojangFriendlyOS()
|
|
||||||
&& (rule.os.version == null || new RegExp(rule.os.version).test(os.release))){
|
|
||||||
if(rule.action === 'allow'){
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(rule.action === 'disallow'){
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(rule.features != null){
|
|
||||||
// We don't have many 'features' in the index at the moment.
|
|
||||||
// This should be fine for a while.
|
|
||||||
if(rule.features.has_custom_resolution != null && rule.features.has_custom_resolution === true){
|
|
||||||
if(ConfigManager.getFullscreen()){
|
|
||||||
rule.values = [
|
|
||||||
'--fullscreen',
|
|
||||||
'true'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
checksum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO splice not push
|
|
||||||
if(checksum === args[i].rules.length){
|
|
||||||
if(typeof args[i].value === 'string'){
|
|
||||||
args[i] = args[i].value
|
|
||||||
} else if(typeof args[i].value === 'object'){
|
|
||||||
//args = args.concat(args[i].value)
|
|
||||||
args.splice(i, 1, ...args[i].value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrement i to reprocess the resolved value
|
|
||||||
i--
|
|
||||||
} else {
|
|
||||||
args[i] = null
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(typeof args[i] === 'string'){
|
|
||||||
if(argDiscovery.test(args[i])){
|
|
||||||
const identifier = args[i].match(argDiscovery)[1]
|
|
||||||
let val = null
|
|
||||||
switch(identifier){
|
|
||||||
case 'auth_player_name':
|
|
||||||
val = this.authUser.displayName.trim()
|
|
||||||
break
|
|
||||||
case 'version_name':
|
|
||||||
//val = versionData.id
|
|
||||||
val = this.server.getID()
|
|
||||||
break
|
|
||||||
case 'game_directory':
|
|
||||||
val = this.gameDir
|
|
||||||
break
|
|
||||||
case 'assets_root':
|
|
||||||
val = path.join(this.commonDir, 'assets')
|
|
||||||
break
|
|
||||||
case 'assets_index_name':
|
|
||||||
val = this.versionData.assets
|
|
||||||
break
|
|
||||||
case 'auth_uuid':
|
|
||||||
val = this.authUser.uuid.trim()
|
|
||||||
break
|
|
||||||
case 'auth_access_token':
|
|
||||||
val = this.authUser.accessToken
|
|
||||||
break
|
|
||||||
case 'user_type':
|
|
||||||
val = 'mojang'
|
|
||||||
break
|
|
||||||
case 'version_type':
|
|
||||||
val = this.versionData.type
|
|
||||||
break
|
|
||||||
case 'resolution_width':
|
|
||||||
val = ConfigManager.getGameWidth()
|
|
||||||
break
|
|
||||||
case 'resolution_height':
|
|
||||||
val = ConfigManager.getGameHeight()
|
|
||||||
break
|
|
||||||
case 'natives_directory':
|
|
||||||
val = args[i].replace(argDiscovery, tempNativePath)
|
|
||||||
break
|
|
||||||
case 'launcher_name':
|
|
||||||
val = args[i].replace(argDiscovery, 'Helios-Launcher')
|
|
||||||
break
|
|
||||||
case 'launcher_version':
|
|
||||||
val = args[i].replace(argDiscovery, this.launcherVersion)
|
|
||||||
break
|
|
||||||
case 'classpath':
|
|
||||||
val = this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if(val != null){
|
|
||||||
args[i] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forge Specific Arguments
|
|
||||||
args = args.concat(this.forgeData.arguments.game)
|
|
||||||
|
|
||||||
// Filter null values
|
|
||||||
args = args.filter(arg => {
|
|
||||||
return arg != null
|
|
||||||
})
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the arguments required by forge.
|
|
||||||
*
|
|
||||||
* @returns {Array.<string>} An array containing the arguments required by forge.
|
|
||||||
*/
|
|
||||||
_resolveForgeArgs(){
|
|
||||||
const mcArgs = this.forgeData.minecraftArguments.split(' ')
|
|
||||||
const argDiscovery = /\${*(.*)}/
|
|
||||||
|
|
||||||
// Replace the declared variables with their proper values.
|
|
||||||
for(let i=0; i<mcArgs.length; ++i){
|
|
||||||
if(argDiscovery.test(mcArgs[i])){
|
|
||||||
const identifier = mcArgs[i].match(argDiscovery)[1]
|
|
||||||
let val = null
|
|
||||||
switch(identifier){
|
|
||||||
case 'auth_player_name':
|
|
||||||
val = this.authUser.displayName.trim()
|
|
||||||
break
|
|
||||||
case 'version_name':
|
|
||||||
//val = versionData.id
|
|
||||||
val = this.server.getID()
|
|
||||||
break
|
|
||||||
case 'game_directory':
|
|
||||||
val = this.gameDir
|
|
||||||
break
|
|
||||||
case 'assets_root':
|
|
||||||
val = path.join(this.commonDir, 'assets')
|
|
||||||
break
|
|
||||||
case 'assets_index_name':
|
|
||||||
val = this.versionData.assets
|
|
||||||
break
|
|
||||||
case 'auth_uuid':
|
|
||||||
val = this.authUser.uuid.trim()
|
|
||||||
break
|
|
||||||
case 'auth_access_token':
|
|
||||||
val = this.authUser.accessToken
|
|
||||||
break
|
|
||||||
case 'user_type':
|
|
||||||
val = 'mojang'
|
|
||||||
break
|
|
||||||
case 'user_properties': // 1.8.9 and below.
|
|
||||||
val = '{}'
|
|
||||||
break
|
|
||||||
case 'version_type':
|
|
||||||
val = this.versionData.type
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if(val != null){
|
|
||||||
mcArgs[i] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Autoconnect to the selected server.
|
|
||||||
if(ConfigManager.getAutoConnect() && this.server.isAutoConnect()){
|
|
||||||
const serverURL = new URL('my://' + this.server.getAddress())
|
|
||||||
mcArgs.push('--server')
|
|
||||||
mcArgs.push(serverURL.hostname)
|
|
||||||
if(serverURL.port){
|
|
||||||
mcArgs.push('--port')
|
|
||||||
mcArgs.push(serverURL.port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare game resolution
|
|
||||||
if(ConfigManager.getFullscreen()){
|
|
||||||
mcArgs.push('--fullscreen')
|
|
||||||
mcArgs.push(true)
|
|
||||||
} else {
|
|
||||||
mcArgs.push('--width')
|
|
||||||
mcArgs.push(ConfigManager.getGameWidth())
|
|
||||||
mcArgs.push('--height')
|
|
||||||
mcArgs.push(ConfigManager.getGameHeight())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mod List File Argument
|
|
||||||
mcArgs.push('--modListFile')
|
|
||||||
if(this._isBelowOneDotSeven()) {
|
|
||||||
mcArgs.push(path.basename(this.fmlDir))
|
|
||||||
} else {
|
|
||||||
mcArgs.push('absolute:' + this.fmlDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// LiteLoader
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
mcArgs.push('--modRepo')
|
|
||||||
mcArgs.push(this.llDir)
|
|
||||||
|
|
||||||
// Set first arg to liteloader tweak class
|
|
||||||
mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker')
|
|
||||||
mcArgs.unshift('--tweakClass')
|
|
||||||
}
|
|
||||||
|
|
||||||
return mcArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the full classpath argument list for this process. This method will resolve all Mojang-declared
|
|
||||||
* libraries as well as the libraries declared by the server. Since mods are permitted to declare libraries,
|
|
||||||
* this method requires all enabled mods as an input
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {Array.<string>} An array containing the paths of each library required by this process.
|
|
||||||
*/
|
|
||||||
classpathArg(mods, tempNativePath){
|
|
||||||
let cpArgs = []
|
|
||||||
|
|
||||||
// Add the version.jar to the classpath.
|
|
||||||
const version = this.versionData.id
|
|
||||||
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
|
|
||||||
|
|
||||||
if(this.usingLiteLoader){
|
|
||||||
cpArgs.push(this.llPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the Mojang declared libraries.
|
|
||||||
const mojangLibs = this._resolveMojangLibraries(tempNativePath)
|
|
||||||
|
|
||||||
// Resolve the server declared libraries.
|
|
||||||
const servLibs = this._resolveServerLibraries(mods)
|
|
||||||
|
|
||||||
// Merge libraries, server libs with the same
|
|
||||||
// maven identifier will override the mojang ones.
|
|
||||||
// Ex. 1.7.10 forge overrides mojang's guava with newer version.
|
|
||||||
const finalLibs = {...mojangLibs, ...servLibs}
|
|
||||||
cpArgs = cpArgs.concat(Object.values(finalLibs))
|
|
||||||
|
|
||||||
return cpArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the libraries defined by Mojang's version data. This method will also extract
|
|
||||||
* native libraries and point to the correct location for its classpath.
|
|
||||||
*
|
|
||||||
* TODO - clean up function
|
|
||||||
*
|
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
|
||||||
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
|
|
||||||
*/
|
|
||||||
_resolveMojangLibraries(tempNativePath){
|
|
||||||
const libs = {}
|
|
||||||
|
|
||||||
const libArr = this.versionData.libraries
|
|
||||||
fs.ensureDirSync(tempNativePath)
|
|
||||||
for(let i=0; i<libArr.length; i++){
|
|
||||||
const lib = libArr[i]
|
|
||||||
if(LibraryInternal.validateRules(lib.rules, lib.natives)){
|
|
||||||
if(lib.natives == null){
|
|
||||||
const dlInfo = lib.downloads
|
|
||||||
const artifact = dlInfo.artifact
|
|
||||||
const to = path.join(this.libPath, artifact.path)
|
|
||||||
const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':'))
|
|
||||||
libs[versionIndependentId] = to
|
|
||||||
} else {
|
|
||||||
// Extract the native library.
|
|
||||||
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/']
|
|
||||||
const artifact = lib.downloads.classifiers[lib.natives[LibraryInternal.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
|
|
||||||
|
|
||||||
// Location of native zip.
|
|
||||||
const to = path.join(this.libPath, artifact.path)
|
|
||||||
|
|
||||||
let zip = new AdmZip(to)
|
|
||||||
let zipEntries = zip.getEntries()
|
|
||||||
|
|
||||||
// Unzip the native zip.
|
|
||||||
for(let i=0; i<zipEntries.length; i++){
|
|
||||||
const fileName = zipEntries[i].entryName
|
|
||||||
|
|
||||||
let shouldExclude = false
|
|
||||||
|
|
||||||
// Exclude noted files.
|
|
||||||
exclusionArr.forEach(function(exclusion){
|
|
||||||
if(fileName.indexOf(exclusion) > -1){
|
|
||||||
shouldExclude = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Extract the file.
|
|
||||||
if(!shouldExclude){
|
|
||||||
fs.writeFile(path.join(tempNativePath, fileName), zipEntries[i].getData(), (err) => {
|
|
||||||
if(err){
|
|
||||||
logger.error('Error while extracting native library:', err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the libraries declared by this server in order to add them to the classpath.
|
|
||||||
* This method will also check each enabled mod for libraries, as mods are permitted to
|
|
||||||
* declare libraries.
|
|
||||||
*
|
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
|
||||||
* @returns {{[id: string]: string}} An object containing the paths of each library this server requires.
|
|
||||||
*/
|
|
||||||
_resolveServerLibraries(mods){
|
|
||||||
const mdls = this.server.getModules()
|
|
||||||
let libs = {}
|
|
||||||
|
|
||||||
// Locate Forge/Libraries
|
|
||||||
for(let mdl of mdls){
|
|
||||||
const type = mdl.getType()
|
|
||||||
if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Library){
|
|
||||||
libs[mdl.getVersionlessID()] = mdl.getArtifact().getPath()
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const res = this._resolveModuleLibraries(mdl)
|
|
||||||
if(res.length > 0){
|
|
||||||
libs = {...libs, ...res}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for any libraries in our mod list.
|
|
||||||
for(let i=0; i<mods.length; i++){
|
|
||||||
if(mods.sub_modules != null){
|
|
||||||
const res = this._resolveModuleLibraries(mods[i])
|
|
||||||
if(res.length > 0){
|
|
||||||
libs = {...libs, ...res}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively resolve the path of each library required by this module.
|
|
||||||
*
|
|
||||||
* @param {Object} mdl A module object from the server distro index.
|
|
||||||
* @returns {Array.<string>} An array containing the paths of each library this module requires.
|
|
||||||
*/
|
|
||||||
_resolveModuleLibraries(mdl){
|
|
||||||
if(!mdl.hasSubModules()){
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
let libs = []
|
|
||||||
for(let sm of mdl.getSubModules()){
|
|
||||||
if(sm.getType() === DistroManager.Types.Library){
|
|
||||||
libs.push(sm.getArtifact().getPath())
|
|
||||||
}
|
|
||||||
// If this module has submodules, we need to resolve the libraries for those.
|
|
||||||
// To avoid unnecessary recursive calls, base case is checked here.
|
|
||||||
if(mdl.hasSubModules()){
|
|
||||||
const res = this._resolveModuleLibraries(sm)
|
|
||||||
if(res.length > 0){
|
|
||||||
libs = libs.concat(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return libs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ProcessBuilder
|
|
@ -1,67 +0,0 @@
|
|||||||
import { connect } from '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.
|
|
||||||
*/
|
|
||||||
export function getStatus(address: string, port: number | string = 25565){
|
|
||||||
|
|
||||||
let sanitizedPort: number
|
|
||||||
|
|
||||||
if(port == null || port == ''){
|
|
||||||
sanitizedPort = 25565
|
|
||||||
}
|
|
||||||
if(typeof port === 'string'){
|
|
||||||
sanitizedPort = parseInt(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const socket = connect(sanitizedPort, address, () => {
|
|
||||||
let buff = Buffer.from([0xFE, 0x01])
|
|
||||||
socket.write(buff)
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.setTimeout(2500, () => {
|
|
||||||
socket.end()
|
|
||||||
reject({
|
|
||||||
code: 'ETIMEDOUT',
|
|
||||||
errno: 'ETIMEDOUT',
|
|
||||||
address,
|
|
||||||
sanitizedPort
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('data', (data) => {
|
|
||||||
if(data != null){
|
|
||||||
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