diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/lang.js | 9 | ||||
-rw-r--r-- | src/app/launcher.js | 5 | ||||
-rw-r--r-- | src/app/main.js | 22 | ||||
-rw-r--r-- | src/cli.js | 19 | ||||
-rw-r--r-- | src/index.js | 49 | ||||
-rw-r--r-- | src/lang.js | 22 | ||||
-rw-r--r-- | src/requests.js | 2 | ||||
-rw-r--r-- | src/utils.js | 104 |
8 files changed, 200 insertions, 32 deletions
diff --git a/src/app/lang.js b/src/app/lang.js index 5cc9708..6fdcd8d 100644 --- a/src/app/lang.js +++ b/src/app/lang.js @@ -1,12 +1,19 @@ +// Replaces strings in the HTML will language strings properly. This +// searches for %%<string>%%, aka, %%gui.exit%% will be replaced with +// "Exit", this works without issues. function setlang() { + // Finds %%%% strings html = document.body.innerHTML.split("%%"); for (let i = 0; i < html.length; i++) { + // Simply checks to make sure it is actually a lang string. if (html[i][0] != " " && html[i][html[i].length - 1] != " ") { + // Replaces it with it's string html[i] = lang(html[i]) } } - + + // Replaces the original HTML with the translated/replaced HTML document.body.innerHTML = html.join(""); } diff --git a/src/app/launcher.js b/src/app/launcher.js index 2c8d123..60c0d18 100644 --- a/src/app/launcher.js +++ b/src/app/launcher.js @@ -1,5 +1,7 @@ const markdown = require("marked").parse; +// Changes the main page +// This is the tabs in the sidebar function page(page) { let pages = document.querySelectorAll(".mainContainer .contentContainer") let btns = document.querySelectorAll(".gamesContainer button") @@ -18,6 +20,7 @@ function page(page) { }; page(0) +// Updates the Viper release notes ipcRenderer.on("vp-notes", (event, response) => { let content = ""; @@ -28,11 +31,13 @@ ipcRenderer.on("vp-notes", (event, response) => { vpReleaseNotes.innerHTML = markdown(content); }); + async function loadVpReleases() { ipcRenderer.send("get-vp-notes"); }; loadVpReleases(); +// Updates the Northstar release notes ipcRenderer.on("ns-notes", (event, response) => { let content = ""; diff --git a/src/app/main.js b/src/app/main.js index 893572a..5f0cc9a 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -5,6 +5,7 @@ const { ipcRenderer, shell } = require("electron"); const lang = require("../lang"); let shouldInstallNorthstar = false; +// Base settings var settings = { gamepath: "", autoupdate: true, @@ -16,8 +17,10 @@ var settings = { ] } +// Sets the lang to the system default ipcRenderer.send("setlang", settings.lang); +// Loads the settings if (fs.existsSync("viper.json")) { settings = {...settings, ...JSON.parse(fs.readFileSync("viper.json", "utf8"))}; settings.zip = path.join(settings.gamepath + "/northstar.zip"); @@ -38,11 +41,11 @@ function update() {ipcRenderer.send("update")} // Reports to the main process about game path status. // @param {boolean} value is game path loaded - function setpath(value = false) { ipcRenderer.send("setpath", value); } +// Tells the main process to launch or install Northstar function launch() { if (shouldInstallNorthstar) { update(); @@ -51,17 +54,23 @@ function launch() { ipcRenderer.send("launch"); } } + +// Tells the main process to launch the vanilla game function launchVanilla() {ipcRenderer.send("launchVanilla")} +// In conjunction with utils.js' winLog(), it'll send log messages in +// the devTools from utils.js function log(msg) { console.log(msg); - // welcome.innerHTML = msg; } +// Disables or enables certain buttons when for example +// updating/installing Northstar. function setButtons(state) { playNsBtn.disabled = !state; } +// Frontend part of updating Northstar ipcRenderer.on("ns-update-event", (event, key) => { document.getElementById("update").innerText = `(${lang(key)})`; console.log(key); @@ -90,6 +99,7 @@ function select(entry) { } } +// Mod selection function selected(all) { let selected = ""; if (all) { @@ -138,17 +148,21 @@ function selected(all) { } } +// Tells the main process to install a mod function installmod() { ipcRenderer.send("installmod") } +// Frontend part of settings a new game path ipcRenderer.on("newpath", (event, newpath) => { settings.gamepath = newpath; }) +// Continuation of log() ipcRenderer.on("log", (event, msg) => {log(msg)}) ipcRenderer.on("alert", (event, msg) => {alert(msg)}) +// Updates the installed mods ipcRenderer.on("mods", (event, mods) => { modcount.innerHTML = `${lang("gui.mods.count")} ${mods.all.length}`; modsdiv.innerHTML = ""; @@ -169,6 +183,7 @@ ipcRenderer.on("mods", (event, mods) => { select(lastselected); }) +// Updates version numbers ipcRenderer.on("version", (event, versions) => { vpversion.innerText = versions.vp; nsversion.innerText = versions.ns; @@ -187,17 +202,20 @@ ipcRenderer.on("version", (event, versions) => { } }); ipcRenderer.send("getversion"); +// When an update is available it'll ask the user about it ipcRenderer.on("updateavailable", () => { if (confirm(lang("gui.update.available"))) { ipcRenderer.send("updatenow"); } }) +// Error out when no game path is set ipcRenderer.on("nopathselected", () => { alert(lang("gui.gamepath.must")); exit(); }); +// Error out when game path is wrong ipcRenderer.on("wrongpath", () => { alert(lang("gui.gamepath.wrong")); setpath(false); @@ -8,6 +8,9 @@ const cli = app.commandLine; const lang = require("./lang"); function hasArgs() { + // Makes sure the GUI isn't launched. + // TODO: Perhaps we should get a better way of doing this, at the + // very least we should use a switch case here. if (cli.hasSwitch("cli") || cli.hasSwitch("help") || cli.hasSwitch("mods") || @@ -24,11 +27,20 @@ function hasArgs() { } else {return false} } +// Exits the CLI, when run without CLI being on it'll do nothing, this +// is needed as without even if no code is executed it'll continue to +// run as Electron is still technically running. function exit(code) { if (hasArgs()) {process.exit(code)} } +// General CLI initialization +// +// A lot of the CLI is handled through events sent back to the main +// process or utils.js to handle, this is because we re-use these events +// for the renderer as well. async function init() { + // --help menu/argument if (cli.hasSwitch("help")) { console.log(`options: --help ${lang("cli.help.help")} @@ -48,10 +60,14 @@ async function init() { exit(); } + // --update if (cli.hasSwitch("update")) {ipcMain.emit("update")} + // --version if (cli.hasSwitch("version")) {ipcMain.emit("versioncli")} + // --setpath if (cli.hasSwitch("setpath")) { + // Checks to verify the path is legitimate if (cli.getSwitchValue("setpath") != "") { ipcMain.emit("setpathcli", cli.getSwitchValue("setpath")); } else { @@ -60,6 +76,7 @@ async function init() { } } + // --launch if (cli.hasSwitch("launch")) { switch(cli.getSwitchValue("launch")) { case "vanilla": @@ -71,10 +88,12 @@ async function init() { } } + // Mod related args, --installmod, --removemod, --togglemod if (cli.hasSwitch("installmod")) {ipcMain.emit("installmod")} if (cli.hasSwitch("removemod")) {ipcMain.emit("removemod", "", cli.getSwitchValue("removemod"))} if (cli.hasSwitch("togglemod")) {ipcMain.emit("togglemod", "", cli.getSwitchValue("togglemod"))} + // Prints out the list of mods if (cli.hasSwitch("mods")) {ipcMain.emit("getmods")} } diff --git a/src/index.js b/src/index.js index 1dce746..3f50840 100644 --- a/src/index.js +++ b/src/index.js @@ -10,12 +10,20 @@ const utils = require("./utils"); const cli = require("./cli"); const requests = require("./requests"); +// Starts the actual BrowserWindow, which is only run when using the +// GUI, for the CLI this function is never called. function start() { win = new BrowserWindow({ width: 1000, height: 600, + + // Hides the window initially, it'll be shown when the DOM is + // loaded, as to not cause visual issues. show: false, title: "Viper", + + // In the future we may want to allow the user to resize the window, + // as it's fairly responsive, but for now we won't allow that. resizable: false, titleBarStyle: "hidden", frame: false, @@ -26,8 +34,10 @@ function start() { }, }); + // When --debug is added it'll open the dev tools if (cli.hasParam("debug")) {win.openDevTools()} + // General setup win.removeMenu(); win.loadFile(__dirname + "/app/index.html"); @@ -48,11 +58,15 @@ function start() { win.webContents.send("updateavailable") }); + // Updates and restarts Viper, if user says yes to do so. + // Otherwise it'll do it on the next start up. ipcMain.on("updatenow", () => { autoUpdater.quitAndInstall(); }) } +// General events used to handle utils.js stuff without requiring the +// module inside the file that sent the event. { ipcMain.on("installmod", () => { if (cli.hasArgs()) { utils.mods.install(cli.param("installmod")) @@ -80,19 +94,6 @@ ipcMain.on("setpath", (event, value) => { } }); -ipcMain.on("newpath", (event, newpath) => { - if (newpath === false && !win.isVisible()) { - win.webContents.send("nopathselected"); - } else { - _sendVersionsInfo(); - if (!win.isVisible()) { - win.show(); - } - } -}); ipcMain.on("wrongpath", (event) => { - win.webContents.send("wrongpath"); -}); - function _sendVersionsInfo() { win.webContents.send("version", { ns: utils.getNSVersion(), @@ -101,8 +102,10 @@ function _sendVersionsInfo() { }); } -ipcMain.on("getversion", () => _sendVersionsInfo()); +// Sends the version info back to the renderer +ipcMain.on("getversion", () => {_sendVersionsInfo()}); +// Prints out version info for the CLI ipcMain.on("versioncli", () => { console.log("Viper: v" + require("../package.json").version); console.log("Northstar: " + utils.getNSVersion()); @@ -132,9 +135,25 @@ ipcMain.on("getmods", (event) => { cli.exit(0); } }) +// } +ipcMain.on("newpath", (event, newpath) => { + if (newpath === false && !win.isVisible()) { + win.webContents.send("nopathselected"); + } else { + _sendVersionsInfo(); + if (!win.isVisible()) { + win.show(); + } + } +}); ipcMain.on("wrongpath", (event) => { + win.webContents.send("wrongpath"); +}); + +// Ensures ./ is the config folder where viper.json is located. process.chdir(app.getPath("appData")); +// Starts the GUI or CLI if (cli.hasArgs()) { if (cli.hasParam("updatevp")) { utils.updatevp(true); @@ -148,9 +167,11 @@ if (cli.hasArgs()) { }) } +// Returns cached requests ipcMain.on("get-ns-notes", async () => { win.webContents.send("ns-notes", await requests.getNsReleaseNotes()); }); + ipcMain.on("get-vp-notes", async () => { win.webContents.send("vp-notes", await requests.getVpReleaseNotes()); }); diff --git a/src/lang.js b/src/lang.js index d404e8e..b3cbb43 100644 --- a/src/lang.js +++ b/src/lang.js @@ -1,15 +1,23 @@ const fs = require("fs"); -var lang = "en"; +var lang = "en"; // Default language + +// Loads fallback/default language strings var langDef = JSON.parse(fs.readFileSync(__dirname + `/lang/en.json`, "utf8")); + +// If settins are set it'll try to set the language to that instead of +// the default, however if it can't find it, it'll still fallback to the +// default language. This might happen as the default language is +// retrieved from the renderer's navigator.language, which may have +// languages we don't support yet. if (fs.existsSync("viper.json")) { lang = JSON.parse(fs.readFileSync("viper.json", "utf8")).lang; - if (! lang) {lang = "en"} + if (! lang) {lang = "en"} // Uses fallback, if language isn't set if (! fs.existsSync(__dirname + `/lang/${lang}.json`)) { if (fs.existsSync(__dirname + `/lang/${lang.replace(/-.*$/, "")}.json`)) { lang = lang.replace(/-.*$/, ""); } else { - lang = "en"; + lang = "en"; // Uses fallback if language doesn't exist } } } @@ -17,12 +25,14 @@ if (fs.existsSync("viper.json")) { var langObj = JSON.parse(fs.readFileSync(__dirname + `/lang/${lang}.json`, "utf8")); module.exports = (string) => { - if (langObj[string]) { + if (langObj[string]) { // Returns string from language return langObj[string]; - } else { - if (langDef[string]) { + } else { // If string doesn't exist + if (langDef[string]) { // Retrieves from default lang instead return langDef[string]; } else { + // If it's not in the default lang either, it returns the + // string, this is absolute fallback. return string; } } diff --git a/src/requests.js b/src/requests.js index 5452b0d..16b1330 100644 --- a/src/requests.js +++ b/src/requests.js @@ -71,6 +71,7 @@ function getLatestNsVersionLink() { return cache[NORTHSTAR_LATEST_RELEASE_KEY]["body"].assets[0].browser_download_url; } +// Returns and caches the Northstar release notes async function getNsReleaseNotes() { return new Promise(resolve => { let cache = _getRequestsCache(); @@ -107,6 +108,7 @@ async function getNsReleaseNotes() { }); } +// Returns and caches the Viper release notes async function getVpReleaseNotes() { return new Promise(resolve => { let cache = _getRequestsCache(); diff --git a/src/utils.js b/src/utils.js index e6fa5b0..50ffed0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -17,17 +17,22 @@ const { https } = require("follow-redirects"); process.chdir(app.getPath("appData")); +// Base settings var settings = { gamepath: "", lang: "en-US", autoupdate: true, zip: "/northstar.zip", + + // These files won't be overwritten when installing/updating + // Northstar, useful for config file. excludes: [ "ns_startup_args.txt", "ns_startup_args_dedi.txt" ] } +// Creates the settings file with the base settings if it doesn't exist. if (fs.existsSync("viper.json")) { settings = {...settings, ...JSON.parse(fs.readFileSync("viper.json", "utf8"))}; settings.zip = path.join(settings.gamepath + "/northstar.zip"); @@ -35,10 +40,14 @@ if (fs.existsSync("viper.json")) { console.log(lang("general.missingpath")); } - +// A simple function that checks if the game is running, which we use to +// not update Northstar when it is running. async function isGameRunning() { return new Promise(resolve => { let procs = ["Titanfall2.exe", "Titanfall2-unpacked.exe", "NorthstarLauncher.exe"]; + // 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"; @@ -59,6 +68,10 @@ async function isGameRunning() { }); } +// 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. northstar_auto_updates: { if (!settings.autoupdate || !fs.existsSync("viper.json") || settings.gamepath.length === 0) { break northstar_auto_updates; @@ -69,6 +82,7 @@ northstar_auto_updates: { let distantVersion = await requests.getLatestNsVersion(); console.log(lang("cli.autoupdates.checking")); + // Checks if NS is outdated if (localVersion !== distantVersion) { console.log(lang("cli.autoupdates.available")); if (await isGameRunning()) { @@ -87,7 +101,9 @@ northstar_auto_updates: { setTimeout( _checkForUpdates, - 15 * 60 * 1000 // update checking interval must be bigger than cache validity duration + 15 * 60 * 1000 + // interval in between each update check + // by default 15 minutes. ); } @@ -95,10 +111,14 @@ northstar_auto_updates: { } +// 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. function setpath(win) { - if (! win) { + if (! win) { // CLI settings.gamepath = cli.param("setpath"); - } else { + } else { // GUI dialog.showOpenDialog({properties: ["openDirectory"]}).then(res => { if (res.canceled) { ipcMain.emit("newpath", null, false); @@ -121,10 +141,15 @@ function setpath(win) { cli.exit(); } +// As to not have to do the same one liner a million times, this +// function exists, as the name suggests, it simply writes the current +// settings to the disk. function saveSettings() { fs.writeFileSync(app.getPath("appData") + "/viper.json", JSON.stringify(settings)); } +// Returns the current Northstar version +// If not installed it'll return "unknown" function getNSVersion() { var versionFilePath = path.join(settings.gamepath, "ns_version.txt"); @@ -137,11 +162,11 @@ function getNSVersion() { } -/** - * Loads up Titanfall|2 version from gameversion.txt file. - * TODO This file is present on Origin install, should check if it's present with - * Steam install as well. - */ +// Returns the Titanfall 2 version from gameversion.txt file. +// If it fails it simply returns "unknown" +// +// TODO: This file is present on Origin install, should check if it's +// present with Steam install as well. function getTF2Version() { var versionFilePath = path.join(settings.gamepath, "gameversion.txt"); if (fs.existsSync(versionFilePath)) { @@ -151,7 +176,17 @@ function getTF2Version() { } } +// 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. async function update() { + // Renames excluded files to <file>.excluded for (let i = 0; i < settings.excludes.length; i++) { let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]); if (fs.existsSync(exclude)) { @@ -165,6 +200,7 @@ async function update() { const latestAvailableVersion = await requests.getLatestNsVersion(); + // Makes sure it is not already the latest version if (version === latestAvailableVersion) { ipcMain.emit("ns-update-event", "cli.update.uptodate.short"); console.log(lang("cli.update.uptodate"), version); @@ -179,11 +215,14 @@ async function update() { ipcMain.emit("ns-update-event", "cli.update.downloading"); } + // 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"); @@ -194,11 +233,14 @@ async function update() { winLog(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. fs.createReadStream(settings.zip).pipe(unzip.Extract({path: settings.gamepath})) .on("finish", () => { fs.writeFileSync(path.join(settings.gamepath, "ns_version.txt"), latestAvailableVersion); ipcMain.emit("getversion"); + // Renames excluded files to their original name for (let i = 0; i < settings.excludes.length; i++) { let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]); if (fs.existsSync(exclude + ".excluded")) { @@ -216,6 +258,11 @@ async function update() { }) } +// 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. function updatevp(autoinstall) { const { autoUpdater } = require("electron-updater"); @@ -231,6 +278,10 @@ function updatevp(autoinstall) { autoUpdater.checkForUpdatesAndNotify(); } +// Launches the game +// +// Either Northstar or Vanilla. Linux support is not currently a thing, +// however it'll be added at some point. function launch(version) { if (process.platform == "linux") { console.error("error:", lang("cli.launch.linuxerror")) @@ -250,16 +301,27 @@ function launch(version) { } } +// Logs into the dev tools of the renderer function winLog(msg) { ipcMain.emit("winLog", msg, msg); } +// Sends an alert to the renderer function winAlert(msg) { ipcMain.emit("winAlert", msg, msg); } +// Used to manage mods. +// +// We can both get list of disabled mods, remove/install/toggle mods and +// other things akin to that, all kinds of mod related stuff let modpath = path.join(settings.gamepath, "R2Northstar/mods"); const mods = { + // Returns a list of mods + // + // It'll return 3 arrays, all, enabled, disabled. all being a + // combination of the other two, enabled being enabled mods, and you + // guessed it, disabled being disabled mods. list: () => { if (getNSVersion() == "unknown") { winLog(lang("general.notinstalled")) @@ -319,6 +381,13 @@ const mods = { all: [...mods, ...disabled] }; }, + + // Gets information about a mod + // + // Folder name, version, name and whatever else is in the mod.json, + // keep in mind if the mod developer didn't format their JSON file + // the absolute basics will be provided and we can't know the + // version or similar. get: (mod) => { if (getNSVersion() == "unknown") { winLog(lang("general.notinstalled")) @@ -337,6 +406,12 @@ const mods = { return false; }, + + // Installs mods from a file path + // + // Either a zip or folder is supported, we'll also try to search + // inside the zip or folder to see if buried in another folder or + // not, as sometimes that's the case. install: (mod) => { if (getNSVersion() == "unknown") { winLog(lang("general.notinstalled")) @@ -411,6 +486,10 @@ const mods = { }catch(err) {return notamod()} } }, + // Removes mods + // + // Takes in the names of the mod then removes it, no confirmation, + // that'd be up to the GUI. remove: (mod) => { if (getNSVersion() == "unknown") { winLog(lang("general.notinstalled")) @@ -454,6 +533,13 @@ const mods = { cli.exit(1); } }, + + // Toggles mods + // + // If a mod is enabled it'll disable it, vice versa it'll enable it + // if it's disabled. You could have a direct .disable() function if + // you checked for if a mod is already disable and if not run the + // function. However we currently have no need for that. toggle: (mod, fork) => { if (getNSVersion() == "unknown") { winLog(lang("general.notinstalled")) |