diff --git a/src/main.js b/src/main.js index fb647ca..9173146 100644 --- a/src/main.js +++ b/src/main.js @@ -6,16 +6,22 @@ const os = require("node:os"); const child_process = require("node:child_process"); const electron = require("electron"); +const update = require(path.join(__dirname, "update.js")); + let configFilePath; +let plutoniumInstallDirectory; switch (os.platform()) { case "win32": configFilePath = path.join(process.env["LOCALAPPDATA"], "Plutonium", "open-plutonium-launcher.json"); + plutoniumInstallDirectory = path.join(process.env["LOCALAPPDATA"], "Plutonium"); break; case "linux": configFilePath = path.join(os.userInfo().homedir, ".config", "open-plutonium-launcher.json"); + plutoniumInstallDirectory = path.join(os.userInfo().homedir, ".local", "share", "Plutonium"); break; case "darwin": configFilePath = path.join(os.userInfo().homedir, "Library", "Application Support", "open-plutonium-launcher.json"); + plutoniumInstallDirectory = path.join(os.userInfo().homedir, "Library", "Application Support", "Plutonium"); break; default: electron.dialog.showErrorBox("Incompatible system", "Sorry, your operating system doesn't seem to be supported..."); @@ -93,6 +99,101 @@ function fetchPlutoniumManifest() { }); } +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, { + "detached": true, + "stdio": "ignore" + }); + + gameProcess.on("spawn", function () { + resolve(true); + }); + gameProcess.on("error", function (error) { + reject(error); + }); + }); + }); +} + electron.app.once("ready", function () { createMainWindow(); fetchPlutoniumManifest().then(function (manifest) { @@ -167,3 +268,28 @@ electron.ipcMain.handle("login", function (event, username, password) { 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); + }); + }); +}); diff --git a/src/update.js b/src/update.js index 2524d8a..f9233bc 100644 --- a/src/update.js +++ b/src/update.js @@ -63,25 +63,37 @@ function processDownloadQueue(baseDirectory, baseURL, fileEntries) { let queue = [...fileEntries]; let finishCallback = function () { - if (queue.length > 0) { + runningDownloads--; + 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++) { if (queue.length < 1) break; let currentEntry = queue.shift(); - 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); + runningDownloads++; - this.retries++; - queue.push(this); - }).bind(currentEntry)); + downloadFile(baseURL + currentEntry.hash, path.join(baseDirectory, currentEntry.name)).then(finishCallback).catch((errorCallback).bind(currentEntry)); } }); } @@ -107,9 +119,15 @@ module.exports = { }, "downloadFiles": function (baseDirectory, baseURL, fileEntries) { return new Promise(function (resolve, reject) { - processDownloadQueue(baseDirectory, baseURL, fileEntries.filter(function (entry) { + let filesToProcess = fileEntries.filter(function (entry) { return !entry.ok; - })).then(function () { + }); + + if (filesToProcess.length < 1) { + return resolve(true); + } + + processDownloadQueue(baseDirectory, baseURL, filesToProcess).then(function () { resolve(true); }).catch(function (error) { reject(error);