ForgeWrapper support, ESLint added

This commit is contained in:
Pierce 2020-05-28 22:39:13 -04:00
parent 9e21e8189c
commit 99b7ff2367
9 changed files with 2446 additions and 767 deletions

21
.eslintrc.json Normal file
View file

@ -0,0 +1,21 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": [
"standard"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"camelcase": "off",
"no-template-curly-in-string": "off"
}
}

View file

@ -72,7 +72,7 @@ launcher.on('data', (e) => console.log(e));
| `options.version.custom` | String | The name of the folder, jar file, and version json in the version folder. | False |
| `options.memory.max` | String | Max amount of memory being used by Minecraft. | True |
| `options.memory.min` | String | Min amount of memory being used by Minecraft. | True |
| `options.forge` | String | Path to Universal Forge Jar. (Only for versions below 1.13+ | False |
| `options.forge` | String | Path to Forge Jar. (Versions below 1.13 should be the "universal" jar while versions above 1.13+ should be the "installer" jar)| False |
| `options.javaPath` | String | Path to the JRE executable file, will default to `java` if not entered. | False |
| `options.server.host` | String | Host url to the server, don't include the port. | False |
| `options.server.port` | String | Port of the host url, will default to `25565` if not entered. | False |
@ -118,16 +118,8 @@ let opts = {
##### Custom
If you are loading up a client outside of vanilla Minecraft or Forge (Optifine and for an example), you'll need to download the needed files yourself if you don't provide downloads url downloads like Forge and Fabric. If no version jar is specified, MCLC will default back to the normal MC jar so mods like Fabric work.
##### Installer
You'll need to provide the folder created in the versions if you're running the new forge like so
```json
{
"version": {
"number": "1.14.2",
"type": "release",
"custom": "1.14.2-forge-26.0.63"
},
"installer": "forge-1.14.2-26.0.63-installer.jar"
}
This runs an executable with specified launch arguments. Was used to support Forge 1.13 before ForgeWrapper.
```
#### Authenticator Functions

View file

@ -1,6 +1,6 @@
const request = require('request');
const uuid = require('uuid/v1');
let api_url = "https://authserver.mojang.com";
const request = require('request')
const uuid = require('uuid/v1')
let api_url = 'https://authserver.mojang.com'
module.exports.getAuth = function (username, password) {
return new Promise((resolve, reject) => {
@ -11,16 +11,16 @@ module.exports.getAuth = function (username, password) {
uuid: uuid(),
name: username,
user_properties: JSON.stringify({})
};
}
return resolve(user);
return resolve(user)
}
const requestObject = {
url: api_url + "/authenticate",
url: api_url + '/authenticate',
json: {
agent: {
name: "Minecraft",
name: 'Minecraft',
version: 1
},
username: username,
@ -28,12 +28,12 @@ module.exports.getAuth = function (username, password) {
clientToken: uuid(),
requestUser: true
}
};
}
request.post(requestObject, function (error, response, body) {
if (error) return reject(error);
if (error) return reject(error)
if (!body || !body.selectedProfile) {
return reject("Validation error: " + response.statusMessage);
return reject(new Error('Validation error: ' + response.statusMessage))
}
const userProfile = {
@ -43,48 +43,48 @@ module.exports.getAuth = function (username, password) {
name: body.selectedProfile.name,
selected_profile: body.selectedProfile,
user_properties: JSON.stringify(body.user.properties || {})
};
}
resolve(userProfile);
});
});
};
resolve(userProfile)
})
})
}
module.exports.validate = function (access_token, client_token) {
return new Promise((resolve, reject) => {
const requestObject = {
url: api_url + "/validate",
url: api_url + '/validate',
json: {
"accessToken": access_token,
"clientToken": client_token
accessToken: access_token,
clientToken: client_token
}
}
};
request.post(requestObject, async function (error, response, body) {
if (error) return reject(error);
if (error) return reject(error)
if (!body) resolve(true);
else reject(body);
});
});
};
if (!body) resolve(true)
else reject(body)
})
})
}
module.exports.refreshAuth = function (accessToken, clientToken, selectedProfile) {
return new Promise((resolve, reject) => {
const requestObject = {
url: api_url + "/refresh",
url: api_url + '/refresh',
json: {
"accessToken": accessToken,
"clientToken": clientToken,
"selectedProfile": selectedProfile,
"requestUser": true
accessToken: accessToken,
clientToken: clientToken,
selectedProfile: selectedProfile,
requestUser: true
}
}
};
request.post(requestObject, function (error, response, body) {
if (error) return reject(error);
if (error) return reject(error)
if (!body || !body.selectedProfile) {
return reject("Validation error: " + response.statusMessage);
return reject(new Error('Validation error: ' + response.statusMessage))
}
const userProfile = {
@ -93,51 +93,51 @@ module.exports.refreshAuth = function (accessToken, clientToken, selectedProfile
uuid: body.selectedProfile.id,
name: body.selectedProfile.name,
user_properties: JSON.stringify(body.user.properties || {})
};
}
resolve(userProfile);
});
});
};
resolve(userProfile)
})
})
}
module.exports.invalidate = function (accessToken, clientToken) {
return new Promise((resolve, reject) => {
const requestObject = {
url: api_url + "/invalidate",
url: api_url + '/invalidate',
json: {
"accessToken": accessToken,
"clientToken": clientToken
accessToken: accessToken,
clientToken: clientToken
}
}
};
request.post(requestObject, function (error, response, body) {
if (error) return reject(error);
if (error) return reject(error)
if (!body) resolve(true);
else reject(body);
});
});
};
if (!body) resolve(true)
else reject(body)
})
})
}
module.exports.signOut = function (username, password) {
return new Promise((resolve, reject) => {
const requestObject = {
url: api_url + "/signout",
url: api_url + '/signout',
json: {
"username": username,
"password": password
username: username,
password: password
}
}
};
request.post(requestObject, function (error, response, body) {
if (error) return reject(error);
if (error) return reject(error)
if (!body) resolve(true);
else reject(body);
});
});
};
module.exports.changeApiUrl = function(url) {
api_url = url;
if (!body) resolve(true)
else reject(body)
})
})
}
module.exports.changeApiUrl = function (url) {
api_url = url
}

