aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/lang.js9
-rw-r--r--src/app/launcher.js5
-rw-r--r--src/app/main.js22
-rw-r--r--src/cli.js19
-rw-r--r--src/index.js49
-rw-r--r--src/lang.js22
-rw-r--r--src/requests.js2
-rw-r--r--src/utils.js104
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);
diff --git a/src/cli.js b/src/cli.js
index d5ee3ad..cef2295 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -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"))