Compare commits

..

No commits in common. "7b02311f54c3c70c567933ce6205243e5f5ecd3b" and "a748c07a8dcbe7681a45533e888a6a5eb0744d2f" have entirely different histories.

3 changed files with 11 additions and 286 deletions

View File

@ -3,8 +3,3 @@
A Plutonium launcher that aims to make old CoD games more accessible A Plutonium launcher that aims to make old CoD games more accessible
----- -----
Currently being written from scratch, please be patient. Currently being written from scratch, please be patient.
## To do
- Make each HTTPS request its own function instead of binding them to an event
- De-duplicate code (HTTPS requests in src/main.js for example)
- Make a better UI/UX

View File

@ -1,28 +1,21 @@
const path = require("node:path"); const path = require("node:path");
const fs = require("node:fs"); const fs = require("node:fs");
const fsPromises = require("node:fs/promises");
const https = require("node:https"); const https = require("node:https");
const process = require("node:process"); const process = require("node:process");
const os = require("node:os"); const os = require("node:os");
const child_process = require("node:child_process"); const child_process = require("node:child_process");
const electron = require("electron"); const electron = require("electron");
const update = require(path.join(__dirname, "update.js"));
let configFilePath; let configFilePath;
let plutoniumInstallDirectory;
switch (os.platform()) { switch (os.platform()) {
case "win32": case "win32":
configFilePath = path.join(process.env["LOCALAPPDATA"], "Plutonium", "open-plutonium-launcher.json"); configFilePath = path.join(process.env["LOCALAPPDATA"], "Plutonium", "open-plutonium-launcher.json");
plutoniumInstallDirectory = path.join(process.env["LOCALAPPDATA"], "Plutonium");
break; break;
case "linux": case "linux":
configFilePath = path.join(os.userInfo().homedir, ".config", "open-plutonium-launcher.json"); configFilePath = path.join(os.userInfo().homedir, ".config", "open-plutonium-launcher.json");
plutoniumInstallDirectory = path.join(os.userInfo().homedir, ".local", "share", "Plutonium");
break; break;
case "darwin": case "darwin":
configFilePath = path.join(os.userInfo().homedir, "Library", "Application Support", "open-plutonium-launcher.json"); configFilePath = path.join(os.userInfo().homedir, "Library", "Application Support", "open-plutonium-launcher.json");
plutoniumInstallDirectory = path.join(os.userInfo().homedir, "Library", "Application Support", "Plutonium");
break; break;
default: default:
electron.dialog.showErrorBox("Incompatible system", "Sorry, your operating system doesn't seem to be supported..."); electron.dialog.showErrorBox("Incompatible system", "Sorry, your operating system doesn't seem to be supported...");
@ -44,15 +37,6 @@ let userInfo = {
"emailVerified": false, "emailVerified": false,
"avatar": "https://forum.plutonium.pw/assets/uploads/system/avatar-default.png" "avatar": "https://forum.plutonium.pw/assets/uploads/system/avatar-default.png"
}; };
let config = {
"user-info": userInfo,
"game-directories": {
"iw5": "",
"t4": "",
"t5": "",
"t6": ""
}
};
function createMainWindow() { function createMainWindow() {
mainWindow = new electron.BrowserWindow({ mainWindow = new electron.BrowserWindow({
@ -75,34 +59,6 @@ function createMainWindow() {
}); });
} }
function readConfig(filePath) {
return new Promise(function (resolve, reject) {
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return reject(new Error("Config file doesn't exist or is not a file."));
fsPromises.readFile(filePath, "utf-8").then(function (contents) {
try {
let data = JSON.parse(contents);
resolve(data);
} catch (error) {
reject(error);
}
}).catch(function (error) {
reject(error);
});
});
}
function writeConfig(filePath, data = {}) {
return new Promise(function (resolve, reject) {
fsPromises.writeFile(filePath, JSON.stringify(data, null, "\t")).then(function () {
resolve();
}).catch(function (error) {
reject(error);
});
});
}
function fetchPlutoniumManifest() { function fetchPlutoniumManifest() {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
let request = https.request("https://cdn.plutonium.pw/updater/prod/info.json", { let request = https.request("https://cdn.plutonium.pw/updater/prod/info.json", {
@ -137,158 +93,6 @@ function fetchPlutoniumManifest() {
}); });
} }
function validatePlutoniumLogin(token) {
return new Promise(function (resolve, reject) {
let request = https.request("https://nix.plutonium.pw/api/auth/validate", {
"method": "GET",
"headers": {
"Content-Length": 0,
"Authorization": "UserToken " + token,
"X-Plutonium-Revision": String(plutoniumManifest.revision),
"User-Agent": "Nix/3.0"
}
}, function (response) {
if (response.statusCode !== 200) {
return resolve({
"status": "unauthenticated",
"successful": false
});
}
response.setEncoding("utf-8");
let body = "";
response.on("data", function (chunk) {
body += chunk;
});
response.on("end", function () {
try {
let data = JSON.parse(body);
if (!("userId" in data)) {
reject(new Error("Authentication seems to be successful but no user identifier was returned."));
}
resolve({
"status": "authenticated",
"successful": true,
...data
});
userInfo = {
"token": token,
...data
};
} catch (error) {
reject(error);
}
});
});
request.on("error", function (error) {
reject(error);
});
request.end();
});
}
function getPlutoniumSession(game, token) {
return new Promise(function (resolve, reject) {
let payload = JSON.stringify({
"game": game
});
let request = https.request("https://nix.plutonium.pw/api/auth/session", {
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Content-Length": payload.length,
"Authorization": "UserToken " + token,
"X-Plutonium-Revision": String(plutoniumManifest.revision),
"User-Agent": "Nix/3.0"
}
}, function (response) {
if (response.statusCode !== 200) {
return resolve({
"status": "unauthenticated",
"successful": false
});
}
response.setEncoding("utf-8");
let body = "";
response.on("data", function (chunk) {
body += chunk;
});
response.on("end", function () {
try {
let data = JSON.parse(body);
if (!("token" in data)) {
reject(new Error("Authentication seems to be successful but no token was returned."));
}
resolve({
"status": "authenticated",
"successful": true,
...data
});
userInfo = data;
mainWindow.loadFile(path.join(__dirname, "src", "views", "games.html"));
} catch (error) {
reject(error);
}
});
});
request.on("error", function (error) {
reject(error);
});
request.write(payload);
request.end();
});
}
function launch(plutoniumInstallDirectory, game, gameInstallDirectory, online) {
return new Promise(function (resolve, reject) {
getPlutoniumSession(game, userInfo.token).then(function (data) {
if (!data.successful) {
return reject(new Error("Authentication has failed."));
}
let bootstrapperBinary = path.join(plutoniumInstallDirectory, "bin", "plutonium-bootstrapper-win32.exe");
let bootstrapperArguments = [game, gameInstallDirectory];
if (online) {
bootstrapperArguments.push("-token");
bootstrapperArguments.push(data.token);
} else {
bootstrapperArguments.push("+name");
bootstrapperArguments.push(userInfo.username);
bootstrapperArguments.push("-lan");
}
let gameProcess = child_process.spawn(bootstrapperBinary, bootstrapperArguments, {
"cwd": plutoniumInstallDirectory,
"detached": true,
"stdio": "ignore"
});
gameProcess.on("spawn", function () {
resolve(true);
});
gameProcess.on("error", function (error) {
reject(error);
});
});
});
}
electron.app.once("ready", function () { electron.app.once("ready", function () {
createMainWindow(); createMainWindow();
fetchPlutoniumManifest().then(function (manifest) { fetchPlutoniumManifest().then(function (manifest) {
@ -296,33 +100,6 @@ electron.app.once("ready", function () {
}).catch(function (error) { }).catch(function (error) {
electron.dialog.showErrorBox("Error", "The Plutonium launcher manifest could not be fetched, auto-updating might not be possible.\n" + error.message); electron.dialog.showErrorBox("Error", "The Plutonium launcher manifest could not be fetched, auto-updating might not be possible.\n" + error.message);
}); });
if (fs.existsSync(configFilePath)) {
readConfig(configFilePath).then(function (data) {
config = data;
if (config["user-info"].token === "V3ryS3cr3t4uth3nt1c4t10nT0k3n") {
return;
} else {
validatePlutoniumLogin(config["user-info"].token).then(function (data) {
if (!data.successful) {
config["user-info"].token = "V3ryS3cr3t4uth3nt1c4t10nT0k3n";
return writeConfig(configFilePath, config).catch(function (error) {
console.error(error);
});
}
mainWindow.loadFile(path.join(__dirname, "src", "views", "games.html"));
});
}
}).catch(function (error) {
electron.dialog.showErrorBox("Error", "Configuration file could not be read.\n" + error.message);
});
} else {
writeConfig(configFilePath, config).catch(function (error) {
electron.dialog.showErrorBox("Error", "Configuration file doesn't exist and could not be created.\n" + error.message);
});
}
}); });
electron.app.on("window-all-closed", function () { electron.app.on("window-all-closed", function () {
@ -374,10 +151,6 @@ electron.ipcMain.handle("login", function (event, username, password) {
}); });
userInfo = data; userInfo = data;
config["user-info"] = userInfo;
writeConfig(configFilePath, config).catch(function (error) {
console.error(error);
});
mainWindow.loadFile(path.join(__dirname, "src", "views", "games.html")); mainWindow.loadFile(path.join(__dirname, "src", "views", "games.html"));
} catch (error) { } catch (error) {
@ -394,28 +167,3 @@ electron.ipcMain.handle("login", function (event, username, password) {
request.end(); request.end();
}); });
}); });
electron.ipcMain.handle("launch", function (event, game, online = true) {
return new Promise(function (resolve, reject) {
if (!(game in config.gameDirectories)) {
return resolve(false);
}
let gameInstallDirectory = config.gameDirectories[game];
update.checkFiles(plutoniumManifest, plutoniumInstallDirectory).then(function (fileList) {
let filesToUpdate = fileList.filter(function (file) {
return !file.ok;
});
update.downloadFiles(plutoniumInstallDirectory, plutoniumManifest.baseUrl, filesToUpdate).then(function () {
launch(plutoniumInstallDirectory, game, gameInstallDirectory, online).then(function (result) {
resolve(result);
});
}).catch(function (error) {
reject(error);
});
}).catch(function (error) {
reject(error);
});
});
});

