aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/css/grid.css148
-rw-r--r--src/app/css/launcher.css21
-rw-r--r--src/app/css/popups.css160
-rw-r--r--src/app/css/theming.css9
-rw-r--r--src/app/index.html29
-rw-r--r--src/app/js/browser.js (renamed from src/app/browser.js)65
-rw-r--r--src/app/js/launcher.js (renamed from src/app/launcher.js)6
-rw-r--r--src/app/js/misc.js44
-rw-r--r--src/app/js/mods.js81
-rw-r--r--src/app/js/settings.js (renamed from src/app/settings.js)0
-rw-r--r--src/app/js/toast.js (renamed from src/app/toast.js)6
-rw-r--r--src/app/main.css11
-rw-r--r--src/app/main.js110
-rw-r--r--src/cli.js5
-rw-r--r--src/extras/findgame.js85
-rw-r--r--src/index.js51
-rw-r--r--src/lang.js8
-rw-r--r--src/lang/de.json8
-rw-r--r--src/lang/en.json8
-rw-r--r--src/lang/es.json8
-rw-r--r--src/lang/fr.json10
-rw-r--r--src/modules/find.js (renamed from src/extras/find.js)0
-rw-r--r--src/modules/json.js35
-rw-r--r--src/modules/mods.js552
-rw-r--r--src/modules/requests.js (renamed from src/extras/requests.js)10
-rw-r--r--src/modules/settings.js70
-rw-r--r--src/utils.js508
27 files changed, 1150 insertions, 898 deletions
diff --git a/src/app/css/grid.css b/src/app/css/grid.css
new file mode 100644
index 0000000..5c4019c
--- /dev/null
+++ b/src/app/css/grid.css
@@ -0,0 +1,148 @@
+.grid .el, .popup .misc, .popup .loading {
+ --spacing: calc(var(--padding) / 2);
+ --height: calc(var(--padding) * 3.5);
+ --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;
+}
+
+.grid .el, .popup #search,
+.popup #close, .popup .misc button,
+.option .actions select, .option .actions input {
+ 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(50% - var(--spacing) * 4);
+}
+
+.popup .misc, .popup #search, .option .actions input {
+ --height: var(--mischeight);
+}
+
+.popup .misc {
+ display: flex;
+}
+
+.popup .misc.vertical {
+ display: block;
+}
+
+.popup .misc.fixed {
+ width: 100%;
+ position: fixed;
+}
+
+.popup #search,
+.option .actions input,
+.option .actions select {
+ border: none;
+ outline: none;
+ transition: filter 0.15s ease-in-out;
+ width: calc(100% - var(--spacing) * 2);
+}
+
+.popup #search:focus,
+.option .actions input:focus,
+.option .actions button:active {
+ filter: brightness(1.5);
+}
+
+.popup .misc button {
+ --height: calc(var(--padding) * 1.5);
+
+ padding: 0px;
+ margin-left: 0px;
+ padding: 0px !important;
+ width: var(--height) !important;
+}
+
+.popup .misc button img {
+ opacity: 0.6;
+ width: var(--height);
+ transform: scale(0.5);
+ height: var(--height) !important;
+}
+
+.popup .misc button:last-child {
+ margin-left: 0px !important;
+}
+
+.popup#preview #close,
+.popup .misc.vertical button {
+ margin: var(--spacing) var(--spacing) 0 auto !important;
+}
+
+.popup .loading {
+ width: 100%;
+ color: white;
+ display: flex;
+ position: absolute;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ height: calc(100% - var(--mischeight) - var(--height));
+}
+
+.popup .message {
+ color: white;
+ text-align: center;
+ margin: var(--padding);
+ width: calc(100% - var(--padding));
+}
+
+.grid .el .image, .grid .el .image img {
+ width: var(--height);
+ height: var(--height);
+ margin-right: var(--spacing);
+ border-radius: var(--spacing);
+}
+
+.grid .el .image img.blur {
+ z-index: -1;
+ position: relative;
+ filter: blur(10px);
+ top: calc(var(--height) * -1 + 5px);
+}
+
+.grid .el .text {
+ overflow: hidden;
+}
+
+.grid .el .title, .grid .el .description {
+ height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.grid .el .title {
+ font-size: 1.2em;
+ font-weight: 700;
+}
+
+.popup .message #loadmore {
+ background: rgb(var(--blue2));
+}
+
+.grid .el .description {font-size: 0.8em}
+.grid .el button {
+ background: rgb(var(--blue));
+ margin-top: var(--spacing);
+}
+
+.grid .el button.info {
+ background: rgb(var(--blue2));
+}
+
diff --git a/src/app/css/launcher.css b/src/app/css/launcher.css
index ce54ddf..5698179 100644
--- a/src/app/css/launcher.css
+++ b/src/app/css/launcher.css
@@ -151,6 +151,15 @@
pointer-events: none;
}
+.section .release-block {
+ margin-top: 0px;
+ border-radius: 5px;
+ background: var(--bg);
+ padding: var(--padding);
+ backdrop-filter: blur(15px);
+ margin-bottom: var(--padding);
+}
+
.contentBody img {max-width: 100%}
.contentBody .img {text-align: center}
@@ -233,14 +242,6 @@
background: rgb(var(--red)) !important;
}
-#installmod {background: rgb(var(--blue))}
-#findmod {background: rgb(var(--blue2))}
-
-#togglemod {background: rgb(var(--orange))}
-#toggleall {background: rgb(var(--orange2))}
-
-#removemod {background: rgb(var(--red))}
-#removeall {background: rgb(var(--red2))}
button:disabled {
opacity: 0.5;
pointer-events: none;
@@ -261,13 +262,15 @@ code {
}
#nsMods .line {
+ width: 100%;
display: flex;
align-items: center;
+ margin: calc(var(--padding) / 2);
margin-top: calc(var(--padding) / 2);
}
#modsdiv {
- height: 50vh;
+ height: 80vh;
overflow-y: scroll;
border-radius: 5px;
background: var(--bg);
diff --git a/src/app/css/popups.css b/src/app/css/popups.css
index 826955b..8f47d0a 100644
--- a/src/app/css/popups.css
+++ b/src/app/css/popups.css
@@ -64,11 +64,6 @@
100% {opacity: 1.0}
}
-#browserEntries {
- display: flex;
- flex-wrap: wrap;
-}
-
.popup webview {
width: 78%;
margin: 0 auto;
@@ -82,154 +77,6 @@
filter: opacity(0.0);
pointer-events: none;
}
-
-.popup .el, .popup .misc, .popup .loading {
- --spacing: calc(var(--padding) / 2);
- --height: calc(var(--padding) * 3.5);
- --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;
-}
-
-.popup .el, .popup #search,
-.popup #close, .popup .misc button,
-.option .actions select, .option .actions input {
- 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(50% - var(--spacing) * 4);
-}
-
-.popup .misc, .popup #search, .option .actions input {
- --height: var(--mischeight);
-}
-
-.popup .misc {
- display: flex;
-}
-
-.popup .misc.vertical {
- display: block;
-}
-
-.popup .misc.fixed {
- width: 100%;
- position: fixed;
-}
-
-.popup #search,
-.option .actions input,
-.option .actions select {
- border: none;
- outline: none;
- transition: filter 0.15s ease-in-out;
- width: calc(100% - var(--spacing) * 2);
-}
-
-.popup #search:focus,
-.option .actions input:focus,
-.option .actions button:active {
- filter: brightness(1.5);
-}
-
-.popup .misc button {
- --height: calc(var(--padding) * 1.5);
-
- padding: 0px;
- margin-left: 0px;
- padding: 0px !important;
- width: var(--height) !important;
-}
-
-.popup .misc button img {
- opacity: 0.6;
- width: var(--height);
- transform: scale(0.5);
- height: var(--height) !important;
-}
-
-.popup .misc button:last-child {
- margin-left: 0px !important;
-}
-
-.popup#preview #close,
-.popup .misc.vertical button {
- margin: var(--spacing) var(--spacing) 0 auto !important;
-}
-
-.popup .loading {
- width: 100%;
- color: white;
- display: flex;
- position: absolute;
- text-align: center;
- align-items: center;
- justify-content: center;
- height: calc(100% - var(--mischeight) - var(--height));
-}
-
-.popup .message {
- color: white;
- text-align: center;
- margin: var(--padding);
- width: calc(100% - var(--padding));
-}
-
-.popup .el .image, .popup .el .image img {
- width: var(--height);
- height: var(--height);
- margin-right: var(--spacing);
- border-radius: var(--spacing);
-}
-
-.popup .el .image img.blur {
- z-index: -1;
- position: relative;
- filter: blur(10px);
- top: calc(var(--height) * -1 + 5px);
-}
-
-.popup .el .text {
- overflow: hidden;
-}
-
-.popup .el .title, .popup .el .description {
- height: 1.2em;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-.popup .el .title {
- font-size: 1.2em;
- font-weight: 700;
-}
-
-.popup .message #loadmore {
- background: rgb(var(--blue2));
-}
-
-.popup .el .description {font-size: 0.8em}
-.popup .el button {
- background: rgb(var(--blue));
- margin-top: var(--spacing);
-}
-
-.popup .el button.info {
- background: rgb(var(--blue2));
-}
/* } */
/* settings popup { */
@@ -356,6 +203,12 @@
.title {
display: flex;
+ margin-top: calc(var(--padding) * 2);
+ margin-bottom: calc(var(--padding) / 2);
+}
+
+.title:first-child {
+ margin-top: 0px;
}
.title img {
@@ -365,6 +218,7 @@
}
.title h2 {
+ margin: 0px;
margin-left: calc(var(--padding) / 3);
}
diff --git a/src/app/css/theming.css b/src/app/css/theming.css
index 2d45b1d..6d7e223 100644
--- a/src/app/css/theming.css
+++ b/src/app/css/theming.css
@@ -60,3 +60,12 @@ a:hover {
color: black !important;
background: rgb(var(--red)) !important;
}
+
+.blue {background: rgb(var(--blue)) !important}
+.blue2 {background: rgb(var(--blue2)) !important}
+
+.orange {background: rgb(var(--orange)) !important}
+.orange2 {background: rgb(var(--orange2)) !important}
+
+.red {background: rgb(var(--red)) !important}
+.red2 {background: rgb(var(--red2)) !important}
diff --git a/src/app/index.html b/src/app/index.html
index c32d18f..f073cac 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -179,7 +179,7 @@
<img src="icons/close.png">
</button>
</div>
- <div id="browserEntries">
+ <div id="browserEntries" class="grid">
<div class="loading">%%gui.browser.loading%%</div>
</div>
</div>
@@ -252,19 +252,18 @@
</div>
</div>
<div id="nsMods" class="hidden section">
- <div id="modsdiv">
- </div>
+ <div id="modsdiv" class="grid">
<div class="line">
<div class="text" id="modcount">%%gui.mods%%</div>
<div class="buttons modbtns">
- <button id="removemod" onclick="selected().remove()">%%gui.mods.remove%%</button>
- <button id="removeall" onclick="selected(true).remove()">%%gui.mods.removeall%%</button>
- <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="findmod" onclick="Browser.toggle(true)">%%gui.mods.find%%</button>
+ <button id="removeall" class="red2" onclick="mods.remove('allmods')">%%gui.mods.removeall%%</button>
+ <button id="toggleall" class="orange2" onclick="selected(true).toggle(true)">%%gui.mods.toggleall%%</button>
+ <button id="installmod" class="blue" onclick="installmod()">%%gui.mods.install%%</button>
+ <button id="findmod" class="blue2" onclick="Browser.toggle(true)">%%gui.mods.find%%</button>
</div>
</div>
+
+ </div>
</div>
<div id="nsRelease" class="hidden section"></div>
</div>
@@ -286,9 +285,11 @@
<script src="lang.js"></script>
<script src="main.js"></script>
- <script src="toast.js"></script>
- <script src="browser.js"></script>
- <script src="settings.js"></script>
- <script src="launcher.js"></script>
+ <script src="js/misc.js"></script>
+ <script src="js/mods.js"></script>
+ <script src="js/toast.js"></script>
+ <script src="js/browser.js"></script>
+ <script src="js/settings.js"></script>
+ <script src="js/launcher.js"></script>
</body>
-<
+</html>
diff --git a/src/app/browser.js b/src/app/js/browser.js
index db68d31..fcb79a2 100644
--- a/src/app/browser.js
+++ b/src/app/js/browser.js
@@ -319,16 +319,17 @@ function BrowserEl(properties) {
}
let installstr = lang("gui.browser.install");
+ let normalized_mods = [];
- if (normalize(modsdiv.innerText.split("\n")).includes(normalize(properties.title))) {
+ for (let i = 0; i < modsobj.all; i++) {
+ normalized_mods.push(normalize(mods_list[i].Name));
+ }
+
+ if (normalized_mods.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");
- }
+ if (version.is_newer(properties.version, modsobj.all[i].Version)) {
+ installstr = lang("gui.browser.update");
}
} else {
for (let i = 0; i < modsobj.all.length; i++) {
@@ -342,9 +343,7 @@ function BrowserEl(properties) {
if (title.includes(folder) || title.includes(manifestname)) {
installstr = lang("gui.browser.reinstall");
- if (folder == title
- && "v" + modsobj.all[i].Version != properties.version) {
-
+ if (version.is_newer(properties.version, modsobj.all[i].Version)) {
installstr = lang("gui.browser.update");
}
}
@@ -363,16 +362,35 @@ function BrowserEl(properties) {
<div class="text">
<div class="title">${properties.title}</div>
<div class="description">${properties.description}</div>
- <button class="install" onclick='installFromURL("${properties.download}", ${JSON.stringify(properties.dependencies)}, true)'>${installstr}</button>
+ <button class="install" onclick=''>${installstr}</button>
<button class="info" onclick="Preview.set('${properties.url}')">${lang('gui.browser.view')}</button>
<button class="visual">${properties.version}</button>
<button class="visual">${lang("gui.browser.madeby")} ${properties.author}</button>
</div>
`
+ entry.querySelector("button.install").addEventListener("click", () => {
+ installFromURL(
+ properties.download,
+ JSON.stringify(properties.dependencies),
+ true, properties.author
+ )
+ })
+
browserEntries.appendChild(entry);
}
+let recent_toasts = {};
+function add_recent_toast(name, timeout = 3000) {
+ if (recent_toasts[name]) {return}
+
+ recent_toasts[name] = true;
+
+ setTimeout(() => {
+ delete recent_toasts[name];
+ }, timeout)
+}
+
ipcRenderer.on("removed-mod", (event, mod) => {
setButtons(true);
Browser.setbutton(mod.name, lang("gui.browser.install"));
@@ -382,6 +400,9 @@ ipcRenderer.on("removed-mod", (event, mod) => {
})
ipcRenderer.on("failed-mod", (event, modname) => {
+ if (recent_toasts["failed" + modname]) {return}
+ add_recent_toast("failed" + modname);
+
setButtons(true);
new Toast({
timeout: 10000,
@@ -391,6 +412,19 @@ ipcRenderer.on("failed-mod", (event, modname) => {
})
})
+ipcRenderer.on("duped-mod", (event, modname) => {
+ if (recent_toasts["duped" + modname]) {return}
+ add_recent_toast("duped" + modname);
+
+ setButtons(true);
+ new Toast({
+ timeout: 10000,
+ scheme: "warning",
+ title: lang("gui.toast.title.duped"),
+ description: modname + " " + lang("gui.toast.desc.duped")
+ })
+})
+
ipcRenderer.on("no-internet", (event, modname) => {
setButtons(true);
new Toast({
@@ -402,6 +436,9 @@ ipcRenderer.on("no-internet", (event, modname) => {
})
ipcRenderer.on("installed-mod", (event, mod) => {
+ if (recent_toasts["installed" + mod.name]) {return}
+ add_recent_toast("installed" + mod.name);
+
setButtons(true);
Browser.setbutton(mod.name, lang("gui.browser.reinstall"));
@@ -421,7 +458,11 @@ ipcRenderer.on("installed-mod", (event, mod) => {
})
if (installqueue.length != 0) {
- installFromURL("https://thunderstore.io/package/download/" + installqueue[0]);
+ installFromURL(
+ "https://thunderstore.io/package/download/" + installqueue[0].pkg,
+ false, false, installqueue[0].author
+ )
+
installqueue.shift();
}
})
diff --git a/src/app/launcher.js b/src/app/js/launcher.js
index e1dbbe0..5330b7a 100644
--- a/src/app/launcher.js
+++ b/src/app/js/launcher.js
@@ -31,7 +31,11 @@ function formatRelease(notes) {
} else {
for (let release of notes) {
if (release.prerelease) {continue}
- content += "# " + release.name + "\n\n" + release.body + "\n\n\n";
+ let new_content = "# " + release.name + "\n\n" + release.body + "\n\n\n";
+ content +=
+ "<div class='release-block'>\n"
+ + markdown(new_content, {breaks: true}) + "\n" +
+ "</div>";
}
content = content.replaceAll(/\@(\S+)/g, `<a href="https://github.com/$1">@$1</a>`);
diff --git a/src/app/js/misc.js b/src/app/js/misc.js
new file mode 100644
index 0000000..b35f239
--- /dev/null
+++ b/src/app/js/misc.js
@@ -0,0 +1,44 @@
+version = {
+ is_newer: (version1, version2) => {
+ version1 = version.format(version1, true).split(".");
+ version2 = version.format(version2, true).split(".");
+
+ for (let i = 0; i < version1.length; i++) {
+
+
+ let nums = [
+ parseInt(version1[i]) || 0,
+ parseInt(version2[i]) || 0
+ ];
+ if (nums[0] > nums[1]) {
+ return true;
+ } else if (nums[0] < nums[1]) {
+ return false;
+ }
+ }
+
+ return false;
+ },
+ format: (version_number, no_leading_v) => {
+ version_number = version_number.trim();
+
+ if (no_leading_v) {
+ if (version_number[0] == "v") {
+ return version_number.slice(1, version_number.length);
+ }
+
+ return version_number;
+ if (no_leading_v) {
+ return version_number
+ }
+
+ return "v" + version_number;
+ } else {
+ if (version_number[0] != "v") {
+ return "v" + version_number;
+ }
+ }
+
+ return version_number;
+ }
+}
diff --git a/src/app/js/mods.js b/src/app/js/mods.js
new file mode 100644
index 0000000..39e904e
--- /dev/null
+++ b/src/app/js/mods.js
@@ -0,0 +1,81 @@
+var mods = {};
+
+mods.load = (mods_obj) => {
+ modcount.innerHTML = `${lang("gui.mods.count")} ${mods_obj.all.length}`;
+
+ let normalized_names = [];
+
+ let set_mod = (mod) => {
+ let image_url = "";
+ let normalized_name = "mod-list-" + normalize(mod.Name);
+
+ normalized_names.push(normalized_name);
+
+ if (document.getElementById(normalized_name)) {
+ return;
+ }
+
+ let div = document.createElement("div");
+ div.classList.add("el");
+ div.id = normalized_name;
+
+ div.innerHTML += `
+ <div class="image">
+ <img src="${image_url}">
+ <img class="blur" src="${image_url}">
+ </div>
+ <div class="text">
+ <div class="title">${mod.Name}</div>
+ <div class="description">${mod.Description}</div>
+ <button class="red" onclick="mods.remove('${mod.Name}')">Remove</button>
+ <button class="visual">${mod.Version}</button>
+ <button class="visual">by ${mod.Author || "Unknown"}</button>
+ </div>
+ `;
+
+ if (! image_url) {
+ div.querySelector(".image").remove();
+ }
+
+ modsdiv.append(div);
+ }
+
+ for (let i = 0; i < mods_obj.all.length; i++) {
+ set_mod(mods_obj.all[i]);
+ }
+
+ let mod_els = document.querySelectorAll("#modsdiv .el");
+ for (let i = 0; i < mod_els.length; i++) {
+ if (! normalized_names.includes(mod_els[i].id)) {
+ mod_els[i].remove();
+ }
+ }
+}
+
+mods.remove = (mod) => {
+ if (mod.match(/^northstar\./)) {
+ if (! confirm(lang("gui.mods.required.confirm"))) {
+ return;
+ }
+ } else if (mod == "allmods") {
+ if (! confirm(lang("gui.mods.removeall.confirm"))) {
+ return;
+ }
+ }
+
+ ipcRenderer.send("remove-mod", mod);
+}
+
+mods.toggle = (mod) => {
+ if (mod.match(/^Northstar\./)) {
+ if (! confirm(lang("gui.mods.required.confirm"))) {
+ return;
+ }
+ } else if (mod == "allmods") {
+ if (! confirm(lang("gui.mods.toggleall.confirm"))) {
+ return;
+ }
+ }
+
+ ipcRenderer.send("toggle-mod", mod);
+}
diff --git a/src/app/settings.js b/src/app/js/settings.js
index 34975fe..34975fe 100644
--- a/src/app/settings.js
+++ b/src/app/js/settings.js
diff --git a/src/app/toast.js b/src/app/js/toast.js
index 9cb8996..3bc1745 100644
--- a/src/app/toast.js
+++ b/src/app/js/toast.js
@@ -3,6 +3,7 @@ function Toast(properties) {
fg: "#000000",
bg: "#FFFFFF",
timeout: 3000,
+ callback: () => {},
title: "Untitled Toast",
description: "No description provided for toast",
...properties
@@ -34,7 +35,10 @@ function Toast(properties) {
el.style.background = toast.bg;
el.id = id;
- el.setAttribute("onclick", `dismissToast(${id})`);
+ el.addEventListener("click", () => {
+ dismissToast(id);
+ toast.callback();
+ })
el.innerHTML = `
<div class="title">${toast.title}</div>
diff --git a/src/app/main.css b/src/app/main.css
index f39c5a1..17ed3ee 100644
--- a/src/app/main.css
+++ b/src/app/main.css
@@ -1,3 +1,4 @@
+@import "css/grid.css";
@import "css/dragui.css";
@import "css/toasts.css";
@import "css/popups.css";
@@ -27,7 +28,7 @@ button {
button:hover {filter: brightness(110%)}
button:active {filter: brightness(90%)}
-.popup, #modsdiv {
+.popup, #modsdiv, .release-block {
outline: 1px solid #444444;
border: 3px solid var(--bg);
}
@@ -105,3 +106,11 @@ a, button, #close, #nsRelease, #vpReleaseNotes,
.mod, #overlay, #modsdiv, #winbtns, .contentMenu {
-webkit-app-region: no-drag;
}
+
+/* grids */
+
+.grid {
+ display: flex;
+ flex-wrap: wrap;
+}
+
diff --git a/src/app/main.js b/src/app/main.js
index ad1a44f..6120428 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -153,68 +153,24 @@ ipcRenderer.on("ns-update-event", (event, key) => {
}
});
-let lastselected = "";
-function select(entry) {
- let entries = document.querySelectorAll("#modsdiv .mod .modtext");
-
- for (let i = 0; i < entries.length; i++) {
- if (entries[i].innerHTML == entry) {
- lastselected = entry;
- entries[i].parentElement.classList.add("selected");
- } else {
- entries[i].parentElement.classList.remove("selected");
- }
- }
-}
-
-// Mod selection
-function selected(all) {
- let selected = "";
- if (all) {
- selected = "allmods"
- } else {
- selected = document.querySelector(".mod.selected .modtext");
- if (selected != null) {
- selected = selected.innerHTML;
- } else {
- alert(lang("gui.mods.nothingselected"));
- return {
- remove: () => {},
- toggle: () => {},
- }
+ipcRenderer.on("unknown-error", (event, err) => {
+ new Toast({
+ timeout: 10000,
+ scheme: "error",
+ title: lang("gui.toast.title.unknown_error"),
+ description: lang("gui.toast.desc.unknown_error"),
+ callback: () => {
+ new Toast({
+ timeout: 15000,
+ scheme: "error",
+ title: "",
+ description: err.stack.replaceAll("\n", "<br>")
+ })
}
- }
-
- return {
- remove: () => {
-
- if (selected.match(/^Northstar\./)) {
- if (! confirm(lang("gui.mods.required.confirm"))) {
- return;
- }
- } else if (selected == "allmods") {
- if (! confirm(lang("gui.mods.removeall.confirm"))) {
- return;
- }
- }
+ })
- ipcRenderer.send("remove-mod", selected);
- },
- toggle: () => {
- if (selected.match(/^Northstar\./)) {
- if (! confirm(lang("gui.mods.required.confirm"))) {
- return;
- }
- } else if (selected == "allmods") {
- if (! confirm(lang("gui.mods.toggleall.confirm"))) {
- return;
- }
- }
-
- ipcRenderer.send("toggle-mod", selected);
- }
- }
-}
+ console.error(err.stack)
+})
let installqueue = [];
@@ -231,7 +187,7 @@ function installFromPath(path) {
}
// Tells the main process to install a mod from a URL
-function installFromURL(url, dependencies, clearqueue) {
+function installFromURL(url, dependencies, clearqueue, author) {
if (clearqueue) {installqueue = []};
let prettydepends = [];
@@ -244,7 +200,11 @@ function installFromURL(url, dependencies, clearqueue) {
depend = dependencies[i].replaceAll("-", "/");
let pkg = depend.split("/");
if (! isModInstalled(pkg[1])) {
- newdepends.push(depend);
+ newdepends.push({
+ pkg: depend,
+ author: pkg[0]
+ });
+
prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.madeby")} ${pkg[0]}`);
}
}
@@ -261,7 +221,7 @@ function installFromURL(url, dependencies, clearqueue) {
}
setButtons(false);
- ipcRenderer.send("install-from-url", url, dependencies);
+ ipcRenderer.send("install-from-url", url, author);
if (dependencies) {
installqueue = dependencies;
@@ -294,27 +254,11 @@ ipcRenderer.on("log", (event, msg) => {log(msg)})
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 = "";
-
- let newmod = (name, disabled) => {
- if (disabled) {
- disabled = `<span class="disabled">${lang("gui.mods.disabledtag")}</span>`
- } else {
- disabled = ""
- }
-
- modsdiv.innerHTML += `<div onclick="select('${name}')" class="mod"><span class="modtext">${name}</span>${disabled}</div>`;
- }
-
- for (let i = 0; i < mods.enabled.length; i++) {newmod(mods.enabled[i].Name)}
- for (let i = 0; i < mods.disabled.length; i++) {newmod(mods.disabled[i].Name, " - Disabled")}
+ipcRenderer.on("mods", (event, mods_obj) => {
+ modsobj = mods_obj;
+ if (! mods_obj) {return}
- select(lastselected);
+ mods.load(mods_obj);
})
// Updates version numbers
diff --git a/src/cli.js b/src/cli.js
index dcc609d..affcd9e 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -6,6 +6,7 @@ const events = new Emitter();
const cli = app.commandLine;
const lang = require("./lang");
+const json = require("./modules/json");
function hasArgs() {
// Makes sure the GUI isn't launched.
@@ -38,7 +39,7 @@ function exit(code) {
// gamepath to be able to work.
function gamepathExists() {
if (fs.existsSync("viper.json")) {
- gamepath = JSON.parse(fs.readFileSync("viper.json", "utf8")).gamepath;
+ gamepath = json("viper.json").gamepath;
if (! fs.existsSync(gamepath)) {
console.error(`error: ${lang("cli.gamepath.lost")}`);
@@ -59,8 +60,8 @@ async function init() {
if (cli.hasSwitch("help")) {
console.log(`options:
--help ${lang("cli.help.help")}
- --debug ${lang("cli.help.debug")}
--version ${lang("cli.help.version")}
+ --devtools ${lang("cli.help.devtools")}
--cli ${lang("cli.help.cli")}
--update ${lang("cli.help.update")}
diff --git a/src/extras/findgame.js b/src/extras/findgame.js
deleted file mode 100644
index 615c5b4..0000000
--- a/src/extras/findgame.js
+++ /dev/null
@@ -1,85 +0,0 @@
-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);
-
- let values = Object.values(data["libraryfolders"]);
- if (typeof values[values.length - 1] != "object") {
- values.pop(1);
- }
-
- // `.length - 1` This is because the last value is `contentstatsid`
- for (let i = 0; i < values.length; i++) {
- let data_array = Object.values(values[i]);
-
- if (fs.existsSync(data_array[0] + "/steamapps/common/Titanfall2/Titanfall2.exe")) {
- console.log("Found game in:", data_array[0]);
- return data_array[0] + "/steamapps/common/Titanfall2";
- } else {
- console.log("Game not in:", data_array[0]);
- }
- }
- }
-
- let folders = [];
- switch (process.platform) {
- case "win32":
- folders = ["C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf"];
- break
- case "linux":
- case "openbsd":
- case "freebsd":
- let home = app.getPath("home");
- folders = [
- path.join(home, "/.steam/steam/steamapps/libraryfolders.vdf"),
- path.join(home, ".var/app/com.valvesoftware.Steam/.steam/steam/steamapps/libraryfolders.vdf"),
- path.join(home, ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/libraryfolders.vdf")
- ]
- break
- }
-
- if (folders.length > 0) {
- for (let i = 0; i < folders.length; i++) {
- if (! fs.existsSync(folders[i])) {continue}
- console.log("Searching VDF file at:", folders[i]);
-
- let data = fs.readFileSync(folders[i]);
- let read_vdf = readvdf(data.toString());
- if (read_vdf) {return read_vdf}
- }
- }
-
- if (gamepath) {
- return gamepath;
- } else {
- return false;
- }
-}
diff --git a/src/index.js b/src/index.js
index 879dbcf..2f4285e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,9 +3,15 @@ const path = require("path");
const { autoUpdater } = require("electron-updater");
const { app, ipcMain, BrowserWindow, dialog } = require("electron");
+// ensures PWD/CWD is the config folder where viper.json is located
+process.chdir(app.getPath("appData"));
+
const utils = require("./utils");
const cli = require("./cli");
-const requests = require("./extras/requests");
+const json = require("./modules/json");
+const mods = require("./modules/mods");
+const settings = require("./modules/settings");
+const requests = require("./modules/requests");
var log = console.log;
@@ -25,6 +31,8 @@ function start() {
// as it's fairly responsive, but for now we won't allow that.
resizable: false,
+ userAgent: "test",
+
frame: false,
titleBarStyle: "hidden",
icon: path.join(__dirname, "assets/icons/512x512.png"),
@@ -35,19 +43,21 @@ function start() {
},
});
- // when --debug is added it'll open the dev tools
- if (cli.hasParam("debug")) {win.openDevTools()}
+ // when --devtools is added it'll open the dev tools
+ if (cli.hasParam("devtools")) {win.openDevTools()}
// general setup
win.removeMenu();
- win.loadFile(__dirname + "/app/index.html");
+ win.loadURL("file://" + __dirname + "/app/index.html", {
+ userAgent: "viper/" + json(path.join(__dirname, "../package.json")).version,
+ });
win.send = (channel, data) => {
win.webContents.send(channel, data);
}; send = win.send;
ipcMain.on("exit", () => {
- if (utils.settings.originkill) {
+ if (settings.originkill) {
utils.isOriginRunning().then((running) => {
if (running) {
utils.killOrigin().then(process.exit(0))
@@ -71,18 +81,24 @@ function start() {
ipcMain.on("win-alert", (event, ...args) => {send("alert", ...args)});
// mod states
+ ipcMain.on("duped-mod", (event, modname) => {send("duped-mod", modname)});
ipcMain.on("failed-mod", (event, modname) => {send("failed-mod", modname)});
ipcMain.on("removed-mod", (event, modname) => {send("removed-mod", modname)});
- ipcMain.on("gui-getmods", (event, ...args) => {send("mods", utils.mods.list())});
+ ipcMain.on("gui-getmods", (event, ...args) => {send("mods", mods.list())});
ipcMain.on("installed-mod", (event, modname) => {send("installed-mod", modname)});
ipcMain.on("no-internet", () => {send("no-internet")});
+ process.on("uncaughtException", (err) => {
+ send("unknown-error", err);
+ console.error(err);
+ });
+
// install calls
- ipcMain.on("install-from-path", (event, path) => {utils.mods.install(path)});
- ipcMain.on("install-from-url", (event, url) => {utils.mods.installFromURL(url)});
+ ipcMain.on("install-from-path", (event, path) => {mods.install(path)});
+ ipcMain.on("install-from-url", (event, url, author) => {mods.installFromURL(url, author)});
win.webContents.on("dom-ready", () => {
- send("mods", utils.mods.list());
+ send("mods", mods.list());
});
// ensures gamepath still exists and is valid on startup
@@ -94,7 +110,7 @@ function start() {
}
});
- ipcMain.on("save-settings", (event, obj) => {utils.saveSettings(obj)});
+ ipcMain.on("save-settings", (event, obj) => {settings.save(obj)});
// allows renderer to check for updates
ipcMain.on("ns-update-event", (event) => {send("ns-update-event", event)});
@@ -105,7 +121,7 @@ function start() {
})
// start auto-update process
- if (utils.settings.autoupdate) {
+ if (settings.autoupdate) {
if (cli.hasParam("no-vp-updates")) {
utils.handleNorthstarUpdating();
} else {
@@ -130,11 +146,11 @@ function start() {
// module inside the file that sent the event. {
ipcMain.on("install-mod", () => {
if (cli.hasArgs()) {
- utils.mods.install(cli.param("installmod"));
+ mods.install(cli.param("installmod"));
} else {
dialog.showOpenDialog({properties: ["openFile"]}).then(res => {
if (res.filePaths.length != 0) {
- utils.mods.install(res.filePaths[0]);
+ mods.install(res.filePaths[0]);
} else {
send("set-buttons", true);
}
@@ -142,8 +158,8 @@ ipcMain.on("install-mod", () => {
}
})
-ipcMain.on("remove-mod", (event, mod) => {utils.mods.remove(mod)});
-ipcMain.on("toggle-mod", (event, mod) => {utils.mods.toggle(mod)});
+ipcMain.on("remove-mod", (event, mod) => {mods.remove(mod)});
+ipcMain.on("toggle-mod", (event, mod) => {mods.toggle(mod)});
ipcMain.on("launch-ns", () => {utils.launch()});
ipcMain.on("launch-vanilla", () => {utils.launch("vanilla")});
@@ -187,7 +203,7 @@ ipcMain.on("version-cli", () => {
// sends installed mods info to renderer
ipcMain.on("getmods", () => {
- let mods = utils.mods.list();
+ let mods = mods.list();
if (mods.all.length > 0) {
log(`${utils.lang("general.mods.installed")} ${mods.all.length}`);
log(`${utils.lang("general.mods.enabled")} ${mods.enabled.length}`);
@@ -223,9 +239,6 @@ ipcMain.on("newpath", (event, newpath) => {
win.send("wrong-path");
});
-// ensures PWD/CWD 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("update-viper")) {
diff --git a/src/lang.js b/src/lang.js
index f2fab3a..85488d8 100644
--- a/src/lang.js
+++ b/src/lang.js
@@ -1,6 +1,8 @@
const fs = require("fs");
-const enLang = JSON.parse(fs.readFileSync(__dirname + `/lang/en.json`, "utf8"));
+const json = require("./modules/json");
+
+const enLang = json(__dirname + "/lang/en.json");
let lang = "";
var langObj = {};
@@ -14,7 +16,7 @@ function _loadTranslation(forcedlang) {
}
try {
- opts = JSON.parse(fs.readFileSync("viper.json", "utf8"));
+ opts = json("viper.json");
}catch (e) {}
lang = opts.lang;
@@ -39,7 +41,7 @@ function _loadTranslation(forcedlang) {
lang = "en";
}
- langObj = JSON.parse(fs.readFileSync(__dirname + `/lang/${lang}.json`, "utf8"));
+ langObj = json(__dirname + `/lang/${lang}.json`);
}
diff --git a/src/lang/de.json b/src/lang/de.json
index f8f2d63..e281ff5 100644
--- a/src/lang/de.json
+++ b/src/lang/de.json
@@ -2,7 +2,7 @@
"lang.title": "German - Deutsch",
"cli.help.help": "Zeigt die Hilfe Nachricht an.",
- "cli.help.debug": "Öffnet Entwickler/Debug Werkzeuge.",
+ "cli.help.devtools": "Öffnet Entwickler/Debug Werkzeuge.",
"cli.help.version": "Gibt die Versions Informationen aus.",
"cli.help.cli": "Zwingt die CLI Einstellung auf \"an\".",
"cli.help.update": "Aktualisiert den Installationspfad von Northstar, durch den gegeben Installationspfad von Titanfall 2.",
@@ -56,9 +56,7 @@
"gui.mods.disabledtag": "Deaktiviert",
"gui.mods.install": "Installiere den Mod",
"gui.mods.find": "Suche nach Mods",
- "gui.mods.toggle": "Aktiviere/Deaktiviere den Mod",
"gui.mods.toggleall": "Aktiviere/Deaktiviere alle Mods",
- "gui.mods.remove": "Entferne den Mod",
"gui.mods.removeall": "Entferne alle Mods",
"gui.mods.nothingselected": "Es wurde kein Mod ausgewählt.",
"gui.mods.toggleall.confirm": "Das Deaktivieren aller Mods kann zum Deaktiveren von Mods führen die von Northstar benötigt werden. Bist du dir sicher das du diese Aktion durchführen willst?",
@@ -134,9 +132,13 @@
"gui.toast.title.installed": "Mod installiert!",
"gui.toast.title.failed": "Fehler beim Installieren des Mods!",
"gui.toast.title.malformed": "Fehlerhafte Ordnerstruktur!",
+ "gui.toast.title.duped": "Duplizierter Ordner name!",
+ "gui.toast.title.unknown_error": "Unbekannter Fehler!",
"gui.toast.desc.installed": "wurde installiert!",
"gui.toast.desc.malformed": "hat eine fehlerhafte Ordnerstruktur, falls du der Entwickler bist, solltest du dies beheben.",
"gui.toast.desc.failed": "Ein unbekannter Fehler ist aufgetaucht beim Installieren, die Schuld kann beim Autor liegen oder bei Viper selbst!",
+ "gui.toast.desc.duped": "hat mehrere Ordner in sich mit dem selben Namen, wodurch ein duplizierter Ordner ensteht! Falls du der Entwickler bist solltest du dies beheben!",
+ "gui.toast.desc.unknown_error": "Ein unbekannter Fehler ist aufgetreten für mehr details drücken! Es wird empfohlen einen Screenshot von der detalierten Fehlernachricht zu machen wenn ein Bug-Report erstellt wird!",
"gui.toast.noInternet.title": "Kein Internet",
"gui.toast.noInternet.desc": "Viper funktioniert möglicherweise nicht korrekt",
diff --git a/src/lang/en.json b/src/lang/en.json
index 7ea0d39..e2cf29f 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -2,7 +2,7 @@
"lang.title": "English",
"cli.help.help": "shows this help message",
- "cli.help.debug": "opens the dev/debug tools",
+ "cli.help.devtools": "opens the dev/debug tools",
"cli.help.version": "outputs version info",
"cli.help.cli": "forces the CLI to enable",
"cli.help.update": "updates Northstar from your set game path",
@@ -56,9 +56,7 @@
"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",
"gui.mods.removeall": "Remove All",
"gui.mods.nothingselected": "You've not selected a mod.",
"gui.mods.toggleall.confirm": "Toggling all mods could disable mods required for Northstar to function. Are you sure?",
@@ -139,11 +137,15 @@
"gui.gamepath.lost": "Gamepath no longer exists/can't be found!\n\nMake sure your drive is mounted properly, or if you moved your game location that you update the game path.\n\nViper may not work properly until next restart!",
"gui.toast.title.installed": "Mod installed!",
+ "gui.toast.title.duped": "Duplicate folder names!",
"gui.toast.title.failed": "Failed to install",
"gui.toast.title.malformed": "Incorrect folder structure!",
+ "gui.toast.title.unknown_error": "Unknown Error!",
"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.",
+ "gui.toast.desc.duped": "has multiple mod folders in it, with the same name, causing duplicate folders, if you're the developer, you should fix this.",
+ "gui.toast.desc.unknown_error": "An unknown error occurred, click for more details. You may want to take a screenshot of the detailed error when filing a bug report.",
"wine.invalidprefix": "The selected Wine prefix doesn't exist, and is therefore invalid.",
"wine.originnotfound": "Origin can't be found in the selected Wine prefix.",
diff --git a/src/lang/es.json b/src/lang/es.json
index f80c9db..aaee7b9 100644
--- a/src/lang/es.json
+++ b/src/lang/es.json
@@ -2,7 +2,7 @@
"lang.title": "Spanish - Español",
"cli.help.help": "muestra este mensaje de ayuda",
- "cli.help.debug": "habre las herramientas de desarrollador/depuración ",
+ "cli.help.devtools": "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",
@@ -56,9 +56,7 @@
"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?",
@@ -135,9 +133,13 @@
"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.title.duped": "tiene varias carpetas de la modificación con el mismo nombre, lo que genera carpetas duplicadas. Si eres el desarrollador, deberías arreglar esto.",
+ "gui.toast.title.unknown_error": "¡Error desconocido!",
"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.",
+ "gui.toast.desc.duped": "¡Nombres de las carpetas duplicados!",
+ "gui.toast.desc.unknown_error": "Ha ocurrido un error desconocido, presiona para más detalles. Recomendamos que tomes una captura de pantalla del error con sus detalles cuando reportes un error.",
"gui.running": "Ejecutándose",
"gui.server.player": "jugador",
diff --git a/src/lang/fr.json b/src/lang/fr.json
index b3468f8..48b3a69 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -2,7 +2,7 @@
"lang.title": "French - Français",
"cli.help.help": "affiche ce message d'aide",
- "cli.help.debug": "affiche les outils de développement",
+ "cli.help.devtools": "affiche les outils de développement",
"cli.help.version": "retourne des informations sur la version du logiciel",
"cli.help.cli": "force l'activation de la CLI",
"cli.help.update": "met à jour Northstar sur le chemin du jeu précisé",
@@ -56,9 +56,7 @@
"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",
"gui.mods.removeall": "Tout supprimer",
"gui.mods.nothingselected": "Aucun mod n'est sélectionné.",
"gui.mods.toggleall.confirm": "Cette action pourrait désactiver des mods nécessaires au bon fonctionnement de Northstar. Souhaitez-vous faire cela ?",
@@ -135,15 +133,19 @@
"gui.selectpath": "Veuillez sélectionner le dossier où se trouve le client Titanfall 2.",
"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.gamepath.wrong": "Ce dossier ne contient pas le jeu Titanfall 2, et n'est donc pas valide.",
"gui.gamepath.lost": "Le chemin du jeu ne peut être trouvé / n'existe plus !\n\nVeuillez vérifier que votre disqué est correctement monté, ou, si vous avez déplacé votre jeu, que vous avez mis à jour le chemin du dossier.\n\nViper ne fonctionnera pas correctement jusqu'au prochain redémarrage.",
"gui.toast.title.installed": "Mod installé !",
+ "gui.toast.title.duped": "Nom de dossier dupliqué !",
"gui.toast.title.failed": "L'installation a échoué",
"gui.toast.title.malformed": "La structure du dossier du mod est incorrecte.",
+ "gui.toast.title.unknown_error": "Erreur inconnue",
"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.",
+ "gui.toast.desc.duped": "contient plusieurs dossiers ayant le même nom ; si vous êtes le développer, vous devriez réparer ceci.",
+ "gui.toast.desc.unknown_error": "Une erreur inconnue est survenue, cliquez pour plus de détails. Vous devriez prendre une capture d'écran de l'erreur si vous comptez créer un ticket.",
"wine.invalidprefix": "Le préfixe Wine sélectionné n'existe pas, et n'est donc pas valide.",
"wine.originnotfound": "Origin ne peut être trouvé dans le préfixe Wine sélectionné.",
diff --git a/src/extras/find.js b/src/modules/find.js
index 25c1b38..25c1b38 100644
--- a/src/extras/find.js
+++ b/src/modules/find.js
diff --git a/src/modules/json.js b/src/modules/json.js
new file mode 100644
index 0000000..5c099b1
--- /dev/null
+++ b/src/modules/json.js
@@ -0,0 +1,35 @@
+const fs = require("fs");
+const repair = require("jsonrepair");
+
+function read(file) {
+ let json = false;
+
+ // make sure the file actually exists
+ if (! fs.existsSync(file)) {
+ return false;
+ }
+
+ // make sure we're actually reading a file
+ if (! fs.statSync(file).isFile()) {
+ return false;
+ }
+
+ // read the file
+ let file_content = fs.readFileSync(file, "utf8");
+
+ // attempt to parse it
+ try {
+ json = JSON.parse(file_content);
+ }catch(err) {
+ // attempt to repair then parse
+ try {
+ json = JSON.parse(repair(file_content));
+ }catch(repair_err) {
+ return false;
+ }
+ }
+
+ return json;
+}
+
+module.exports = read;
diff --git a/src/modules/mods.js b/src/modules/mods.js
new file mode 100644
index 0000000..9aa2c81
--- /dev/null
+++ b/src/modules/mods.js
@@ -0,0 +1,552 @@
+const path = require("path");
+const fs = require("fs-extra");
+const unzip = require("unzipper");
+const copy = require("recursive-copy");
+const { app, ipcMain } = require("electron");
+const { https } = require("follow-redirects");
+
+const json = require("./json");
+const settings = require("./settings");
+
+const cli = require("../cli");
+const lang = require("../lang");
+const utils = require("../utils");
+
+var mods = {
+ installing: [],
+ dupe_msg_sent: false,
+}
+
+function update_path() {
+ mods.path = path.join(settings.gamepath, "R2Northstar/mods");
+}; update_path();
+
+// 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.
+mods.list = () => {
+ update_path();
+
+ if (utils.getNSVersion() == "unknown") {
+ utils.winLog(lang("general.notinstalled"));
+ console.log("error: " + lang("general.notinstalled"));
+ cli.exit(1);
+ return false;
+ }
+
+ let enabled = [];
+ let disabled = [];
+
+ if (! fs.existsSync(mods.path)) {
+ fs.mkdirSync(path.join(mods.path), {recursive: true});
+ return {
+ enabled: [],
+ disabled: [],
+ all: []
+ };
+ }
+
+ let files = fs.readdirSync(mods.path);
+ files.forEach((file) => {
+ if (fs.statSync(path.join(mods.path, file)).isDirectory()) {
+ let modjson = path.join(mods.path, file, "mod.json");
+ if (fs.existsSync(modjson)) {
+ let mod = json(modjson);
+ if (! mod) {return}
+
+ let obj = {
+ Author: false,
+ Version: "unknown",
+ Name: "unknown",
+ FolderName: file,
+ ...mod}
+
+ obj.Disabled = ! mods.modfile.get(obj.Name);
+
+ let manifest_file = path.join(mods.path, file, "manifest.json");
+ if (fs.existsSync(manifest_file)) {
+ let manifest = json(manifest_file);
+ if (manifest != false) {
+ obj.ManifestName = manifest.name;
+ if (obj.Version == "unknown") {
+ obj.Version = manifest.version_number;
+ }
+ }
+ }
+
+ let author_file = path.join(mods.path, file, "thunderstore_author.txt");
+ if (fs.existsSync(author_file)) {
+ obj.Author = fs.readFileSync(author_file, "utf8");
+ }
+
+ if (obj.Disabled) {
+ disabled.push(obj);
+ } else {
+ enabled.push(obj);
+ }
+ }
+ }
+ })
+
+ return {
+ enabled: enabled,
+ disabled: disabled,
+ all: [...enabled, ...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.
+mods.get = (mod) => {
+ update_path();
+
+ if (utils.getNSVersion() == "unknown") {
+ utils.winLog(lang("general.notinstalled"));
+ console.log("error: " + lang("general.notinstalled"));
+ cli.exit(1);
+ return false;
+ }
+
+ let list = mods.list().all;
+
+ for (let i = 0; i < list.length; i++) {
+ if (list[i].Name == mod) {
+ return list[i];
+ } else {continue}
+ }
+
+ return false;
+}
+
+function modfile_pre() {
+ mods.modfile.file = path.join(mods.path, "..", "enabledmods.json");
+
+ if (! fs.existsSync(mods.path)) {
+ fs.mkdirSync(path.join(mods.path), {recursive: true});
+ }
+
+ if (! fs.existsSync(mods.modfile.file)) {
+ fs.writeFileSync(mods.modfile.file, "{}");
+ }
+}
+
+// Manages the enabledmods.json file
+//
+// It can both return info about the file, but also toggle mods in it,
+// generate the file itself, and so on.
+mods.modfile = {};
+
+mods.modfile.gen = () => {
+ modfile_pre();
+
+ let names = {};
+ let list = mods.list().all;
+ for (let i = 0; i < list.length; i++) {
+ names[list[i].Name] = true
+ }
+
+ fs.writeFileSync(mods.modfile.file, JSON.stringify(names));
+}
+
+mods.modfile.disable = (mod) => {
+ modfile_pre();
+
+ let data = json(mods.modfile.file);
+ data[mod] = false;
+ fs.writeFileSync(mods.modfile.file, JSON.stringify(data));
+}
+
+mods.modfile.enable = (mod) => {
+ modfile_pre();
+
+ let data = json(mods.modfile.file);
+ data[mod] = true;
+ fs.writeFileSync(mods.modfile.file, JSON.stringify(data));
+}
+
+mods.modfile.toggle = (mod) => {
+ modfile_pre();
+
+ let data = json(mods.modfile.file);
+ if (data[mod] != undefined) {
+ data[mod] = ! data[mod];
+ } else {
+ data[mod] = false;
+ }
+
+ fs.writeFileSync(mods.modfile.file, JSON.stringify(data));
+}
+
+mods.modfile.get = (mod) => {
+ modfile_pre();
+
+ let data = json(mods.modfile.file);
+
+ if (data[mod]) {
+ return true;
+ } else if (data[mod] === false) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+// 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.
+mods.install = (mod, opts) => {
+ update_path();
+
+ let modname = mod.replace(/^.*(\\|\/|\:)/, "");
+
+ opts = {
+ forked: false,
+ author: false,
+ destname: false,
+ malformed: false,
+ manifest_file: false,
+ ...opts
+ }
+
+ if (! opts.forked) {
+ mods.installing = [];
+ mods.dupe_msg_sent = false;
+ }
+
+ if (utils.getNSVersion() == "unknown") {
+ utils.winLog(lang("general.notinstalled"));
+ console.log("error: " + lang("general.notinstalled"));
+ cli.exit(1);
+ return false;
+ }
+
+ let notamod = () => {
+ utils.winLog(lang("gui.mods.notamod"));
+ console.log("error: " + lang("cli.mods.notamod"));
+ cli.exit(1);
+ return false;
+ }
+
+ let installed = () => {
+ console.log(lang("cli.mods.installed"));
+ cli.exit();
+
+ utils.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("installed-mod", "", {
+ name: modname,
+ malformed: opts.malformed,
+ });
+
+ ipcMain.emit("gui-getmods");
+ return true;
+ }
+
+ if (! fs.existsSync(mod)) {return notamod()}
+
+ if (fs.statSync(mod).isDirectory()) {
+ utils.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()) {
+
+
+ if (! json(path.join(mod, "mod.json"))) {
+ ipcMain.emit("failed-mod");
+ return notamod();
+ }
+
+ if (fs.existsSync(path.join(mods.path, modname))) {
+ fs.rmSync(path.join(mods.path, modname), {recursive: true});
+ }
+
+ let copydest = path.join(mods.path, modname);
+ if (typeof opts.destname == "string") {
+ copydest = path.join(mods.path, opts.destname)
+ }
+
+ copy(mod, copydest, (err) => {
+ if (err) {
+ ipcMain.emit("failed-mod");
+ return;
+ }
+
+ copy(opts.manifest_file, path.join(copydest, "manifest.json"), (err) => {
+ if (err) {
+ ipcMain.emit("failed-mod");
+ return;
+ }
+
+ if (opts.author) {
+ fs.writeFileSync(
+ path.join(copydest, "thunderstore_author.txt"),
+ opts.author
+ )
+ }
+
+ return installed();
+ });
+ });
+
+ return;
+ } else {
+ mod_files = fs.readdirSync(mod);
+
+ for (let i = 0; i < mod_files.length; i++) {
+ if (fs.statSync(path.join(mod, mod_files[i])).isDirectory()) {
+ if (fs.existsSync(path.join(mod, mod_files[i], "mod.json")) &&
+ fs.statSync(path.join(mod, mod_files[i], "mod.json")).isFile()) {
+
+ let mod_name = mod_files[i];
+ let use_mod_name = false;
+
+ while (mods.installing.includes(mod_name)) {
+ if (! mods.dupe_msg_sent) {
+ mods.dupe_msg_sent = true;
+ ipcMain.emit("duped-mod", "", mod_name);
+ }
+
+ use_mod_name = true;
+ mod_name = mod_name + " (dupe)";
+ }
+
+ mods.installing.push(mod_name);
+
+ let install = false;
+ if (use_mod_name) {
+ install = mods.install(path.join(mod, mod_files[i]), {
+ ...opts,
+ forked: true,
+ destname: mod_name,
+ })
+ } else {
+ install = mods.install(path.join(mod, mod_files[i]), {
+ ...opts,
+ forked: true
+ })
+ }
+
+ if (install) {return true};
+ }
+ }
+ }
+
+ return notamod();
+ }
+ } else {
+ utils.winLog(lang("gui.mods.extracting"));
+ let cache = path.join(app.getPath("userData"), "Archives");
+ if (fs.existsSync(cache)) {
+ fs.rmSync(cache, {recursive: true});
+ fs.mkdirSync(path.join(cache, "mods"), {recursive: true});
+ } else {
+ fs.mkdirSync(path.join(cache, "mods"), {recursive: true});
+ }
+
+ try {
+ if (mod.replace(/.*\./, "").toLowerCase() == "zip") {
+ fs.createReadStream(mod).pipe(unzip.Extract({path: cache}))
+ .on("finish", () => {
+ 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"), {
+ ...opts,
+
+ forked: true,
+ malformed: true,
+ manifest_file: manifest,
+ destname: require(manifest).name
+ })) {
+
+ 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, {
+ ...opts,
+ forked: true,
+ destname: false,
+ manifest_file: manifest
+ })) {
+
+ return true
+ }
+ }, 1000)
+ }
+ }
+
+ if (files.length == 0) {
+ ipcMain.emit("failed-mod");
+ return notamod();
+ }
+ }
+
+ return notamod();
+ }
+
+ if (mods.install(cache, {
+ ...opts,
+ forked: true
+ })) {
+ installed();
+ } else {return notamod()}
+ }, 1000)
+ });
+ } else {
+ return notamod();
+ }
+ }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()
+mods.installFromURL = (url, author) => {
+ update_path();
+
+ 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);
+
+ stream.on("finish", () => {
+ stream.close();
+ mods.install(modlocation, {
+ author: author
+ })
+ })
+ })
+}
+
+// Removes mods
+//
+// Takes in the names of the mod then removes it, no confirmation,
+// that'd be up to the GUI.
+mods.remove = (mod) => {
+ update_path();
+
+ if (utils.getNSVersion() == "unknown") {
+ utils.winLog(lang("general.notinstalled"));
+ console.log("error: " + lang("general.notinstalled"));
+ cli.exit(1);
+ return false;
+ }
+
+ if (mod == "allmods") {
+ let modlist = mods.list().all;
+ for (let i = 0; i < modlist.length; i++) {
+ mods.remove(modlist[i].Name);
+ }
+ return
+ }
+
+ let disabled = path.join(mods.path, "disabled");
+ if (! fs.existsSync(disabled)) {
+ fs.mkdirSync(disabled);
+ }
+
+ let mod_name = mods.get(mod).FolderName;
+ if (! mod_name) {
+ console.log("error: " + lang("cli.mods.cantfind"));
+ cli.exit(1);
+ return;
+ }
+
+ let path_to_mod = path.join(mods.path, mod_name);
+
+ if (mods.get(mod).Disabled) {
+ path_to_mod = path.join(disabled, mod_name);
+ }
+
+ if (fs.statSync(path_to_mod).isDirectory()) {
+ let manifestname = null;
+ if (fs.existsSync(path.join(path_to_mod, "manifest.json"))) {
+ manifestname = require(path.join(path_to_mod, "manifest.json")).name;
+ }
+
+ fs.rmSync(path_to_mod, {recursive: true});
+ console.log(lang("cli.mods.removed"));
+ cli.exit();
+ ipcMain.emit("gui-getmods");
+ ipcMain.emit("removed-mod", "", {
+ name: mod.replace(/^.*(\\|\/|\:)/, ""),
+ manifestname: manifestname
+ });
+ } else {
+ 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.
+mods.toggle = (mod, fork) => {
+ update_path();
+
+ if (utils.getNSVersion() == "unknown") {
+ utils.winLog(lang("general.notinstalled"));
+ console.log("error: " + lang("general.notinstalled"));
+ cli.exit(1);
+ return false;
+ }
+
+ if (mod == "allmods") {
+ let modlist = mods.list().all;
+ for (let i = 0; i < modlist.length; i++) {
+ mods.toggle(modlist[i].Name, true);
+ }
+
+ console.log(lang("cli.mods.toggledall"));
+ cli.exit(0);
+ return
+ }
+
+ mods.modfile.toggle(mod);
+ if (! fork) {
+ console.log(lang("cli.mods.toggled"));
+ cli.exit();
+ }
+ ipcMain.emit("gui-getmods");
+}
+
+module.exports = mods;
diff --git a/src/extras/requests.js b/src/modules/requests.js
index a5f3121..6841fe2 100644
--- a/src/extras/requests.js
+++ b/src/modules/requests.js
@@ -1,9 +1,10 @@
const { app } = require("electron");
const path = require("path");
const fs = require("fs");
-const { https } = require("follow-redirects");
+const { https, http } = require("follow-redirects");
const lang = require("../lang");
+const json = require("./json");
// all requests results are stored in this file
const cachePath = path.join(app.getPath("cache"), "viper-requests.json");
@@ -11,6 +12,7 @@ const NORTHSTAR_LATEST_RELEASE_KEY = "nsLatestRelease";
const NORTHSTAR_RELEASE_NOTES_KEY = "nsReleaseNotes";
const VIPER_RELEASE_NOTES_KEY = "vpReleaseNotes";
+const user_agent = "viper/" + json(path.join(__dirname, "../../package.json")).version;
function _saveCache(data) {
fs.writeFileSync(cachePath, JSON.stringify(data));
@@ -40,7 +42,7 @@ async function getLatestNsVersion() {
port: 443,
path: "/repos/R2Northstar/Northstar/releases/latest",
method: "GET",
- headers: { "User-Agent": "viper" }
+ headers: { "User-Agent": user_agent }
},
response => {
@@ -90,7 +92,7 @@ async function getNsReleaseNotes() {
port: 443,
path: "/repos/R2Northstar/Northstar/releases",
method: "GET",
- headers: { "User-Agent": "viper" }
+ headers: { "User-Agent": user_agent }
},
response => {
@@ -141,7 +143,7 @@ async function getVpReleaseNotes() {
port: 443,
path: "/repos/0negal/viper/releases",
method: "GET",
- headers: { "User-Agent": "viper" }
+ headers: { "User-Agent": user_agent }
},
response => {
diff --git a/src/modules/settings.js b/src/modules/settings.js
new file mode 100644
index 0000000..d0a2db1
--- /dev/null
+++ b/src/modules/settings.js
@@ -0,0 +1,70 @@
+const fs = require("fs");
+const path = require("path");
+const app = require("electron").app;
+
+const json = require("./json");
+const lang = require("../lang");
+
+var invalid_settings = false;
+
+// Base settings
+var settings = {
+ gamepath: "",
+ lang: "en-US",
+ nsupdate: true,
+ autolang: true,
+ forcedlang: "en",
+ autoupdate: true,
+ originkill: false,
+ nsargs: "-multiple",
+ zip: "/northstar.zip",
+
+ // These files won't be overwritten when installing/updating
+ // Northstar, useful for config files
+ 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")) {
+ let conf = json("viper.json");
+
+ // Validates viper.json
+ if (! conf) {
+ invalid_settings = true;
+ }
+
+ settings = {...settings, ...conf};
+ settings.zip = path.join(settings.gamepath + "/northstar.zip");
+
+ let args = path.join(settings.gamepath, "ns_startup_args.txt");
+ if (fs.existsSync(args)) {
+ settings.nsargs = fs.readFileSync(args, "utf8");
+ }
+} else {
+ console.log(lang("general.missingpath"));
+}
+
+// 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.
+//
+// You can also pass a settings object to the function and it'll try and
+// merge it together with the already existing settings
+settings.save = (obj = {}) => {
+ if (invalid_settings) {return false}
+
+ let settings_content = {...settings, ...obj};
+
+ delete settings_content.save;
+
+ if (fs.existsSync(settings.gamepath)) {
+ fs.writeFileSync(path.join(settings.gamepath, "ns_startup_args.txt"), settings.nsargs);
+ }
+
+ fs.writeFileSync(app.getPath("appData") + "/viper.json", JSON.stringify({...settings, ...obj}));
+}
+
+module.exports = settings;
diff --git a/src/utils.js b/src/utils.js
index e6a3245..615057f 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,47 +1,22 @@
const path = require("path");
const fs = require("fs-extra");
-const copy = require("recursive-copy");
-const { app, dialog, ipcMain, Notification } = require("electron");
+const { dialog, ipcMain, Notification } = require("electron");
const Emitter = require("events");
const events = new Emitter();
const cli = require("./cli");
const lang = require("./lang");
-const find = require("./extras/find");
-const requests = require("./extras/requests");
+
+const find = require("./modules/find");
+const json = require("./modules/json");
+const settings = require("./modules/settings");
+const requests = require("./modules/requests");
const unzip = require("unzipper");
-const repair = require("jsonrepair");
const exec = require("child_process").exec;
const { https } = require("follow-redirects");
-process.chdir(app.getPath("appData"));
-
-var invalidsettings = false;
-
-// Base settings
-var settings = {
- gamepath: "",
- lang: "en-US",
- nsupdate: true,
- autolang: true,
- wineprefix: "",
- forcedlang: "en",
- autoupdate: true,
- originkill: false,
- nsargs: "-multiple",
- zip: "/northstar.zip",
- winebin: "/usr/bin/wine64",
-
- // These files won't be overwritten when installing/updating
- // Northstar, useful for config files
- excludes: [
- "ns_startup_args.txt",
- "ns_startup_args_dedi.txt"
- ]
-}
-
// Logs into the dev tools of the renderer
function winLog(msg) {
ipcMain.emit("win-log", msg, msg);
@@ -52,29 +27,6 @@ function winAlert(msg) {
ipcMain.emit("win-alert", msg, msg);
}
-// Creates the settings file with the base settings if it doesn't exist.
-if (fs.existsSync("viper.json")) {
- let conf = fs.readFileSync("viper.json", "utf8");
- let json = "{}";
-
- // Validates viper.json
- try {
- json = JSON.parse(conf);
- }catch (e) {
- invalidsettings = true;
- }
-
- settings = {...settings, ...json};
- settings.zip = path.join(settings.gamepath + "/northstar.zip");
-
- let args = path.join(settings.gamepath, "ns_startup_args.txt");
- if (fs.existsSync(args)) {
- settings.nsargs = fs.readFileSync(args, "utf8");
- }
-} else {
- 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() {
@@ -196,7 +148,7 @@ async function setpath(win, forcedialog) {
function setGamepath(folder) {
settings.gamepath = folder;
settings.zip = path.join(settings.gamepath + "/northstar.zip");
- saveSettings();
+ settings.save();
win.webContents.send("newpath", settings.gamepath);
ipcMain.emit("newpath", null, settings.gamepath);
@@ -210,7 +162,7 @@ async function setpath(win, forcedialog) {
function setGamepath(folder, forcedialog) {
settings.gamepath = folder;
settings.zip = path.join(settings.gamepath + "/northstar.zip");
- saveSettings();
+ settings.save();
win.webContents.send("newpath", settings.gamepath);
ipcMain.emit("newpath", null, settings.gamepath);
}
@@ -243,24 +195,6 @@ async function setpath(win, forcedialog) {
}
}
-// 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.
-//
-// You can also pass a settings object to the function and it'll try and
-// merge it together with the already existing settings
-function saveSettings(obj = {}) {
- if (invalidsettings) {return false}
-
- settings = {...settings, ...obj};
-
- if (fs.existsSync(settings.gamepath)) {
- fs.writeFileSync(path.join(settings.gamepath, "ns_startup_args.txt"), settings.nsargs);
- }
-
- fs.writeFileSync(app.getPath("appData") + "/viper.json", JSON.stringify({...settings, ...obj}));
-}
-
// Returns the current Northstar version
// If not installed it'll return "unknown"
function getNSVersion() {
@@ -292,7 +226,7 @@ function getNSVersion() {
}
try {
- add("v" + JSON.parse(fs.readFileSync(versionFile, "utf8")).Version);
+ add("v" + json(versionFile).Version);
}catch(err) {
add("unknown");
}
@@ -554,423 +488,6 @@ function gamepathExists() {
return fs.existsSync(settings.gamepath);
}
-// 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: () => {
- let modpath = path.join(settings.gamepath, "R2Northstar/mods");
-
- if (getNSVersion() == "unknown") {
- winLog(lang("general.notinstalled"));
- console.log("error: " + lang("general.notinstalled"));
- cli.exit(1);
- return false;
- }
-
- let enabled = [];
- let disabled = [];
-
- if (! fs.existsSync(modpath)) {
- fs.mkdirSync(path.join(modpath), {recursive: true});
- return {
- enabled: [],
- disabled: [],
- all: []
- };
- }
-
- files = fs.readdirSync(modpath);
- files.forEach((file) => {
- if (fs.statSync(path.join(modpath, file)).isDirectory()) {
- let modjson = path.join(modpath, file, "mod.json");
- if (fs.existsSync(modjson)) {
- let mod = JSON.parse(repair(fs.readFileSync(modjson, "utf8")));
-
- let obj = {
- Version: "unknown",
- Name: "unknown",
- FolderName: file,
- ...mod}
-
- obj.Disabled = ! mods.modfile().get(obj.Name);
-
- let manifestfile = path.join(modpath, file, "manifest.json");
- if (fs.existsSync(manifestfile)) {
- let manifest = JSON.parse(repair(fs.readFileSync(manifestfile, "utf8")));
-
- obj.ManifestName = manifest.name;
- if (obj.Version == "unknown") {
- obj.Version = manifest.version_number;
- }
- }
-
- if (obj.Disabled) {
- disabled.push(obj);
- } else {
- enabled.push(obj);
- }
- }
- }
- })
-
- return {
- enabled: enabled,
- disabled: disabled,
- all: [...enabled, ...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) => {
- let modpath = path.join(settings.gamepath, "R2Northstar/mods");
-
- if (getNSVersion() == "unknown") {
- winLog(lang("general.notinstalled"));
- console.log("error: " + lang("general.notinstalled"));
- cli.exit(1);
- return false;
- }
-
- let list = mods.list().all;
-
- for (let i = 0; i < list.length; i++) {
- if (list[i].Name == mod) {
- return list[i];
- } else {continue}
- }
-
- return false;
- },
-
- // Manages the enabledmods.json file
- //
- // It can both return info about the file, but also toggle mods in
- // it, generate the file itself, and so on.
- modfile: () => {
- let modpath = path.join(settings.gamepath, "R2Northstar/mods");
- let file = path.join(modpath, "..", "enabledmods.json");
-
- if (! fs.existsSync(modpath)) {
- fs.mkdirSync(path.join(modpath), {recursive: true});
- }
-
- if (! fs.existsSync(file)) {
- fs.writeFileSync(file, "{}");
- }
-
- return {
- gen: () => {
- let names = {};
- let list = mods.list().all;
- for (let i = 0; i < list.length; i++) {
- names[list[i].Name] = true
- }
-
- fs.writeFileSync(file, JSON.stringify(names));
- },
- disable: (mod) => {
- let data = JSON.parse(repair(fs.readFileSync(file, "utf8")));
- data[mod] = false;
- fs.writeFileSync(file, JSON.stringify(data));
- },
- enable: (mod) => {
- let data = JSON.parse(repair(fs.readFileSync(file, "utf8")));
- data[mod] = true;
- fs.writeFileSync(file, JSON.stringify(data));
- },
- toggle: (mod) => {
- let data = JSON.parse(repair(fs.readFileSync(file, "utf8")));
- if (data[mod] != undefined) {
- data[mod] = ! data[mod];
- } else {
- data[mod] = false;
- }
-
- fs.writeFileSync(file, JSON.stringify(data));
- },
- get: (mod) => {
- let data = JSON.parse(repair(fs.readFileSync(file, "utf8")));
- let names = Object.keys(data);
-
- if (data[mod]) {
- return true;
- } else if (data[mod] === false) {
- return false;
- } else {
- return true;
- }
- }
- };
- },
-
- // 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, destname, manifestfile, malformed = false) => {
- let modname = mod.replace(/^.*(\\|\/|\:)/, "");
-
- if (getNSVersion() == "unknown") {
- winLog(lang("general.notinstalled"));
- console.log("error: " + lang("general.notinstalled"));
- cli.exit(1);
- return false;
- }
-
- let notamod = () => {
- winLog(lang("gui.mods.notamod"));
- console.log("error: " + lang("cli.mods.notamod"));
- cli.exit(1);
- return false;
- }
-
- let installed = () => {
- console.log(lang("cli.mods.installed"));
- 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("installed-mod", "", {
- name: modname,
- malformed: malformed,
- });
- ipcMain.emit("gui-getmods");
- return true;
- }
-
- if (! fs.existsSync(mod)) {return notamod()}
-
- 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()) {
-
- 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 {
- files = fs.readdirSync(mod);
-
- for (let i = 0; i < files.length; i++) {
- if (fs.statSync(path.join(mod, files[i])).isDirectory()) {
- 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};
- }
- }
- }
-
- 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(path.join(cache, "mods"), {recursive: true});
- } else {
- fs.mkdirSync(path.join(cache, "mods"), {recursive: true});
- }
-
- try {
- if (mod.replace(/.*\./, "").toLowerCase() == "zip") {
- fs.createReadStream(mod).pipe(unzip.Extract({path: cache}))
- .on("finish", () => {
- 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("failed-mod");
- return notamod();
- }
- }
-
- return notamod();
- }
-
- if (mods.install(cache)) {
- installed();
- } else {return notamod()}
- }, 1000)
- });
- } else {
- return notamod();
- }
- }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);
-
- 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"));
- cli.exit(1);
- return false;
- }
-
- if (mod == "allmods") {
- let modlist = mods.list().all;
- for (let i = 0; i < modlist.length; i++) {
- mods.remove(modlist[i].Name);
- }
- return
- }
-
- let disabled = path.join(modpath, "disabled");
- if (! fs.existsSync(disabled)) {
- fs.mkdirSync(disabled);
- }
-
- let modName = mods.get(mod).FolderName;
- if (! modName) {
- console.log("error: " + lang("cli.mods.cantfind"));
- cli.exit(1);
- return;
- }
-
- let modPath = path.join(modpath, modName);
-
- if (mods.get(mod).Disabled) {
- modPath = path.join(disabled, modName);
- }
-
- 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("gui-getmods");
- ipcMain.emit("removed-mod", "", {
- name: mod.replace(/^.*(\\|\/|\:)/, ""),
- manifestname: manifestname
- });
- } else {
- 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"));
- console.log("error: " + lang("general.notinstalled"));
- cli.exit(1);
- return false;
- }
-
- if (mod == "allmods") {
- let modlist = mods.list().all;
- for (let i = 0; i < modlist.length; i++) {
- mods.toggle(modlist[i].Name, true);
- }
-
- console.log(lang("cli.mods.toggledall"));
- cli.exit(0);
- return
- }
-
- mods.modfile().toggle(mod);
- if (! fork) {
- console.log(lang("cli.mods.toggled"));
- cli.exit();
- }
- ipcMain.emit("gui-getmods");
- }
-};
-
setInterval(async () => {
if (gamepathExists()) {
ipcMain.emit("gui-getmods");
@@ -990,7 +507,6 @@ setInterval(async () => {
}, 1500)
module.exports = {
- mods,
winLog,
updateViper,
@@ -1004,16 +520,12 @@ module.exports = {
isGameRunning,
isOriginRunning,
-
- settings,
- saveSettings,
-
setpath,
gamepathExists,
lang,
setlang: (lang) => {
settings.lang = lang;
- saveSettings();
+ settings.save();
},
}