pimi-launcher-core/components/handler.js

1080 lines
32 KiB
JavaScript
Raw Normal View History

2023-06-18 15:07:15 +03:00
const fs = require("fs");
const path = require("path");
const request = require("request");
const checksum = require("checksum");
const Zip = require("adm-zip");
const child = require("child_process");
let counter = 0;
2018-10-30 01:13:58 +03:00
2019-05-23 01:28:48 +03:00
class Handler {
2023-06-18 15:07:15 +03:00
constructor(client) {
this.client = client;
this.options = client.options;
2020-05-29 05:39:13 +03:00
this.baseRequest = request.defaults({
pool: { maxSockets: this.options.overrides.maxSockets || 2 },
2023-06-18 15:07:15 +03:00
timeout: this.options.timeout || 10000,
});
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
checkJava(java) {
return new Promise((resolve) => {
2020-05-29 05:39:13 +03:00
child.exec(`"${java}" -version`, (error, stdout, stderr) => {
if (error) {
resolve({
run: false,
2023-06-18 15:07:15 +03:00
message: error,
});
2020-05-29 05:39:13 +03:00
} else {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
`[PiMi]: Using Java version ${stderr.match(/"(.*?)"/).pop()} ${
stderr.includes("64-Bit") ? "64-bit" : "32-Bit"
}`
);
2020-05-29 05:39:13 +03:00
resolve({
2023-06-18 15:07:15 +03:00
run: true,
});
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
});
});
2020-05-29 05:39:13 +03:00
}
2018-10-30 01:13:58 +03:00
2023-06-18 15:07:15 +03:00
downloadAsync(url, directory, name, retry, type) {
return new Promise((resolve) => {
fs.mkdirSync(directory, { recursive: true });
2023-06-18 15:07:15 +03:00
const _request = this.baseRequest(url);
2018-10-30 01:13:58 +03:00
2023-06-18 15:07:15 +03:00
let receivedBytes = 0;
let totalBytes = 0;
2023-06-18 15:07:15 +03:00
_request.on("response", (data) => {
2020-05-29 05:39:13 +03:00
if (data.statusCode === 404) {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
`[PiMi]: Failed to download ${url} due to: File not found...`
);
return resolve(false);
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
totalBytes = parseInt(data.headers["content-length"]);
});
_request.on("error", async (error) => {
this.client.emit(
"debug",
`[PiMi]: Failed to download asset to ${path.join(
directory,
name
)} due to\n${error}.` + ` Retrying... ${retry}`
);
if (retry) await this.downloadAsync(url, directory, name, false, type);
resolve();
});
_request.on("data", (data) => {
receivedBytes += data.length;
this.client.emit("download-status", {
2020-05-29 05:39:13 +03:00
name: name,
type: type,
current: receivedBytes,
2023-06-18 15:07:15 +03:00
total: totalBytes,
});
});
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
const file = fs.createWriteStream(path.join(directory, name));
_request.pipe(file);
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
file.once("finish", () => {
this.client.emit("download", name);
2020-05-29 05:39:13 +03:00
resolve({
failed: false,
2023-06-18 15:07:15 +03:00
asset: null,
});
});
file.on("error", async (e) => {
this.client.emit(
"debug",
`[PiMi]: Failed to download asset to ${path.join(
directory,
name
)} due to\n${e}.` + ` Retrying... ${retry}`
);
if (fs.existsSync(path.join(directory, name)))
fs.unlinkSync(path.join(directory, name));
if (retry) await this.downloadAsync(url, directory, name, false, type);
resolve();
});
});
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
checkSum(hash, file) {
2020-05-29 05:39:13 +03:00
return new Promise((resolve, reject) => {
checksum.file(file, (err, sum) => {
2020-06-19 19:26:58 +03:00
if (err) {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
`[PiMi]: Failed to check file hash due to ${err}`
);
resolve(false);
2020-06-19 19:26:58 +03:00
} else {
2023-06-18 15:07:15 +03:00
resolve(hash === sum);
2020-06-19 19:26:58 +03:00
}
2023-06-18 15:07:15 +03:00
});
});
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
getVersion() {
return new Promise((resolve) => {
const versionJsonPath =
this.options.overrides.versionJson ||
path.join(
this.options.directory,
`${this.options.version.number}.json`
);
2020-05-29 05:39:13 +03:00
if (fs.existsSync(versionJsonPath)) {
2023-06-18 15:07:15 +03:00
this.version = JSON.parse(fs.readFileSync(versionJsonPath));
return resolve(this.version);
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
const manifest = `${this.options.overrides.url.meta}/mc/game/version_manifest.json`;
const cache = this.options.cache
? `${this.options.cache}/json`
: `${this.options.root}/cache/json`;
2020-05-29 05:39:13 +03:00
request.get(manifest, (error, response, body) => {
2023-06-18 15:07:15 +03:00
if (error && error.code !== "ENOTFOUND") return resolve(error);
if (!error) {
if (!fs.existsSync(cache)) {
2023-06-18 15:07:15 +03:00
fs.mkdirSync(cache, { recursive: true });
this.client.emit("debug", "[PiMi]: Cache directory created.");
}
2023-06-18 15:07:15 +03:00
fs.writeFile(
path.join(`${cache}/version_manifest.json`),
body,
(err) => {
if (err) return resolve(err);
this.client.emit("debug", "[PiMi]: Cached version_manifest.json");
}
);
}
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
let parsed;
if (error && error.code === "ENOTFOUND") {
parsed = JSON.parse(
fs.readFileSync(`${cache}/version_manifest.json`)
);
} else {
2023-06-18 15:07:15 +03:00
parsed = JSON.parse(body);
}
2020-05-29 05:39:13 +03:00
for (const desiredVersion in parsed.versions) {
2023-06-18 15:07:15 +03:00
if (
parsed.versions[desiredVersion].id === this.options.version.number
) {
request.get(
parsed.versions[desiredVersion].url,
(error, response, body) => {
if (error && error.code !== "ENOTFOUND") return resolve(error);
if (!error) {
fs.writeFile(
path.join(`${cache}/${this.options.version.number}.json`),
body,
(err) => {
if (err) return resolve(err);
this.client.emit(
"debug",
`[PiMi]: Cached ${this.options.version.number}.json`
);
}
);
}
this.client.emit(
"debug",
"[PiMi]: Parsed version from version manifest"
);
if (error && error.code === "ENOTFOUND") {
this.version = JSON.parse(
fs.readFileSync(
`${cache}/${this.options.version.number}.json`
)
);
} else {
this.version = JSON.parse(body);
}
return resolve(this.version);
}
2023-06-18 15:07:15 +03:00
);
2020-05-29 05:39:13 +03:00
}
}
2023-06-18 15:07:15 +03:00
});
});
2020-05-29 05:39:13 +03:00
}
2018-10-30 01:13:58 +03:00
2023-06-18 15:07:15 +03:00
async getJar() {
await this.downloadAsync(
this.version.downloads.client.url,
this.options.directory,
`${
this.options.version.custom
? this.options.version.custom
: this.options.version.number
}.jar`,
true,
"version-jar"
);
fs.writeFileSync(
path.join(this.options.directory, `${this.options.version.number}.json`),
JSON.stringify(this.version, null, 4)
);
return this.client.emit(
"debug",
"[PiMi]: Downloaded version jar and wrote version json"
);
2020-05-29 05:39:13 +03:00
}
2018-10-30 01:13:58 +03:00
2023-06-18 15:07:15 +03:00
async getAssets() {
const assetDirectory = path.resolve(
this.options.overrides.assetRoot || path.join(this.options.root, "assets")
);
const assetId = this.options.version.custom || this.options.version.number;
if (
!fs.existsSync(path.join(assetDirectory, "indexes", `${assetId}.json`))
) {
await this.downloadAsync(
this.version.assetIndex.url,
path.join(assetDirectory, "indexes"),
`${assetId}.json`,
true,
"asset-json"
);
2019-05-23 01:28:48 +03:00
}
2018-10-30 01:13:58 +03:00
2023-06-18 15:07:15 +03:00
const index = JSON.parse(
fs.readFileSync(path.join(assetDirectory, "indexes", `${assetId}.json`), {
encoding: "utf8",
})
);
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit("progress", {
type: "assets",
2020-05-29 05:39:13 +03:00
task: 0,
2023-06-18 15:07:15 +03:00
total: Object.keys(index.objects).length,
});
await Promise.all(
Object.keys(index.objects).map(async (asset) => {
const hash = index.objects[asset].hash;
const subhash = hash.substring(0, 2);
const subAsset = path.join(assetDirectory, "objects", subhash);
if (
!fs.existsSync(path.join(subAsset, hash)) ||
!(await this.checkSum(hash, path.join(subAsset, hash)))
) {
await this.downloadAsync(
`${this.options.overrides.url.resource}/${subhash}/${hash}`,
subAsset,
hash,
true,
"assets"
);
counter++;
this.client.emit("progress", {
type: "assets",
task: counter,
total: Object.keys(index.objects).length,
});
}
})
);
counter = 0;
2020-05-29 05:39:13 +03:00
// Copy assets to legacy if it's an older Minecraft version.
2020-07-08 06:36:31 +03:00
if (this.isLegacy()) {
2023-06-18 15:07:15 +03:00
if (fs.existsSync(path.join(assetDirectory, "legacy"))) {
this.client.emit(
"debug",
"[PiMi]: The 'legacy' directory is no longer used as Minecraft looks " +
"for the resouces folder regardless of what is passed in the assetDirecotry launch option. I'd " +
`recommend removing the directory (${path.join(
assetDirectory,
"legacy"
)})`
);
2020-10-03 11:16:03 +03:00
}
2023-06-18 15:07:15 +03:00
const legacyDirectory = path.join(this.options.root, "resources");
this.client.emit(
"debug",
`[PiMi]: Copying assets over to ${legacyDirectory}`
);
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit("progress", {
type: "assets-copy",
2020-05-29 05:39:13 +03:00
task: 0,
2023-06-18 15:07:15 +03:00
total: Object.keys(index.objects).length,
});
await Promise.all(
Object.keys(index.objects).map(async (asset) => {
const hash = index.objects[asset].hash;
const subhash = hash.substring(0, 2);
const subAsset = path.join(assetDirectory, "objects", subhash);
const legacyAsset = asset.split("/");
legacyAsset.pop();
if (
!fs.existsSync(path.join(legacyDirectory, legacyAsset.join("/")))
) {
fs.mkdirSync(path.join(legacyDirectory, legacyAsset.join("/")), {
recursive: true,
});
}
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
if (!fs.existsSync(path.join(legacyDirectory, asset))) {
fs.copyFileSync(
path.join(subAsset, hash),
path.join(legacyDirectory, asset)
);
}
counter++;
this.client.emit("progress", {
type: "assets-copy",
task: counter,
total: Object.keys(index.objects).length,
});
2020-05-29 05:39:13 +03:00
})
2023-06-18 15:07:15 +03:00
);
2019-05-23 01:28:48 +03:00
}
2023-06-18 15:07:15 +03:00
counter = 0;
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit("debug", "[PiMi]: Downloaded assets");
2020-05-29 05:39:13 +03:00
}
2019-05-23 01:28:48 +03:00
2023-06-18 15:07:15 +03:00
parseRule(lib) {
2020-05-29 05:39:13 +03:00
if (lib.rules) {
if (lib.rules.length > 1) {
2023-06-18 15:07:15 +03:00
if (
lib.rules[0].action === "allow" &&
lib.rules[1].action === "disallow" &&
lib.rules[1].os.name === "osx"
) {
return this.getOS() === "osx";
} else {
2023-06-18 15:07:15 +03:00
return true;
}
2020-05-29 05:39:13 +03:00
} else {
2023-06-18 15:07:15 +03:00
if (lib.rules[0].action === "allow" && lib.rules[0].os)
return lib.rules[0].os.name !== this.getOS();
2020-05-29 05:39:13 +03:00
}
} else {
2023-06-18 15:07:15 +03:00
return false;
}
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
async getNatives() {
const nativeDirectory = path.resolve(
this.options.overrides.natives ||
path.join(this.options.root, "natives", this.version.id)
);
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
if (parseInt(this.version.id.split(".")[1]) >= 19)
return this.options.overrides.cwd || this.options.root;
2022-06-13 02:03:22 +03:00
2023-06-18 15:07:15 +03:00
if (
!fs.existsSync(nativeDirectory) ||
!fs.readdirSync(nativeDirectory).length
) {
fs.mkdirSync(nativeDirectory, { recursive: true });
2020-05-29 05:39:13 +03:00
const natives = async () => {
2023-06-18 15:07:15 +03:00
const natives = [];
await Promise.all(
this.version.libraries.map(async (lib) => {
if (!lib.downloads || !lib.downloads.classifiers) return;
if (this.parseRule(lib)) return;
const native =
this.getOS() === "osx"
? lib.downloads.classifiers["natives-osx"] ||
lib.downloads.classifiers["natives-macos"]
: lib.downloads.classifiers[`natives-${this.getOS()}`];
natives.push(native);
})
);
return natives;
};
const stat = await natives();
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit("progress", {
type: "natives",
2020-05-29 05:39:13 +03:00
task: 0,
2023-06-18 15:07:15 +03:00
total: stat.length,
});
await Promise.all(
stat.map(async (native) => {
if (!native) return;
const name = native.path.split("/").pop();
await this.downloadAsync(
native.url,
nativeDirectory,
name,
true,
"natives"
);
if (
!(await this.checkSum(
native.sha1,
path.join(nativeDirectory, name)
))
) {
await this.downloadAsync(
native.url,
nativeDirectory,
name,
true,
"natives"
);
}
try {
new Zip(path.join(nativeDirectory, name)).extractAllTo(
nativeDirectory,
true
);
} catch (e) {
// Only doing a console.warn since a stupid error happens. You can basically ignore this.
// if it says Invalid file name, just means two files were downloaded and both were deleted.
// All is well.
console.warn(e);
}
fs.unlinkSync(path.join(nativeDirectory, name));
counter++;
this.client.emit("progress", {
type: "natives",
task: counter,
total: stat.length,
});
2020-05-29 05:39:13 +03:00
})
2023-06-18 15:07:15 +03:00
);
this.client.emit("debug", "[PiMi]: Downloaded and extracted natives");
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
counter = 0;
this.client.emit("debug", `[PiMi]: Set native path to ${nativeDirectory}`);
2019-02-07 16:14:16 +03:00
2023-06-18 15:07:15 +03:00
return nativeDirectory;
2020-05-29 05:39:13 +03:00
}
2019-04-29 00:52:37 +03:00
2023-06-18 15:07:15 +03:00
fwAddArgs() {
const forgeWrapperAgrs = [
2023-06-18 15:07:15 +03:00
`-Dforgewrapper.librariesDir=${path.resolve(
this.options.overrides.libraryRoot ||
path.join(this.options.root, "libraries")
)}`,
`-Dforgewrapper.installer=${this.options.forge}`,
2023-06-18 15:07:15 +03:00
`-Dforgewrapper.minecraft=${this.options.mcPath}`,
];
this.options.customArgs
2023-06-18 15:07:15 +03:00
? (this.options.customArgs =
this.options.customArgs.concat(forgeWrapperAgrs))
: (this.options.customArgs = forgeWrapperAgrs);
}
2023-06-18 15:07:15 +03:00
isModernForge(json) {
return (
json.inheritsFrom &&
json.inheritsFrom.split(".")[1] >= 12 &&
!(
json.inheritsFrom === "1.12.2" &&
json.id.split(".")[json.id.split(".").length - 1] === "2847"
)
);
2021-01-09 05:07:08 +03:00
}
2023-06-18 15:07:15 +03:00
async getForgedWrapped() {
let json = null;
let installerJson = null;
const versionPath = path.join(
this.options.root,
"forge",
`${this.version.id}`,
"version.json"
);
// Since we're building a proper "custom" JSON that will work nativly with PiMi, the version JSON will not
// be re-generated on the next run.
if (fs.existsSync(versionPath)) {
try {
2023-06-18 15:07:15 +03:00
json = JSON.parse(fs.readFileSync(versionPath));
if (
!json.forgeWrapperVersion ||
!(json.forgeWrapperVersion === this.options.overrides.fw.version)
) {
this.client.emit(
"debug",
"[PiMi]: Old ForgeWrapper has generated this version JSON, re-generating"
);
} else {
2023-06-18 15:07:15 +03:00
// If forge is modern, add ForgeWrappers launch arguments and set forge to null so PiMi treats it as a custom json.
2021-01-09 05:07:08 +03:00
if (this.isModernForge(json)) {
2023-06-18 15:07:15 +03:00
this.fwAddArgs();
this.options.forge = null;
2021-01-09 05:07:08 +03:00
}
2023-06-18 15:07:15 +03:00
return json;
}
} catch (e) {
2023-06-18 15:07:15 +03:00
console.warn(e);
this.client.emit(
"debug",
"[PiMi]: Failed to parse Forge version JSON, re-generating"
);
}
2019-05-23 01:28:48 +03:00
}
2019-02-07 16:14:16 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
"[PiMi]: Generating a proper version json, this might take a bit"
);
const zipFile = new Zip(this.options.forge);
json = zipFile.readAsText("version.json");
if (zipFile.getEntry("install_profile.json"))
installerJson = zipFile.readAsText("install_profile.json");
2020-05-29 05:39:13 +03:00
try {
2023-06-18 15:07:15 +03:00
json = JSON.parse(json);
if (installerJson) installerJson = JSON.parse(installerJson);
2020-05-29 05:39:13 +03:00
} catch (e) {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
"[PiMi]: Failed to load json files for ForgeWrapper, using Vanilla instead"
);
return null;
2019-05-23 01:28:48 +03:00
}
2023-06-18 15:07:15 +03:00
// Adding the installer libraries as mavenFiles so PiMi downloads them but doesn't add them to the class paths.
if (installerJson) {
json.mavenFiles
2023-06-18 15:07:15 +03:00
? (json.mavenFiles = json.mavenFiles.concat(installerJson.libraries))
: (json.mavenFiles = installerJson.libraries);
}
2018-10-30 01:13:58 +03:00
// Holder for the specifc jar ending which depends on the specifc forge version.
2023-06-18 15:07:15 +03:00
let jarEnding = "universal";
// We need to handle modern forge differently than legacy.
2021-01-09 05:07:08 +03:00
if (this.isModernForge(json)) {
2023-06-18 15:07:15 +03:00
// If forge is modern and above 1.12.2, we add ForgeWrapper to the libraries so PiMi includes it in the classpaths.
if (json.inheritsFrom !== "1.12.2") {
this.fwAddArgs();
const fwName = `ForgeWrapper-${this.options.overrides.fw.version}.jar`;
const fwPathArr = [
"io",
"github",
"zekerzhayard",
"ForgeWrapper",
this.options.overrides.fw.version,
];
json.libraries.push({
2023-06-18 15:07:15 +03:00
name: fwPathArr.join(":"),
downloads: {
artifact: {
2023-06-18 15:07:15 +03:00
path: [...fwPathArr, fwName].join("/"),
2021-08-08 23:05:50 +03:00
url: `${this.options.overrides.fw.baseUrl}${this.options.overrides.fw.version}/${fwName}`,
sha1: this.options.overrides.fw.sh1,
2023-06-18 15:07:15 +03:00
size: this.options.overrides.fw.size,
},
},
});
json.mainClass = "io.github.zekerzhayard.forgewrapper.installer.Main";
jarEnding = "launcher";
// Providing a download URL to the universal jar mavenFile so it can be downloaded properly.
for (const library of json.mavenFiles) {
2023-06-18 15:07:15 +03:00
const lib = library.name.split(":");
if (lib[0] === "net.minecraftforge" && lib[1].includes("forge")) {
library.downloads.artifact.url =
"https://files.minecraftforge.net/maven/" +
library.downloads.artifact.path;
break;
}
}
} else {
// Remove the forge dependent since we're going to overwrite the first entry anyways.
for (const library in json.mavenFiles) {
2023-06-18 15:07:15 +03:00
const lib = json.mavenFiles[library].name.split(":");
if (lib[0] === "net.minecraftforge" && lib[1].includes("forge")) {
delete json.mavenFiles[library];
break;
}
2020-05-29 05:39:13 +03:00
}
}
} else {
2023-06-18 15:07:15 +03:00
// Modifying legacy library format to play nice with PiMi's downloadToDirectory function.
await Promise.all(
json.libraries.map(async (library) => {
const lib = library.name.split(":");
if (lib[0] === "net.minecraftforge" && lib[1].includes("forge"))
return;
let url = this.options.overrides.url.mavenForge;
const name = `${lib[1]}-${lib[2]}.jar`;
if (!library.url) {
if (library.serverreq || library.clientreq) {
url = this.options.overrides.url.defaultRepoForge;
} else {
return;
}
}
2023-06-18 15:07:15 +03:00
library.url = url;
const downloadLink = `${url}${lib[0].replace(/\./g, "/")}/${lib[1]}/${
lib[2]
}/${name}`;
// Checking if the file still exists on Forge's server, if not, replace it with the fallback.
// Not checking for sucess, only if it 404s.
this.baseRequest(downloadLink, (error, response, body) => {
if (error) {
this.client.emit(
"debug",
`[PiMi]: Failed checking request for ${downloadLink}`
);
} else {
if (response.statusCode === 404)
library.url = this.options.overrides.url.fallbackMaven;
}
});
})
2023-06-18 15:07:15 +03:00
);
}
// If a downloads property exists, we modify the inital forge entry to include ${jarEnding} so ForgeWrapper can work properly.
// If it doesn't, we simply remove it since we're already providing the universal jar.
if (json.libraries[0].downloads) {
2023-06-18 15:07:15 +03:00
if (json.libraries[0].name.includes("minecraftforge")) {
json.libraries[0].name = json.libraries[0].name + `:${jarEnding}`;
json.libraries[0].downloads.artifact.path =
json.libraries[0].downloads.artifact.path.replace(
".jar",
`-${jarEnding}.jar`
);
json.libraries[0].downloads.artifact.url =
"https://files.minecraftforge.net/maven/" +
json.libraries[0].downloads.artifact.path;
2021-08-22 02:06:21 +03:00
}
} else {
2023-06-18 15:07:15 +03:00
delete json.libraries[0];
}
2020-11-27 03:22:41 +03:00
// Removing duplicates and null types
2023-06-18 15:07:15 +03:00
json.libraries = this.cleanUp(json.libraries);
if (json.mavenFiles) json.mavenFiles = this.cleanUp(json.mavenFiles);
2020-11-27 03:22:41 +03:00
2023-06-18 15:07:15 +03:00
json.forgeWrapperVersion = this.options.overrides.fw.version;
2021-08-08 23:13:01 +03:00
// Saving file for next run!
2023-06-18 15:07:15 +03:00
if (
!fs.existsSync(path.join(this.options.root, "forge", this.version.id))
) {
fs.mkdirSync(path.join(this.options.root, "forge", this.version.id), {
recursive: true,
});
}
2023-06-18 15:07:15 +03:00
fs.writeFileSync(versionPath, JSON.stringify(json, null, 4));
2020-11-27 03:22:41 +03:00
2023-06-18 15:07:15 +03:00
// Make PiMi treat modern forge as a custom version json rather then legacy forge.
if (this.isModernForge(json)) this.options.forge = null;
2023-06-18 15:07:15 +03:00
return json;
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
runInstaller(path) {
return new Promise((resolve) => {
const installer = child.exec(path);
installer.on("close", (code) => resolve(code));
});
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
async downloadToDirectory(directory, libraries, eventName) {
const libs = [];
await Promise.all(
libraries.map(async (library) => {
if (!library) return;
if (this.parseRule(library)) return;
const lib = library.name.split(":");
let jarPath;
let name;
if (
library.downloads &&
library.downloads.artifact &&
library.downloads.artifact.path
) {
name =
library.downloads.artifact.path.split("/")[
library.downloads.artifact.path.split("/").length - 1
];
jarPath = path.join(
directory,
this.popString(library.downloads.artifact.path)
);
} else {
name = `${lib[1]}-${lib[2]}${lib[3] ? "-" + lib[3] : ""}.jar`;
jarPath = path.join(
directory,
`${lib[0].replace(/\./g, "/")}/${lib[1]}/${lib[2]}`
);
}
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
if (
!fs.existsSync(path.join(jarPath, name)) ||
!this.checkSum(
library.downloads.artifact.sha1,
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
);
}
2018-10-30 01:13:58 +03:00
}
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
counter++;
this.client.emit("progress", {
type: eventName,
task: counter,
total: libraries.length,
});
libs.push(`${jarPath}${path.sep}${name}`);
2020-05-29 05:39:13 +03:00
})
2023-06-18 15:07:15 +03:00
);
counter = 0;
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
return libs;
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
async getClasses(classJson) {
let libs = [];
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
const libraryDirectory = path.resolve(
this.options.overrides.libraryRoot ||
path.join(this.options.root, "libraries")
);
2020-07-23 00:19:48 +03:00
2020-05-29 05:39:13 +03:00
if (classJson) {
if (classJson.mavenFiles) {
2023-06-18 15:07:15 +03:00
await this.downloadToDirectory(
libraryDirectory,
classJson.mavenFiles,
"classes-maven-custom"
);
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
libs = await this.downloadToDirectory(
libraryDirectory,
classJson.libraries,
"classes-custom"
);
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
const parsed = this.version.libraries.map((lib) => {
if (lib.downloads && lib.downloads.artifact && !this.parseRule(lib))
return lib;
});
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
libs = libs.concat(
await this.downloadToDirectory(libraryDirectory, parsed, "classes")
);
counter = 0;
2020-05-29 05:39:13 +03:00
// Temp Quilt support
2023-06-18 15:07:15 +03:00
if (classJson) libs.sort();
2023-01-30 04:16:27 +03:00
2023-06-18 15:07:15 +03:00
this.client.emit("debug", "[PiMi]: Collected class paths");
return libs;
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
popString(path) {
const tempArray = path.split("/");
tempArray.pop();
return tempArray.join("/");
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
cleanUp(array) {
const newArray = [];
2020-07-08 06:36:31 +03:00
for (const classPath in array) {
2023-06-18 15:07:15 +03:00
if (newArray.includes(array[classPath]) || array[classPath] === null)
continue;
newArray.push(array[classPath]);
2020-07-08 06:36:31 +03:00
}
2023-06-18 15:07:15 +03:00
return newArray;
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
formatQuickPlay() {
const types = {
2023-06-18 15:07:15 +03:00
singleplayer: "--quickPlaySingleplayer",
multiplayer: "--quickPlayMultiplayer",
realms: "--quickPlayRealms",
legacy: null,
};
const { type, identifier, path } = this.options.quickPlay;
const keys = Object.keys(types);
if (!keys.includes(type)) {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
`[PiMi]: quickPlay type is not valid. Valid types are: ${keys.join(
", "
)}`
);
return null;
}
2023-06-18 15:07:15 +03:00
const returnArgs =
type === "legacy"
? [
"--server",
identifier.split(":")[0],
"--port",
identifier.split(":")[1] || "25565",
]
: [types[type], identifier];
if (path) returnArgs.push("--quickPlayPath", path);
return returnArgs;
}
2023-06-18 15:07:15 +03:00
async getLaunchOptions(modification) {
const type = modification || this.version;
2020-05-29 05:39:13 +03:00
let args = type.minecraftArguments
2023-06-18 15:07:15 +03:00
? type.minecraftArguments.split(" ")
: type.arguments.game;
const assetRoot = path.resolve(
this.options.overrides.assetRoot || path.join(this.options.root, "assets")
);
2020-07-08 06:36:31 +03:00
const assetPath = this.isLegacy()
2023-06-18 15:07:15 +03:00
? path.join(this.options.root, "resources")
: path.join(assetRoot);
const minArgs = this.options.overrides.minArgs || this.isLegacy() ? 5 : 11;
if (args.length < minArgs)
args = args.concat(
this.version.minecraftArguments
? this.version.minecraftArguments.split(" ")
: this.version.arguments.game
);
if (this.options.customLaunchArgs)
args = args.concat(this.options.customLaunchArgs);
this.options.authorization = await Promise.resolve(
this.options.authorization
);
this.options.authorization.meta = this.options.authorization.meta
? this.options.authorization.meta
: { type: "mojang" };
2020-05-29 05:39:13 +03:00
const fields = {
2023-06-18 15:07:15 +03:00
"${auth_access_token}": this.options.authorization.access_token,
"${auth_session}": this.options.authorization.access_token,
"${auth_player_name}": this.options.authorization.name,
"${auth_uuid}": this.options.authorization.uuid,
"${auth_xuid}":
this.options.authorization.meta.xuid ||
this.options.authorization.access_token,
"${user_properties}": this.options.authorization.user_properties,
"${user_type}": this.options.authorization.meta.type,
"${version_name}": this.options.version.number,
"${assets_index_name}":
this.options.overrides.assetIndex ||
this.options.version.custom ||
this.options.version.number,
"${game_directory}":
this.options.overrides.gameDirectory || this.options.root,
"${assets_root}": assetPath,
"${game_assets}": assetPath,
"${version_type}": this.options.version.type,
"${clientid}":
this.options.authorization.meta.clientId ||
this.options.authorization.client_token ||
this.options.authorization.access_token,
"${resolution_width}": this.options.window
? this.options.window.width
: 856,
"${resolution_height}": this.options.window
? this.options.window.height
: 482,
};
if (
this.options.authorization.meta.demo &&
(this.options.features
? !this.options.features.includes("is_demo_user")
: true)
) {
args.push("--demo");
}
const replaceArg = (obj, index) => {
if (Array.isArray(obj.value)) {
for (const arg of obj.value) {
2023-06-18 15:07:15 +03:00
args.push(arg);
}
} else {
2023-06-18 15:07:15 +03:00
args.push(obj.value);
}
2023-06-18 15:07:15 +03:00
delete args[index];
};
2020-05-29 05:39:13 +03:00
for (let index = 0; index < args.length; index++) {
2023-06-18 15:07:15 +03:00
if (typeof args[index] === "object") {
if (args[index].rules) {
2023-06-18 15:07:15 +03:00
if (!this.options.features) continue;
const featureFlags = [];
for (const rule of args[index].rules) {
2023-06-18 15:07:15 +03:00
featureFlags.push(...Object.keys(rule.features));
}
2023-06-18 15:07:15 +03:00
let hasAllRules = true;
for (const feature of this.options.features) {
if (!featureFlags.includes(feature)) {
2023-06-18 15:07:15 +03:00
hasAllRules = false;
}
}
2023-06-18 15:07:15 +03:00
if (hasAllRules) replaceArg(args[index], index);
} else {
2023-06-18 15:07:15 +03:00
replaceArg(args[index], index);
}
} else {
if (Object.keys(fields).includes(args[index])) {
2023-06-18 15:07:15 +03:00
args[index] = fields[args[index]];
}
2020-05-29 05:39:13 +03:00
}
}
2020-05-29 05:39:13 +03:00
if (this.options.window) {
2022-09-13 19:10:05 +03:00
// eslint-disable-next-line no-unused-expressions
2020-05-29 05:39:13 +03:00
this.options.window.fullscreen
2023-06-18 15:07:15 +03:00
? args.push("--fullscreen")
: () => {
2023-06-18 15:07:15 +03:00
if (
this.options.features
? !this.options.features.includes("has_custom_resolution")
: true
) {
args.push(
"--width",
this.options.window.width,
"--height",
this.options.window.height
);
}
};
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
if (this.options.server)
this.client.emit(
"debug",
"[PiMi]: server and port are deprecated launch flags. Use the quickPlay field."
);
if (this.options.quickPlay) args = args.concat(this.formatQuickPlay());
2020-05-29 05:39:13 +03:00
if (this.options.proxy) {
args.push(
2023-06-18 15:07:15 +03:00
"--proxyHost",
2020-05-29 05:39:13 +03:00
this.options.proxy.host,
2023-06-18 15:07:15 +03:00
"--proxyPort",
this.options.proxy.port || "8080",
"--proxyUser",
2020-05-29 05:39:13 +03:00
this.options.proxy.username,
2023-06-18 15:07:15 +03:00
"--proxyPass",
2020-05-29 05:39:13 +03:00
this.options.proxy.password
2023-06-18 15:07:15 +03:00
);
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
args = args.filter(
(value) => typeof value === "string" || typeof value === "number"
);
this.client.emit("debug", "[PiMi]: Set launch options");
return args;
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
async getJVM() {
2020-05-29 05:39:13 +03:00
const opts = {
2023-06-18 15:07:15 +03:00
windows:
"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump",
osx: "-XstartOnFirstThread",
linux: "-Xss1M",
};
return opts[this.getOS()];
2020-05-29 05:39:13 +03:00
}
2023-06-18 15:07:15 +03:00
isLegacy() {
return (
this.version.assets === "legacy" || this.version.assets === "pre-1.6"
);
2020-07-08 06:36:31 +03:00
}
2023-06-18 15:07:15 +03:00
getOS() {
2020-05-29 05:39:13 +03:00
if (this.options.os) {
2023-06-18 15:07:15 +03:00
return this.options.os;
2020-05-29 05:39:13 +03:00
} else {
switch (process.platform) {
2023-06-18 15:07:15 +03:00
case "win32":
return "windows";
case "darwin":
return "osx";
default:
return "linux";
2020-05-29 05:39:13 +03:00
}
}
2020-05-29 05:39:13 +03:00
}
2020-08-24 06:57:28 +03:00
// To prevent launchers from breaking when they update. Will be reworked with rewrite.
2023-06-18 15:07:15 +03:00
getMemory() {
2020-08-24 06:57:28 +03:00
if (!this.options.memory) {
2023-06-18 15:07:15 +03:00
this.client.emit("debug", "[PiMi]: Memory not set! Setting 1GB as MAX!");
2020-08-24 06:57:28 +03:00
this.options.memory = {
min: 512,
2023-06-18 15:07:15 +03:00
max: 1023,
};
2020-08-24 06:57:28 +03:00
}
if (!isNaN(this.options.memory.max) && !isNaN(this.options.memory.min)) {
if (this.options.memory.max < this.options.memory.min) {
2023-06-18 15:07:15 +03:00
this.client.emit(
"debug",
"[PiMi]: MIN memory is higher then MAX! Resetting!"
);
this.options.memory.max = 1023;
this.options.memory.min = 512;
2020-08-24 06:57:28 +03:00
}
2023-06-18 15:07:15 +03:00
return [`${this.options.memory.max}M`, `${this.options.memory.min}M`];
} else {
return [`${this.options.memory.max}`, `${this.options.memory.min}`];
}
2020-08-24 06:57:28 +03:00
}
2023-06-18 15:07:15 +03:00
async extractPackage(options = this.options) {
if (options.clientPackage.startsWith("http")) {
await this.downloadAsync(
options.clientPackage,
options.root,
"clientPackage.zip",
true,
"client-package"
);
options.clientPackage = path.join(options.root, "clientPackage.zip");
2020-06-19 19:26:58 +03:00
}
2023-06-18 15:07:15 +03:00
new Zip(options.clientPackage).extractAllTo(options.root, true);
if (options.removePackage) fs.unlinkSync(options.clientPackage);
2020-05-29 05:39:13 +03:00
2023-06-18 15:07:15 +03:00
return this.client.emit("package-extract", true);
2020-05-29 05:39:13 +03:00
}
2019-05-23 01:28:48 +03:00
}
2023-06-18 15:07:15 +03:00
module.exports = Handler;