BREAKING CHANGES: v3

Version 3 merge
This commit is contained in:
Pierce 2019-05-22 20:31:33 -04:00 committed by GitHub
commit e5afd60ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 507 additions and 514 deletions

2
.travis.yml Normal file
View file

@ -0,0 +1,2 @@
language: node_js
script: echo "npm test temporarily disabled"

127
README.md
View file

@ -1,4 +1,6 @@
![logo](https://pierce.is-serious.business/44U1xXh.png)
##### This project is near complete.
[![Build Status](https://travis-ci.com/Pierce01/MinecraftLauncher-core.svg?branch=master)](https://travis-ci.com/Pierce01/MinecraftLauncher-core)
MCLC is a NodeJS solution for launching modded and vanilla Minecraft without having to download and format everything yourself.
Basically a core for your Electron or script based launchers.
@ -13,57 +15,56 @@ https://discord.gg/8uYVbXP
### Standard Example
```javascript
const launcher = require('minecraft-launcher-core');
const { Client, Authenticator } = require('minecraft-launcher-core');
launcher.authenticator.getAuth("email", "password").then(auth => {
// Save the auth to a file so it can be used later on!
launcher.core({
authorization: auth,
let opts = {
authorization: async () => { return await Authenticator.getAuth(username, password) },
clientPackage: null,
forge: null,
root: "C:/Users/user/AppData/Roaming/.mc",
root: "./minecraft",
os: "windows",
version: {
number: "1.13.2",
number: "1.14",
type: "release"
},
memory: {
max: "3000",
min: "1000"
max: "6000",
min: "4000"
}
});
});
}
const launcher = new Client(opts);
launcher.launch();
launcher.on('debug', (e) => console.log(e));
launcher.on('data', (e) => console.log(e));
launcher.on('error', (e) => console.log(e));
```
### Usage
##### launcher.core Options
##### Client Options
| Parameter | Type | Description | Required |
|--------------------------|--------|-------------------------------------------------------------------------------------------|----------|
|--------------------------|----------|-------------------------------------------------------------------------------------------|----------|
| `options.authorization` | Object | The result from `getAuth` function, allows the client to login in online or offline mode. | True |
| `options.clientPackage` | String | Path to the client package zip file. | False |
| `options.root` | String | Path where you want the launcher to work in. like `C:/Users/user/AppData/Roaming/.mc` | True |
| `options.os` | String | windows, osx or linux | True |
| `options.javaPath` | String | Path to the JRE executable file, will default to `java` if not entered. | False |
| `options.root` | String | Path where you want the launcher to work in. like `C:/Users/user/AppData/Roaming/.mc`, | True |
| `options.os` | String | windows, osx or linux, | True |
| `options.version.number` | String | Minecraft version that is going to be launched. | True |
| `options.version.type` | String | Any string. The actual Minecraft launcher uses `release` and `snapshot`. | True |
| `options.version.custom` | String | Name of the jar, json, and folder of the custom client you are launching with. (Optifine) | False |
| `options.memory.max` | String | Max amount of memory being used by Minectaft | True |
| `options.memory.min` | String | Min amount of memory being used by Minectaft | True |
| `options.forge` | String | Path to Universal Forge Jar | False |
| `options.customArgs` | String | Array of custom JVM options | False |
| `options.server.host` | String | Host url to the server, don't include the port | False |
| `options.memory.max` | String | Max amount of memory being used by Minectaft. | True |
| `options.forge.path` | String | Path to Universal Forge Jar. | 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 |
| `options.proxy.host` | String | Host url to the proxy, don't include the port | False |
| `options.proxy.host` | String | Host url to the proxy, don't include the port. | False |
| `options.proxy.port` | String | Port of the host proxy, will default to `8080` if not entered. | False |
| `options.proxy.username` | String | Username for the proxy. | False |
| `options.proxy.password` | String | Password for the proxy. | False |
| `options.timeout` | Interger | Timeout on download requests. | False |
##### Note
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. Still need to provide the version jar.
#### launcher.authenticator Functions
#### Authenticator Functions
##### getAuth
@ -86,6 +87,20 @@ if you don't provide downloads url downloads like Forge and Fabric. Still need t
| `client_token` | String | Token being checked if it's the same client that the access_token was created from. | True |
| `selected_profile` | Object | Json Object that was returned from Mojangs auth api. | True |
##### invalidate
| Parameter | Type | Description | Required |
|--------------|--------|-------------------------------------------------------------------|----------|
| `access_token` | String | Token being checked if it can be used to login with (online mode). | True |
| `client_token` | String | Token being checked if it's the same client that the access_token was created from. | True |
##### signOut
| Parameter | Type | Description | Required |
|--------------|--------|--------------------------------------|----------|
| `username` | String | Username used to login with | True |
| `password` | String | Password used to login with | True |
#### Events
| Event Name | Type | Description |
@ -98,66 +113,6 @@ if you don't provide downloads url downloads like Forge and Fabric. Still need t
| `download` | String | Emitted when a file successfully downloads |
| `download-status` | Object | Emitted when data is received while downloading |
| `debug` | String | Emitted when functions occur, made to help debug if errors occur |
#### Client Package Function
Client Packages allow the client to run offline on setup. This function should be used outside the actual launcher.
this function is in the `handler` component.
##### makePackage
| Parameter | Type | Description | Required |
|------------|--------|-----------------------------------------------------------------------|----------|
| `versions` | Array | Array of the versions being downloaded and being made into a package. | True |
| `os` | String | OS that the package will be loaded on. OS specific natives need this. | True |
### Other Examples
##### Using Validate and Refresh
```javascript
let auth = require("pathToUserAuthJson.json");
const validateCheck = await launcher.authenticator.validate(auth.access_token);
if(!validateCheck) {
auth = await launcher.authenticator.refreshAuth(auth.access_token, auth.client_token, auth.selected_profile);
}
launcher.core({
authorization: auth,
clientPackage: null,
root: "directory",
os: "windows",
version: {
number: "1.13.2",
type: "MCC-Launcher"
},
memory: {
max: "500",
min: "100"
}
});
```
##### Using With Forge
```js
launcher.authenticator.getAuth("email", "password").then(auth => {
launcher.core({
authorization: auth,
clientPackage: null,
root: "C:/Users/user/AppData/Roaming/.mc",
forge: "C:/Users/user/Desktop/forge.jar",
os: "windows",
version: {
number: "1.12.2", // needs to be the same as the Forge version
type: "MCC-Launcher"
},
memory: {
max: "500",
min: "100"
}
});
});
```
#### What should it look like running from console?

View file

@ -2,7 +2,6 @@ const request = require('request');
const uuid = require('uuid/v1');
const api_url = "https://authserver.mojang.com";
module.exports.getAuth = function (username, password) {
return new Promise(resolve => {
if(!password) {
@ -82,7 +81,6 @@ module.exports.refreshAuth = function (accessToken, clientToken, selectedProfile
request.post(requestObject, function(error, response, body) {
if (error) resolve(error);
console.log(body);
if(!body.selectedProfile) {
throw new Error("Validation error: " + response.statusMessage);
}
@ -99,3 +97,39 @@ module.exports.refreshAuth = function (accessToken, clientToken, selectedProfile
});
});
};
module.exports.invalidate = function(accessToken, clientToken) {
return new Promise(resolve => {
const requestObject = {
url: api_url + "/invalidate",
json: {
"accessToken": accessToken,
"clientToken": clientToken
}
};
request.post(requestObject, function(error, response, body) {
if (error) resolve(error);
if(!body) resolve(true); else resolve(false);
});
});
};
module.exports.signOut = function(username, password) {
return new Promise(resolve => {
const requestObject = {
url: api_url + "/invalidate",
json: {
"username": username,
"password": password
}
};
request.post(requestObject, function(error, response, body) {
if (error) resolve(error);
if(!body) resolve(true); else resolve(false);
});
});
};

View file

@ -4,14 +4,19 @@ const path = require('path');
const request = require('request');
const checksum = require('checksum');
const zip = require('adm-zip');
const event = require('./events');
class Handler {
constructor(client) {
this.client = client;
this.options = client.options;
this.version = undefined;
}
function downloadAsync (url, directory, name) {
downloadAsync(url, directory, name) {
return new Promise(resolve => {
shelljs.mkdir('-p', directory);
const _request = request(url, {timeout: 10000});
const _request = request(url, {timeout: this.options.timeout || 10000});
_request.on('error', function(error) {
resolve({
@ -27,7 +32,7 @@ function downloadAsync (url, directory, name) {
_request.on('data', (data) => {
let size = 0;
if(fs.existsSync(path.join(directory, name))) size = fs.statSync(path.join(directory, name))["size"];
event.emit('download-status', {
this.client.emit('download-status', {
"name": name,
"current": Math.round(size / 10000),
"total": data.length
@ -37,13 +42,13 @@ function downloadAsync (url, directory, name) {
const file = fs.createWriteStream(path.join(directory, name));
_request.pipe(file);
file.once('finish', function() {
event.emit('download', name);
file.once('finish', () => {
this.client.emit('download', name);
resolve({failed: false, asset: null});
});
file.on('error', (e) => {
event.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}`);
this.client.emit('debug', `[MCLC]: Failed to download asset to ${path.join(directory, name)} due to\n${e}`);
if(fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name));
resolve({
failed: true,
@ -57,69 +62,71 @@ function downloadAsync (url, directory, name) {
});
}
function checkSum(hash, file, size) {
checkSum(hash, file) {
return new Promise(resolve => {
checksum.file(file, (err, sum) => resolve(hash === sum));
});
}
module.exports.getVersion = function (version, directory) {
getVersion() {
return new Promise(resolve => {
if(fs.existsSync(path.join(directory, `${version}.json`))) {
resolve(require(path.join(directory, `${version}.json`)));
if(fs.existsSync(path.join(this.options.directory, `${this.options.version}.json`))) {
this.version = require(path.join(this.options.directory, `${this.options.version}.json`));
resolve(this.version);
return;
}
const manifest = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
request.get(manifest, function(error, response, body) {
request.get(manifest, (error, response, body) => {
if (error) resolve(error);
const parsed = JSON.parse(body);
for (const desiredVersion in parsed.versions) {
if(parsed.versions[desiredVersion].id === version) {
request.get(parsed.versions[desiredVersion].url, function(error, response, body) {
if(parsed.versions[desiredVersion].id === this.options.version.number) {
request.get(parsed.versions[desiredVersion].url, (error, response, body) => {
if (error) resolve(error);
event.emit('debug', `[MCLC]: Parsed version from version manifest`);
resolve(JSON.parse(body));
this.client.emit('debug', `[MCLC]: Parsed version from version manifest`);
this.version = JSON.parse(body);
resolve(this.version);
});
}
}
});
});
};
}
module.exports.getJar = function (version, number, directory) {
getJar() {
return new Promise(async (resolve)=> {
await downloadAsync(version.downloads.client.url, directory, `${number}.jar`);
await this.downloadAsync(this.version.downloads.client.url, directory, `${this.options.version.number}.jar`);
fs.writeFileSync(path.join(directory, `${number}.json`), JSON.stringify(version, null, 4));
fs.writeFileSync(path.join(directory, `${this.options.version.number}.json`), JSON.stringify(this.options.version, null, 4));
event.emit('debug', '[MCLC]: Downloaded version jar and wrote version json');
this.client.emit('debug', '[MCLC]: Downloaded version jar and wrote version json');
resolve();
});
};
}
module.exports.getAssets = function (directory, version) {
getAssets() {
return new Promise(async(resolve) => {
const assetsUrl = 'https://resources.download.minecraft.net';
const failed = [];
if(!fs.existsSync(path.join(directory, 'assets', 'indexes', `${version.assetIndex.id}.json`))) {
await downloadAsync(version.assetIndex.url, path.join(directory, 'assets', 'indexes'), `${version.assetIndex.id}.json`);
if(!fs.existsSync(path.join(this.options.directory, 'assets', 'indexes', `${this.version.assetIndex.id}.json`))) {
await this.downloadAsync(this.version.assetIndex.url, path.join(this.options.directory, 'assets', 'indexes'), `${this.version.assetIndex.id}.json`);
}
const index = require(path.join(directory, 'assets', 'indexes',`${version.assetIndex.id}.json`));
const index = require(path.join(this.options.directory, 'assets', 'indexes',`${this.version.assetIndex.id}.json`));
await Promise.all(Object.keys(index.objects).map(async asset => {
const hash = index.objects[asset].hash;
const subhash = hash.substring(0,2);
const assetDirectory = path.join(directory, 'assets', 'objects', subhash);
const assetDirectory = path.join(this.options.directory, 'assets', 'objects', subhash);
if(!fs.existsSync(path.join(assetDirectory, hash)) || !await checkSum(hash, path.join(assetDirectory, hash))) {
const download = await downloadAsync(`${assetsUrl}/${subhash}/${hash}`, assetDirectory, hash);
if(!fs.existsSync(path.join(assetDirectory, hash)) || !await this.checkSum(hash, path.join(assetDirectory, hash))) {
const download = await this.downloadAsync(`${assetsUrl}/${subhash}/${hash}`, assetDirectory, hash);
if(download.failed) failed.push(download.asset);
}
@ -127,55 +134,55 @@ module.exports.getAssets = function (directory, version) {
// why do we have this? B/c sometimes Minecraft's resource site times out!
if(failed) {
await Promise.all(failed.map(async asset => await downloadAsync(asset.url, asset.directory, asset.name)))
await Promise.all(failed.map(async asset => await this.downloadAsync(asset.url, asset.directory, asset.name)))
}
// Copy assets to legacy if it's an older Minecarft version.
if(version.assets === "legacy" || version.assets === "pre-1.6") {
if(this.version.assets === "legacy" || this.version.assets === "pre-1.6") {
await Promise.all(Object.keys(index.objects).map(async asset => {
const hash = index.objects[asset].hash;
const subhash = hash.substring(0,2);
const assetDirectory = path.join(directory, 'assets', 'objects', subhash);
const assetDirectory = path.join(this.options.directory, 'assets', 'objects', subhash);
let legacyAsset = asset.split('/');
legacyAsset.pop();
if(!fs.existsSync(path.join(directory, 'assets', 'legacy', legacyAsset.join('/')))) {
shelljs.mkdir('-p', path.join(directory, 'assets', 'legacy', legacyAsset.join('/')));
if(!fs.existsSync(path.join(this.options.directory, 'assets', 'legacy', legacyAsset.join('/')))) {
shelljs.mkdir('-p', path.join(this.options.directory, 'assets', 'legacy', legacyAsset.join('/')));
}
if (!fs.existsSync(path.join(directory, 'assets', 'legacy', asset))) {
fs.copyFileSync(path.join(assetDirectory, hash), path.join(directory, 'assets', 'legacy', asset))
if (!fs.existsSync(path.join(this.options.directory, 'assets', 'legacy', asset))) {
fs.copyFileSync(path.join(assetDirectory, hash), path.join(this.options.directory, 'assets', 'legacy', asset))
}
}));
}
event.emit('debug', '[MCLC]: Downloaded assets');
this.client.emit('debug', '[MCLC]: Downloaded assets');
resolve();
});
};
}
module.exports.getNatives = function (root, version, os) {
getNatives() {
return new Promise(async(resolve) => {
let nativeDirectory;
if(fs.existsSync(path.join(root, 'natives', version.id))) {
nativeDirectory = path.join(root, 'natives', version.id);
if(fs.existsSync(path.join(this.options.root, 'natives', this.version.id))) {
nativeDirectory = path.join(this.options.root, 'natives', this.version.id);
} else {
nativeDirectory = path.join(root, "natives", version.id);
nativeDirectory = path.join(this.options.root, "natives", this.version.id);
shelljs.mkdir('-p', nativeDirectory);
await Promise.all(version.libraries.map(async function (lib) {
await Promise.all(version.libraries.map(async (lib) => {
if (!lib.downloads.classifiers) return;
const type = `natives-${os}`;
const type = `natives-${this.options.os}`;
const native = lib.downloads.classifiers[type];
if (native) {
const name = native.path.split('/').pop();
await downloadAsync(native.url, nativeDirectory, name);
if(!await checkSum(native.sha1, path.join(nativeDirectory, name))) {
await downloadAsync(native.url, nativeDirectory, name);
await this.downloadAsync(native.url, nativeDirectory, name);
if(!await this.checkSum(native.sha1, path.join(nativeDirectory, name))) {
await this.downloadAsync(native.url, nativeDirectory, name);
}
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.
@ -186,21 +193,21 @@ module.exports.getNatives = function (root, version, os) {
shelljs.rm(path.join(nativeDirectory, name));
}
}));
event.emit('debug', '[MCLC]: Downloaded and extracted natives');
this.client.emit('debug', '[MCLC]: Downloaded and extracted natives');
}
event.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`);
this.client.emit('debug', `[MCLC]: Set native path to ${nativeDirectory}`);
resolve(nativeDirectory);
});
};
}
module.exports.getForgeDependencies = async function(root, version, forgeJarPath) {
async getForgeDependencies() {
if(!fs.existsSync(path.join(root, 'forge'))) {
shelljs.mkdir('-p', path.join(root, 'forge'));
}
await new zip(forgeJarPath).extractEntryTo('version.json', path.join(root, 'forge', `${version.id}`), false, true);
await new zip(this.options.forge).extractEntryTo('version.json', path.join(this.options.root, 'forge', `${this.version.id}`), false, true);
const forge = require(path.join(root, 'forge', `${version.id}`, 'version.json'));
const forge = require(path.join(this.options.root, 'forge', `${this.version.id}`, 'version.json'));
const mavenUrl = 'http://files.minecraftforge.net/maven/';
const defaultRepo = 'https://libraries.minecraft.net/';
const paths = [];
@ -230,63 +237,63 @@ module.exports.getForgeDependencies = async function(root, version, forgeJarPath
}
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath);
await downloadAsync(downloadLink, jarPath, name);
await this.downloadAsync(downloadLink, jarPath, name);
paths.push(`${jarPath}${path.sep}${name}`);
}));
event.emit('debug', '[MCLC]: Downloaded Forge dependencies');
this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies');
return {paths, forge};
};
}
module.exports.getClasses = function (options, version) {
getClasses() {
return new Promise(async (resolve) => {
const libs = [];
if(options.version.custom) {
const customJarJson = require(path.join(options.root, 'versions', options.version.custom, `${options.version.custom}.json`));
if(this.options.version.custom) {
const customJarJson = require(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`));
await Promise.all(customJarJson.libraries.map(async library => {
const lib = library.name.split(':');
const jarPath = path.join(options.root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`);
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 downloadAsync(url, jarPath, name);
await this.downloadAsync(url, jarPath, name);
}
}
libs.push(`${jarPath}/${name}`);
}));
}
await Promise.all(version.libraries.map(async (_lib) => {
await Promise.all(this.version.libraries.map(async (_lib) => {
if(!_lib.downloads.artifact) return;
const libraryPath = _lib.downloads.artifact.path;
const libraryUrl = _lib.downloads.artifact.url;
const libraryHash = _lib.downloads.artifact.sha1;
const libraryDirectory = path.join(options.root, 'libraries', libraryPath);
const libraryDirectory = path.join(this.options.root, 'libraries', libraryPath);
if(!fs.existsSync(libraryDirectory) || !await checkSum(libraryHash, libraryDirectory)) {
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 downloadAsync(libraryUrl, directory, name);
await this.downloadAsync(libraryUrl, directory, name);
}
libs.push(libraryDirectory);
}));
event.emit('debug', '[MCLC]: Collected class paths');
this.client.emit('debug', '[MCLC]: Collected class paths');
resolve(libs)
});
};
}
module.exports.cleanUp = async function(array) {
static cleanUp(array) {
const newArray = [];
for(let argument in array) {
@ -295,98 +302,68 @@ module.exports.cleanUp = async function(array) {
}
return newArray;
};
}
module.exports.getLaunchOptions = function (version, modification, options) {
getLaunchOptions(modification) {
return new Promise(resolve => {
let type = modification || version;
let type = modification || this.version;
let arguments = type.minecraftArguments ? type.minecraftArguments.split(' ') : type.arguments.game;
const assetPath = version.assets === "legacy" || version.assets === "pre-1.6" ? path.join(options.root, 'assets', 'legacy') : path.join(options.root, 'assets');
let args = type.minecraftArguments ? type.minecraftArguments.split(' ') : type.arguments.game;
const assetPath = this.version.assets === "legacy" || this.version.assets === "pre-1.6" ? path.join(this.options.root, 'assets', 'legacy') : path.join(this.options.root, 'assets');
if(arguments.length < 5) arguments = arguments.concat(version.minecraftArguments ? version.minecraftArguments.split(' ') : version.arguments.game);
if(args.length < 5) args = args.concat(this.version.minecraftArguments ? this.version.minecraftArguments.split(' ') : this.version.arguments.game);
const fields = {
'${auth_access_token}': options.authorization.access_token,
'${auth_session}': options.authorization.access_token,
'${auth_player_name}': options.authorization.name,
'${auth_uuid}': options.authorization.uuid,
'${user_properties}': options.authorization.user_properties,
'${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,
'${user_properties}': this.options.authorization.user_properties,
'${user_type}': 'mojang',
'${version_name}': options.version.number,
'${assets_index_name}': version.assetIndex.id,
'${game_directory}': path.join(options.root),
'${version_name}': this.options.version.number,
'${assets_index_name}': this.version.assetIndex.id,
'${game_directory}': path.join(this.options.root),
'${assets_root}': assetPath,
'${game_assets}': assetPath,
'${version_type}': options.version.type
'${version_type}': this.options.version.type
};
for (let index = 0; index < arguments.length; index++) {
if (Object.keys(fields).includes(arguments[index])) {
arguments[index] = fields[arguments[index]];
for (let index = 0; index < args.length; index++) {
if (Object.keys(fields).includes(args[index])) {
args[index] = fields[args[index]];
}
}
if(options.server) arguments.push('--server', options.server.host, '--port', options.server.port || "25565");
if(options.proxy) arguments.push(
if(this.options.server) args.push('--server', this.options.server.host, '--port', this.options.server.port || "25565");
if(this.options.proxy) args.push(
'--proxyHost',
options.proxy.host,
this.options.proxy.host,
'--proxyPort',
options.proxy.port || "8080",
this.options.proxy.port || "8080",
'--proxyUser',
options.proxy.username,
this.options.proxy.username,
'--proxyPass',
options.proxy.password
this.options.proxy.password
);
event.emit('debug', '[MCLC]: Set launch options');
resolve(arguments);
this.client.emit('debug', '[MCLC]: Set launch options');
resolve(args);
});
};
}
module.exports.getJVM = function (version, options) {
return new Promise(resolve => {
switch(options.os) {
async getJVM() {
switch(this.options.os) {
case "windows": {
resolve("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump");
break;
return "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"
}
case "osx": {
resolve("-XstartOnFirstThread");
break;
return "-XstartOnFirstThread"
}
case "linux": {
resolve("-Xss1M");
break;
return "-Xss1M"
}
}
}
});
};
module.exports.makePackage = async function(versions, os) {
const directory = path.join(process.cwd(), 'clientpackage');
for(const version in versions) {
const versionFile = await this.getVersion(versions[version], directory);
await this.getNatives(`${directory}/natives/${versions[version]}`, versionFile, os, true);
await this.getJar(versionFile, versions[version], `${directory}/versions/${versions[version]}`);
await this.getClasses(directory, versionFile);
await this.getAssets(directory, versionFile);
}
const archive = new zip();
archive.addLocalFolder(directory);
archive.writeZip(`${directory}.zip`);
};
module.exports.extractPackage = function(root, clientPackage) {
return new Promise(async resolve => {
if(clientPackage.startsWith('http')) {
await downloadAsync(clientPackage, root, "clientPackage.zip");
clientPackage = path.join(root, "clientPackage.zip")
}
new zip(clientPackage).extractAllTo(root, true);
event.emit('package-extract', true);
resolve();
});
};
module.exports = Handler;

View file

@ -2,43 +2,53 @@ const child = require('child_process');
const event = require('./events');
const path = require('path');
const handler = require('./handler');
const packager = require('./package');
const fs = require('fs');
const EventEmitter = require('events').EventEmitter;
class MCLCore extends EventEmitter {
constructor(options) {
super();
module.exports = async function (options) {
options.root = path.resolve(options.root);
if(!fs.existsSync(options.root)) {
event.emit('debug', '[MCLC]: Attempting to create root folder');
fs.mkdirSync(options.root);
this.options = options;
this.handler = new handler(this);
}
if(options.clientPackage) {
event.emit('debug', `[MCLC]: Extracting client package to ${options.root}`);
await handler.extractPackage(options.root, options.clientPackage);
async launch() {
this.options.root = path.resolve(this.options.root);
if(!fs.existsSync(this.options.root)) {
this.emit('debug', '[MCLC]: Attempting to create root folder');
fs.mkdirSync(this.options.root);
}
const directory = path.join(options.root, 'versions', options.version.number);
options.directory = directory;
const versionFile = await handler.getVersion(options.version.number, options.directory);
const mcPath = options.version.custom ? path.join(options.root, 'versions', options.version.custom , `${options.version.custom}.jar`):
path.join(directory, `${options.version.number}.jar`);
const nativePath = await handler.getNatives(options.root, versionFile, options.os);
if(this.options.clientPackage) {
this.emit('debug', `[MCLC]: Extracting client package to ${this.options.root}`);
await packager.extractPackage(this.options.root, this.options.clientPackage);
}
const 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.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)) {
event.emit('debug', '[MCLC]: Attempting to download Minecraft version jar');
await handler.getJar(versionFile, options.version.number, directory);
this.emit('debug', '[MCLC]: Attempting to download Minecraft version jar');
await this.handler.getJar();
}
let forge = null;
let custom = null;
if(options.forge) {
if(options.forge.path) process.emitWarning('\'options.forge.path\' is deprecated and will be removed. Use \'options.forge\' instead');
event.emit('debug', '[MCLC]: Detected Forge in options, getting dependencies');
forge = await handler.getForgeDependencies(options.root, versionFile, options.forge.path || options.forge);
if(this.options.forge) {
this.emit('debug', '[MCLC]: Detected Forge in options, getting dependencies');
forge = await this.handler.getForgeDependencies();
}
if(options.version.custom) {
event.emit('debug', '[MCLC]: Detected custom in options, setting custom version file');
custom = require(path.join(options.root, 'versions', options.version.custom, `${options.version.custom}.json`));
if(this.options.version.custom) {
this.emit('debug', '[MCLC]: Detected custom in options, setting custom version file');
custom = require(path.join(this.options.root, 'versions', this.options.version.custom, `${this.options.version.custom}.json`));
}
const args = [];
@ -50,19 +60,19 @@ module.exports = async function (options) {
'-Dfml.ignorePatchDiscrepancies=true',
'-Dfml.ignoreInvalidMinecraftCertificates=true',
`-Djava.library.path=${nativePath}`,
`-Xmx${options.memory.max}M`,
`-Xms${options.memory.min}M`
`-Xmx${this.options.memory.max}M`,
`-Xms${this.options.memory.min}M`
];
jvm.push(await handler.getJVM(versionFile, options));
if(options.customArgs) jvm = jvm.concat(options.customArgs);
jvm.push(await this.handler.getJVM());
if(this.options.customArgs) jvm = jvm.concat(this.options.customArgs);
const classes = await handler.getClasses(options, versionFile);
const classes = await this.handler.getClasses();
let classPaths = ['-cp'];
const separator = options.os === "windows" ? ";" : ":";
event.emit('debug', `[MCLC]: Using ${separator} to separate class paths`);
const separator = this.options.os === "windows" ? ";" : ":";
this.emit('debug', `[MCLC]: Using ${separator} to separate class paths`);
if(forge) {
event.emit('debug', '[MCLC]: Setting Forge class paths');
classPaths.push(`${options.forge.path || options.forge}${separator}${forge.paths.join(separator)}${separator}${classes.join(separator)}${separator}${mcPath}`);
this.emit('debug', '[MCLC]: Setting Forge class paths');
classPaths.push(`${this.options.forge.path || this.options.forge}${separator}${forge.paths.join(separator)}${separator}${classes.join(separator)}${separator}${mcPath}`);
classPaths.push(forge.forge.mainClass)
} else {
const file = custom || versionFile;
@ -72,19 +82,22 @@ module.exports = async function (options) {
classPaths = await handler.cleanUp(classPaths);
// Download version's assets
event.emit('debug', '[MCLC]: Attempting to download assets');
await handler.getAssets(options.root, versionFile);
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 handler.getLaunchOptions(versionFile, modification, options);
const launchOptions = await this.handler.getLaunchOptions(modification);
const launchArguments = args.concat(jvm, classPaths, launchOptions);
event.emit('arguments', launchArguments);
event.emit('debug', launchArguments.join(' '));
const minecraft = child.spawn(options.javaPath ? options.javaPath : 'java', launchArguments);
minecraft.stdout.on('data', (data) => event.emit('data', data));
minecraft.stderr.on('data', (data) => event.emit('error', data));
minecraft.on('close', (code) => event.emit('close', code));
};
const minecraft = child.spawn(this.options.javaPath ? this.options.javaPath : 'java', launchArguments);
minecraft.stdout.on('data', (data) => this.emit('data', data));
minecraft.stderr.on('data', (data) => this.emit('error', data));
minecraft.on('close', (code) => this.emit('close', code));
}
}
module.exports = MCLCore;

14
components/package.js Normal file
View file

@ -0,0 +1,14 @@
const path = require('path');
const zip = require('adm-zip');
module.exports.extractPackage = function(root, clientPackage) {
return new Promise(async resolve => {
if(clientPackage.startsWith('http')) {
await downloadAsync(clientPackage, root, "clientPackage.zip");
clientPackage = path.join(root, "clientPackage.zip")
}
new zip(clientPackage).extractAllTo(root, true);
this.client.emit('package-extract', true);
resolve();
});
};

View file

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

View file

@ -1,6 +1,6 @@
{
"name": "minecraft-launcher-core",
"version": "2.8.1",
"version": "3.0.0",
"description": "Module that downloads Minecraft assets and runs Minecraft. Also Supports Forge",
"main": "index.js",
"dependencies": {