diff options
Diffstat (limited to 'src/modules')
-rw-r--r-- | src/modules/gamepath.js | 86 | ||||
-rw-r--r-- | src/modules/is_running.js | 66 | ||||
-rw-r--r-- | src/modules/kill.js | 27 | ||||
-rw-r--r-- | src/modules/launch.js | 34 | ||||
-rw-r--r-- | src/modules/mods.js | 20 | ||||
-rw-r--r-- | src/modules/update.js | 239 | ||||
-rw-r--r-- | src/modules/window.js | 15 |
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; |