mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 10:34:18 +00:00
added mcl lib
This commit is contained in:
parent
9e94bf199e
commit
57bf2c58e8
167
src/main/lib/mcl/authenticator.js
Normal file
167
src/main/lib/mcl/authenticator.js
Normal file
@ -0,0 +1,167 @@
|
||||
const request = require('request')
|
||||
const { v3 } = require('uuid')
|
||||
|
||||
let uuid
|
||||
let api_url = 'https://authserver.mojang.com'
|
||||
|
||||
function parsePropts(array) {
|
||||
if (array) {
|
||||
const newObj = {}
|
||||
for (const entry of array) {
|
||||
if (newObj[entry.name]) {
|
||||
newObj[entry.name].push(entry.value)
|
||||
} else {
|
||||
newObj[entry.name] = [entry.value]
|
||||
}
|
||||
}
|
||||
return JSON.stringify(newObj)
|
||||
} else {
|
||||
return '{}'
|
||||
}
|
||||
}
|
||||
|
||||
function getUUID(value) {
|
||||
if (!uuid) {
|
||||
uuid = v3(value, v3.DNS)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
const Authenticator = {
|
||||
getAuth: (username, password, client_token = null) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUUID(username)
|
||||
if (!password) {
|
||||
const user = {
|
||||
access_token: uuid,
|
||||
client_token: client_token || uuid,
|
||||
uuid,
|
||||
name: username,
|
||||
user_properties: '{}'
|
||||
}
|
||||
|
||||
return resolve(user)
|
||||
}
|
||||
|
||||
const requestObject = {
|
||||
url: api_url + '/authenticate',
|
||||
json: {
|
||||
agent: {
|
||||
name: 'Minecraft',
|
||||
version: 1
|
||||
},
|
||||
username,
|
||||
password,
|
||||
clientToken: uuid,
|
||||
requestUser: true
|
||||
}
|
||||
}
|
||||
|
||||
request.post(requestObject, function (error, response, body) {
|
||||
if (error) return reject(error)
|
||||
if (!body || !body.selectedProfile) {
|
||||
return reject(new Error('Validation error: ' + response.statusMessage))
|
||||
}
|
||||
|
||||
const userProfile = {
|
||||
access_token: body.accessToken,
|
||||
client_token: body.clientToken,
|
||||
uuid: body.selectedProfile.id,
|
||||
name: body.selectedProfile.name,
|
||||
selected_profile: body.selectedProfile,
|
||||
user_properties: parsePropts(body.user.properties)
|
||||
}
|
||||
|
||||
resolve(userProfile)
|
||||
})
|
||||
})
|
||||
},
|
||||
validate: (accessToken, clientToken) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestObject = {
|
||||
url: api_url + '/validate',
|
||||
json: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
}
|
||||
|
||||
request.post(requestObject, async function (error, response, body) {
|
||||
if (error) return reject(error)
|
||||
|
||||
if (!body) resolve(true)
|
||||
else reject(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
refreshAuth: (accessToken, clientToken) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestObject = {
|
||||
url: api_url + '/refresh',
|
||||
json: {
|
||||
accessToken,
|
||||
clientToken,
|
||||
requestUser: true
|
||||
}
|
||||
}
|
||||
|
||||
request.post(requestObject, function (error, response, body) {
|
||||
if (error) return reject(error)
|
||||
if (!body || !body.selectedProfile) {
|
||||
return reject(new Error('Validation error: ' + response.statusMessage))
|
||||
}
|
||||
|
||||
const userProfile = {
|
||||
access_token: body.accessToken,
|
||||
client_token: getUUID(body.selectedProfile.name),
|
||||
uuid: body.selectedProfile.id,
|
||||
name: body.selectedProfile.name,
|
||||
user_properties: parsePropts(body.user.properties)
|
||||
}
|
||||
|
||||
return resolve(userProfile)
|
||||
})
|
||||
})
|
||||
},
|
||||
invalidate: (accessToken, clientToken) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestObject = {
|
||||
url: api_url + '/invalidate',
|
||||
json: {
|
||||
accessToken,
|
||||
clientToken
|
||||
}
|
||||
}
|
||||
|
||||
request.post(requestObject, function (error, response, body) {
|
||||
if (error) return reject(error)
|
||||
|
||||
if (!body) return resolve(true)
|
||||
else return reject(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
signOut: (username, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestObject = {
|
||||
url: api_url + '/signout',
|
||||
json: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
|
||||
request.post(requestObject, function (error, response, body) {
|
||||
if (error) return reject(error)
|
||||
|
||||
if (!body) return resolve(true)
|
||||
else return reject(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
changeApiUrl: (url) => {
|
||||
api_url = url
|
||||
}
|
||||
}
|
||||
|
||||
export default Authenticator
|
783
src/main/lib/mcl/handler.js
Normal file
783
src/main/lib/mcl/handler.js
Normal file
@ -0,0 +1,783 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const request = require('request')
|
||||
const checksum = require('checksum')
|
||||
const Zip = require('adm-zip')
|
||||
const child = require('child_process')
|
||||
let counter = 0
|
||||
|
||||
export default class Handler {
|
||||
constructor (client) {
|
||||
this.client = client
|
||||
this.options = client.options
|
||||
this.baseRequest = request.defaults({
|
||||
pool: { maxSockets: this.options.overrides.maxSockets || 2 },
|
||||
timeout: this.options.timeout || 10000
|
||||
})
|
||||
}
|
||||
|
||||
checkJava (java) {
|
||||
return new Promise(resolve => {
|
||||
child.exec(`"${java}" -version`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
resolve({
|
||||
run: false,
|
||||
message: error
|
||||
})
|
||||
} else {
|
||||
this.client.emit('debug', `[MCLC]: Using Java version ${stderr.match(/"(.*?)"/).pop()} ${stderr.includes('64-Bit') ? '64-bit' : '32-Bit'}`)
|
||||
resolve({
|
||||
run: true
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
downloadAsync (url, directory, name, retry, type) {
|
||||
return new Promise(resolve => {
|
||||
fs.mkdirSync(directory, { recursive: true })
|
||||
|
||||
const _request = this.baseRequest(url)
|
||||
|
||||
let receivedBytes = 0
|
||||
let totalBytes = 0
|
||||
|
||||
_request.on('response', (data) => {
|
||||
if (data.statusCode === 404) {
|
||||
this.client.emit('debug', `[MCLC]: Failed to download ${url} due to: File not found...`)
|
||||
return resolve(false)
|
||||
}
|
||||
|
||||
totalBytes = parseInt(data.headers['content-length'])
|
||||
})
|
||||
|
||||
_request.on('error', async (error) => {
|
||||
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${error}.` +
|
||||
` Retrying... ${retry}`)
|
||||
if (retry) await this.downloadAsync(url, directory, name, false, type)
|
||||
resolve()
|
||||
})
|
||||
|
||||
_request.on('data', (data) => {
|
||||
receivedBytes += data.length
|
||||
this.client.emit('download-status', {
|
||||
name: name,
|
||||
type: type,
|
||||
current: receivedBytes,
|
||||
total: totalBytes
|
||||
})
|
||||
})
|
||||
|
||||
const file = fs.createWriteStream(path.join(directory, name))
|
||||
_request.pipe(file)
|
||||
|
||||
file.once('finish', () => {
|
||||
this.client.emit('download', name)
|
||||
resolve({
|
||||
failed: false,
|
||||
asset: null
|
||||
})
|
||||
})
|
||||
|
||||
file.on('error', async (e) => {
|
||||
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}.` +
|
||||
` Retrying... ${retry}`)
|
||||
if (fs.existsSync(path.join(directory, name))) fs.unlinkSync(path.join(directory, name))
|
||||
if (retry) await this.downloadAsync(url, directory, name, false, type)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
checkSum (hash, file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
checksum.file(file, (err, sum) => {
|
||||
if (err) {
|
||||
this.client.emit('debug', `[MCLC]: Failed to check file hash due to ${err}`)
|
||||
resolve(false)
|
||||
} else {
|
||||
resolve(hash === sum)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getVersion () {
|
||||
return new Promise(resolve => {
|
||||
const versionJsonPath = this.options.overrides.versionJson || path.join(this.options.directory, `${this.options.version.number}.json`)
|
||||
|
||||
if (fs.existsSync(versionJsonPath)) {
|
||||
this.version = JSON.parse(fs.readFileSync(versionJsonPath))
|
||||
|
||||
return resolve(this.version)
|
||||
}
|
||||
|
||||
const manifest = `${this.options.overrides.url.meta}/mc/game/version_manifest.json`
|
||||
|
||||
const cache = this.options.cache ? `${this.options.cache}/json` : `${this.options.root}/cache/json`
|
||||
|
||||
request.get(manifest, (error, response, body) => {
|
||||
if (error && error.code !== 'ENOTFOUND') {
|
||||
return resolve(error)
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
if (!fs.existsSync(cache)) {
|
||||
fs.mkdirSync(cache, { recursive: true })
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Cache directory created.')
|
||||
}
|
||||
|
||||
fs.writeFile(path.join(`${cache}/version_manifest.json`), body, (err) => {
|
||||
if (err) {
|
||||
return resolve(err)
|
||||
}
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Cached version_manifest.json (from request)')
|
||||
})
|
||||
}
|
||||
|
||||
let parsed = null
|
||||
|
||||
if (error && (error.code === 'ENOTFOUND')) {
|
||||
parsed = JSON.parse(fs.readFileSync(`${cache}/version_manifest.json`))
|
||||
} else {
|
||||
parsed = JSON.parse(body)
|
||||
}
|
||||
|
||||
const versionManifest = parsed.versions.find((version) => {
|
||||
return version.id === this.options.version.number
|
||||
})
|
||||
|
||||
if (!versionManifest) {
|
||||
return resolve(new Error(`Version not found`))
|
||||
}
|
||||
|
||||
request.get(versionManifest.url, (error, response, body) => {
|
||||
if (error && error.code !== 'ENOTFOUND') {
|
||||
return resolve(error)
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
fs.writeFile(path.join(`${cache}/${this.options.version.number}.json`), body, (err) => {
|
||||
if (err) {
|
||||
return resolve(err)
|
||||
}
|
||||
|
||||
this.client.emit('debug', `[MCLC]: Cached ${this.options.version.number}.json`)
|
||||
})
|
||||
}
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Parsed version from version manifest')
|
||||
|
||||
if (error && (error.code === 'ENOTFOUND')) {
|
||||
this.version = JSON.parse(fs.readFileSync(`${cache}/${this.options.version.number}.json`))
|
||||
} else {
|
||||
this.version = JSON.parse(body)
|
||||
}
|
||||
|
||||
this.client.emit('debug', this.version)
|
||||
|
||||
return resolve(this.version)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getJar () {
|
||||
await this.downloadAsync(this.version.downloads.client.url, this.options.directory, `${this.options.version.custom ? this.options.version.custom : this.options.version.number}.jar`, true, 'version-jar')
|
||||
fs.writeFileSync(path.join(this.options.directory, `${this.options.version.number}.json`), JSON.stringify(this.version, null, 4))
|
||||
return this.client.emit('debug', '[MCLC]: Downloaded version jar and wrote version json')
|
||||
}
|
||||
|
||||
async getAssets () {
|
||||
const assetDirectory = path.resolve(this.options.overrides.assetRoot || path.join(this.options.root, 'assets'))
|
||||
const assetId = this.options.version.custom || this.options.version.number
|
||||
if (!fs.existsSync(path.join(assetDirectory, 'indexes', `${assetId}.json`))) {
|
||||
await this.downloadAsync(this.version.assetIndex.url, path.join(assetDirectory, 'indexes'),
|
||||
`${assetId}.json`, true, 'asset-json')
|
||||
}
|
||||
|
||||
const index = JSON.parse(fs.readFileSync(path.join(assetDirectory, 'indexes', `${assetId}.json`), { encoding: 'utf8' }))
|
||||
|
||||
this.client.emit('progress', {
|
||||
type: 'assets',
|
||||
task: 0,
|
||||
total: Object.keys(index.objects).length
|
||||
})
|
||||
|
||||
await Promise.all(Object.keys(index.objects).map(async asset => {
|
||||
const hash = index.objects[asset].hash
|
||||
const subhash = hash.substring(0, 2)
|
||||
const subAsset = path.join(assetDirectory, 'objects', subhash)
|
||||
|
||||
if (!fs.existsSync(path.join(subAsset, hash)) || !await this.checkSum(hash, path.join(subAsset, hash))) {
|
||||
await this.downloadAsync(`${this.options.overrides.url.resource}/${subhash}/${hash}`, subAsset, hash,
|
||||
true, 'assets')
|
||||
}
|
||||
counter++
|
||||
this.client.emit('progress', {
|
||||
type: 'assets',
|
||||
task: counter,
|
||||
total: Object.keys(index.objects).length
|
||||
})
|
||||
}))
|
||||
counter = 0
|
||||
|
||||
// Copy assets to legacy if it's an older Minecraft version.
|
||||
if (this.isLegacy()) {
|
||||
if (fs.existsSync(path.join(assetDirectory, 'legacy'))) {
|
||||
this.client.emit('debug', '[MCLC]: The \'legacy\' directory is no longer used as Minecraft looks ' +
|
||||
'for the resouces folder regardless of what is passed in the assetDirecotry launch option. I\'d ' +
|
||||
`recommend removing the directory (${path.join(assetDirectory, 'legacy')})`)
|
||||
}
|
||||
|
||||
const legacyDirectory = path.join(this.options.root, 'resources')
|
||||
this.client.emit('debug', `[MCLC]: Copying assets over to ${legacyDirectory}`)
|
||||
|
||||
this.client.emit('progress', {
|
||||
type: 'assets-copy',
|
||||
task: 0,
|
||||
total: Object.keys(index.objects).length
|
||||
})
|
||||
|
||||
await Promise.all(Object.keys(index.objects).map(async asset => {
|
||||
const hash = index.objects[asset].hash
|
||||
const subhash = hash.substring(0, 2)
|
||||
const subAsset = path.join(assetDirectory, 'objects', subhash)
|
||||
|
||||
const legacyAsset = asset.split('/')
|
||||
legacyAsset.pop()
|
||||
|
||||
if (!fs.existsSync(path.join(legacyDirectory, legacyAsset.join('/')))) {
|
||||
fs.mkdirSync(path.join(legacyDirectory, legacyAsset.join('/')), { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(legacyDirectory, asset))) {
|
||||
fs.copyFileSync(path.join(subAsset, hash), path.join(legacyDirectory, asset))
|
||||
}
|
||||
counter++
|
||||
this.client.emit('progress', {
|
||||
type: 'assets-copy',
|
||||
task: counter,
|
||||
total: Object.keys(index.objects).length
|
||||
})
|
||||
}))
|
||||
}
|
||||
counter = 0
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Downloaded assets')
|
||||
}
|
||||
|
||||
parseRule (lib) {
|
||||
if (lib.rules) {
|
||||
if (lib.rules.length > 1) {
|
||||
if (lib.rules[0].action === 'allow' &&
|
||||
lib.rules[1].action === 'disallow' &&
|
||||
lib.rules[1].os.name === 'osx') {
|
||||
return this.getOS() === 'osx'
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (lib.rules[0].action === 'allow' && lib.rules[0].os) return lib.rules[0].os.name !== this.getOS()
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async getNatives () {
|
||||
const nativeDirectory = path.resolve(this.options.overrides.natives || path.join(this.options.root, 'natives', this.version.id))
|
||||
|
||||
if (parseInt(this.version.id.split('.')[1]) >= 19) return this.options.overrides.cwd || this.options.root
|
||||
|
||||
if (!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) {
|
||||
fs.mkdirSync(nativeDirectory, { recursive: true })
|
||||
|
||||
const natives = async () => {
|
||||
const natives = []
|
||||
await Promise.all(this.version.libraries.map(async (lib) => {
|
||||
if (!lib.downloads || !lib.downloads.classifiers) return
|
||||
if (this.parseRule(lib)) return
|
||||
|
||||
const native = this.getOS() === 'osx'
|
||||
? lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos']
|
||||
: lib.downloads.classifiers[`natives-${this.getOS()}`]
|
||||
|
||||
natives.push(native)
|
||||
}))
|
||||
return natives
|
||||
}
|
||||
const stat = await natives()
|
||||
|
||||
this.client.emit('progress', {
|
||||
type: 'natives',
|
||||
task: 0,
|
||||
total: stat.length
|
||||
})
|
||||
|
||||
await Promise.all(stat.map(async (native) => {
|
||||
if (!native) return
|
||||
const name = native.path.split('/').pop()
|
||||
await this.downloadAsync(native.url, nativeDirectory, name, true, 'natives')
|
||||
if (!await this.checkSum(native.sha1, path.join(nativeDirectory, name))) {
|
||||
await this.downloadAsync(native.url, nativeDirectory, name, true, 'natives')
|
||||
}
|
||||
try {
|
||||
new Zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true)
|
||||
} catch (e) {
|
||||
// Only doing a console.warn since a stupid error happens. You can basically ignore this.
|
||||
// if it says Invalid file name, just means two files were downloaded and both were deleted.
|
||||
// All is well.
|
||||
console.warn(e)
|
||||
}
|
||||
fs.unlinkSync(path.join(nativeDirectory, name))
|
||||
counter++
|
||||
this.client.emit('progress', {
|
||||
type: 'natives',
|
||||
task: counter,
|
||||
total: stat.length
|
||||
})
|
||||
}))
|
||||
this.client.emit('debug', '[MCLC]: Downloaded and extracted natives')
|
||||
}
|
||||
|
||||
counter = 0
|
||||
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`)
|
||||
|
||||
return nativeDirectory
|
||||
}
|
||||
|
||||
fwAddArgs () {
|
||||
const forgeWrapperAgrs = [
|
||||
`-Dforgewrapper.librariesDir=${path.resolve(this.options.overrides.libraryRoot || path.join(this.options.root, 'libraries'))}`,
|
||||
`-Dforgewrapper.installer=${this.options.forge}`,
|
||||
`-Dforgewrapper.minecraft=${this.options.mcPath}`
|
||||
]
|
||||
this.options.customArgs
|
||||
? this.options.customArgs = this.options.customArgs.concat(forgeWrapperAgrs)
|
||||
: this.options.customArgs = forgeWrapperAgrs
|
||||
}
|
||||
|
||||
isModernForge (json) {
|
||||
return json.inheritsFrom && json.inheritsFrom.split('.')[1] >= 12 && !(json.inheritsFrom === '1.12.2' && (json.id.split('.')[json.id.split('.').length - 1]) === '2847')
|
||||
}
|
||||
|
||||
async getForgedWrapped () {
|
||||
let json = null
|
||||
let installerJson = null
|
||||
const versionPath = path.join(this.options.root, 'forge', `${this.version.id}`, 'version.json')
|
||||
// Since we're building a proper "custom" JSON that will work nativly with MCLC, the version JSON will not
|
||||
// be re-generated on the next run.
|
||||
if (fs.existsSync(versionPath)) {
|
||||
try {
|
||||
json = JSON.parse(fs.readFileSync(versionPath))
|
||||
if (!json.forgeWrapperVersion || !(json.forgeWrapperVersion === this.options.overrides.fw.version)) {
|
||||
this.client.emit('debug', '[MCLC]: Old ForgeWrapper has generated this version JSON, re-generating')
|
||||
} else {
|
||||
// If forge is modern, add ForgeWrappers launch arguments and set forge to null so MCLC treats it as a custom json.
|
||||
if (this.isModernForge(json)) {
|
||||
this.fwAddArgs()
|
||||
this.options.forge = null
|
||||
}
|
||||
return json
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
this.client.emit('debug', '[MCLC]: Failed to parse Forge version JSON, re-generating')
|
||||
}
|
||||
}
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Generating a proper version json, this might take a bit')
|
||||
const zipFile = new Zip(this.options.forge)
|
||||
json = zipFile.readAsText('version.json')
|
||||
if (zipFile.getEntry('install_profile.json')) installerJson = zipFile.readAsText('install_profile.json')
|
||||
|
||||
try {
|
||||
json = JSON.parse(json)
|
||||
if (installerJson) installerJson = JSON.parse(installerJson)
|
||||
} catch (e) {
|
||||
this.client.emit('debug', '[MCLC]: Failed to load json files for ForgeWrapper, using Vanilla instead')
|
||||
return null
|
||||
}
|
||||
// Adding the installer libraries as mavenFiles so MCLC downloads them but doesn't add them to the class paths.
|
||||
if (installerJson) {
|
||||
json.mavenFiles
|
||||
? json.mavenFiles = json.mavenFiles.concat(installerJson.libraries)
|
||||
: json.mavenFiles = installerJson.libraries
|
||||
}
|
||||
|
||||
// Holder for the specifc jar ending which depends on the specifc forge version.
|
||||
let jarEnding = 'universal'
|
||||
// We need to handle modern forge differently than legacy.
|
||||
if (this.isModernForge(json)) {
|
||||
// If forge is modern and above 1.12.2, we add ForgeWrapper to the libraries so MCLC includes it in the classpaths.
|
||||
if (json.inheritsFrom !== '1.12.2') {
|
||||
this.fwAddArgs()
|
||||
const fwName = `ForgeWrapper-${this.options.overrides.fw.version}.jar`
|
||||
const fwPathArr = ['io', 'github', 'zekerzhayard', 'ForgeWrapper', this.options.overrides.fw.version]
|
||||
json.libraries.push({
|
||||
name: fwPathArr.join(':'),
|
||||
downloads: {
|
||||
artifact: {
|
||||
path: [...fwPathArr, fwName].join('/'),
|
||||
url: `${this.options.overrides.fw.baseUrl}${this.options.overrides.fw.version}/${fwName}`,
|
||||
sha1: this.options.overrides.fw.sh1,
|
||||
size: this.options.overrides.fw.size
|
||||
}
|
||||
}
|
||||
})
|
||||
json.mainClass = 'io.github.zekerzhayard.forgewrapper.installer.Main'
|
||||
jarEnding = 'launcher'
|
||||
|
||||
// Providing a download URL to the universal jar mavenFile so it can be downloaded properly.
|
||||
for (const library of json.mavenFiles) {
|
||||
const lib = library.name.split(':')
|
||||
if (lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) {
|
||||
library.downloads.artifact.url = 'https://files.minecraftforge.net/maven/' + library.downloads.artifact.path
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove the forge dependent since we're going to overwrite the first entry anyways.
|
||||
for (const library in json.mavenFiles) {
|
||||
const lib = json.mavenFiles[library].name.split(':')
|
||||
if (lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) {
|
||||
delete json.mavenFiles[library]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Modifying legacy library format to play nice with MCLC's downloadToDirectory function.
|
||||
await Promise.all(json.libraries.map(async library => {
|
||||
const lib = library.name.split(':')
|
||||
if (lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) return
|
||||
|
||||
let url = this.options.overrides.url.mavenForge
|
||||
const name = `${lib[1]}-${lib[2]}.jar`
|
||||
|
||||
if (!library.url) {
|
||||
if (library.serverreq || library.clientreq) {
|
||||
url = this.options.overrides.url.defaultRepoForge
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
library.url = url
|
||||
const downloadLink = `${url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`
|
||||
// Checking if the file still exists on Forge's server, if not, replace it with the fallback.
|
||||
// Not checking for sucess, only if it 404s.
|
||||
this.baseRequest(downloadLink, (error, response, body) => {
|
||||
if (error) {
|
||||
this.client.emit('debug', `[MCLC]: Failed checking request for ${downloadLink}`)
|
||||
} else {
|
||||
if (response.statusCode === 404) library.url = this.options.overrides.url.fallbackMaven
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
// If a downloads property exists, we modify the inital forge entry to include ${jarEnding} so ForgeWrapper can work properly.
|
||||
// If it doesn't, we simply remove it since we're already providing the universal jar.
|
||||
if (json.libraries[0].downloads) {
|
||||
if (json.libraries[0].name.includes('minecraftforge')) {
|
||||
json.libraries[0].name = json.libraries[0].name + `:${jarEnding}`
|
||||
json.libraries[0].downloads.artifact.path = json.libraries[0].downloads.artifact.path.replace('.jar', `-${jarEnding}.jar`)
|
||||
json.libraries[0].downloads.artifact.url = 'https://files.minecraftforge.net/maven/' + json.libraries[0].downloads.artifact.path
|
||||
}
|
||||
} else {
|
||||
delete json.libraries[0]
|
||||
}
|
||||
|
||||
// Removing duplicates and null types
|
||||
json.libraries = this.cleanUp(json.libraries)
|
||||
if (json.mavenFiles) json.mavenFiles = this.cleanUp(json.mavenFiles)
|
||||
|
||||
json.forgeWrapperVersion = this.options.overrides.fw.version
|
||||
|
||||
// Saving file for next run!
|
||||
if (!fs.existsSync(path.join(this.options.root, 'forge', this.version.id))) {
|
||||
fs.mkdirSync(path.join(this.options.root, 'forge', this.version.id), { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(versionPath, JSON.stringify(json, null, 4))
|
||||
|
||||
// Make MCLC treat modern forge as a custom version json rather then legacy forge.
|
||||
if (this.isModernForge(json)) this.options.forge = null
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
runInstaller (path) {
|
||||
return new Promise(resolve => {
|
||||
const installer = child.exec(path)
|
||||
installer.on('close', (code) => resolve(code))
|
||||
})
|
||||
}
|
||||
|
||||
async downloadToDirectory (directory, libraries, eventName) {
|
||||
const libs = []
|
||||
|
||||
await Promise.all(libraries.map(async library => {
|
||||
if (!library) return
|
||||
if (this.parseRule(library)) return
|
||||
const lib = library.name.split(':')
|
||||
|
||||
let jarPath
|
||||
let name
|
||||
if (library.downloads && library.downloads.artifact && library.downloads.artifact.path) {
|
||||
name = library.downloads.artifact.path.split('/')[library.downloads.artifact.path.split('/').length - 1]
|
||||
jarPath = path.join(directory, this.popString(library.downloads.artifact.path))
|
||||
} else {
|
||||
name = `${lib[1]}-${lib[2]}${lib[3] ? '-' + lib[3] : ''}.jar`
|
||||
jarPath = path.join(directory, `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`)
|
||||
}
|
||||
|
||||
const downloadLibrary = async library => {
|
||||
if (library.url) {
|
||||
const url = `${library.url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`
|
||||
await this.downloadAsync(url, jarPath, name, true, eventName)
|
||||
} else if (library.downloads && library.downloads.artifact) {
|
||||
await this.downloadAsync(library.downloads.artifact.url, jarPath, name, true, eventName)
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(jarPath, name))) downloadLibrary(library)
|
||||
else if (library.downloads && library.downloads.artifact) {
|
||||
if (!this.checkSum(library.downloads.artifact.sha1, path.join(jarPath, name))) downloadLibrary(library)
|
||||
}
|
||||
|
||||
counter++
|
||||
this.client.emit('progress', {
|
||||
type: eventName,
|
||||
task: counter,
|
||||
total: libraries.length
|
||||
})
|
||||
libs.push(`${jarPath}${path.sep}${name}`)
|
||||
}))
|
||||
counter = 0
|
||||
|
||||
return libs
|
||||
}
|
||||
|
||||
async getClasses (classJson) {
|
||||
let libs = []
|
||||
|
||||
const libraryDirectory = path.resolve(this.options.overrides.libraryRoot || path.join(this.options.root, 'libraries'))
|
||||
|
||||
if (classJson) {
|
||||
if (classJson.mavenFiles) {
|
||||
await this.downloadToDirectory(libraryDirectory, classJson.mavenFiles, 'classes-maven-custom')
|
||||
}
|
||||
libs = (await this.downloadToDirectory(libraryDirectory, classJson.libraries, 'classes-custom'))
|
||||
}
|
||||
|
||||
const parsed = this.version.libraries.map(lib => {
|
||||
if (lib.downloads && lib.downloads.artifact && !this.parseRule(lib)) return lib
|
||||
})
|
||||
|
||||
libs = libs.concat((await this.downloadToDirectory(libraryDirectory, parsed, 'classes')))
|
||||
counter = 0
|
||||
|
||||
// Temp Quilt support
|
||||
if (classJson) libs.sort()
|
||||
|
||||
this.client.emit('debug', '[MCLC]: Collected class paths')
|
||||
return libs
|
||||
}
|
||||
|
||||
popString (path) {
|
||||
const tempArray = path.split('/')
|
||||
tempArray.pop()
|
||||
return tempArray.join('/')
|
||||
}
|
||||
|
||||
cleanUp (array) {
|
||||
const newArray = []
|
||||
for (const classPath in array) {
|
||||
if (newArray.includes(array[classPath]) || array[classPath] === null) continue
|
||||
newArray.push(array[classPath])
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
|
||||
formatQuickPlay () {
|
||||
const types = {
|
||||
singleplayer: '--quickPlaySingleplayer',
|
||||
multiplayer: '--quickPlayMultiplayer',
|
||||
realms: '--quickPlayRealms',
|
||||
legacy: null
|
||||
}
|
||||
const { type, identifier, path } = this.options.quickPlay
|
||||
const keys = Object.keys(types)
|
||||
if (!keys.includes(type)) {
|
||||
this.client.emit('debug', `[MCLC]: quickPlay type is not valid. Valid types are: ${keys.join(', ')}`)
|
||||
return null
|
||||
}
|
||||
const returnArgs = type === 'legacy'
|
||||
? ['--server', identifier.split(':')[0], '--port', identifier.split(':')[1] || '25565']
|
||||
: [types[type], identifier]
|
||||
if (path) returnArgs.push('--quickPlayPath', path)
|
||||
return returnArgs
|
||||
}
|
||||
|
||||
async getLaunchOptions (modification) {
|
||||
const type = Object.assign({}, this.version, modification)
|
||||
|
||||
let args = type.minecraftArguments
|
||||
? type.minecraftArguments.split(' ')
|
||||
: type.arguments.game
|
||||
const assetRoot = path.resolve(this.options.overrides.assetRoot || path.join(this.options.root, 'assets'))
|
||||
const assetPath = this.isLegacy()
|
||||
? path.join(this.options.root, 'resources')
|
||||
: path.join(assetRoot)
|
||||
|
||||
const minArgs = this.options.overrides.minArgs || this.isLegacy() ? 5 : 11
|
||||
if (args.length < minArgs) args = args.concat(this.version.minecraftArguments ? this.version.minecraftArguments.split(' ') : this.version.arguments.game)
|
||||
if (this.options.customLaunchArgs) args = args.concat(this.options.customLaunchArgs)
|
||||
|
||||
this.options.authorization = await Promise.resolve(this.options.authorization)
|
||||
this.options.authorization.meta = this.options.authorization.meta ? this.options.authorization.meta : { type: 'mojang' }
|
||||
const fields = {
|
||||
'${auth_access_token}': this.options.authorization.access_token,
|
||||
'${auth_session}': this.options.authorization.access_token,
|
||||
'${auth_player_name}': this.options.authorization.name,
|
||||
'${auth_uuid}': this.options.authorization.uuid,
|
||||
'${auth_xuid}': this.options.authorization.meta.xuid || this.options.authorization.access_token,
|
||||
'${user_properties}': this.options.authorization.user_properties,
|
||||
'${user_type}': this.options.authorization.meta.type,
|
||||
'${version_name}': this.options.version.number,
|
||||
'${assets_index_name}': this.options.overrides.assetIndex || this.options.version.custom || this.options.version.number,
|
||||
'${game_directory}': this.options.overrides.gameDirectory || this.options.root,
|
||||
'${assets_root}': assetPath,
|
||||
'${game_assets}': assetPath,
|
||||
'${version_type}': this.options.version.type,
|
||||
'${clientid}': this.options.authorization.meta.clientId || (this.options.authorization.client_token || this.options.authorization.access_token),
|
||||
'${resolution_width}': this.options.window ? this.options.window.width : 856,
|
||||
'${resolution_height}': this.options.window ? this.options.window.height : 482
|
||||
}
|
||||
|
||||
if (this.options.authorization.meta.demo && (this.options.features ? !this.options.features.includes('is_demo_user') : true)) {
|
||||
args.push('--demo')
|
||||
}
|
||||
|
||||
const replaceArg = (obj, index) => {
|
||||
if (Array.isArray(obj.value)) {
|
||||
for (const arg of obj.value) {
|
||||
args.push(arg)
|
||||
}
|
||||
} else {
|
||||
args.push(obj.value)
|
||||
}
|
||||
delete args[index]
|
||||
}
|
||||
|
||||
for (let index = 0; index < args.length; index++) {
|
||||
if (typeof args[index] === 'object') {
|
||||
if (args[index].rules) {
|
||||
if (!this.options.features) continue
|
||||
const featureFlags = []
|
||||
for (const rule of args[index].rules) {
|
||||
featureFlags.push(...Object.keys(rule.features))
|
||||
}
|
||||
let hasAllRules = true
|
||||
for (const feature of this.options.features) {
|
||||
if (!featureFlags.includes(feature)) {
|
||||
hasAllRules = false
|
||||
}
|
||||
}
|
||||
if (hasAllRules) replaceArg(args[index], index)
|
||||
} else {
|
||||
replaceArg(args[index], index)
|
||||
}
|
||||
} else {
|
||||
if (Object.keys(fields).includes(args[index])) {
|
||||
args[index] = fields[args[index]]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.options.window) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.options.window.fullscreen
|
||||
? args.push('--fullscreen')
|
||||
: () => {
|
||||
if (this.options.features ? !this.options.features.includes('has_custom_resolution') : true) {
|
||||
args.push('--width', this.options.window.width, '--height', this.options.window.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.options.server) this.client.emit('debug', '[MCLC]: server and port are deprecated launch flags. Use the quickPlay field.')
|
||||
if (this.options.quickPlay) args = args.concat(this.formatQuickPlay())
|
||||
if (this.options.proxy) {
|
||||
args.push(
|
||||
'--proxyHost',
|
||||
this.options.proxy.host,
|
||||
'--proxyPort',
|
||||
this.options.proxy.port || '8080',
|
||||
'--proxyUser',
|
||||
this.options.proxy.username,
|
||||
'--proxyPass',
|
||||
this.options.proxy.password
|
||||
)
|
||||
}
|
||||
args = args.filter(value => typeof value === 'string' || typeof value === 'number')
|
||||
this.client.emit('debug', '[MCLC]: Set launch options')
|
||||
return args
|
||||
}
|
||||
|
||||
async getJVM () {
|
||||
const opts = {
|
||||
windows: '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump',
|
||||
osx: '-XstartOnFirstThread',
|
||||
linux: '-Xss1M'
|
||||
}
|
||||
return opts[this.getOS()]
|
||||
}
|
||||
|
||||
isLegacy () {
|
||||
return this.version.assets === 'legacy' || this.version.assets === 'pre-1.6'
|
||||
}
|
||||
|
||||
getOS () {
|
||||
if (this.options.os) {
|
||||
return this.options.os
|
||||
} else {
|
||||
switch (process.platform) {
|
||||
case 'win32': return 'windows'
|
||||
case 'darwin': return 'osx'
|
||||
default: return 'linux'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To prevent launchers from breaking when they update. Will be reworked with rewrite.
|
||||
getMemory () {
|
||||
if (!this.options.memory) {
|
||||
this.client.emit('debug', '[MCLC]: Memory not set! Setting 1GB as MAX!')
|
||||
this.options.memory = {
|
||||
min: 512,
|
||||
max: 1023
|
||||
}
|
||||
}
|
||||
if (!isNaN(this.options.memory.max) && !isNaN(this.options.memory.min)) {
|
||||
if (this.options.memory.max < this.options.memory.min) {
|
||||
this.client.emit('debug', '[MCLC]: MIN memory is higher then MAX! Resetting!')
|
||||
this.options.memory.max = 1023
|
||||
this.options.memory.min = 512
|
||||
}
|
||||
return [`${this.options.memory.max}M`, `${this.options.memory.min}M`]
|
||||
} else { return [`${this.options.memory.max}`, `${this.options.memory.min}`] }
|
||||
}
|
||||
|
||||
async extractPackage (options = this.options) {
|
||||
if (options.clientPackage.startsWith('http')) {
|
||||
await this.downloadAsync(options.clientPackage, options.root, 'clientPackage.zip', true, 'client-package')
|
||||
options.clientPackage = path.join(options.root, 'clientPackage.zip')
|
||||
}
|
||||
new Zip(options.clientPackage).extractAllTo(options.root, true)
|
||||
if (options.removePackage) fs.unlinkSync(options.clientPackage)
|
||||
|
||||
return this.client.emit('package-extract', true)
|
||||
}
|
||||
}
|
223
src/main/lib/mcl/launcher.js
Normal file
223
src/main/lib/mcl/launcher.js
Normal file
@ -0,0 +1,223 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import {EventEmitter} from "events"
|
||||
import child from "child_process"
|
||||
|
||||
import Handler from "./handler"
|
||||
|
||||
export default class MCLCore extends EventEmitter {
|
||||
async launch(options, callbacks = {}) {
|
||||
try {
|
||||
this.options = { ...options }
|
||||
|
||||
this.options.root = path.resolve(this.options.root)
|
||||
|
||||
this.options.overrides = {
|
||||
detached: true,
|
||||
...this.options.overrides,
|
||||
url: {
|
||||
meta: 'https://launchermeta.mojang.com',
|
||||
resource: 'https://resources.download.minecraft.net',
|
||||
mavenForge: 'http://files.minecraftforge.net/maven/',
|
||||
defaultRepoForge: 'https://libraries.minecraft.net/',
|
||||
fallbackMaven: 'https://search.maven.org/remotecontent?filepath=',
|
||||
...this.options.overrides
|
||||
? this.options.overrides.url
|
||||
: undefined
|
||||
},
|
||||
fw: {
|
||||
baseUrl: 'https://github.com/ZekerZhayard/ForgeWrapper/releases/download/',
|
||||
version: '1.5.6',
|
||||
sh1: 'b38d28e8b7fde13b1bc0db946a2da6760fecf98d',
|
||||
size: 34715,
|
||||
...this.options.overrides
|
||||
? this.options.overrides.fw
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
this.handler = new Handler(this)
|
||||
|
||||
this.printVersion()
|
||||
|
||||
const java = await this.handler.checkJava(this.options.javaPath || 'java')
|
||||
|
||||
if (!java.run) {
|
||||
this.emit('debug', `[MCLC]: Couldn't start Minecraft due to: ${java.message}`)
|
||||
this.emit('close', 1)
|
||||
return null
|
||||
}
|
||||
|
||||
this.createRootDirectory()
|
||||
this.createGameDirectory()
|
||||
|
||||
await this.extractPackage()
|
||||
|
||||
if (this.options.installer) {
|
||||
// So installers that create a profile in launcher_profiles.json can run without breaking.
|
||||
const profilePath = path.join(this.options.root, 'launcher_profiles.json')
|
||||
if (!fs.existsSync(profilePath) || !JSON.parse(fs.readFileSync(profilePath)).profiles) {
|
||||
fs.writeFileSync(profilePath, JSON.stringify({ profiles: {} }, null, 4))
|
||||
}
|
||||
const code = await this.handler.runInstaller(this.options.installer)
|
||||
if (!this.options.version.custom && code === 0) {
|
||||
this.emit('debug', '[MCLC]: Installer successfully ran, but no custom version was provided')
|
||||
}
|
||||
this.emit('debug', `[MCLC]: Installer closed with code ${code}`)
|
||||
}
|
||||
|
||||
const directory = this.options.overrides.directory || path.join(this.options.root, 'versions', this.options.version.custom ? this.options.version.custom : this.options.version.number)
|
||||
this.options.directory = directory
|
||||
|
||||
const versionFile = await this.handler.getVersion()
|
||||
|
||||
const mcPath = this.options.overrides.minecraftJar || (this.options.version.custom
|
||||
? path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.jar`)
|
||||
: path.join(directory, `${this.options.version.number}.jar`))
|
||||
|
||||
this.options.mcPath = mcPath
|
||||
|
||||
const nativePath = await this.handler.getNatives()
|
||||
|
||||
if (!fs.existsSync(mcPath)) {
|
||||
this.emit('debug', '[MCLC]: Attempting to download Minecraft version jar')
|
||||
|
||||
if (typeof callbacks.install === "function") {
|
||||
callbacks.install()
|
||||
}
|
||||
|
||||
await this.handler.getJar()
|
||||
}
|
||||
|
||||
const modifyJson = await this.getModifyJson()
|
||||
|
||||
const args = []
|
||||
|
||||
let jvm = [
|
||||
'-XX:-UseAdaptiveSizePolicy',
|
||||
'-XX:-OmitStackTraceInFastThrow',
|
||||
'-Dfml.ignorePatchDiscrepancies=true',
|
||||
'-Dfml.ignoreInvalidMinecraftCertificates=true',
|
||||
`-Djava.library.path=${nativePath}`,
|
||||
`-Xmx${this.handler.getMemory()[0]}`,
|
||||
`-Xms${this.handler.getMemory()[1]}`
|
||||
]
|
||||
if (this.handler.getOS() === 'osx') {
|
||||
if (parseInt(versionFile.id.split('.')[1]) > 12) jvm.push(await this.handler.getJVM())
|
||||
} else jvm.push(await this.handler.getJVM())
|
||||
|
||||
if (this.options.customArgs) jvm = jvm.concat(this.options.customArgs)
|
||||
if (this.options.overrides.logj4ConfigurationFile) {
|
||||
jvm.push(`-Dlog4j.configurationFile=${path.resolve(this.options.overrides.logj4ConfigurationFile)}`)
|
||||
}
|
||||
// https://help.minecraft.net/hc/en-us/articles/4416199399693-Security-Vulnerability-in-Minecraft-Java-Edition
|
||||
if (parseInt(versionFile.id.split('.')[1]) === 18 && !parseInt(versionFile.id.split('.')[2])) jvm.push('-Dlog4j2.formatMsgNoLookups=true')
|
||||
if (parseInt(versionFile.id.split('.')[1]) === 17) jvm.push('-Dlog4j2.formatMsgNoLookups=true')
|
||||
if (parseInt(versionFile.id.split('.')[1]) < 17) {
|
||||
if (!jvm.find(arg => arg.includes('Dlog4j.configurationFile'))) {
|
||||
const configPath = path.resolve(this.options.overrides.cwd || this.options.root)
|
||||
const intVersion = parseInt(versionFile.id.split('.')[1])
|
||||
if (intVersion >= 12) {
|
||||
await this.handler.downloadAsync('https://launcher.mojang.com/v1/objects/02937d122c86ce73319ef9975b58896fc1b491d1/log4j2_112-116.xml',
|
||||
configPath, 'log4j2_112-116.xml', true, 'log4j')
|
||||
jvm.push('-Dlog4j.configurationFile=log4j2_112-116.xml')
|
||||
} else if (intVersion >= 7) {
|
||||
await this.handler.downloadAsync('https://launcher.mojang.com/v1/objects/dd2b723346a8dcd48e7f4d245f6bf09e98db9696/log4j2_17-111.xml',
|
||||
configPath, 'log4j2_17-111.xml', true, 'log4j')
|
||||
jvm.push('-Dlog4j.configurationFile=log4j2_17-111.xml')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const classes = this.options.overrides.classes || this.handler.cleanUp(await this.handler.getClasses(modifyJson))
|
||||
const classPaths = ['-cp']
|
||||
const separator = this.handler.getOS() === 'windows' ? ';' : ':'
|
||||
|
||||
this.emit('debug', `[MCLC]: Using ${separator} to separate class paths`)
|
||||
|
||||
// Handling launch arguments.
|
||||
const file = modifyJson || versionFile
|
||||
|
||||
// So mods like fabric work.
|
||||
const jar = fs.existsSync(mcPath)
|
||||
? `${separator}${mcPath}`
|
||||
: `${separator}${path.join(directory, `${this.options.version.number}.jar`)}`
|
||||
classPaths.push(`${this.options.forge ? this.options.forge + separator : ''}${classes.join(separator)}${jar}`)
|
||||
classPaths.push(file.mainClass)
|
||||
|
||||
this.emit('debug', '[MCLC]: Attempting to download assets')
|
||||
|
||||
if (typeof callbacks.init_assets === "function") {
|
||||
callbacks.init_assets()
|
||||
}
|
||||
|
||||
await this.handler.getAssets()
|
||||
|
||||
// Forge -> Custom -> Vanilla
|
||||
const launchOptions = await this.handler.getLaunchOptions(modifyJson)
|
||||
|
||||
const launchArguments = args.concat(jvm, classPaths, launchOptions)
|
||||
this.emit('arguments', launchArguments)
|
||||
this.emit('debug', `[MCLC]: Launching with arguments ${launchArguments.join(' ')}`)
|
||||
|
||||
return this.startMinecraft(launchArguments)
|
||||
} catch (e) {
|
||||
this.emit('debug', `[MCLC]: Failed to start due to ${e}, closing...`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
printVersion() {
|
||||
if (fs.existsSync(path.join(__dirname, '..', 'package.json'))) {
|
||||
const { version } = require('../package.json')
|
||||
this.emit('debug', `[MCLC]: MCLC version ${version}`)
|
||||
} else { this.emit('debug', '[MCLC]: Package JSON not found, skipping MCLC version check.') }
|
||||
}
|
||||
|
||||
createRootDirectory() {
|
||||
if (!fs.existsSync(this.options.root)) {
|
||||
this.emit('debug', '[MCLC]: Attempting to create root folder')
|
||||
fs.mkdirSync(this.options.root)
|
||||
}
|
||||
}
|
||||
|
||||
createGameDirectory() {
|
||||
if (this.options.overrides.gameDirectory) {
|
||||
this.options.overrides.gameDirectory = path.resolve(this.options.overrides.gameDirectory)
|
||||
if (!fs.existsSync(this.options.overrides.gameDirectory)) {
|
||||
fs.mkdirSync(this.options.overrides.gameDirectory, { recursive: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async extractPackage() {
|
||||
if (this.options.clientPackage) {
|
||||
this.emit('debug', `[MCLC]: Extracting client package to ${this.options.root}`)
|
||||
await this.handler.extractPackage()
|
||||
}
|
||||
}
|
||||
|
||||
async getModifyJson() {
|
||||
let modifyJson = null
|
||||
|
||||
if (this.options.forge) {
|
||||
this.options.forge = path.resolve(this.options.forge)
|
||||
this.emit('debug', '[MCLC]: Detected Forge in options, getting dependencies')
|
||||
modifyJson = await this.handler.getForgedWrapped()
|
||||
} else if (this.options.version.custom) {
|
||||
this.emit('debug', '[MCLC]: Detected custom in options, setting custom version file')
|
||||
modifyJson = modifyJson || JSON.parse(fs.readFileSync(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`), { encoding: 'utf8' }))
|
||||
}
|
||||
|
||||
return modifyJson
|
||||
}
|
||||
|
||||
startMinecraft(launchArguments) {
|
||||
const minecraft = child.spawn(this.options.javaPath ? this.options.javaPath : 'java', launchArguments,
|
||||
{ cwd: this.options.overrides.cwd || this.options.root, detached: this.options.overrides.detached })
|
||||
minecraft.stdout.on('data', (data) => this.emit('data', data.toString('utf-8')))
|
||||
minecraft.stderr.on('data', (data) => this.emit('data', data.toString('utf-8')))
|
||||
minecraft.on('close', (code) => this.emit('close', code))
|
||||
return minecraft
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user