aboutsummaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/gamepath.js86
-rw-r--r--src/modules/is_running.js66
-rw-r--r--src/modules/kill.js27
-rw-r--r--src/modules/launch.js34
-rw-r--r--src/modules/mods.js20
-rw-r--r--src/modules/update.js239
-rw-r--r--src/modules/window.js15
7 files changed, 477 insertions, 10 deletions
diff --git a/src/modules/gamepath.js b/src/modules/gamepath.js
new file mode 100644
index 0000000..93290ec
--- /dev/null
+++ b/src/modules/gamepath.js
@@ -0,0 +1,86 @@
+const path = require("path");
+const fs = require("fs-extra");
+
+const { dialog, ipcMain } = require("electron");
+
+const cli = require("../cli");
+
+const settings = require("./settings");
+
+let gamepath = {};
+
+// Returns true/false depending on if the gamepath currently exists/is
+// mounted, used to avoid issues...
+gamepath.exists = () => {
+ return fs.existsSync(settings.gamepath);
+}
+
+// Requests to set the game path
+//
+// If running with CLI it takes in the --setpath argument otherwise it
+// open the systems file browser for the user to select a path.
+gamepath.set = async (win, forcedialog) => {
+ function set_gamepath(folder) {
+ settings.gamepath = folder;
+ settings.zip = path.join(settings.gamepath + "/northstar.zip");
+ settings.save();
+ win.webContents.send("newpath", settings.gamepath);
+ ipcMain.emit("newpath", null, settings.gamepath);
+
+ modpath = path.join(settings.gamepath, "R2Northstar/mods");
+ }
+
+ if (! win) { // CLI
+ set_gamepath(cli.param("setpath"));
+ } else { // GUI
+ if (! forcedialog) {
+ function set_gamepath(folder, forcedialog) {
+ settings.gamepath = folder;
+ settings.zip = path.join(settings.gamepath + "/northstar.zip");
+ settings.save();
+ win.webContents.send("newpath", settings.gamepath);
+ ipcMain.emit("newpath", null, settings.gamepath);
+ }
+
+ let gamepath = await findgame();
+ if (gamepath) {
+ set_gamepath(gamepath);
+ return;
+ }
+
+ win.alert(lang("general.missingpath"));
+ }
+
+ // Fallback to manual selection
+ dialog.showOpenDialog({properties: ["openDirectory"]}).then(res => {
+ if (res.canceled) {
+ ipcMain.emit("newpath", null, false);
+ return;
+ }
+ if (! fs.existsSync(path.join(res.filePaths[0], "Titanfall2.exe"))) {
+ ipcMain.emit("wrong-path");
+ return;
+ }
+
+ set_gamepath(res.filePaths[0]);
+
+ cli.exit();
+ return;
+ }).catch(err => {console.error(err)})
+ }
+}
+
+// periodically check for the gamepath still existing
+setInterval(() => {
+ if (gamepath.exists()) {
+ ipcMain.emit("gui-getmods");
+ } else {
+ if (fs.existsSync("viper.json")) {
+ if (settings.gamepath != "") {
+ ipcMain.emit("gamepath-lost");
+ }
+ }
+ }
+}, 1500)
+
+module.exports = gamepath;
diff --git a/src/modules/is_running.js b/src/modules/is_running.js
new file mode 100644
index 0000000..cc91ded
--- /dev/null
+++ b/src/modules/is_running.js
@@ -0,0 +1,66 @@
+const exec = require("child_process").exec;
+
+let is_running = {};
+
+// a simple function that checks whether any of a given set of process
+// names are running, you can either input a string or an Array of
+// strings
+async function check_processes(processes) {
+ if (typeof processes == "string") {
+ processes = [processes];
+ }
+
+ return new Promise(resolve => {
+ if (! Array.isArray(processes)) {
+ reject(false);
+ }
+
+ // While we could use a Node module to do this instead, I
+ // decided not to do so. As this achieves exactly the same
+ // thing. And it's not much more clunky.
+ let cmd = (() => {
+ switch (process.platform) {
+ case "linux": return "ps -A";
+ case "win32": return "tasklist";
+ }
+ })();
+
+ exec(cmd, (err, stdout) => {
+ for (let i = 0; i < processes.length; i++) {
+ if (stdout.includes(processes[i])) {
+ resolve(true);
+ break
+ }
+
+ if (i == processes.length - 1) {resolve(false)}
+ }
+ });
+ });
+}
+
+is_running.game = async () => {
+ return await check_processes([
+ "NorthstarLauncher.exe",
+ "Titanfall2.exe", "Titanfall2-unpacked.exe"
+ ])
+}
+
+is_running.origin = async () => {
+ return await check_processes([
+ "Origin.exe",
+ ])
+}
+
+is_running.titanfall = async () => {
+ return await check_processes([
+ "Titanfall2.exe", "Titanfall2-unpacked.exe"
+ ])
+}
+
+is_running.northstar = async () => {
+ return await check_processes([
+ "NorthstarLauncher.exe",
+ ])
+}
+
+module.exports = is_running;
diff --git a/src/modules/kill.js b/src/modules/kill.js
new file mode 100644
index 0000000..7482bed
--- /dev/null
+++ b/src/modules/kill.js
@@ -0,0 +1,27 @@
+const exec = require("child_process").exec;
+
+// a simple function to kill processes with a certain name
+async function kill(process_name) {
+ return new Promise(resolve => {
+ let proc = process_name;
+ let cmd = (() => {
+ switch (process.platform) {
+ case "linux": return "killall " + proc;
+ case "win32": return "taskkill /IM " + proc + " /F";
+ }
+ })();
+
+ exec(cmd, (err, stdout) => {
+ // just try and fail silently if we don't find it w/e
+ resolve(true);
+ });
+ });
+}
+
+kill.process = kill;
+
+kill.origin = () => {
+ return kill("Origin.exe");
+}
+
+module.exports = kill;
diff --git a/src/modules/launch.js b/src/modules/launch.js
new file mode 100644
index 0000000..765c348
--- /dev/null
+++ b/src/modules/launch.js
@@ -0,0 +1,34 @@
+const exec = require("child_process").exec;
+
+const cli = require("../cli");
+const lang = require("../lang");
+
+const win = require("./window");
+const settings = require("./settings");
+
+// Launches the game
+//
+// Either Northstar or Vanilla. Linux support is not currently a thing,
+// however it'll be added at some point.
+function launch(game_version) {
+ if (process.platform == "linux") {
+ win.alert(lang("cli.launch.linuxerror"));
+ console.error("error:", lang("cli.launch.linuxerror"));
+ cli.exit(1);
+ return;
+ }
+
+ process.chdir(settings.gamepath);
+ switch(game_version) {
+ case "vanilla":
+ console.log(lang("general.launching"), "Vanilla...");
+ exec("Titanfall2.exe", {cwd: settings.gamepath});
+ break;
+ default:
+ console.log(lang("general.launching"), "Northstar...");
+ exec("NorthstarLauncher.exe", {cwd: settings.gamepath});
+ break;
+ }
+}
+
+module.exports = launch;
diff --git a/src/modules/mods.js b/src/modules/mods.js
index 22865c2..6abed96 100644
--- a/src/modules/mods.js
+++ b/src/modules/mods.js
@@ -6,12 +6,12 @@ const { app, ipcMain } = require("electron");
const { https } = require("follow-redirects");
const json = require("./json");
+const win = require("./window");
const version = require("./version");
const settings = require("./settings");
const cli = require("../cli");
const lang = require("../lang");
-const utils = require("../utils");
var mods = {
installing: [],
@@ -31,7 +31,7 @@ mods.list = () => {
update_path();
if (version.northstar() == "unknown") {
- utils.winLog(lang("general.notinstalled"));
+ win.log(lang("general.notinstalled"));
console.log("error: " + lang("general.notinstalled"));
cli.exit(1);
return false;
@@ -108,7 +108,7 @@ mods.get = (mod) => {
update_path();
if (version.northstar() == "unknown") {
- utils.winLog(lang("general.notinstalled"));
+ win.log(lang("general.notinstalled"));
console.log("error: " + lang("general.notinstalled"));
cli.exit(1);
return false;
@@ -223,14 +223,14 @@ mods.install = (mod, opts) => {
}
if (version.northstar() == "unknown") {
- utils.winLog(lang("general.notinstalled"));
+ win.log(lang("general.notinstalled"));
console.log("error: " + lang("general.notinstalled"));
cli.exit(1);
return false;
}
let notamod = () => {
- utils.winLog(lang("gui.mods.notamod"));
+ win.log(lang("gui.mods.notamod"));
console.log("error: " + lang("cli.mods.notamod"));
cli.exit(1);
return false;
@@ -240,7 +240,7 @@ mods.install = (mod, opts) => {
console.log(lang("cli.mods.installed"));
cli.exit();
- utils.winLog(lang("gui.mods.installedmod"));
+ win.log(lang("gui.mods.installedmod"));
if (modname == "mods") {
let manifest = path.join(app.getPath("userData"), "Archives/manifest.json");
@@ -262,7 +262,7 @@ mods.install = (mod, opts) => {
if (! fs.existsSync(mod)) {return notamod()}
if (fs.statSync(mod).isDirectory()) {
- utils.winLog(lang("gui.mods.installing"));
+ win.log(lang("gui.mods.installing"));
files = fs.readdirSync(mod);
if (fs.existsSync(path.join(mod, "mod.json")) &&
fs.statSync(path.join(mod, "mod.json")).isFile()) {
@@ -351,7 +351,7 @@ mods.install = (mod, opts) => {
return notamod();
}
} else {
- utils.winLog(lang("gui.mods.extracting"));
+ win.log(lang("gui.mods.extracting"));
let cache = path.join(app.getPath("userData"), "Archives");
if (fs.existsSync(cache)) {
fs.rmSync(cache, {recursive: true});
@@ -464,7 +464,7 @@ mods.remove = (mod) => {
update_path();
if (version.northstar() == "unknown") {
- utils.winLog(lang("general.notinstalled"));
+ win.log(lang("general.notinstalled"));
console.log("error: " + lang("general.notinstalled"));
cli.exit(1);
return false;
@@ -525,7 +525,7 @@ mods.toggle = (mod, fork) => {
update_path();
if (version.northstar() == "unknown") {
- utils.winLog(lang("general.notinstalled"));
+ win.log(lang("general.notinstalled"));
console.log("error: " + lang("general.notinstalled"));
cli.exit(1);
return false;
diff --git a/src/modules/update.js b/src/modules/update.js
new file mode 100644
index 0000000..cfdb42b
--- /dev/null
+++ b/src/modules/update.js
@@ -0,0 +1,239 @@
+const path = require("path");
+const fs = require("fs-extra");
+const { ipcMain, Notification } = require("electron");
+
+const cli = require("../cli");
+const lang = require("../lang");
+
+const win = require("./window");
+const version = require("./version");
+const settings = require("./settings");
+const requests = require("./requests");
+const gamepath = require("./gamepath");
+const is_running = require("./is_running");
+
+const unzip = require("unzipper");
+const { https } = require("follow-redirects");
+
+let update = {};
+
+// renames excluded files to their original name
+function restore_excluded_files() {
+ if (! gamepath.exists()) {return}
+
+ for (let i = 0; i < settings.excludes.length; i++) {
+ let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]);
+ if (fs.existsSync(exclude + ".excluded")) {
+ fs.renameSync(exclude + ".excluded", exclude);
+ }
+ }
+}; restore_excluded_files();
+
+// renames excluded files to <file>.excluded, the list of files to be
+// exluded is set in the settings (settings.excludes)
+function exclude_files() {
+ for (let i = 0; i < settings.excludes.length; i++) {
+ let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]);
+ if (fs.existsSync(exclude)) {
+ fs.renameSync(exclude, exclude + ".excluded");
+ }
+ }
+}
+
+// whether update.northstar_auto_update() has already been run before
+let is_auto_updating = false;
+
+// Handles auto updating Northstar.
+//
+// It uses isGameRunning() to ensure it doesn't run while the game is
+// running, as that may have all kinds of issues.
+update.northstar_autoupdate = () => {
+ if (! settings.nsupdate || ! fs.existsSync("viper.json") || settings.gamepath.length === 0) {
+ return;
+ }
+
+ if (is_auto_updating) {return}
+
+ async function _checkForUpdates() {
+ is_auto_updating = true;
+
+ console.log(lang("cli.autoupdates.checking"));
+
+ // Checks if NS is outdated
+ if (await northstar_update_available()) {
+ console.log(lang("cli.autoupdates.available"));
+ if (await is_running.game()) {
+ console.log(lang("cli.autoupdates.gamerunning"));
+ new Notification({
+ title: lang("gui.nsupdate.gaming.title"),
+ body: lang("gui.nsupdate.gaming.body")
+ }).show();
+ } else {
+ console.log(lang("cli.autoupdates.updatingns"));
+ update.northstar();
+ }
+ } else {
+ console.log(lang("cli.autoupdates.noupdate"));
+ }
+
+ setTimeout(
+ _checkForUpdates,
+ 15 * 60 * 1000
+ // interval in between each update check
+ // by default 15 minutes.
+ );
+ }
+
+ _checkForUpdates();
+}
+
+// returns whether an update is available for Northstar
+async function northstar_update_available() {
+ let local = version.northstar();
+ let distant = await requests.getLatestNsVersion();
+
+ if (distant == false) {
+ return false;
+ }
+
+ // checks if NS is outdated
+ if (local !== distant) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Updates Viper itself
+//
+// This uses electron updater to easily update and publish releases, it
+// simply fetches it from GitHub and updates if it's outdated, very
+// useful. Not much we have to do on our side.
+update.viper = (autoinstall) => {
+ const { autoUpdater } = require("electron-updater");
+
+ if (! autoUpdater.isUpdaterActive()) {
+ if (settings.nsupdate) {
+ update.northstar_autoupdate();
+ }
+
+ return cli.exit();
+ }
+
+ if (autoinstall) {
+ autoUpdater.on("update-downloaded", (info) => {
+ autoUpdater.quitAndInstall();
+ });
+ }
+
+ autoUpdater.on("error", (info) => {cli.exit(1)});
+ autoUpdater.on("update-not-available", (info) => {
+ // only check for NS updates if Viper itself has no updates and
+ // if NS auto updates is enabled.
+ if (settings.nsupdate || cli.hasArgs()) {
+ update.northstar_autoupdate();
+ }
+ cli.exit();
+ });
+
+ autoUpdater.checkForUpdatesAndNotify();
+}
+
+// Installs/Updates Northstar
+//
+// If Northstar is already installed it'll be an update, otherwise it'll
+// install it. It simply downloads the Northstar archive from GitHub, if
+// it's outdated, then extracts it into the game path.
+//
+// As to handle not overwriting files we rename certain files to
+// <file>.excluded, then rename them back after the extraction. The
+// unzip module does not support excluding files directly.
+update.northstar = async () => {
+ if (await is_running.game()) {
+ console.log(lang("cli.autoupdates.gamerunning"));
+ return false;
+ }
+
+ if (! gamepath.exists()) {return}
+
+ ipcMain.emit("ns-update-event", "cli.update.checking");
+ console.log(lang("cli.update.checking"));
+ let ns_version = version.northstar();
+
+ const latest_version = await requests.getLatestNsVersion();
+ console.log(latest_version)
+ if (latest_version == false) {
+ ipcMain.emit("ns-update-event", "cli.update.noInternet");
+ return;
+ }
+
+ // Makes sure it is not already the latest version
+ if (! await northstar_update_available()) {
+ ipcMain.emit("ns-update-event", "cli.update.uptodate.short");
+ console.log(lang("cli.update.uptodate"), ns_version);
+
+ win.log(lang("gui.update.uptodate"));
+ cli.exit();
+ return;
+ } else {
+ if (ns_version != "unknown") {
+ console.log(lang("cli.update.current"), ns_version);
+ };
+
+ console.log(lang("cli.update.downloading") + ":", latest_version);
+ ipcMain.emit("ns-update-event", "cli.update.downloading");
+ }
+
+ exclude_files();
+
+ // Start the download of the zip
+ https.get(requests.getLatestNsVersionLink(), (res) => {
+ let stream = fs.createWriteStream(settings.zip);
+ res.pipe(stream);
+
+ let received = 0;
+ // Progress messages, we should probably switch this to
+ // percentage instead of how much is downloaded.
+ res.on("data", (chunk) => {
+ received += chunk.length;
+ ipcMain.emit("ns-update-event", lang("gui.update.downloading") + " " + (received / 1024 / 1024).toFixed(1) + "mb");
+ })
+
+ stream.on("finish", () => {
+ stream.close();
+ let extract = fs.createReadStream(settings.zip);
+
+ win.log(lang("gui.update.extracting"));
+ ipcMain.emit("ns-update-event", "gui.update.extracting");
+ console.log(lang("cli.update.downloaddone"));
+ // Extracts the zip, this is the part where we're actually
+ // installing Northstar.
+ extract.pipe(unzip.Extract({path: settings.gamepath}))
+
+ let max = received;
+ received = 0;
+
+ extract.on("data", (chunk) => {
+ received += chunk.length;
+ let percent = Math.floor(received / max * 100);
+ ipcMain.emit("ns-update-event", lang("gui.update.extracting") + " " + percent + "%");
+ })
+
+ extract.on("end", () => {
+ extract.close();
+ ipcMain.emit("getversion");
+
+ restore_excluded_files();
+
+ ipcMain.emit("gui-getmods");
+ ipcMain.emit("get-version");
+ ipcMain.emit("ns-update-event", "cli.update.uptodate.short");
+ win.log(lang("gui.update.finished"));
+ console.log(lang("cli.update.finished"));
+ cli.exit();
+ })
+ })
+ })
+}
+
+module.exports = update;
diff --git a/src/modules/window.js b/src/modules/window.js
new file mode 100644
index 0000000..3b5c76f
--- /dev/null
+++ b/src/modules/window.js
@@ -0,0 +1,15 @@
+const ipcMain = require("electron").ipcMain;
+
+let win = {};
+
+// logs into the dev tools of the renderer
+win.log = (msg) => {
+ ipcMain.emit("win-log", msg, msg);
+}
+
+// sends an alert to the renderer
+win.alert = (msg) => {
+ ipcMain.emit("win-alert", msg, msg);
+}
+
+module.exports = win;