BIN
components/fw.jar Normal file

Binary file not shown.

View file

@ -1,24 +1,24 @@
const fs = require('fs');
const shelljs = require('shelljs');
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;
const fs = require('fs')
const shelljs = require('shelljs')
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
class Handler {
constructor(client) {
this.client = client;
this.options = client.options;
this.version = undefined;
constructor (client) {
this.client = client
this.options = client.options
this.version = undefined
this.baseRequest = request.defaults({
pool: {maxSockets: this.options.overrides.maxSockets || 2},
pool: { maxSockets: this.options.overrides.maxSockets || 2 },
timeout: this.options.timeout || 10000
});
})
}
checkJava(java) {
checkJava (java) {
return new Promise(resolve => {
child.exec(`"${java}" -version`, (error, stdout, stderr) => {
if (error) {
@ -27,457 +27,459 @@ class Handler {
message: error
})
} else {
this.client.emit('debug', `[MCLC]: Using Java version ${stderr.match(/"(.*?)"/).pop()} ${stderr.includes('64-Bit') ? '64-bit': '32-Bit'}`);
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) {
downloadAsync (url, directory, name, retry, type) {
return new Promise(resolve => {
shelljs.mkdir('-p', directory);
shelljs.mkdir('-p', directory)
const _request = this.baseRequest(url);
const _request = this.baseRequest(url)
let received_bytes = 0;
let total_bytes = 0;
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...`);
resolve(false);
this.client.emit('debug', `[MCLC]: Failed to download ${url} due to: File not found...`)
resolve(false)
}
total_bytes = parseInt(data.headers['content-length']);
});
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();
});
` Retrying... ${retry}`)
if (retry) await this.downloadAsync(url, directory, name, false, type)
resolve()
})
_request.on('data', (data) => {
received_bytes += data.length;
receivedBytes += data.length
this.client.emit('download-status', {
"name": name,
"type": type,
"current": received_bytes,
"total": total_bytes
name: name,
type: type,
current: receivedBytes,
total: totalBytes
})
})
});
const file = fs.createWriteStream(path.join(directory, name));
_request.pipe(file);
const file = fs.createWriteStream(path.join(directory, name))
_request.pipe(file)
file.once('finish', () => {
this.client.emit('download', name);
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))) shelljs.rm(path.join(directory, name));
if (retry) await this.downloadAsync(url, directory, name, false, type);
resolve();
});
});
` Retrying... ${retry}`)
if (fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name))
if (retry) await this.downloadAsync(url, directory, name, false, type)
resolve()
})
})
}
checkSum(hash, file) {
return new Promise(resolve => {
checksum.file(file, (err, sum) => resolve(hash === sum));
});
checkSum (hash, file) {
return new Promise((resolve, reject) => {
checksum.file(file, (err, sum) => {
err
? reject(new Error(err))
: resolve(hash === sum)
})
})
}
getVersion() {
getVersion () {
return new Promise(resolve => {
const versionJsonPath = this.options.overrides.versionJson || path.join(this.options.directory, `${this.options.version.number}.json`);
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));
resolve(this.version);
return;
this.version = JSON.parse(fs.readFileSync(versionJsonPath))
resolve(this.version)
return
}
const manifest = `${this.options.overrides.url.meta}/mc/game/version_manifest.json`;
const manifest = `${this.options.overrides.url.meta}/mc/game/version_manifest.json`
request.get(manifest, (error, response, body) => {
if (error) resolve(error);
if (error) resolve(error)
const parsed = JSON.parse(body);
const parsed = JSON.parse(body)
for (const desiredVersion in parsed.versions) {
if (parsed.versions[desiredVersion].id === this.options.version.number) {
request.get(parsed.versions[desiredVersion].url, (error, response, body) => {
if (error) resolve(error);
if (error) resolve(error)
this.client.emit('debug', `[MCLC]: Parsed version from version manifest`);
this.version = JSON.parse(body);
resolve(this.version);
});
this.client.emit('debug', '[MCLC]: Parsed version from version manifest')
this.version = JSON.parse(body)
resolve(this.version)
})
}
}
});
});
})
})
}
getJar() {
return new Promise(async (resolve) => {
await this.downloadAsync(this.version.downloads.client.url, this.options.directory, `${this.options.version.number}.jar`, true, 'version-jar');
async getJar () {
await this.downloadAsync(this.version.downloads.client.url, this.options.directory, `${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));
fs.writeFileSync(path.join(this.options.directory, `${this.options.version.number}.json`), JSON.stringify(this.version, null, 4))
this.client.emit('debug', '[MCLC]: Downloaded version jar and wrote version json');
resolve();
});
return this.client.emit('debug', '[MCLC]: Downloaded version jar and wrote version json')
}
getAssets() {
return new Promise(async(resolve) => {
if(!fs.existsSync(path.join(this.options.root, 'assets', 'indexes', `${this.version.assetIndex.id}.json`))) {
async getAssets () {
if (!fs.existsSync(path.join(this.options.root, 'assets', 'indexes', `${this.version.assetIndex.id}.json`))) {
await this.downloadAsync(this.version.assetIndex.url, path.join(this.options.root, 'assets', 'indexes'),
`${this.version.assetIndex.id}.json`, true, 'asset-json');
`${this.version.assetIndex.id}.json`, true, 'asset-json')
}
const index = JSON.parse(fs.readFileSync(path.join(this.options.root, 'assets', 'indexes',`${this.version.assetIndex.id}.json`), { encoding: 'utf8' }));
const index = JSON.parse(fs.readFileSync(path.join(this.options.root, 'assets', 'indexes', `${this.version.assetIndex.id}.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 assetDirectory = this.options.overrides.assetRoot || path.join(this.options.root, 'assets');
const subAsset = path.join(assetDirectory, 'objects', subhash);
const hash = index.objects[asset].hash
const subhash = hash.substring(0, 2)
const assetDirectory = this.options.overrides.assetRoot || path.join(this.options.root, 'assets')
const subAsset = path.join(assetDirectory, 'objects', subhash)
if(!fs.existsSync(path.join(subAsset, hash)) || !await this.checkSum(hash, path.join(subAsset, hash))) {
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 = counter + 1;
true, 'assets')
counter = counter + 1
this.client.emit('progress', {
type: 'assets',
task: counter,
total: Object.keys(index.objects).length
})
}
}));
counter = 0;
}))
counter = 0
// Copy assets to legacy if it's an older Minecraft version.
if(this.version.assets === "legacy" || this.version.assets === "pre-1.6") {
const assetDirectory = this.options.overrides.assetRoot || path.join(this.options.root, 'assets');
this.client.emit('debug', `[MCLC]: Copying assets over to ${path.join(assetDirectory, 'legacy')}`);
if (this.version.assets === 'legacy' || this.version.assets === 'pre-1.6') {
const assetDirectory = this.options.overrides.assetRoot || path.join(this.options.root, 'assets')
this.client.emit('debug', `[MCLC]: Copying assets over to ${path.join(assetDirectory, 'legacy')}`)
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 hash = index.objects[asset].hash
const subhash = hash.substring(0, 2)
const subAsset = path.join(assetDirectory, 'objects', subhash)
let legacyAsset = asset.split('/');
legacyAsset.pop();
const legacyAsset = asset.split('/')
legacyAsset.pop()
if(!fs.existsSync(path.join(assetDirectory, 'legacy', legacyAsset.join('/')))) {
shelljs.mkdir('-p', path.join(assetDirectory, 'legacy', legacyAsset.join('/')));
if (!fs.existsSync(path.join(assetDirectory, 'legacy', legacyAsset.join('/')))) {
shelljs.mkdir('-p', path.join(assetDirectory, 'legacy', legacyAsset.join('/')))
}
if (!fs.existsSync(path.join(assetDirectory, 'legacy', asset))) {
fs.copyFileSync(path.join(subAsset, hash), path.join(assetDirectory, 'legacy', asset))
}
counter = counter + 1;
counter = counter + 1
this.client.emit('progress', {
type: 'assets-copy',
task: counter,
total: Object.keys(index.objects).length
})
}));
}))
}
counter = 0;
counter = 0
this.client.emit('debug', '[MCLC]: Downloaded assets');
resolve();
});
this.client.emit('debug', '[MCLC]: Downloaded assets')
}
parseRule(lib) {
if(lib.rules) {
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';
return this.getOS() === 'osx'
} else {
return true;
return true
}
} else {
if (lib.rules[0].action === 'allow' && lib.rules[0].os) return this.getOS() !== 'osx';
if (lib.rules[0].action === 'allow' && lib.rules[0].os) return this.getOS() !== 'osx'
}
} else {
return false
}
}
getNatives() {
return new Promise(async(resolve) => {
const nativeDirectory = this.options.overrides.natives || path.join(this.options.root, 'natives', this.version.id);
async getNatives () {
const nativeDirectory = this.options.overrides.natives || path.join(this.options.root, 'natives', this.version.id)
if(!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) {
shelljs.mkdir('-p', nativeDirectory);
if (!fs.existsSync(nativeDirectory) || !fs.readdirSync(nativeDirectory).length) {
shelljs.mkdir('-p', nativeDirectory)
const natives = () => {
return new Promise(async resolve => {
const natives = [];
const natives = async () => {
const natives = []
await Promise.all(this.version.libraries.map(async (lib) => {
if (!lib.downloads.classifiers) return;
if (this.parseRule(lib)) return;
if (!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()}`];
const native = this.getOS() === 'osx'
? lib.downloads.classifiers['natives-osx'] || lib.downloads.classifiers['natives-macos']
: lib.downloads.classifiers[`natives-${this.getOS()}`]
natives.push(native);
}));
resolve (natives);
})
};
const stat = await natives();
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) => {
// Edge case on some systems where native is undefined and throws an error, this should fix it.
if(!native) return;
const name = native.path.split('/').pop();
await this.downloadAsync(native.url, nativeDirectory, name, true, 'natives');
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');
await this.downloadAsync(native.url, nativeDirectory, name, true, 'natives')
}
try {
new zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true);
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);
console.warn(e)
}
shelljs.rm(path.join(nativeDirectory, name));
counter = counter + 1;
shelljs.rm(path.join(nativeDirectory, name))
counter = counter + 1
this.client.emit('progress', {
type: 'natives',
task: counter,
total: stat.length
})
}));
this.client.emit('debug', '[MCLC]: Downloaded and extracted natives');
}))
this.client.emit('debug', '[MCLC]: Downloaded and extracted natives')
}
counter = 0;
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`);
resolve(nativeDirectory);
});
counter = 0
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`)
return nativeDirectory
}
async getForgeDependenciesLegacy() {
if(!fs.existsSync(path.join(this.options.root, 'forge'))) {
shelljs.mkdir('-p', path.join(this.options.root, 'forge'));
// Not bothering to rewrite this.
async getForgeDependenciesLegacy () {
if (!fs.existsSync(path.join(this.options.root, 'forge'))) {
shelljs.mkdir('-p', path.join(this.options.root, 'forge'))
}
const zipFile = new Zip(this.options.forge)
if (zipFile.getEntry('install_profile.json')) {
this.client.emit('debug', '[MCLC]: Detected Forge installer, will treat as custom with ForgeWrapper')
return false
}
try {
await new zip(this.options.forge).extractEntryTo('version.json', path.join(this.options.root, 'forge', `${this.version.id}`), false, true);
} catch(e) {
this.client.emit('debug', `[MCLC]: Unable to extract version.json from the forge jar due to ${e}`);
return null;
await zipFile.extractEntryTo('version.json', path.join(this.options.root, 'forge', `${this.version.id}`), false, true)
} catch (e) {
this.client.emit('debug', `[MCLC]: Unable to extract version.json from the forge jar due to ${e}`)
return null
}
const forge = JSON.parse(fs.readFileSync(path.join(this.options.root, 'forge', `${this.version.id}`, 'version.json'), { encoding: 'utf8' }));
const paths = [];
const forge = JSON.parse(fs.readFileSync(path.join(this.options.root, 'forge', `${this.version.id}`, 'version.json'), { encoding: 'utf8' }))
const paths = []
this.client.emit('progress', {
type: 'forge',
task: 0,
total: forge.libraries.length
});
})
await Promise.all(forge.libraries.map(async library => {
const lib = library.name.split(':');
const lib = library.name.split(':')
if(lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) return;
if (lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) return
let url = this.options.overrides.url.mavenForge;
const jarPath = path.join(this.options.root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`);
const name = `${lib[1]}-${lib[2]}.jar`;
let url = this.options.overrides.url.mavenForge
const jarPath = path.join(this.options.root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`)
const name = `${lib[1]}-${lib[2]}.jar`
if(!library.url) {
if(library.serverreq || library.clientreq) {
url = this.options.overrides.url.defaultRepoForge;
if (!library.url) {
if (library.serverreq || library.clientreq) {
url = this.options.overrides.url.defaultRepoForge
} else {
return
}
}
const downloadLink = `${url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`;
const downloadLink = `${url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`
if(fs.existsSync(path.join(jarPath, name))) {
paths.push(`${jarPath}${path.sep}${name}`);
counter = counter + 1;
this.client.emit('progress', { type: 'forge', task: counter, total: forge.libraries.length});
return;
if (fs.existsSync(path.join(jarPath, name))) {
paths.push(`${jarPath}${path.sep}${name}`)
counter = counter + 1
this.client.emit('progress', { type: 'forge', task: counter, total: forge.libraries.length })
return
}
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath);
if (!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath)
const download = await this.downloadAsync(downloadLink, jarPath, name, true, 'forge');
if(!download) await this.downloadAsync(`${this.options.overrides.url.fallbackMaven}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`, jarPath, name, true, 'forge');
const download = await this.downloadAsync(downloadLink, jarPath, name, true, 'forge')
if (!download) await this.downloadAsync(`${this.options.overrides.url.fallbackMaven}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${name}`, jarPath, name, true, 'forge')
paths.push(`${jarPath}${path.sep}${name}`);
counter = counter + 1;
paths.push(`${jarPath}${path.sep}${name}`)
counter = counter + 1
this.client.emit('progress', {
type: 'forge',
task: counter,
total: forge.libraries.length
})
}));
}))
counter = 0;
this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies');
counter = 0
this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies')
return {paths, forge};
return { paths, forge }
}
runInstaller(path) {
getForgedWrapped () {
return new Promise(resolve => {
const installer = child.exec(path);
installer.on('close', (code) => resolve());
const launchArgs = `"${this.options.javaPath ? this.options.javaPath : 'java'}" -jar ${path.resolve(this.options.forgeWrapper.jar)}` +
` --installer=${this.options.forge} --instance=${this.options.root} ` +
`--saveTo=${path.join(this.options.root, 'libraries', 'io', 'github', 'zekerzhayard', 'ForgeWrapper', this.options.forgeWrapper.version)}`
const fw = child.exec(launchArgs)
fw.on('close', (e) => {
resolve(JSON.parse(fs.readFileSync(path.join(this.options.root, 'forge', this.version.id, 'version.json'), { encoding: 'utf8' })))
})
})
}
getClasses() {
return new Promise(async (resolve) => {
const libs = [];
if(this.options.version.custom) {
const customJarJson = JSON.parse(fs.readFileSync(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`), { encoding: 'utf8'}));
this.client.emit('progress', {
type: 'classes-custom',
task: 0,
total: customJarJson.libraries.length
});
await Promise.all(customJarJson.libraries.map(async library => {
const lib = library.name.split(':');
const jarPath = path.join(this.options.root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`);
const name = `${lib[1]}-${lib[2]}.jar`;
if(!fs.existsSync(path.join(jarPath, name))) {
if(library.url) {
const url = `${library.url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${lib[1]}-${lib[2]}.jar`;
await this.downloadAsync(url, jarPath, name, true, 'classes-custom');
}
}
counter = counter + 1;
this.client.emit('progress', {
type: 'classes-custom',
task: counter,
total: customJarJson.libraries.length
});
libs.push(`${jarPath}${path.sep}${name}`);
}));
counter = 0;
}
const parsedClasses = () => {
return new Promise(async resolve => {
const classes = [];
await Promise.all(this.version.libraries.map(async (_lib) => {
if(!_lib.downloads.artifact) return;
if(this.parseRule(_lib)) return;
classes.push(_lib);
}));
resolve(classes);
})
};
const parsed = await parsedClasses();
this.client.emit('progress', {
type: 'classes',
task: 0,
total: parsed.length
});
await Promise.all(parsed.map(async (_lib) => {
const libraryPath = _lib.downloads.artifact.path;
const libraryUrl = _lib.downloads.artifact.url;
const libraryHash = _lib.downloads.artifact.sha1;
const libraryDirectory = path.join(this.options.root, 'libraries', libraryPath);
if(!fs.existsSync(libraryDirectory) || !await this.checkSum(libraryHash, libraryDirectory)) {
let directory = libraryDirectory.split(path.sep);
const name = directory.pop();
directory = directory.join(path.sep);
await this.downloadAsync(libraryUrl, directory, name, true, 'classes');
}
counter = counter + 1;
this.client.emit('progress', {
type: 'classes',
task: counter,
total: parsed.length
});
libs.push(libraryDirectory);
}));
counter = 0;
this.client.emit('debug', '[MCLC]: Collected class paths');
resolve(libs)
});
}
static cleanUp(array) {
runInstaller (path) {
return new Promise(resolve => {
const newArray = [];
for(let classPath in array) {
if(newArray.includes(array[classPath])) continue;
newArray.push(array[classPath]);
}
resolve(newArray);
const installer = child.exec(path)
installer.on('close', (code) => resolve())
})
}
getLaunchOptions(modification) {
return new Promise(async resolve => {
let type = modification || this.version;
async downloadToDirectory (directory, libraries, eventName) {
const libs = []
let args = type.minecraftArguments ? type.minecraftArguments.split(' ') : type.arguments.game;
const assetRoot = this.options.overrides.assetRoot || path.join(this.options.root, 'assets');
const assetPath = this.version.assets === "legacy" || this.version.assets === "pre-1.6" ? path.join(assetRoot, 'legacy') : path.join(assetRoot);
await Promise.all(libraries.map(async library => {
if (!library) return
const lib = library.name.split(':')
const minArgs = this.options.overrides.minArgs || (this.version.assets === "legacy" || this.version.assets === "pre-1.6") ? 5 : 11;
if(args.length < minArgs) args = args.concat(this.version.minecraftArguments ? this.version.minecraftArguments.split(' ') : this.version.arguments.game);
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(this.options.root, directory, this.popString(library.downloads.artifact.path))
} else {
name = `${lib[1]}-${lib[2]}${lib[3] ? '-' + lib[3] : ''}.jar`
jarPath = path.join(this.options.root, directory, `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`)
}
this.options.authorization = await Promise.resolve(this.options.authorization);
if (!fs.existsSync(path.join(jarPath, name))) {
// Simple lib support, forgot which addon needed this but here you go, Mr special.
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)
}
}
counter = counter + 1
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 = []
if (classJson) {
if (classJson.mavenFiles) {
await this.downloadToDirectory('libraries', classJson.mavenFiles, 'classes-maven-custom')
}
libs = (await this.downloadToDirectory('libraries', classJson.libraries, 'classes-custom'))
}
const parsed = this.version.libraries.map(lib => {
if (lib.downloads.artifact && !this.parseRule(lib)) return lib
})
libs = libs.concat((await this.downloadToDirectory('libraries', parsed, 'classes')))
counter = 0
this.client.emit('debug', '[MCLC]: Collected class paths')
return libs
}
popString (path) {
const tempArray = path.split('/')
tempArray.pop()
return tempArray.join('/')
}
static cleanUp (array) {
return new Promise(resolve => {
const newArray = []
for (const classPath in array) {
if (newArray.includes(array[classPath])) continue
newArray.push(array[classPath])
}
resolve(newArray)
})
}
async getLaunchOptions (modification) {
const type = modification || this.version
let args = type.minecraftArguments
? type.minecraftArguments.split(' ')
: type.arguments.game
const assetRoot = this.options.overrides.assetRoot || path.join(this.options.root, 'assets')
const assetPath = this.version.assets === 'legacy' ||
this.version.assets === 'pre-1.6'
? path.join(assetRoot, 'legacy')
: path.join(assetRoot)
const minArgs = this.options.overrides.minArgs || (this.version.assets === 'legacy' || this.version.assets === 'pre-1.6') ? 5 : 11
if (args.length < minArgs) args = args.concat(this.version.minecraftArguments ? this.version.minecraftArguments.split(' ') : this.version.arguments.game)
this.options.authorization = await Promise.resolve(this.options.authorization)
const fields = {
'${auth_access_token}': this.options.authorization.access_token,
@ -492,66 +494,72 @@ class Handler {
'${assets_root}': assetPath,
'${game_assets}': assetPath,
'${version_type}': this.options.version.type
};
}
for (let index = 0; index < args.length; index++) {
if(typeof args[index] === 'object') args.splice(index, 2);
if (typeof args[index] === 'object') args.splice(index, 2)
if (Object.keys(fields).includes(args[index])) {
args[index] = fields[args[index]];
args[index] = fields[args[index]]
}
}
if(this.options.window) this.options.window.fullscreen ? args.push('--fullscreen') : args.push('--width', this.options.window.width, '--height', this.options.window.height);
if(this.options.server) args.push('--server', this.options.server.host, '--port', this.options.server.port || "25565");
if(this.options.proxy) args.push(
if (this.options.window) {
this.options.window.fullscreen
? args.push('--fullscreen')
: args.push('--width', this.options.window.width, '--height', this.options.window.height)
}
if (this.options.server) args.push('--server', this.options.server.host, '--port', this.options.server.port || '25565')
if (this.options.proxy) {
args.push(
'--proxyHost',
this.options.proxy.host,
'--proxyPort',
this.options.proxy.port || "8080",
this.options.proxy.port || '8080',
'--proxyUser',
this.options.proxy.username,
'--proxyPass',
this.options.proxy.password
);
if(this.options.customLaunchArgs) this.options.customLaunchArgs.forEach(customArg => args = args.concat(customArg.split(' ')))
this.client.emit('debug', '[MCLC]: Set launch options');
resolve(args);
});
)
}
if (this.options.customLaunchArgs) args = args.concat(this.options.customLaunchArgs)
this.client.emit('debug', '[MCLC]: Set launch options')
return args
}
async getJVM() {
async getJVM () {
const opts = {
"windows": "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump",
"osx": "-XstartOnFirstThread",
"linux": "-Xss1M"
};
windows: '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump',
osx: '-XstartOnFirstThread',
linux: '-Xss1M'
}
return opts[this.getOS()]
}
getOS() {
if(this.options.os) {
return this.options.os;
getOS () {
if (this.options.os) {
return this.options.os
} else {
switch(process.platform) {
case "win32": return "windows";
case "darwin": return "osx";
default: return "linux";
switch (process.platform) {
case 'win32': return 'windows'
case 'darwin': return 'osx'
default: return 'linux'
}
}
}
extractPackage(options = this.options) {
async extractPackage (options = this.options) {
return new Promise(async resolve => {
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")
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);
this.client.emit('package-extract', true);
if(options.removePackage) shelljs.rm(options.clientPackage);
resolve();
});
new Zip(options.clientPackage).extractAllTo(options.root, true)
this.client.emit('package-extract', true)
if (options.removePackage) shelljs.rm(options.clientPackage)
resolve()
})
}
}
module.exports = Handler;
module.exports = Handler

View file

@ -1,87 +1,91 @@
const child = require('child_process');
const path = require('path');
const handler = require('./handler');
const fs = require('fs');
const EventEmitter = require('events').EventEmitter;
const child = require('child_process')
const path = require('path')
const Handler = require('./handler')
const fs = require('fs')
const EventEmitter = require('events').EventEmitter
class MCLCore extends EventEmitter {
constructor() {
super();
}
async launch(options) {
this.options = options;
this.options.root = path.resolve(this.options.root);
async launch (options = this.options) {
this.options = options
this.options.root = path.resolve(this.options.root)
this.options.overrides = {
...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
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
}
};
this.handler = new handler(this);
// Lets the events register. our magic switch!
await void(0);
if(fs.existsSync(path.join(__dirname,'..', 'package.json'))) {
this.emit('debug', `[MCLC]: MCLC version ${JSON.parse(fs.readFileSync(path.join(__dirname,'..', 'package.json'), { encoding: 'utf8' })).version}`);
} else { this.emit('debug', `[MCLC]: Package JSON not found, skipping MCLC version check.`); }
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;
}
// ForgeWrapper fork that is maintained on a side repo (https://github.com/Pierce01/ForgeWrapper)
this.options.forgeWrapper = {
jar: path.join(__dirname, 'fw.jar'),
version: '1.4.1-mclc'
}
if(!fs.existsSync(this.options.root)) {
this.emit('debug', '[MCLC]: Attempting to create root folder');
fs.mkdirSync(this.options.root);
this.handler = new Handler(this)
if (fs.existsSync(path.join(__dirname, '..', 'package.json'))) {
this.emit('debug', `[MCLC]: MCLC version ${JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), { encoding: 'utf8' })).version}`)
} else { this.emit('debug', '[MCLC]: Package JSON not found, skipping MCLC version check.') }
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
}
if(this.options.clientPackage) {
this.emit('debug', `[MCLC]: Extracting client package to ${this.options.root}`);
await this.handler.extractPackage();
if (!fs.existsSync(this.options.root)) {
this.emit('debug', '[MCLC]: Attempting to create root folder')
fs.mkdirSync(this.options.root)
}
if(this.options.installer) {
if (this.options.clientPackage) {
this.emit('debug', `[MCLC]: Extracting client package to ${this.options.root}`)
await this.handler.extractPackage()
}
if (this.options.installer) {
// So the forge installer can run without breaking :)
const profilePath = path.join(this.options.root, 'launcher_profiles.json');
if(!fs.existsSync(profilePath))
fs.writeFileSync(profilePath, JSON.stringify({}, null, 4));
const profilePath = path.join(this.options.root, 'launcher_profiles.json')
if (!fs.existsSync(profilePath)) { fs.writeFileSync(profilePath, JSON.stringify({}, null, 4)) }
await this.handler.runInstaller(this.options.installer)
}
const directory = this.options.overrides.directory || path.join(this.options.root, 'versions', this.options.version.number);
this.options.directory = directory;
const directory = this.options.overrides.directory || path.join(this.options.root, 'versions', this.options.version.number)
this.options.directory = directory
// Version JSON for the main launcher folder
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`));
const nativePath = await this.handler.getNatives();
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`))
const nativePath = await this.handler.getNatives()
if (!fs.existsSync(mcPath)) {
this.emit('debug', '[MCLC]: Attempting to download Minecraft version jar');
await this.handler.getJar();
this.emit('debug', '[MCLC]: Attempting to download Minecraft version jar')
await this.handler.getJar()
}
let forge = null;
let custom = null;
if(this.options.forge) {
this.emit('debug', '[MCLC]: Detected Forge in options, getting dependencies');
forge = await this.handler.getForgeDependenciesLegacy();
let forge = null
let custom = null
if (this.options.forge) {
this.options.forge = path.resolve(this.options.forge)
this.emit('debug', '[MCLC]: Detected Forge in options, getting dependencies')
forge = await this.handler.getForgeDependenciesLegacy()
if (forge === false) custom = await this.handler.getForgedWrapped()
}
if(this.options.version.custom) {
this.emit('debug', '[MCLC]: Detected custom in options, setting custom version file');
custom = JSON.parse(fs.readFileSync(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`), { encoding: 'utf8' }));
if (this.options.version.custom || custom) {
this.emit('debug', '[MCLC]: Detected custom in options, setting custom version file')
custom = custom || JSON.parse(fs.readFileSync(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`), { encoding: 'utf8' }))
}
const args = [];
const args = []
// Jvm
let jvm = [
@ -92,49 +96,51 @@ class MCLCore extends EventEmitter {
`-Djava.library.path=${nativePath}`,
`-Xmx${this.options.memory.max}M`,
`-Xms${this.options.memory.min}M`
];
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.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.customArgs) jvm = jvm.concat(this.options.customArgs)
const classes = this.options.overrides.classes || await handler.cleanUp(await this.handler.getClasses());
let classPaths = ['-cp'];
const separator = this.handler.getOS() === "windows" ? ";" : ":";
this.emit('debug', `[MCLC]: Using ${separator} to separate class paths`);
if(forge) {
this.emit('debug', '[MCLC]: Setting Forge class paths');
classPaths.push(`${path.resolve(this.options.forge)}${separator}${forge.paths.join(separator)}${separator}${classes.join(separator)}${separator}${mcPath}`);
const classes = this.options.overrides.classes || await Handler.cleanUp(await this.handler.getClasses(custom))
const classPaths = ['-cp']
const separator = this.handler.getOS() === 'windows' ? ';' : ':'
this.emit('debug', `[MCLC]: Using ${separator} to separate class paths`)
if (forge) {
this.emit('debug', '[MCLC]: Setting Forge class paths')
classPaths.push(`${path.resolve(this.options.forge)}${separator}${forge.paths.join(separator)}${separator}${classes.join(separator)}${separator}${mcPath}`)
classPaths.push(forge.forge.mainClass)
} else {
const file = custom || versionFile;
const file = custom || versionFile
// So mods like fabric work.
const jar = fs.existsSync(mcPath) ? `${separator}${mcPath}` : `${separator}${path.join(directory, `${this.options.version.number}.jar`)}`;
classPaths.push(`${classes.join(separator)}${jar}`);
classPaths.push(file.mainClass);
const jar = fs.existsSync(mcPath)
? `${separator}${mcPath}`
: `${separator}${path.join(directory, `${this.options.version.number}.jar`)}`
classPaths.push(`${classes.join(separator)}${jar}`)
classPaths.push(file.mainClass)
}
// Download version's assets
this.emit('debug', '[MCLC]: Attempting to download assets');
await this.handler.getAssets();
this.emit('debug', '[MCLC]: Attempting to download assets')
await this.handler.getAssets()
// Launch options. Thank you Lyrus for the reformat <3
const modification = forge ? forge.forge : null || custom ? custom : null;
const launchOptions = await this.handler.getLaunchOptions(modification);
const modification = forge ? forge.forge : null || custom ? custom : null
const launchOptions = await this.handler.getLaunchOptions(modification)
const launchArguments = args.concat(jvm, classPaths, launchOptions);
this.emit('arguments', launchArguments);
this.emit('debug', `[MCLC]: Launching with arguments ${launchArguments.join(' ')}`);
const launchArguments = args.concat(jvm, classPaths, launchOptions)
this.emit('arguments', launchArguments)
this.emit('debug', `[MCLC]: Launching with arguments ${launchArguments.join(' ')}`)
const minecraft = child.spawn(this.options.javaPath ? this.options.javaPath : 'java', launchArguments,
{cwd: this.options.overrides.cwd || this.options.root});
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));
{ cwd: this.options.overrides.cwd || this.options.root })
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;
return minecraft
}
}
module.exports = MCLCore;
module.exports = MCLCore

View file

@ -1,4 +1,4 @@
module.exports = {
Client: require('./components/launcher'),
Authenticator: require('./components/authenticator'),
};
Authenticator: require('./components/authenticator')
}

1647
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "minecraft-launcher-core",
"version": "3.13.3",
"version": "3.14.0",
"description": "Lightweight module that downloads and runs Minecraft using javascript / NodeJS",
"main": "index.js",
"dependencies": {
@ -10,9 +10,16 @@
"shelljs": "^0.8.2",
"uuid": "^3.3.2"
},
"devDependencies": {},
"devDependencies": {
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "eslint ."
},
"repository": {
"type": "git",