aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/browser.js264
-rw-r--r--src/app/icons/no-image.pngbin0 -> 1959 bytes
-rw-r--r--src/app/icons/viper.pngbin137107 -> 59028 bytes
-rw-r--r--src/app/index.html18
-rw-r--r--src/app/main.css298
-rw-r--r--src/app/main.js23
-rw-r--r--src/app/toast.js59
-rw-r--r--src/assets/icons/512x512.pngbin137107 -> 59028 bytes
-rw-r--r--src/assets/icons/icon.icobin201799 -> 194797 bytes
-rw-r--r--src/extras/findgame.js67
-rw-r--r--src/extras/requests.js (renamed from src/requests.js)0
-rw-r--r--src/index.js16
-rw-r--r--src/lang/en.json21
-rw-r--r--src/lang/es.json119
-rw-r--r--src/lang/fr.json21
-rw-r--r--src/lang/maintainers.json13
-rw-r--r--src/utils.js240
17 files changed, 1071 insertions, 88 deletions
diff --git a/src/app/browser.js b/src/app/browser.js
new file mode 100644
index 0000000..ded12fa
--- /dev/null
+++ b/src/app/browser.js
@@ -0,0 +1,264 @@
+const Fuse = require("fuse.js");
+var fuse;
+var packages = [];
+
+var Browser = {
+ maxentries: 50,
+ toggle: (state) => {
+ if (state) {
+ browser.scrollTo(0, 0);
+ overlay.classList.add("shown")
+ browser.classList.add("shown")
+
+ if (browserEntries.querySelectorAll(".el").length == 0) {
+ Browser.loadfront();
+ }
+ return
+ } else if (! state) {
+ if (state != undefined) {
+ overlay.classList.remove("shown")
+ browser.classList.remove("shown")
+ return
+ }
+ }
+
+ browser.scrollTo(0, 0);
+ overlay.classList.toggle("shown")
+ browser.classList.toggle("shown")
+ },
+ loadfront: async () => {
+ Browser.loading();
+
+ if (packages.length < 1) {
+ packages = await (await fetch("https://northstar.thunderstore.io/api/v1/package/")).json();
+
+ fuse = new Fuse(packages, {
+ keys: ["full_name"]
+ })
+ }
+
+ for (let i in packages) {
+ if (i == Browser.maxentries) {Browser.endoflist();break}
+ new BrowserElFromObj(packages[i]);
+ }
+ },
+ loading: (string) => {
+ if (string) {
+ browserEntries.innerHTML = `<div class="loading">${string}</div>`;
+ }
+
+ if (! browserEntries.querySelector(".loading")) {
+ browserEntries.innerHTML = `<div class="loading">${lang('gui.browser.loading')}</div>`;
+ }
+ },
+ endoflist: () => {
+ browserEntries.innerHTML += `<div class="message">${lang('gui.browser.endoflist')}</div>`
+ },
+ search: (string) => {
+ Browser.loading();
+ let res = fuse.search(string);
+
+ if (res.length < 1) {
+ Browser.loading("No results...")
+ return
+ }
+
+ for (let i = 0; i < res.length; i++) {
+ if (i == Browser.maxentries) {Browser.endoflist();break}
+ new BrowserElFromObj(res[i].item);
+ }
+ },
+ setbutton: (mod, string) => {
+ mod = normalize(mod);
+ if (document.getElementById(mod)) {
+ let elems = document.querySelectorAll(`#${mod}`);
+
+ for (let i = 0; i < elems.length; i++) {
+ elems[i].querySelector(".text button").innerHTML = string;
+ }
+ } else {
+ let make = (str) => {
+ if (document.getElementById(str)) {
+ return Browser.setbutton(str, string);
+ } else {
+ return false;
+ }
+ }
+
+ setTimeout(() => {
+ for (let i = 0; i < modsobj.all.length; i++) {
+ let modname = normalize(modsobj.all[i].Name);
+ let modfolder = normalize(modsobj.all[i].FolderName);
+
+ if (mod.includes(modname)) {
+ if (! make(modname)) {
+ if (modsobj.all[i].ManifestName) {
+ make(normalize(modsobj.all[i].ManifestName));
+ }
+ }
+ }
+ else if (mod.includes(modfolder)) {make(modfolder);break}
+ }
+ }, 1501)
+ }
+ }
+}
+
+document.body.addEventListener("keyup", (e) => {
+ if (e.key == "Escape") {Browser.toggle(false)}
+})
+
+function BrowserElFromObj(obj) {
+ let pkg = {...obj, ...obj.versions[0]};
+
+ new BrowserEl({
+ title: pkg.name,
+ image: pkg.icon,
+ author: pkg.owner,
+ url: pkg.package_url,
+ download: pkg.download_url,
+ version: pkg.version_number,
+ description: pkg.description
+ })
+}
+
+function BrowserEl(properties) {
+ properties = {
+ title: "No name",
+ version: "1.0.0",
+ image: "icons/no-image.png",
+ author: "Unnamed Pilot",
+ description: "No description",
+ ...properties
+ }
+
+ if (properties.version[0] != "v") {
+ properties.version = "v" + properties.version;
+ }
+
+ if (browserEntries.querySelector(".loading")) {
+ browserEntries.innerHTML = "";
+ }
+
+ let installstr = lang("gui.browser.install");
+
+ if (normalize(modsdiv.innerText.split("\n")).includes(normalize(properties.title))) {
+ installstr = lang("gui.browser.reinstall");
+
+ for (let i = 0; i < modsobj.all.length; i++) {
+ if (normalize(modsobj.all[i].Name) == normalize(properties.title)
+ && "v" + modsobj.all[i].Version != properties.version) {
+
+ installstr = lang("gui.browser.update");
+ }
+ }
+ } else {
+ for (let i = 0; i < modsobj.all.length; i++) {
+ let title = normalize(properties.title);
+ let folder = normalize(modsobj.all[i].FolderName);
+ let manifestname = null;
+ if (modsobj.all[i].ManifestName) {
+ manifestname = normalize(modsobj.all[i].ManifestName);
+ }
+
+ if (title.includes(folder) || title.includes(manifestname)) {
+ installstr = lang("gui.browser.reinstall");
+
+ if (folder == title
+ && "v" + modsobj.all[i].Version != properties.version) {
+
+ installstr = lang("gui.browser.update");
+ }
+ }
+ }
+ }
+
+ browserEntries.innerHTML += `
+ <div class="el" id="${normalize(properties.title)}">
+ <div class="image">
+ <img src="${properties.image}">
+ </div>
+ <div class="text">
+ <div class="title">${properties.title}</div>
+ <div class="description">${properties.description}</div>
+ <button onclick="installFromURL('${properties.download}')">${installstr}</button>
+ <button onclick="require('electron').shell.openExternal('${properties.url}')">${lang('gui.browser.info')}</button>
+ <button class="visual">${properties.version}</button>
+ <button class="visual">${lang("gui.browser.madeby")} ${properties.author}</button>
+ </div>
+ </div>
+ `
+}
+
+ipcRenderer.on("removedmod", (event, mod) => {
+ setButtons(true);
+ Browser.setbutton(mod.name, lang("gui.browser.install"));
+ if (mod.manifestname) {
+ Browser.setbutton(mod.manifestname, lang("gui.browser.install"));
+ }
+})
+
+ipcRenderer.on("failedmod", (event, modname) => {
+ setButtons(true);
+ new Toast({
+ timeout: 10000,
+ scheme: "error",
+ title: lang("gui.toast.title.failed"),
+ description: lang("gui.toast.desc.failed")
+ })
+})
+
+ipcRenderer.on("installedmod", (event, mod) => {
+ setButtons(true);
+ Browser.setbutton(mod.name, lang("gui.browser.reinstall"));
+
+ if (mod.malformed) {
+ new Toast({
+ timeout: 8000,
+ scheme: "warning",
+ title: lang("gui.toast.title.malformed"),
+ description: mod.name + " " + lang("gui.toast.desc.malformed")
+ })
+ }
+
+ new Toast({
+ scheme: "success",
+ title: lang("gui.toast.title.installed"),
+ description: mod.name + " " + lang("gui.toast.desc.installed")
+ })
+})
+
+function normalize(items) {
+ let main = (string) => {
+ return string.replaceAll(" ", "").replaceAll(".", "").replaceAll("-", "").replaceAll("_", "").toLowerCase()
+ }
+ if (typeof items == "string") {
+ return main(items)
+ } else {
+ let newArray = [];
+ for (let i = 0; i < items.length; i++) {
+ newArray.push(main(items[i]));
+ }
+
+ return newArray;
+ }
+}
+
+let searchtimeout;
+let searchstr = "";
+search.addEventListener("keyup", () => {
+ clearTimeout(searchtimeout);
+
+ if (searchstr != search.value) {
+ if (search.value.replaceAll(" ", "") == "") {
+ searchstr = "";
+ Browser.loadfront();
+ return
+ }
+
+ searchtimeout = setTimeout(() => {
+ Browser.search(search.value);
+ searchstr = search.value;
+ }, 500)
+ }
+})
diff --git a/src/app/icons/no-image.png b/src/app/icons/no-image.png
new file mode 100644
index 0000000..43265d1
--- /dev/null
+++ b/src/app/icons/no-image.png
Binary files differ
diff --git a/src/app/icons/viper.png b/src/app/icons/viper.png
index 281f3dd..69b1cce 100644
--- a/src/app/icons/viper.png
+++ b/src/app/icons/viper.png
Binary files differ
diff --git a/src/app/index.html b/src/app/index.html
index fe383a0..8222416 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -7,11 +7,26 @@
</head>
<body>
<div id="bgHolder"></div>
+ <div id="toasts"></div>
<div id="winbtns">
<div id="minimize" onclick="ipcRenderer.send('minimize')"></div>
<div id="close" onclick="ipcRenderer.send('exit')"></div>
</div>
+
+
+ <div id="overlay" onclick="Browser.toggle(false)"></div>
+ <div id="browser">
+ <div id="misc">
+ <input id="search" placeholder="%%gui.browser.search%%">
+ <button id="close" onclick="Browser.toggle(false)">
+ <img src="icons/close.png">
+ </button>
+ </div>
+ <div id="browserEntries">
+ <div class="loading">%%gui.browser.loading%%</div>
+ </div>
+ </div>
<nav class="gamesContainer">
<button id="vpBtn" onclick="page(0)"></button>
@@ -79,6 +94,7 @@
<button id="togglemod" onclick="selected().toggle()">%%gui.mods.toggle%%</button>
<button id="toggleall" onclick="selected(true).toggle(true)">%%gui.mods.toggleall%%</button>
<button id="installmod" onclick="installmod()">%%gui.mods.install%%</button>
+ <button id="installmod" onclick="Browser.toggle(true)">%%gui.mods.find%%</button>
</div>
</div>
</div>
@@ -102,6 +118,8 @@
<script src="lang.js"></script>
<script src="main.js"></script>
+ <script src="toast.js"></script>
+ <script src="browser.js"></script>
<script src="launcher.js"></script>
</body>
</html>
diff --git a/src/app/main.css b/src/app/main.css
index de0db46..a532490 100644
--- a/src/app/main.css
+++ b/src/app/main.css
@@ -9,7 +9,11 @@
--selbg: rgba(80, 80, 80, 0.5);
--redbg: linear-gradient(45deg, var(--red), #FA4343);
--bluebg: linear-gradient(45deg, var(--blue), #7380ED);
+}
+#browser, #modsdiv {
+ outline: 1px solid #444444;
+ border: 3px solid var(--bg);
}
::-webkit-scrollbar {
@@ -26,12 +30,191 @@
background: var(--red);
}
+::selection {
+ color: black;
+ background: var(--red);
+}
+
+body {
+ margin: 0;
+ overflow: hidden;
+ user-select: none;
+}
+
+body, button, input {font-family: "Roboto", sans-serif}
+
+button {outline: none}
+b, strong {font-weight: 700}
+body, input, button {font-weight: 500}
+
+button {
+ border: none;
+ color: white;
+ outline: none;
+ cursor: pointer;
+ font-weight: 700;
+ padding: 5px 10px;
+ border-radius: 5px;
+ transition: 0.2s ease-in-out;
+}
+
.playBtn, .gamesContainer button, #winbtns div {
cursor: pointer;
}
-#winbtns {
+#browser {
+ --spacing: var(--padding);
+
+ z-index: 2;
+ opacity: 0.0;
+ position: fixed;
+ overflow-y: scroll;
+ top: var(--spacing);
+ pointer-events: none;
+ left: var(--spacing);
+ background: var(--bg);
+ right: var(--spacing);
+ bottom: var(--spacing);
+ transform: scale(0.98);
+ backdrop-filter: blur(15px);
+ border-radius: calc(var(--padding) / 3);
+ transition: opacity 0.15s ease-in-out, transform 0.15s ease-in-out;
+}
+
+#browser.shown {
+ opacity: 1.0;
+ pointer-events: all;
+ transform: scale(1.0);
+}
+
+#overlay {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
z-index: 1;
+ opacity: 0.0;
+ position: fixed;
+ background: var(--bg);
+ pointer-events: none;
+ transition: opacity 0.15s ease-in-out;
+}
+
+#overlay.shown {
+ opacity: 0.8;
+ pointer-events: all;
+}
+
+@keyframes fadein {
+ 0% {opacity: 0.0}
+ 100% {opacity: 1.0}
+}
+
+#browser .el, #browser #misc, #browser .loading {
+ --spacing: calc(var(--padding) / 2);
+ --height: calc(var(--padding) * 3);
+ --mischeight: calc(var(--padding) * 1.5);
+
+ animation-duration: 0.15s;
+ animation-iteration-count: 1;
+ animation-name: fadein;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in-out;
+
+ opacity: 0.0;
+ transition: 0.15s ease-in-out;
+}
+
+#browser .el, #browser #search, #browser #close {
+ color: white;
+ display: flex;
+ align-items: center;
+ height: var(--height);
+ margin: var(--spacing);
+ padding: var(--spacing);
+ background: var(--selbg);
+ border-radius: var(--spacing);
+ width: calc(100% - var(--spacing) * 4);
+}
+
+#browser #misc, #browser #search {
+ --height: var(--mischeight);
+}
+
+#browser #misc {
+ display: flex;
+}
+
+#browser #search {
+ border: none;
+ outline: none;
+ transition: filter 0.15s ease-in-out;
+ width: calc(100% - var(--spacing) * 2);
+}
+
+#browser #search:focus {
+ filter: brightness(1.5);
+}
+
+#browser #close {
+ --height: calc(var(--padding) * 1.5);
+
+ padding: 0px;
+ margin-left: 0px;
+ width: var(--height);
+}
+
+#browser #close img {
+ opacity: 0.6;
+ width: var(--height);
+ height: var(--height);
+ transform: scale(0.6);
+}
+
+#browser .loading {
+ width: 100%;
+ color: white;
+ display: flex;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ height: calc(100% - var(--mischeight) - var(--height));
+}
+
+#browser .message {
+ color: white;
+ text-align: center;
+ margin: var(--padding);
+ width: calc(100% - var(--padding));
+}
+
+#browser .el .image, #browser .el .image img {
+ width: var(--height);
+ height: var(--height);
+ margin-right: var(--spacing);
+ border-radius: var(--spacing);
+}
+
+#browser .el .text {
+ overflow: hidden;
+}
+
+#browser .el .title, #browser .el .description {
+ height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+#browser .el .title {font-size: 1.2em}
+#browser .el .description {font-size: 0.8em}
+#browser .el button {
+ background: var(--blue);
+ margin-top: var(--spacing);
+}
+
+#winbtns {
+ z-index: 2;
display: flex;
position: fixed;
top: var(--padding);
@@ -54,40 +237,18 @@
#winbtns div:hover {opacity: 1.0}
#winbtns div:active {transform: scale(0.98)}
-body {
- margin: 0;
- overflow: hidden;
- user-select: none;
- font-family: "Roboto", sans-serif;
-}
-
-button {outline: none}
-b, strong {font-weight: 700}
-body, input, button {font-weight: 500}
-
-button {
- border: none;
- color: white;
- outline: none;
- font-weight: 700;
- padding: 5px 10px;
- border-radius: 5px;
- transition: 0.2s ease-in-out;
-}
-
button:hover {filter: brightness(110%)}
button:active {filter: brightness(90%)}
img {pointer-events: none}
#bgHolder {
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- position: fixed;
- transform: scale(1.1);
+ top: -5px;
+ left: -5px;
+ right: -5px;
+ bottom: -5px;
+ z-index: -1;
+ position: absolute;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
@@ -162,7 +323,7 @@ img {pointer-events: none}
}
.contentContainer {
- width: 90%;
+ width: 85%;
color: white;
flex-grow: 1;
opacity: 1.0;
@@ -346,6 +507,12 @@ button:disabled {
pointer-events: none;
}
+button.visual {
+ opacity: 1.0;
+ pointer-events: none;
+ background: transparent !important;
+}
+
code {
font-size: 16px;
padding: 2px 5px;
@@ -385,16 +552,81 @@ code {
}
.modbtns button {
- margin-left: calc(var(--padding) / 3);
+ margin-left: var(--spacing);
+ --spacing: calc(var(--padding) / 3);
+ margin-top: calc(var(--spacing) / 2);
+ margin-bottom: calc(var(--spacing) / 2);
+}
+
+#toasts {
+ position: fixed;
+ z-index: 100000;
+ right: calc(var(--padding) * 1.5);
+ bottom: calc(var(--padding) * 1.5);
+}
+
+@keyframes bodyfadeaway {
+ 0% {opacity: 0.0; transform: scale(0.95)}
+ 100% {opacity: 1.0; transform: scale(1.0)}
+}
+
+#toasts .toast {
+ width: 300px;
+ opacity: 0.0;
+ cursor: pointer;
+ overflow: hidden;
+ max-height: 100vh;
+ background: #FFFFFF;
+ transform: scale(0.95);
+ transition: 0.2s ease-in-out;
+ padding: calc(var(--padding) / 2);
+ margin-top: calc(var(--padding) / 2);
+ border-radius: calc(var(--padding) / 2.5);
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
+
+ animation-duration: 0.2s;
+ animation-iteration-count: 1;
+ animation-name: bodyfadeaway;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in-out;
+}
+
+#toasts .toast.hidden {
+ margin-top: 0px;
+ max-height: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ filter: opacity(0.0);
+ transform: scale(0.95);
+}
+
+#toasts .toast:not(.hidden):hover {filter: opacity(0.9)}
+#toasts .toast:not(.hidden):active {filter: opacity(0.8)}
+
+.toast .title {
+}
+
+.toast .description {
+ opacity: 0.8;
+ font-size: 0.8em;
+ font-weight: 600;
}
/* drag control */
-#bgHolder {
+#bgHolder,
+.contentContainer,
+.gamesContainer {
user-select: none;
-webkit-app-region: drag;
}
-a, button, .contentMenu, #close, #nsRelease, #vpReleaseNotes, .mod {
+#overlay.shown ~ #bgHolder,
+#overlay.shown ~ .contentContainer,
+#overlay.shown ~ .gamesContainer {
+ -webkit-app-region: no-drag;
+}
+
+a, button, #close, #nsRelease, #vpReleaseNotes, .mod, #overlay, #modsdiv, #winbtns, .contentMenu {
-webkit-app-region: no-drag;
}
diff --git a/src/app/main.js b/src/app/main.js
index 5f0cc9a..169f86f 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -3,6 +3,7 @@ const path = require("path");
const { ipcRenderer, shell } = require("electron");
const lang = require("../lang");
+var modsobj = {};
let shouldInstallNorthstar = false;
// Base settings
@@ -26,13 +27,11 @@ if (fs.existsSync("viper.json")) {
settings.zip = path.join(settings.gamepath + "/northstar.zip");
if (settings.gamepath.length === 0) {
- alert(lang("general.missingpath"));
setpath(false);
} else {
setpath(true);
}
} else {
- alert(lang("general.missingpath"));
setpath();
}
@@ -68,6 +67,15 @@ function log(msg) {
// updating/installing Northstar.
function setButtons(state) {
playNsBtn.disabled = !state;
+
+ let disablearray = (array) => {
+ for (let i = 0; i < array.length; i++) {
+ array[i].disabled = !state;
+ }
+ }
+
+ disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button"))
+ disablearray(document.querySelectorAll("#browser #browserEntries .text button"))
}
// Frontend part of updating Northstar
@@ -150,12 +158,20 @@ function selected(all) {
// Tells the main process to install a mod
function installmod() {
+ setButtons(false);
ipcRenderer.send("installmod")
}
+// Tells the main process to install a mod from a URL
+function installFromURL(url) {
+ setButtons(false);
+ ipcRenderer.send("installfromurl", url)
+}
+
// Frontend part of settings a new game path
ipcRenderer.on("newpath", (event, newpath) => {
settings.gamepath = newpath;
+ ipcRenderer.send("guigetmods");
})
// Continuation of log()
@@ -164,6 +180,9 @@ ipcRenderer.on("alert", (event, msg) => {alert(msg)})
// Updates the installed mods
ipcRenderer.on("mods", (event, mods) => {
+ modsobj = mods;
+ if (! mods) {return}
+
modcount.innerHTML = `${lang("gui.mods.count")} ${mods.all.length}`;
modsdiv.innerHTML = "";
diff --git a/src/app/toast.js b/src/app/toast.js
new file mode 100644
index 0000000..2a8555e
--- /dev/null
+++ b/src/app/toast.js
@@ -0,0 +1,59 @@
+function Toast(properties) {
+ let toast = {
+ fg: "#000000",
+ bg: "#FFFFFF",
+ timeout: 3000,
+ title: "Untitled Toast",
+ description: "No description provided for toast",
+ ...properties
+ }
+
+ switch(toast.scheme) {
+ case "error":
+ toast.fg = "#FFFFFF";
+ toast.bg = "var(--red)";
+ break
+ case "success":
+ toast.fg = "#FFFFFF";
+ toast.bg = "#60D394";
+ break
+ case "warning":
+ toast.fg = "#FFFFFF";
+ toast.bg = "#FF9B85";
+ break
+ }
+
+
+ let id = Date.now();
+ if (document.getElementById(id)) {id = id + 1}
+ let el = document.createElement("div");
+
+ el.classList.add("toast");
+
+ el.style.color = toast.fg;
+ el.style.background = toast.bg;
+
+ el.id = id;
+ el.setAttribute("onclick", `dismissToast(${id})`);
+
+ el.innerHTML = `
+ <div class="title">${toast.title}</div>
+ <div class="description">${toast.description}</div>
+ `
+
+ toasts.appendChild(el);
+
+ setTimeout(() => {
+ dismissToast(id);
+ }, toast.timeout)
+}
+
+function dismissToast(id) {
+ id = document.getElementById(id);
+ if (id) {
+ id.classList.add("hidden");
+ setTimeout(() => {
+ id.remove();
+ }, 500)
+ }
+}
diff --git a/src/assets/icons/512x512.png b/src/assets/icons/512x512.png
index 281f3dd..69b1cce 100644
--- a/src/assets/icons/512x512.png
+++ b/src/assets/icons/512x512.png
Binary files differ
diff --git a/src/assets/icons/icon.ico b/src/assets/icons/icon.ico
index 32df0c5..06702df 100644
--- a/src/assets/icons/icon.ico
+++ b/src/assets/icons/icon.ico
Binary files differ
diff --git a/src/extras/findgame.js b/src/extras/findgame.js
new file mode 100644
index 0000000..42c9b85
--- /dev/null
+++ b/src/extras/findgame.js
@@ -0,0 +1,67 @@
+const fs = require("fs");
+const path = require("path");
+const vdf = require("simple-vdf");
+const { app } = require("electron");
+
+const util = require("util");
+const exec = util.promisify(require("child_process").exec);
+
+module.exports = async () => {
+ let gamepath = "";
+
+ // Autodetect path
+ // Windows only using powershell and windows registery
+ // Get-Item -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Respawn\Titanfall2\
+ if (process.platform == "win32") {
+ try {
+ const {stdout} = await exec("Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\Respawn\\Titanfall2\\ -Name \"Install Dir\"", {"shell":"powershell.exe"});
+
+ const gamepath = stdout.split('\n')
+ .filter(r => r.indexOf("Install Dir") !== -1)[0]
+ .replace(/\s+/g,' ')
+ .trim()
+ .replace("Install Dir : ","");
+
+ if (gamepath) {return gamepath}
+ } catch (err) {}
+ }
+
+ // Detect using Steam VDF
+ function readvdf(data) {
+ // Parse read_data
+ data = vdf.parse(data);
+
+ // `.length - 1` This is because the last value is `contentstatsid`
+ for (let pathIterator = 0; pathIterator < Object.values(data["libraryfolders"]).length - 1; pathIterator++) {
+ let data_array = Object.values(data["libraryfolders"][pathIterator])
+
+ if (fs.existsSync(data_array[0] + "/steamapps/common/Titanfall2/Titanfall2.exe")) {
+ return data_array[0] + "/steamapps/common/Titanfall2";
+ }
+ }
+ }
+
+ let folder = null;
+ switch (process.platform) {
+ case "win32":
+ folder = "C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf";
+ break
+ case "linux":
+ case "openbsd":
+ case "freebsd":
+ folder = path.join(app.getPath("home"), "/.steam/steam/steamapps/libraryfolders.vdf");
+ break
+ }
+
+ if (fs.existsSync(folder) && folder) {
+ let data = fs.readFileSync(folder)
+ let read_vdf = readvdf(data.toString())
+ if (read_vdf ) {return read_vdf}
+ }
+
+ if (gamepath) {
+ return gamepath;
+ } else {
+ return false;
+ }
+}
diff --git a/src/requests.js b/src/extras/requests.js
index 16b1330..16b1330 100644
--- a/src/requests.js
+++ b/src/extras/requests.js
diff --git a/src/index.js b/src/index.js
index 3f50840..a06cf69 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,7 +8,7 @@ const events = new Emitter();
const utils = require("./utils");
const cli = require("./cli");
-const requests = require("./requests");
+const requests = require("./extras/requests");
// Starts the actual BrowserWindow, which is only run when using the
// GUI, for the CLI this function is never called.
@@ -43,9 +43,13 @@ function start() {
ipcMain.on("exit", () => {process.exit(0)})
ipcMain.on("minimize", () => {win.minimize()})
+ ipcMain.on("installfromurl", (event, url) => {utils.mods.installFromURL(url)})
ipcMain.on("winLog", (event, ...args) => {win.webContents.send("log", ...args)});
ipcMain.on("winAlert", (event, ...args) => {win.webContents.send("alert", ...args)});
ipcMain.on("ns-update-event", (event) => win.webContents.send("ns-update-event", event));
+ ipcMain.on("failedmod", (event, modname) => {win.webContents.send("failedmod", modname)});
+ ipcMain.on("removedmod", (event, modname) => {win.webContents.send("removedmod", modname)});
+ ipcMain.on("installedmod", (event, modname) => {win.webContents.send("installedmod", modname)});
ipcMain.on("guigetmods", (event, ...args) => {win.webContents.send("mods", utils.mods.list())});
win.webContents.on("dom-ready", () => {
@@ -87,9 +91,13 @@ ipcMain.on("launchVanilla", (event) => {utils.launch("vanilla")})
ipcMain.on("update", (event) => {utils.update()})
ipcMain.on("setpathcli", (event) => {utils.setpath()});
ipcMain.on("setpath", (event, value) => {
- if (!value) {
- utils.setpath(win);
- } else if (!win.isVisible()) {
+ if (! value) {
+ if (! win.isVisible()) {
+ utils.setpath(win);
+ } else {
+ utils.setpath(win, true);
+ }
+ } else if (! win.isVisible()) {
win.show();
}
});
diff --git a/src/lang/en.json b/src/lang/en.json
index c2b6f98..27630ec 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -42,13 +42,14 @@
"gui.versions.northstar": "Northstar version",
"gui.exit": "Exit",
"gui.update": "Update",
- "gui.setpath": "Game Path",
+ "gui.setpath": "Change Game Path",
"gui.update.check": "Check for updates",
"gui.mods": "Mods",
"gui.mods.count": "Mods Installed:",
"gui.mods.disabledtag": "Disabled",
"gui.mods.install": "Install Mod",
+ "gui.mods.find": "Find Mods",
"gui.mods.toggle": "Toggle Mod",
"gui.mods.toggleall": "Toggle All",
"gui.mods.remove": "Remove Mod",
@@ -62,6 +63,15 @@
"gui.mods.installing": "Installing mod...",
"gui.mods.installedmod": "Installed mod!",
+ "gui.browser.info": "Info",
+ "gui.browser.madeby": "by",
+ "gui.browser.search": "Search...",
+ "gui.browser.update": "Update",
+ "gui.browser.install": "Install",
+ "gui.browser.reinstall": "Re-Install",
+ "gui.browser.loading": "Loading mods...",
+ "gui.browser.endoflist": "Maximum packages has been loaded.<br>Use the search for finding other packages!",
+
"gui.update.downloading": "Downloading...",
"gui.update.extracting": "Extracting update...",
"gui.update.finished": "Done! Ready to play!",
@@ -80,6 +90,13 @@
"gui.gamepath.must": "The game path must be set to start Viper.",
"gui.gamepath.wrong": "This folder is not a valid game path.",
+ "gui.toast.title.installed": "Mod installed!",
+ "gui.toast.title.failed": "Failed to install",
+ "gui.toast.title.malformed": "Incorrect folder structure!",
+ "gui.toast.desc.installed": "has been installed successfully!",
+ "gui.toast.desc.malformed": "has an incorrect folder structure, if you're the developer, you should fix this.",
+ "gui.toast.desc.failed": "An unknown error occurred while trying to install the mod. This may be the author's fault, and it may also be Viper's fault.",
+
"viper.menu.main": "Viper",
"viper.menu.release": "Release Notes",
"viper.menu.info": "Extras",
@@ -96,7 +113,7 @@
"general.mods.enabled": "Enabled mods:",
"general.mods.disabled": "Disabled mods:",
"general.mods.installed": "Installed mods:",
- "general.missingpath": "Game path is not set!",
+ "general.missingpath": "Game location could not be found automatically! Please select it manually!",
"general.notinstalled": "Northstar is not installed!",
"general.launching": "Launching"
}
diff --git a/src/lang/es.json b/src/lang/es.json
new file mode 100644
index 0000000..8beabef
--- /dev/null
+++ b/src/lang/es.json
@@ -0,0 +1,119 @@
+{
+ "cli.help.help": "muestra este mensaje de ayuda",
+ "cli.help.debug": "habre las herramientas de desarrollador/depuración ",
+ "cli.help.version": "muestra la información de la versión",
+ "cli.help.cli": "obliga la linea de comandos a habilitarse",
+ "cli.help.update": "actualiza Northstar desde la ruta de juego establecida",
+ "cli.help.setpath": "establece la ruta del juego",
+ "cli.help.updatevp": "actualiza Viper si es soportado",
+ "cli.help.installmod": "instala una modificación, desde una carpeta o zip",
+ "cli.help.removemod": "remueve una modificación",
+ "cli.help.togglemod": "alterna el estado de la modificación",
+
+ "cli.setpath.noarg": "No se ha proporcionado ningún argumento para --setpath",
+
+ "cli.update.current": "Versión actual:",
+ "cli.update.downloading": "Descargando...",
+ "cli.update.checking": "Buscando actualizaciones...",
+ "cli.update.downloaddone": "¡Descarga completa! Extrayendo...",
+ "cli.update.finished": "Instalación/Actualización completada!",
+ "cli.update.uptodate": "La ultima versión (%s) ya está instalada, omitiendo actualización.",
+ "cli.update.uptodate.short": "Está actualizado",
+
+ "cli.autoupdates.checking": "Buscando actualizaciones de Northstar...",
+ "cli.autoupdates.available": "¡Actualización de Northsar disponible!",
+ "cli.autoupdates.gamerunning": "El juego se está ejecutando, no se puede actualizar Northstar",
+ "cli.autoupdates.updatingns": "Lanzando proceso de actualización...",
+ "cli.autoupdates.noupdate": "No hay actualizaciones de Northstar disponibles.",
+
+ "cli.launch.linuxerror": "La ejecución del juego en Linux aun no está implementada",
+
+ "cli.mods.failed": "¡Fallo al instalar el mod!",
+ "cli.mods.removed": "¡Modificación removida exitosamente!",
+ "cli.mods.toggled": "¡El estado de la modificación ha cambiado exitosamente!",
+ "cli.mods.installed": "¡Modificiación instalada exitosamente!",
+ "cli.mods.cantfind": "¡No se encuentra una modificación con ese nombre!",
+ "cli.mods.notamod": "La carpeta o el archivo seleccionado no es una modificación",
+ "cli.mods.toggledall": "¡Estado de todas las modiciaciones ha sido cambiado exitosamente!",
+ "cli.mods.improperjson": "%s's mod.json tiene errores de formato",
+
+ "gui.welcome": "Bienvenido a Viper!",
+ "gui.versions.viper": "Versión de Viper",
+ "gui.versions.northstar": "Versión de Northstar",
+ "gui.exit": "Salir",
+ "gui.update": "Actualizar",
+ "gui.setpath": "Cambiar la ruta del juego",
+
+ "gui.update.check": "Buscar actualizaciones",
+ "gui.mods": "Modificaciones (Mods)",
+ "gui.mods.count": "Modificaciones instaladas:",
+ "gui.mods.disabledtag": "Deshabilitado",
+ "gui.mods.install": "Instalar modificación",
+ "gui.mods.find": "Encontrar modificaciones",
+ "gui.mods.toggle": "Alternar modificación",
+ "gui.mods.toggleall": "Alternar todo",
+ "gui.mods.remove": "Remover modificación",
+ "gui.mods.removeall": "Remover todo",
+ "gui.mods.nothingselected": "No has seleccionado una modificación.",
+ "gui.mods.toggleall.confirm": "Alternar todo podría deshabilitar las modificaciones requeridas para que Northstar funcione. ¿Está seguro?",
+ "gui.mods.removeall.confirm": "Eliminar todas las modificaciones generalmente requerirá que reinstales Northstar. ¿Está seguro?",
+ "gui.mods.required.confirm": "Ha seleccionado un mod esencial, es posible que Northstar no funcione sin él. ¿Está seguro?",
+ "gui.mods.notamod": "¡No es una modificación!",
+ "gui.mods.extracting": "Extrayendo modificación...",
+ "gui.mods.installing": "Instalando modificación...",
+ "gui.mods.installedmod": "¡Modificación instalada!",
+
+ "gui.browser.info": "Información",
+ "gui.browser.madeby": "por",
+ "gui.browser.search": "Buscar...",
+ "gui.browser.update": "Actualizar",
+ "gui.browser.install": "Instalar",
+ "gui.browser.reinstall": "Re-Instalar",
+ "gui.browser.loading": "Cargando modificaciones...",
+ "gui.browser.endoflist": "Se ha cargado el máximo de paquetes.<br>¡Usa la búsqueda para encontrar otros paquetes!",
+
+ "gui.update.downloading": "Descargando...",
+ "gui.update.extracting": "Extrayendo actualización...",
+ "gui.update.finished": "¡Hecho! ¡Está listo para jugar!",
+ "gui.update.uptodate": "¡Ya está actualizado!",
+ "gui.update.available": "Hay una actualización disponible para Viper, ¿desea reiniciar y aplicarla?",
+
+ "gui.nsupdate.gaming.title": "¡Actualización de Northstar disponible!",
+ "gui.nsupdate.gaming.body": "Una actualización de northstar está disponible.\nPuedes forzar su instalación despues de cerrar el juego.",
+
+ "gui.launch": "Ejecutar",
+ "gui.launchvanilla": "Vainilla",
+ "gui.launchnorthstar": "Northstar",
+ "gui.installnorthstar": "Instalar",
+
+ "gui.selectpath": "Por favor, ¡elija la ruta del juego!",
+ "gui.gamepath.must": "La ruta del juego debe establecerse para ejecutar Viper.",
+ "gui.gamepath.wrong": "Esta carpeta no es una ruta válida para el juego.",
+
+ "gui.toast.title.installed": "¡Modificación instalada!",
+ "gui.toast.title.failed": "¡Falló al instalar!",
+ "gui.toast.title.malformed": "¡Estructura de las carpetas incorrecta!",
+ "gui.toast.desc.installed": "¡Ha sido instalado exitosamente!",
+ "gui.toast.desc.malformed": "tiene una estructura de carpetas incorrecta, si usted es el desarrollador, debe corregir esto.",
+ "gui.toast.desc.failed": "Se produjo un error desconocido al intentar instalar la modificación. Esto puede ser culpa del autor de la modificación, y también puede ser culpa de Viper.",
+
+ "viper.menu.main": "Viper",
+ "viper.menu.release": "Notas de la versión",
+ "viper.menu.info": "Extras",
+ "viper.menu.info.links": "Links",
+ "viper.menu.info.credits": "Creditos",
+
+ "viper.info.discord": "Unete al Discord:",
+ "viper.info.issues": "Reporta problemas de Viper:",
+
+ "ns.menu.main": "Northstar Launcher",
+ "ns.menu.mods": "Modificiaciones",
+ "ns.menu.release": "Notas de actualización",
+
+ "general.mods.enabled": "Modificaciones habilitadas:",
+ "general.mods.disabled": "Modificaciones deshabilitadas:",
+ "general.mods.installed": "Modificiaciones intaladas:",
+ "general.missingpath": "¡La ruta del jueno no se ha podido encontrar automaticamente! ¡Por favor, elige la ruta manualmente!",
+ "general.notinstalled": "¡Northstar no se ha instalado!",
+ "general.launching": "Ejecutando"
+}
diff --git a/src/lang/fr.json b/src/lang/fr.json
index 59a3090..3bdeec6 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -42,13 +42,14 @@
"gui.versions.northstar": "Version de Northstar",
"gui.exit": "Fermer",
"gui.update": "Mise à jour",
- "gui.setpath": "Chemin du jeu",
+ "gui.setpath": "Mettre à jour le chemin du jeu",
"gui.update.check": "Vérifier les mises à jour",
"gui.mods": "Mods",
"gui.mods.count": "Mods installés :",
"gui.mods.disabledtag": "Désactivé",
"gui.mods.install": "Installer le mod",
+ "gui.mods.find": "Chercher des mods",
"gui.mods.toggle": "Activer/désactiver le mod",
"gui.mods.toggleall": "Activer/désactiver tous les mods",
"gui.mods.remove": "Supprimer le mod",
@@ -62,6 +63,15 @@
"gui.mods.installing": "Installation du mod...",
"gui.mods.installedmod": "Mod installé !",
+ "gui.browser.info": "Info",
+ "gui.browser.madeby": "par",
+ "gui.browser.search": "Rechercher",
+ "gui.browser.update": "Mise à jour",
+ "gui.browser.install": "Installer",
+ "gui.browser.reinstall": "Réinstaller",
+ "gui.browser.loading": "Chargement des mods...",
+ "gui.browser.endoflist": "Fin de la liste de mods.<br>Utilisez la barre de recherche pour en trouver davantage !",
+
"gui.update.downloading": "Téléchargement de la mise à jour...",
"gui.update.extracting": "Extraction des fichiers...",
"gui.update.finished": "Terminé, vous pouvez jouer !",
@@ -80,6 +90,13 @@
"gui.gamepath.must": "Vous devez sélectionner le chemin du dossier du jeu Titanfall 2 pour pouvoir lancer Viper.",
"gui.gamepath.wrong": "Ce dossier ne contient pas le jeu Titanfall 2, et n'est donc pas valide.",
+ "gui.toast.title.installed": "Mod installé !",
+ "gui.toast.title.failed": "L'installation a échoué",
+ "gui.toast.title.malformed": "La structure du dossier du mod est incorrecte.",
+ "gui.toast.desc.installed": "a été installé avec succès !",
+ "gui.toast.desc.malformed": "a une structure de dossier incorrecte ; si vous êtes son développeur, vous devriez réparer ça.",
+ "gui.toast.desc.failed": "Une erreur inconnue est survenue lors de l'installation du mod. Cela peut être du ressort de l'auteur du mod ou de Viper.",
+
"viper.menu.main": "Viper",
"viper.menu.release": "Notes de mises à jour",
"viper.menu.info": "Informations",
@@ -96,7 +113,7 @@
"general.mods.enabled": "Mods activés :",
"general.mods.disabled": "Mods désactivés :",
"general.mods.installed": "Mods installés :",
- "general.missingpath": "Le chemin du client n'est pas défini !",
+ "general.missingpath": "Le chemin du client n'a pu être trouvé automatiquement, merci de le sélectionner manuellement.",
"general.notinstalled": "Northstar n'est pas installé !",
"general.launching": "Lancement"
}
diff --git a/src/lang/maintainers.json b/src/lang/maintainers.json
new file mode 100644
index 0000000..094814a
--- /dev/null
+++ b/src/lang/maintainers.json
@@ -0,0 +1,13 @@
+{
+ "explanation": "This file is for storing contact information for the various maintainers of various languages/localizations",
+
+ "list": {
+ "es": [
+ "https://github.com/AA-Delta"
+ ],
+
+ "fr": [
+ "https://github.com/Alystrasz"
+ ]
+ }
+}
diff --git a/src/utils.js b/src/utils.js
index a41def4..a0c88ef 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,6 +1,6 @@
const path = require("path");
const fs = require("fs-extra");
-const copy = require("copy-dir");
+const copy = require("recursive-copy");
const { app, dialog, ipcMain, Notification } = require("electron");
const Emitter = require("events");
@@ -8,7 +8,8 @@ const events = new Emitter();
const cli = require("./cli");
const lang = require("./lang");
-const requests = require("./requests");
+const requests = require("./extras/requests");
+const findgame = require("./extras/findgame");
const unzip = require("unzipper");
const run = require("child_process").spawn;
@@ -115,10 +116,39 @@ northstar_auto_updates: {
//
// 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) {
+async function setpath(win, forcedialog) {
+ function setGamepath(folder) {
+ settings.gamepath = folder;
+ settings.zip = path.join(settings.gamepath + "/northstar.zip");
+ saveSettings();
+ win.webContents.send("newpath", settings.gamepath);
+ ipcMain.emit("newpath", null, settings.gamepath);
+
+ modpath = path.join(settings.gamepath, "R2Northstar/mods");
+ }
+
if (! win) { // CLI
- settings.gamepath = cli.param("setpath");
+ setGamepath(cli.param("setpath"));
} else { // GUI
+ if (! forcedialog) {
+ function setGamepath(folder, forcedialog) {
+ settings.gamepath = folder;
+ settings.zip = path.join(settings.gamepath + "/northstar.zip");
+ saveSettings();
+ win.webContents.send("newpath", settings.gamepath);
+ ipcMain.emit("newpath", null, settings.gamepath);
+ }
+
+ let gamepath = await findgame();
+ if (gamepath) {
+ setGamepath(gamepath);
+ return;
+ }
+
+ winAlert(lang("general.missingpath"));
+ }
+
+ // Fallback to manual selection
dialog.showOpenDialog({properties: ["openDirectory"]}).then(res => {
if (res.canceled) {
ipcMain.emit("newpath", null, false);
@@ -129,16 +159,12 @@ function setpath(win) {
return;
}
- settings.gamepath = res.filePaths[0];
- settings.zip = path.join(settings.gamepath + "/northstar.zip");
- saveSettings();
- win.webContents.send("newpath", settings.gamepath);
- ipcMain.emit("newpath", null, settings.gamepath);
+ setGamepath(res.filePaths[0])
+
+ cli.exit();
+ return;
}).catch(err => {console.error(err)})
}
-
- saveSettings();
- cli.exit();
}
// As to not have to do the same one liner a million times, this
@@ -176,6 +202,19 @@ function getTF2Version() {
}
}
+
+// Renames excluded files to their original name
+function restoreExcludedFiles() {
+ 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)
+ }
+ }
+}
+// At start, restore excluded files who might have been created by an incomplete update process.
+restoreExcludedFiles();
+
// Installs/Updates Northstar
//
// If Northstar is already installed it'll be an update, otherwise it'll
@@ -186,14 +225,6 @@ function getTF2Version() {
// <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)) {
- fs.renameSync(exclude, exclude + ".excluded")
- }
- }
-
ipcMain.emit("ns-update-event", "cli.update.checking");
console.log(lang("cli.update.checking"));
var version = getNSVersion();
@@ -215,6 +246,14 @@ async function update() {
ipcMain.emit("ns-update-event", "cli.update.downloading");
}
+ // 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)) {
+ fs.renameSync(exclude, exclude + ".excluded")
+ }
+ }
+
// Start the download of the zip
https.get(requests.getLatestNsVersionLink(), (res) => {
let stream = fs.createWriteStream(settings.zip);
@@ -237,16 +276,10 @@ async function update() {
// 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")) {
- fs.renameSync(exclude + ".excluded", exclude)
- }
- }
+ fs.writeFileSync(path.join(settings.gamepath, "ns_version.txt"), latestAvailableVersion);
+ ipcMain.emit("getversion");
+
+ restoreExcludedFiles();
ipcMain.emit("guigetmods");
ipcMain.emit("ns-update-event", "cli.update.uptodate.short");
@@ -323,6 +356,8 @@ const mods = {
// combination of the other two, enabled being enabled mods, and you
// guessed it, disabled being disabled mods.
list: () => {
+ let modpath = path.join(settings.gamepath, "R2Northstar/mods");
+
if (getNSVersion() == "unknown") {
winLog(lang("general.notinstalled"))
console.log("error: " + lang("general.notinstalled"))
@@ -349,9 +384,14 @@ const mods = {
try {
mods.push({...require(path.join(modpath, file, "mod.json")), FolderName: file, Disabled: false})
}catch(err) {
- console.log("error: " + lang("cli.mods.improperjson"), file)
+ if (cli.hasArgs()) {console.log("error: " + lang("cli.mods.improperjson"), file)}
mods.push({Name: file, FolderName: file, Version: "unknown", Disabled: false})
}
+
+ let manifest = path.join(modpath, file, "manifest.json");
+ if (fs.existsSync(manifest)) {
+ try {mods[mods.length - 1].ManifestName = require(manifest).name}catch(err){}
+ }
}
}
})
@@ -368,10 +408,15 @@ const mods = {
try {
disabled.push({...require(path.join(disabledPath, file, "mod.json")), FolderName: file, Disabled: true})
}catch(err) {
- console.log("error: " + lang("cli.mods.improperjson"), file)
+ if (cli.hasArgs()) {console.log("error: " + lang("cli.mods.improperjson"), file)}
disabled.push({Name: file, FolderName: file, Version: "unknown", Disabled: true})
}
}
+
+ let manifest = path.join(modpath, file, "manifest.json");
+ if (fs.existsSync(manifest)) {
+ try {mods[mods.length - 1].ManifestName = require(manifest).name}catch(err){}
+ }
}
})
@@ -389,6 +434,8 @@ const mods = {
// the absolute basics will be provided and we can't know the
// version or similar.
get: (mod) => {
+ let modpath = path.join(settings.gamepath, "R2Northstar/mods");
+
if (getNSVersion() == "unknown") {
winLog(lang("general.notinstalled"))
console.log("error: " + lang("general.notinstalled"))
@@ -462,7 +509,9 @@ const mods = {
// 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) => {
+ install: (mod, destname, manifestfile, malformed = false) => {
+ let modname = mod.replace(/^.*(\\|\/|\:)/, "");
+
if (getNSVersion() == "unknown") {
winLog(lang("general.notinstalled"))
console.log("error: " + lang("general.notinstalled"))
@@ -482,6 +531,19 @@ const mods = {
cli.exit();
winLog(lang("gui.mods.installedmod"))
+
+ if (modname == "mods") {
+ let manifest = path.join(app.getPath("userData"), "Archives/manifest.json")
+
+ if (fs.existsSync(manifest)) {
+ modname = require(manifest).name;
+ }
+ }
+
+ ipcMain.emit("installedmod", "", {
+ name: modname,
+ malformed: malformed,
+ });
ipcMain.emit("guigetmods");
return true;
}
@@ -490,14 +552,17 @@ const mods = {
if (fs.statSync(mod).isDirectory()) {
winLog(lang("gui.mods.installing"))
+ files = fs.readdirSync(mod);
if (fs.existsSync(path.join(mod, "mod.json")) &&
fs.statSync(path.join(mod, "mod.json")).isFile()) {
- copy.sync(mod, path.join(modpath, mod.replace(/^.*(\\|\/|\:)/, "")), {
- mode: true,
- cover: true,
- utimes: true,
- });
+ if (fs.existsSync(path.join(modpath, modname))) {
+ fs.rmSync(path.join(modpath, modname), {recursive: true});
+ }
+ let copydest = path.join(modpath, modname);
+ if (typeof destname == "string") {copydest = path.join(modpath, destname)}
+ copy(mod, copydest)
+ copy(manifestfile, path.join(copydest, "manifest.json"))
return installed();
} else {
@@ -508,39 +573,110 @@ const mods = {
if (fs.existsSync(path.join(mod, files[i], "mod.json")) &&
fs.statSync(path.join(mod, files[i], "mod.json")).isFile()) {
+ mods.install(path.join(mod, files[i]))
if (mods.install(path.join(mod, files[i]))) {return true};
}
}
}
- notamod();
- return false;
+ return notamod();
}
+
+ return notamod();
} else {
winLog(lang("gui.mods.extracting"))
let cache = path.join(app.getPath("userData"), "Archives");
if (fs.existsSync(cache)) {
fs.rmSync(cache, {recursive: true});
- fs.mkdirSync(cache);
+ fs.mkdirSync(path.join(cache, "mods"), {recursive: true});
} else {
- fs.mkdirSync(cache);
+ fs.mkdirSync(path.join(cache, "mods"), {recursive: true});
}
try {
fs.createReadStream(mod).pipe(unzip.Extract({path: cache}))
.on("finish", () => {
- if (mods.install(cache)) {
- installed();
- } else {return notamod()}
+ setTimeout(() => {
+ let manifest = path.join(cache, "manifest.json");
+ if (fs.existsSync(manifest)) {
+ files = fs.readdirSync(path.join(cache, "mods"));
+ if (fs.existsSync(path.join(cache, "mods/mod.json"))) {
+ if (mods.install(path.join(cache, "mods"), require(manifest).name, manifest, true)) {
+ return true;
+ }
+ } else {
+ for (let i = 0; i < files.length; i++) {
+ let mod = path.join(cache, "mods", files[i]);
+ if (fs.statSync(mod).isDirectory()) {
+ setTimeout(() => {
+ if (mods.install(mod, false, manifest)) {return true};
+ }, 1000)
+ }
+ }
+
+ if (files.length == 0) {
+ ipcMain.emit("failedmod");
+ return notamod();
+ }
+ }
+
+ return notamod();
+ }
+
+ if (mods.install(cache)) {
+ installed();
+ } else {return notamod()}
+ }, 1000)
});
}catch(err) {return notamod()}
}
},
+
+ // Installs mods from URL's
+ //
+ // This'll simply download the file that the URL points to and then
+ // install it with mods.install()
+ installFromURL: (url) => {
+ https.get(url, (res) => {
+ let tmp = path.join(app.getPath("cache"), "vipertmp");
+ let modlocation = path.join(tmp, "/mod.zip");
+
+ if (fs.existsSync(tmp)) {
+ if (! fs.statSync(tmp).isDirectory()) {
+ fs.rmSync(tmp)
+ }
+ } else {
+ fs.mkdirSync(tmp)
+ if (fs.existsSync(modlocation)) {
+ fs.rmSync(modlocation)
+ }
+ }
+
+ let stream = fs.createWriteStream(modlocation);
+ 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();
+ mods.install(modlocation);
+ })
+ })
+ },
+
// Removes mods
//
// Takes in the names of the mod then removes it, no confirmation,
// that'd be up to the GUI.
remove: (mod) => {
+ let modpath = path.join(settings.gamepath, "R2Northstar/mods");
+
if (getNSVersion() == "unknown") {
winLog(lang("general.notinstalled"))
console.log("error: " + lang("general.notinstalled"))
@@ -575,10 +711,19 @@ const mods = {
}
if (fs.statSync(modPath).isDirectory()) {
+ let manifestname = null;
+ if (fs.existsSync(path.join(modPath, "manifest.json"))) {
+ manifestname = require(path.join(modPath, "manifest.json")).name;
+ }
+
fs.rmSync(modPath, {recursive: true});
console.log(lang("cli.mods.removed"));
cli.exit();
ipcMain.emit("guigetmods");
+ ipcMain.emit("removedmod", "", {
+ name: mod.replace(/^.*(\\|\/|\:)/, ""),
+ manifestname: manifestname
+ });
} else {
cli.exit(1);
}
@@ -591,6 +736,8 @@ const mods = {
// 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) => {
+ let modpath = path.join(settings.gamepath, "R2Northstar/mods");
+
if (getNSVersion() == "unknown") {
winLog(lang("general.notinstalled"))
console.log("error: " + lang("general.notinstalled"))
@@ -639,6 +786,9 @@ const mods = {
};
console.log(mods.modfile().get())
+setInterval(() => {
+ ipcMain.emit("guigetmods");
+}, 1500)
module.exports = {
mods,