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) ![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. 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. Basically a core for your Electron or script based launchers.
@ -13,57 +15,56 @@ https://discord.gg/8uYVbXP
### Standard Example ### Standard Example
```javascript ```javascript
const launcher = require('minecraft-launcher-core'); const { Client, Authenticator } = require('minecraft-launcher-core');
launcher.authenticator.getAuth("email", "password").then(auth => { let opts = {
// Save the auth to a file so it can be used later on! authorization: async () => { return await Authenticator.getAuth(username, password) },
launcher.core({
authorization: auth,
clientPackage: null, clientPackage: null,
forge: null, root: "./minecraft",
root: "C:/Users/user/AppData/Roaming/.mc",
os: "windows", os: "windows",
version: { version: {
number: "1.13.2", number: "1.14",
type: "release" type: "release"
}, },
memory: { memory: {
max: "3000", max: "6000",
min: "1000" 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 ### Usage
##### launcher.core Options ##### Client Options
| Parameter | Type | Description | Required | | Parameter | Type | Description | Required |
|--------------------------|--------|-------------------------------------------------------------------------------------------|----------| |--------------------------|----------|-------------------------------------------------------------------------------------------|----------|
| `options.authorization` | Object | The result from `getAuth` function, allows the client to login in online or offline mode. | True | | `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.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.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.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.version.number` | String | Minecraft version that is going to be launched. | 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.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.max` | String | Max amount of memory being used by Minectaft | True | | `options.forge.path` | String | Path to Universal Forge Jar. | False |
| `options.memory.min` | String | Min amount of memory being used by Minectaft | True | | `options.server.host` | String | Host url to the server, don't include the port. | False |
| `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.server.port` | String | Port of the host url, will default to `25565` if not entered. | 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.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.username` | String | Username for the proxy. | False |
| `options.proxy.password` | String | Password for the proxy. | False | | `options.proxy.password` | String | Password for the proxy. | False |
| `options.timeout` | Interger | Timeout on download requests. | False |
##### Note ##### 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 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. 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 ##### 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 | | `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 | | `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 #### Events
| Event Name | Type | Description | | 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` | String | Emitted when a file successfully downloads |
| `download-status` | Object | Emitted when data is received while downloading | | `download-status` | Object | Emitted when data is received while downloading |
| `debug` | String | Emitted when functions occur, made to help debug if errors occur | | `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? #### What should it look like running from console?

View file

@ -2,7 +2,6 @@ const request = require('request');
const uuid = require('uuid/v1'); const uuid = require('uuid/v1');
const api_url = "https://authserver.mojang.com"; const api_url = "https://authserver.mojang.com";
module.exports.getAuth = function (username, password) { module.exports.getAuth = function (username, password) {
return new Promise(resolve => { return new Promise(resolve => {
if(!password) { if(!password) {
@ -82,7 +81,6 @@ module.exports.refreshAuth = function (accessToken, clientToken, selectedProfile
request.post(requestObject, function(error, response, body) { request.post(requestObject, function(error, response, body) {
if (error) resolve(error); if (error) resolve(error);
console.log(body);
if(!body.selectedProfile) { if(!body.selectedProfile) {
throw new Error("Validation error: " + response.statusMessage); 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 request = require('request');
const checksum = require('checksum'); const checksum = require('checksum');
const zip = require('adm-zip'); 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 => { return new Promise(resolve => {
shelljs.mkdir('-p', directory); shelljs.mkdir('-p', directory);
const _request = request(url, {timeout: 10000}); const _request = request(url, {timeout: this.options.timeout || 10000});
_request.on('error', function(error) { _request.on('error', function(error) {
resolve({ resolve({
@ -27,7 +32,7 @@ function downloadAsync (url, directory, name) {
_request.on('data', (data) => { _request.on('data', (data) => {
let size = 0; let size = 0;
if(fs.existsSync(path.join(directory, name))) size = fs.statSync(path.join(directory, name))["size"]; 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, "name": name,
"current": Math.round(size / 10000), "current": Math.round(size / 10000),
"total": data.length "total": data.length
@ -37,13 +42,13 @@ function downloadAsync (url, directory, name) {
const file = fs.createWriteStream(path.join(directory, name)); const file = fs.createWriteStream(path.join(directory, name));
_request.pipe(file); _request.pipe(file);
file.once('finish', function() { file.once('finish', () => {
event.emit('download', name); this.client.emit('download', name);
resolve({failed: false, asset: null}); resolve({failed: false, asset: null});
}); });
file.on('error', (e) => { 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)); if(fs.existsSync(path.join(directory, name))) shelljs.rm(path.join(directory, name));
resolve({ resolve({
failed: true, failed: true,
@ -55,71 +60,73 @@ function downloadAsync (url, directory, name) {
}); });
}); });
}); });
} }
function checkSum(hash, file, size) { checkSum(hash, file) {
return new Promise(resolve => { return new Promise(resolve => {
checksum.file(file, (err, sum) => resolve(hash === sum)); checksum.file(file, (err, sum) => resolve(hash === sum));
}); });
} }
module.exports.getVersion = function (version, directory) { getVersion() {
return new Promise(resolve => { return new Promise(resolve => {
if(fs.existsSync(path.join(directory, `${version}.json`))) { if(fs.existsSync(path.join(this.options.directory, `${this.options.version}.json`))) {
resolve(require(path.join(directory, `${version}.json`))); this.version = require(path.join(this.options.directory, `${this.options.version}.json`));
resolve(this.version);
return; return;
} }
const manifest = "https://launchermeta.mojang.com/mc/game/version_manifest.json"; 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); if (error) resolve(error);
const parsed = JSON.parse(body); const parsed = JSON.parse(body);
for (const desiredVersion in parsed.versions) { for (const desiredVersion in parsed.versions) {
if(parsed.versions[desiredVersion].id === version) { if(parsed.versions[desiredVersion].id === this.options.version.number) {
request.get(parsed.versions[desiredVersion].url, function(error, response, body) { request.get(parsed.versions[desiredVersion].url, (error, response, body) => {
if (error) resolve(error); if (error) resolve(error);
event.emit('debug', `[MCLC]: Parsed version from version manifest`); this.client.emit('debug', `[MCLC]: Parsed version from version manifest`);
resolve(JSON.parse(body)); this.version = JSON.parse(body);
resolve(this.version);
}); });
} }
} }
}); });
}); });
}; }
module.exports.getJar = function (version, number, directory) { getJar() {
return new Promise(async (resolve)=> { 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(); resolve();
}); });
}; }
module.exports.getAssets = function (directory, version) { getAssets() {
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
const assetsUrl = 'https://resources.download.minecraft.net'; const assetsUrl = 'https://resources.download.minecraft.net';
const failed = []; const failed = [];
if(!fs.existsSync(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 downloadAsync(version.assetIndex.url, path.join(directory, 'assets', 'indexes'), `${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 => { await Promise.all(Object.keys(index.objects).map(async asset => {
const hash = index.objects[asset].hash; const hash = index.objects[asset].hash;
const subhash = hash.substring(0,2); 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))) { if(!fs.existsSync(path.join(assetDirectory, hash)) || !await this.checkSum(hash, path.join(assetDirectory, hash))) {
const download = await downloadAsync(`${assetsUrl}/${subhash}/${hash}`, assetDirectory, hash); const download = await this.downloadAsync(`${assetsUrl}/${subhash}/${hash}`, assetDirectory, hash);
if(download.failed) failed.push(download.asset); 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! // why do we have this? B/c sometimes Minecraft's resource site times out!
if(failed) { 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. // 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 => { await Promise.all(Object.keys(index.objects).map(async asset => {
const hash = index.objects[asset].hash; const hash = index.objects[asset].hash;
const subhash = hash.substring(0,2); 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('/'); let legacyAsset = asset.split('/');
legacyAsset.pop(); legacyAsset.pop();
if(!fs.existsSync(path.join(directory, 'assets', 'legacy', legacyAsset.join('/')))) { if(!fs.existsSync(path.join(this.options.directory, 'assets', 'legacy', legacyAsset.join('/')))) {
shelljs.mkdir('-p', path.join(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))) { if (!fs.existsSync(path.join(this.options.directory, 'assets', 'legacy', asset))) {
fs.copyFileSync(path.join(assetDirectory, hash), path.join(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(); resolve();
}); });
}; }
module.exports.getNatives = function (root, version, os) { getNatives() {
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
let nativeDirectory; let nativeDirectory;
if(fs.existsSync(path.join(root, 'natives', version.id))) { if(fs.existsSync(path.join(this.options.root, 'natives', this.version.id))) {
nativeDirectory = path.join(root, 'natives', version.id); nativeDirectory = path.join(this.options.root, 'natives', this.version.id);
} else { } else {
nativeDirectory = path.join(root, "natives", version.id); nativeDirectory = path.join(this.options.root, "natives", this.version.id);
shelljs.mkdir('-p', nativeDirectory); 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; if (!lib.downloads.classifiers) return;
const type = `natives-${os}`; const type = `natives-${this.options.os}`;
const native = lib.downloads.classifiers[type]; const native = lib.downloads.classifiers[type];
if (native) { if (native) {
const name = native.path.split('/').pop(); const name = native.path.split('/').pop();
await downloadAsync(native.url, nativeDirectory, name); await this.downloadAsync(native.url, nativeDirectory, name);
if(!await checkSum(native.sha1, path.join(nativeDirectory, name))) { if(!await this.checkSum(native.sha1, path.join(nativeDirectory, name))) {
await downloadAsync(native.url, nativeDirectory, name); await this.downloadAsync(native.url, nativeDirectory, name);
} }
try {new zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true);} catch(e) { 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. // 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)); 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); resolve(nativeDirectory);
}); });
}; }
module.exports.getForgeDependencies = async function(root, version, forgeJarPath) { async getForgeDependencies() {
if(!fs.existsSync(path.join(root, 'forge'))) { if(!fs.existsSync(path.join(root, 'forge'))) {
shelljs.mkdir('-p', 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 mavenUrl = 'http://files.minecraftforge.net/maven/';
const defaultRepo = 'https://libraries.minecraft.net/'; const defaultRepo = 'https://libraries.minecraft.net/';
const paths = []; const paths = [];
@ -230,63 +237,63 @@ module.exports.getForgeDependencies = async function(root, version, forgeJarPath
} }
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath); 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}`); paths.push(`${jarPath}${path.sep}${name}`);
})); }));
event.emit('debug', '[MCLC]: Downloaded Forge dependencies'); this.client.emit('debug', '[MCLC]: Downloaded Forge dependencies');
return {paths, forge}; return {paths, forge};
}; }
module.exports.getClasses = function (options, version) { getClasses() {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const libs = []; const libs = [];
if(options.version.custom) { if(this.options.version.custom) {
const customJarJson = require(path.join(options.root, 'versions', options.version.custom, `${options.version.custom}.json`)); 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 => { await Promise.all(customJarJson.libraries.map(async library => {
const lib = library.name.split(':'); 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`; const name = `${lib[1]}-${lib[2]}.jar`;
if(!fs.existsSync(path.join(jarPath, name))) { if(!fs.existsSync(path.join(jarPath, name))) {
if(library.url) { if(library.url) {
const url = `${library.url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${lib[1]}-${lib[2]}.jar`; 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}`); 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; if(!_lib.downloads.artifact) return;
const libraryPath = _lib.downloads.artifact.path; const libraryPath = _lib.downloads.artifact.path;
const libraryUrl = _lib.downloads.artifact.url; const libraryUrl = _lib.downloads.artifact.url;
const libraryHash = _lib.downloads.artifact.sha1; 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); let directory = libraryDirectory.split(path.sep);
const name = directory.pop(); const name = directory.pop();
directory = directory.join(path.sep); directory = directory.join(path.sep);
await downloadAsync(libraryUrl, directory, name); await this.downloadAsync(libraryUrl, directory, name);
} }
libs.push(libraryDirectory); libs.push(libraryDirectory);
})); }));
event.emit('debug', '[MCLC]: Collected class paths'); this.client.emit('debug', '[MCLC]: Collected class paths');
resolve(libs) resolve(libs)
}); });
}; }
module.exports.cleanUp = async function(array) { static cleanUp(array) {
const newArray = []; const newArray = [];
for(let argument in array) { for(let argument in array) {
@ -295,98 +302,68 @@ module.exports.cleanUp = async function(array) {
} }
return newArray; return newArray;
}; }
module.exports.getLaunchOptions = function (version, modification, options) { getLaunchOptions(modification) {
return new Promise(resolve => { return new Promise(resolve => {
let type = modification || version; let type = modification || this.version;
let arguments = type.minecraftArguments ? type.minecraftArguments.split(' ') : type.arguments.game; let args = 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'); 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 = { const fields = {
'${auth_access_token}': options.authorization.access_token, '${auth_access_token}': this.options.authorization.access_token,
'${auth_session}': options.authorization.access_token, '${auth_session}': this.options.authorization.access_token,
'${auth_player_name}': options.authorization.name, '${auth_player_name}': this.options.authorization.name,
'${auth_uuid}': options.authorization.uuid, '${auth_uuid}': this.options.authorization.uuid,
'${user_properties}': options.authorization.user_properties, '${user_properties}': this.options.authorization.user_properties,
'${user_type}': 'mojang', '${user_type}': 'mojang',
'${version_name}': options.version.number, '${version_name}': this.options.version.number,
'${assets_index_name}': version.assetIndex.id, '${assets_index_name}': this.version.assetIndex.id,
'${game_directory}': path.join(options.root), '${game_directory}': path.join(this.options.root),
'${assets_root}': assetPath, '${assets_root}': assetPath,
'${game_assets}': assetPath, '${game_assets}': assetPath,
'${version_type}': options.version.type '${version_type}': this.options.version.type
}; };
for (let index = 0; index < arguments.length; index++) { for (let index = 0; index < args.length; index++) {
if (Object.keys(fields).includes(arguments[index])) { if (Object.keys(fields).includes(args[index])) {
arguments[index] = fields[arguments[index]]; args[index] = fields[args[index]];
} }
} }
if(options.server) arguments.push('--server', options.server.host, '--port', options.server.port || "25565"); if(this.options.server) args.push('--server', this.options.server.host, '--port', this.options.server.port || "25565");
if(options.proxy) arguments.push( if(this.options.proxy) args.push(
'--proxyHost', '--proxyHost',
options.proxy.host, this.options.proxy.host,
'--proxyPort', '--proxyPort',
options.proxy.port || "8080", this.options.proxy.port || "8080",
'--proxyUser', '--proxyUser',
options.proxy.username, this.options.proxy.username,
'--proxyPass', '--proxyPass',
options.proxy.password this.options.proxy.password
); );
event.emit('debug', '[MCLC]: Set launch options'); this.client.emit('debug', '[MCLC]: Set launch options');
resolve(arguments); resolve(args);
}); });
}; }
module.exports.getJVM = function (version, options) { async getJVM() {
return new Promise(resolve => { switch(this.options.os) {
switch(options.os) {
case "windows": { case "windows": {
resolve("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"); return "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"
break;
} }
case "osx": { case "osx": {
resolve("-XstartOnFirstThread"); return "-XstartOnFirstThread"
break;
} }
case "linux": { case "linux": {
resolve("-Xss1M"); return "-Xss1M"
break;
} }
} }
});
};
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(); module.exports = Handler;
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();
});
};

View file

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

View file

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