View File

@ -63,37 +63,25 @@ function processDownloadQueue(baseDirectory, baseURL, fileEntries) {
let queue = [...fileEntries]; let queue = [...fileEntries];
let finishCallback = function () { let finishCallback = function () {
runningDownloads--; if (queue.length > 0) {
if (queue.length > 0 && runningDownloads < module.exports.concurrentDownloads) {
let currentEntry = queue.shift();
downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((errorCallback).bind(currentEntry));
} else {
resolve();
} }
}; };
let errorCallback = function (error) {
runningDownloads--;
if (!this.retries) {
this.retries = 0;
}
if (this.retries > 3) return reject(error);
this.retries++;
queue.push(this);
};
for (let d = 0; d < module.exports.concurrentDownloads; d++) { for (let d = 0; d < module.exports.concurrentDownloads; d++) {
if (queue.length < 1) break; if (queue.length < 1) break;
let currentEntry = queue.shift(); let currentEntry = queue.shift();
runningDownloads++; downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((function (error) {
if (!this.retries) {
this.retries = 0;
}
if (this.retries > 3) return reject(error);
downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((errorCallback).bind(currentEntry)); this.retries++;
queue.push(this);
}).bind(currentEntry));
} }
}); });
} }
@ -119,15 +107,9 @@ module.exports = {
}, },
"downloadFiles": function (baseDirectory, baseURL, fileEntries) { "downloadFiles": function (baseDirectory, baseURL, fileEntries) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
let filesToProcess = fileEntries.filter(function (entry) { processDownloadQueue(baseDirectory, baseURL, fileEntries.filter(function (entry) {
return !entry.ok; return !entry.ok;
}); })).then(function () {
if (filesToProcess.length < 1) {
return resolve(true);
}
processDownloadQueue(baseDirectory, baseURL, filesToProcess).then(function () {
resolve(true); resolve(true);
}).catch(function (error) { }).catch(function (error) {
reject(error); reject(error);