2018-10-29 18:13:58 -04:00
|
|
|
const fs = require('fs');
|
|
|
|
const shelljs = require('shelljs');
|
|
|
|
const path = require('path');
|
|
|
|
const request = require('request');
|
2018-12-01 13:26:15 -05:00
|
|
|
const zip = require('adm-zip');
|
2019-01-13 20:31:17 -05:00
|
|
|
const event = require('./events');
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
|
|
|
|
function downloadAsync (url, directory, name) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
shelljs.mkdir('-p', directory);
|
|
|
|
|
2019-04-25 08:57:46 -04:00
|
|
|
const _request = request(url, {timeout: 10000});
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
_request.on('error', function(error) {
|
2019-04-26 18:04:32 -04:00
|
|
|
shelljs.rm(path.join(directory, name)); // Prevents duplicates.
|
2018-10-29 18:13:58 -04:00
|
|
|
resolve({
|
|
|
|
failed: true,
|
|
|
|
asset: {
|
|
|
|
url: url,
|
|
|
|
directory: directory,
|
|
|
|
name: name
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-01-28 17:26:04 -05:00
|
|
|
_request.on('data', (data) => {
|
2019-02-10 21:21:14 -05:00
|
|
|
let size = 0;
|
|
|
|
if(fs.existsSync(path.join(directory, name))) size = fs.statSync(path.join(directory, name))["size"];
|
2019-01-28 17:26:04 -05:00
|
|
|
event.emit('download-status', {
|
2019-02-10 21:21:14 -05:00
|
|
|
"name": name,
|
|
|
|
"current": Math.round(size / 10000),
|
2019-01-28 17:26:04 -05:00
|
|
|
"total": data.length
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2018-10-29 18:13:58 -04:00
|
|
|
const file = fs.createWriteStream(path.join(directory, name));
|
|
|
|
_request.pipe(file);
|
|
|
|
|
|
|
|
file.once('finish', function() {
|
2019-02-10 21:21:14 -05:00
|
|
|
event.emit('download', name);
|
2018-10-29 18:13:58 -04:00
|
|
|
resolve({failed: false, asset: null});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-12-01 13:26:15 -05:00
|
|
|
module.exports.getVersion = function (version, directory) {
|
2018-10-29 18:13:58 -04:00
|
|
|
return new Promise(resolve => {
|
2018-12-01 13:26:15 -05:00
|
|
|
if(fs.existsSync(path.join(directory, `${version}.json`))) resolve(require(path.join(directory, `${version}.json`)));
|
|
|
|
|
2018-10-29 18:13:58 -04:00
|
|
|
const manifest = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
|
|
|
request.get(manifest, function(error, response, body) {
|
|
|
|
if (error) resolve(error);
|
|
|
|
|
|
|
|
const parsed = JSON.parse(body);
|
|
|
|
|
2018-10-29 20:55:03 -04:00
|
|
|
for (const desiredVersion in parsed.versions) {
|
2018-10-29 18:13:58 -04:00
|
|
|
if(parsed.versions[desiredVersion].id === version) {
|
|
|
|
request.get(parsed.versions[desiredVersion].url, function(error, response, body) {
|
|
|
|
if (error) resolve(error);
|
|
|
|
|
|
|
|
resolve(JSON.parse(body));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.getJar = function (version, number, directory) {
|
|
|
|
return new Promise(async (resolve)=> {
|
|
|
|
await downloadAsync(version.downloads.client.url, directory, `${number}.jar`);
|
|
|
|
|
|
|
|
fs.writeFileSync(path.join(directory, `${number}.json`), JSON.stringify(version, null, 4));
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.getAssets = function (directory, version) {
|
|
|
|
return new Promise(async(resolve) => {
|
2018-10-29 20:55:03 -04:00
|
|
|
const assetsUrl = 'https://resources.download.minecraft.net';
|
2018-10-29 18:13:58 -04:00
|
|
|
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`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const index = require(path.join(directory, 'assets', 'indexes',`${version.assetIndex.id}.json`));
|
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
await Promise.all(Object.keys(index.objects).map(async asset => {
|
2018-10-29 18:13:58 -04:00
|
|
|
const hash = index.objects[asset].hash;
|
|
|
|
const subhash = hash.substring(0,2);
|
|
|
|
const assetDirectory = path.join(directory, 'assets', 'objects', subhash);
|
|
|
|
|
|
|
|
if(!fs.existsSync(path.join(assetDirectory, hash))) {
|
|
|
|
const download = await downloadAsync(`${assetsUrl}/${subhash}/${hash}`, assetDirectory, hash);
|
|
|
|
|
|
|
|
if(download.failed) failed.push(download.asset);
|
|
|
|
}
|
2019-04-26 18:04:32 -04:00
|
|
|
}));
|
2018-10-29 18:13:58 -04:00
|
|
|
|
2019-04-12 19:26:39 -04:00
|
|
|
// why do we have this? B/c sometimes Minecraft's resource site times out!
|
2018-10-29 18:13:58 -04:00
|
|
|
if(failed) {
|
|
|
|
for (const fail of failed) await downloadAsync(fail.url, fail.directory, fail.name);
|
|
|
|
}
|
|
|
|
|
2019-04-12 19:26:39 -04:00
|
|
|
// Copy assets to legacy if it's an older Minecarft version.
|
2019-03-26 22:45:35 -04:00
|
|
|
if(version.assets === "legacy" || version.assets === "pre-1.6") {
|
2019-04-26 18:04:32 -04:00
|
|
|
await Promise.all(Object.keys(index.objects).map(async asset => {
|
2019-03-26 17:20:36 -04:00
|
|
|
const hash = index.objects[asset].hash;
|
|
|
|
const subhash = hash.substring(0,2);
|
|
|
|
const assetDirectory = path.join(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('/')));
|
|
|
|
}
|
|
|
|
|
2019-03-26 22:45:35 -04:00
|
|
|
if (!fs.existsSync(path.join(directory, 'assets', 'legacy', asset))) {
|
2019-03-26 17:20:36 -04:00
|
|
|
fs.copyFileSync(path.join(assetDirectory, hash), path.join(directory, 'assets', 'legacy', asset))
|
|
|
|
}
|
2019-04-26 18:04:32 -04:00
|
|
|
}));
|
2019-03-26 17:20:36 -04:00
|
|
|
}
|
|
|
|
|
2018-10-29 18:13:58 -04:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.getNatives = function (root, version, os) {
|
|
|
|
return new Promise(async(resolve) => {
|
2018-12-01 13:26:15 -05:00
|
|
|
let nativeDirectory;
|
2018-10-29 18:13:58 -04:00
|
|
|
|
2018-12-01 13:26:15 -05:00
|
|
|
if(fs.existsSync(path.join(root, 'natives', version.id))) {
|
|
|
|
nativeDirectory = path.join(root, 'natives', version.id);
|
|
|
|
} else {
|
|
|
|
nativeDirectory = path.join(root, "natives", version.id);
|
2018-10-29 18:13:58 -04:00
|
|
|
|
2018-12-01 13:26:15 -05:00
|
|
|
shelljs.mkdir('-p', nativeDirectory);
|
2018-10-29 18:13:58 -04:00
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
await Promise.all(version.libraries.map(async function (lib) {
|
2018-12-01 13:26:15 -05:00
|
|
|
if (!lib.downloads.classifiers) return;
|
|
|
|
const type = `natives-${os}`;
|
|
|
|
const native = lib.downloads.classifiers[type];
|
2018-10-29 18:13:58 -04:00
|
|
|
|
2018-12-01 13:26:15 -05:00
|
|
|
if (native) {
|
|
|
|
const name = native.path.split('/').pop();
|
|
|
|
await downloadAsync(native.url, nativeDirectory, name);
|
2019-03-26 17:20:36 -04:00
|
|
|
try {new zip(path.join(nativeDirectory, name)).extractAllTo(nativeDirectory, true);} catch(e) {
|
|
|
|
// Only doing a console.warn since a stupid error happens. You can basically ignore this.
|
|
|
|
// if it says Invalid file name, just means two files were downloaded and both were deleted.
|
|
|
|
// All is well.
|
|
|
|
console.warn(e);
|
|
|
|
}
|
2018-10-29 18:13:58 -04:00
|
|
|
shelljs.rm(path.join(nativeDirectory, name));
|
2018-12-01 13:26:15 -05:00
|
|
|
}
|
2019-04-26 18:04:32 -04:00
|
|
|
}));
|
2018-12-01 13:26:15 -05:00
|
|
|
}
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
resolve(nativeDirectory);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-02-07 08:14:16 -05:00
|
|
|
module.exports.getForgeDependencies = async function(root, version, forgeJarPath) {
|
|
|
|
if(!fs.existsSync(path.join(root, 'forge'))) {
|
|
|
|
shelljs.mkdir('-p', path.join(root, 'forge'));
|
|
|
|
}
|
2019-04-05 21:15:12 -04:00
|
|
|
await new zip(forgeJarPath).extractEntryTo('version.json', path.join(root, 'forge', `${version.id}`), false, true);
|
2019-02-07 08:14:16 -05:00
|
|
|
|
2019-02-08 12:19:54 -05:00
|
|
|
const forge = require(path.join(root, 'forge', `${version.id}`, 'version.json'));
|
|
|
|
const mavenUrl = 'http://files.minecraftforge.net/maven/';
|
|
|
|
const defaultRepo = 'https://libraries.minecraft.net/';
|
2019-02-07 08:14:16 -05:00
|
|
|
const paths = [];
|
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
await Promise.all(forge.libraries.map(async library => {
|
2019-02-07 08:14:16 -05:00
|
|
|
const lib = library.name.split(':');
|
|
|
|
|
|
|
|
if(lib[0] === 'net.minecraftforge' && lib[1].includes('forge')) return;
|
|
|
|
|
2019-02-08 12:19:54 -05:00
|
|
|
let url = mavenUrl;
|
2019-02-07 08:14:16 -05:00
|
|
|
const jarPath = path.join(root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`);
|
|
|
|
const name = `${lib[1]}-${lib[2]}.jar`;
|
|
|
|
|
2019-02-08 12:19:54 -05:00
|
|
|
if(!library.url) {
|
|
|
|
if(library.serverreq || library.clientreq) {
|
|
|
|
url = defaultRepo;
|
|
|
|
} else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const downloadLink = `${url}${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}/${lib[1]}-${lib[2]}.jar`;
|
|
|
|
|
|
|
|
if(fs.existsSync(path.join(jarPath, name))) {
|
2019-04-26 18:04:32 -04:00
|
|
|
paths.push(`${jarPath}${path.sep}${name}`);
|
2019-02-08 12:19:54 -05:00
|
|
|
return;
|
|
|
|
}
|
2019-02-07 08:14:16 -05:00
|
|
|
if(!fs.existsSync(jarPath)) shelljs.mkdir('-p', jarPath);
|
|
|
|
|
2019-02-08 12:19:54 -05:00
|
|
|
await downloadAsync(downloadLink, jarPath, name);
|
2019-02-07 08:14:16 -05:00
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
paths.push(`${jarPath}${path.sep}${name}`);
|
|
|
|
}));
|
2019-02-07 08:14:16 -05:00
|
|
|
|
2019-02-08 12:19:54 -05:00
|
|
|
return {paths, forge};
|
2019-02-07 08:14:16 -05:00
|
|
|
};
|
|
|
|
|
2019-04-05 21:15:12 -04:00
|
|
|
module.exports.getClasses = function (options, version) {
|
2018-10-29 18:13:58 -04:00
|
|
|
return new Promise(async (resolve) => {
|
|
|
|
const libs = [];
|
|
|
|
|
2019-04-05 21:15:12 -04:00
|
|
|
if(options.version.custom) {
|
|
|
|
const customJarJson = require(path.join(options.root, 'versions', options.version.custom, `${options.version.custom}.json`));
|
|
|
|
customJarJson.libraries.map(library => {
|
|
|
|
const lib = library.name.split(':');
|
|
|
|
|
|
|
|
const jarPath = path.join(options.root, 'libraries', `${lib[0].replace(/\./g, '/')}/${lib[1]}/${lib[2]}`);
|
|
|
|
const name = `${lib[1]}-${lib[2]}.jar`;
|
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
libs.push(`${jarPath}/${name}`);
|
2019-04-05 21:15:12 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-26 18:04:32 -04:00
|
|
|
await Promise.all(version.libraries.map(async (_lib) => {
|
2018-10-29 18:13:58 -04:00
|
|
|
if(!_lib.downloads.artifact) return;
|
|
|
|
|
|
|
|
const libraryPath = _lib.downloads.artifact.path;
|
|
|
|
const libraryUrl = _lib.downloads.artifact.url;
|
2019-04-05 21:15:12 -04:00
|
|
|
const libraryDirectory = path.join(options.root, 'libraries', libraryPath);
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
if(!fs.existsSync(libraryDirectory)) {
|
2019-04-26 18:04:32 -04:00
|
|
|
let directory = libraryDirectory.split(path.sep);
|
2018-10-29 18:13:58 -04:00
|
|
|
const name = directory.pop();
|
2019-04-26 18:04:32 -04:00
|
|
|
directory = directory.join(path.sep);
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
await downloadAsync(libraryUrl, directory, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
libs.push(libraryDirectory);
|
2019-04-26 18:04:32 -04:00
|
|
|
}));
|
2018-10-29 18:13:58 -04:00
|
|
|
|
|
|
|
resolve(libs)
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-04-05 21:15:12 -04:00
|
|
|
module.exports.getLaunchOptions = function (version, modification, options) {
|
2018-10-29 18:13:58 -04:00
|
|
|
return new Promise(resolve => {
|
2019-04-05 21:15:12 -04:00
|
|
|
let type = modification || version;
|
|
|
|
|
2019-04-06 13:03:01 -04:00
|
|
|
let arguments = type.minecraftArguments ? type.minecraftArguments.split(' ') : type.arguments.game;
|
2019-03-26 22:45:35 -04:00
|
|
|
const assetPath = version.assets === "legacy" || version.assets === "pre-1.6" ? path.join(options.root, 'assets', 'legacy') : path.join(options.root, 'assets');
|
2019-03-10 20:18:49 -04:00
|
|
|
|
2019-04-26 13:03:39 -04:00
|
|
|
if(arguments.length < 5) arguments = arguments.concat(version.minecraftArguments ? version.minecraftArguments.split(' ') : version.arguments.game);
|
2019-04-06 13:03:01 -04:00
|
|
|
|
2018-10-29 18:13:58 -04:00
|
|
|
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,
|
|
|
|
'${user_type}': 'mojang',
|
|
|
|
'${version_name}': options.version.number,
|
|
|
|
'${assets_index_name}': version.assetIndex.id,
|
|
|
|
'${game_directory}': path.join(options.root),
|
2019-03-26 12:20:36 -04:00
|
|
|
'${assets_root}': assetPath,
|
2019-03-26 17:20:36 -04:00
|
|
|
'${game_assets}': assetPath,
|
2018-10-29 18:13:58 -04:00
|
|
|
'${version_type}': options.version.type
|
|
|
|
};
|
|
|
|
|
|
|
|
for (let index = 0; index < arguments.length; index++) {
|
|
|
|
if (Object.keys(fields).includes(arguments[index])) {
|
|
|
|
arguments[index] = fields[arguments[index]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-10 20:18:49 -04:00
|
|
|
if(options.server) arguments.push('--server', options.server.host, '--port', options.server.port || "25565");
|
|
|
|
if(options.proxy) arguments.push(
|
|
|
|
'--proxyHost',
|
|
|
|
options.proxy.host,
|
|
|
|
'--proxyPort',
|
|
|
|
options.proxy.port || "8080",
|
|
|
|
'--proxyUser',
|
|
|
|
options.proxy.username,
|
|
|
|
'--proxyPass',
|
|
|
|
options.proxy.password
|
|
|
|
);
|
|
|
|
|
2018-10-29 18:13:58 -04:00
|
|
|
resolve(arguments);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.getJVM = function (version, options) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
switch(options.os) {
|
|
|
|
case "windows": {
|
|
|
|
resolve("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "osx": {
|
|
|
|
resolve("-XstartOnFirstThread");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "linux": {
|
|
|
|
resolve("-Xss1M");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2018-12-01 13:26:15 -05:00
|
|
|
|
|
|
|
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) {
|
2019-01-13 20:31:17 -05:00
|
|
|
return new Promise(async resolve => {
|
|
|
|
if(clientPackage.startsWith('http')) {
|
2019-01-28 17:26:04 -05:00
|
|
|
await downloadAsync(clientPackage, root, "clientPackage.zip");
|
2019-01-13 20:31:17 -05:00
|
|
|
clientPackage = path.join(root, "clientPackage.zip")
|
|
|
|
}
|
2018-12-01 13:26:15 -05:00
|
|
|
new zip(clientPackage).extractAllTo(root, true);
|
2019-01-13 20:31:17 -05:00
|
|
|
event.emit('package-extract', true);
|
2018-12-01 13:26:15 -05:00
|
|
|
resolve();
|
|
|
|
});
|
2019-02-10 21:21:14 -05:00
|
|
|
};
|