diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/index.html | 12 | ||||
-rw-r--r-- | src/app/main.css | 48 | ||||
-rw-r--r-- | src/app/main.js | 80 | ||||
-rw-r--r-- | src/cli.js | 30 | ||||
-rw-r--r-- | src/index.js | 43 | ||||
-rw-r--r-- | src/lang/en.json | 33 | ||||
-rw-r--r-- | src/lang/fr.json | 33 | ||||
-rw-r--r-- | src/utils.js | 255 |
8 files changed, 509 insertions, 25 deletions
diff --git a/src/app/index.html b/src/app/index.html index f5d92e0..88d5a17 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -26,6 +26,18 @@ <button id="northstar" onclick="launch()">%%gui.launchnorthstar%%</button> </div> </div> + <div id="modsdiv"> + </div> + <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> + </div> + </div> </div> <script src="lang.js"></script> diff --git a/src/app/main.css b/src/app/main.css index c2ca627..56f17b2 100644 --- a/src/app/main.css +++ b/src/app/main.css @@ -1,15 +1,22 @@ :root { + --padding: 15px; --disabled: #656E7F; - --background: #4C515B; --foreground: #DDE2EB; + --background: #4C515B; + --boxbackground: #666E7F; --subforeground: #AFAFAF; --btnforeground: var(--foreground); + + --red: #C7777F; + --blue: #81A1C1; + --yellow: #ECD19A; } @media (prefers-color-scheme: light) { :root { --background: #FFFFFF; --foreground: #4C566A; + --boxbackground: #EEF0F4; --btnforeground: var(--background); } } @@ -19,6 +26,8 @@ src: url("roboto.ttf"); } +::-webkit-scrollbar {display: none} + body, button, input { font-size: 18px; font-weight: 700; @@ -40,34 +49,56 @@ nobr {white-space: nowrap} .line { display: flex; - margin-top: 15px; + margin-top: var(--padding); +} + +#modsdiv { + padding: 1px; + height: 125px; + overflow-y: scroll; + border-radius: var(--padding); + background: var(--boxbackground); + margin: calc(var(--padding) / 3) var(--padding); +} + +.mod { + margin: calc(var(--padding) / 3); + border-radius: calc(var(--padding) / 2) !important; +} + +.mod.selected {background: var(--background)} + +.mod .disabled { + color: var(--red); + position: relative; + left: var(--padding); } .buttons { text-align: right; margin-left: auto; user-select: none; - margin-right: 7px; + margin-right: calc(var(--padding) / 1.9); } .text {max-width: 38vw} .buttons {max-width: 55vw} -button, .text { +button, .text, .mod { border: none; outline: none; - padding: 5px 15px; user-select: none; border-radius: 50px; transition: 0.2s ease-in-out; + padding: calc(var(--padding) / 3) var(--padding); } #welcome {padding: 0px} button { - margin-bottom: 10px; color: var(--btnforeground); -webkit-app-region: no-drag; + margin-bottom: calc(var(--padding) / 1.5); } button:hover {opacity: 0.9} @@ -77,8 +108,9 @@ button:active { } -#update {background: #81A1C1} #setpath {background: #5E81AC} -#northstar {background: #C7777F} #vanilla, #exit {background: #656E7F} +#update, #installmod {background: var(--blue)} +#togglemod, #toggleall {background: var(--yellow)} +#northstar, #removeall, #removemod {background: var(--red)} button:disabled {background: var(--disabled) !important; opacity: 0.5} diff --git a/src/app/main.js b/src/app/main.js index 00d3551..1678b86 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -59,6 +59,57 @@ function setButtons(state) { } } +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"); + } + } +} + +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: () => {}, + } + } + } + + return { + remove: () => { + if (selected == "allmods") { + if (! confirm(lang("gui.mods.removeall.confirm"))) { + return; + } + } + + ipcRenderer.send("removemod", selected) + }, + toggle: () => { + ipcRenderer.send("togglemod", selected) + } + } +} + +function installmod() { + ipcRenderer.send("installmod") +} + ipcRenderer.on("ns-updated", () => {setButtons(true)}) ipcRenderer.on("ns-updating", () => {setButtons(false)}) @@ -67,10 +118,39 @@ ipcRenderer.on("newpath", (event, newpath) => { }) ipcRenderer.on("log", (event, msg) => {log(msg)}) +ipcRenderer.on("alert", (event, msg) => {alert(msg)}) + +ipcRenderer.on("mods", (event, mods) => { + 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")} + + select(lastselected); +}) ipcRenderer.on("version", (event, versions) => { vpversion.innerText = lang("gui.versions.viper") + ": " + versions.vp; nsversion.innerText = lang("gui.versions.northstar") + ": " + versions.ns; + + if (versions.ns == "unknown") { + let buttons = document.querySelectorAll(".modbtns button"); + + for (let i = 0; i < buttons.length; i++) { + buttons[i].disabled = true; + } + } }); ipcRenderer.send("getversion"); ipcRenderer.on("updateavailable", () => { @@ -10,12 +10,16 @@ const lang = require("./lang"); function hasArgs() { if (cli.hasSwitch("cli") || cli.hasSwitch("help") || + cli.hasSwitch("mods") || cli.hasSwitch("update") || cli.hasSwitch("launch") || cli.hasSwitch("setpath") || cli.hasSwitch("version") || cli.hasSwitch("updatevp") || - cli.hasSwitch("gamepath")) { + cli.hasSwitch("gamepath") || + cli.hasSwitch("togglemod") || + cli.hasSwitch("removemod") || + cli.hasSwitch("installmod")) { return true; } else {return false} } @@ -27,14 +31,18 @@ function exit(code) { 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")} + --help ${lang("cli.help.help")} + --debug ${lang("cli.help.debug")} + --version ${lang("cli.help.version")} - --cli ${lang("cli.help.cli")} - --update ${lang("cli.help.update")} - --updatevp ${lang("cli.help.updatevp")} - --setpath ${lang("cli.help.setpath")}`) + --cli ${lang("cli.help.cli")} + --update ${lang("cli.help.update")} + --updatevp ${lang("cli.help.updatevp")} + --setpath ${lang("cli.help.setpath")} + + --installmod ${lang("cli.help.installmod")} + --removemod ${lang("cli.help.removemod")} + --togglemod ${lang("cli.help.togglemod")}`) // In the future --setpath should be able to understand // relative paths, instead of just absolute ones. exit(); @@ -62,6 +70,12 @@ async function init() { break; } } + + if (cli.hasSwitch("installmod")) {ipcMain.emit("installmod")} + if (cli.hasSwitch("removemod")) {ipcMain.emit("removemod", "", cli.getSwitchValue("removemod"))} + if (cli.hasSwitch("togglemod")) {ipcMain.emit("togglemod", "", cli.getSwitchValue("togglemod"))} + + if (cli.hasSwitch("mods")) {ipcMain.emit("getmods")} } module.exports = { diff --git a/src/index.js b/src/index.js index e421fd1..1b594ad 100644 --- a/src/index.js +++ b/src/index.js @@ -38,6 +38,12 @@ function start() { ipcMain.on("ns-updated", () => {win.webContents.send("ns-updated")}) ipcMain.on("ns-updating", () => {win.webContents.send("ns-updating")}) ipcMain.on("winLog", (event, ...args) => {win.webContents.send("log", ...args)}) + ipcMain.on("winAlert", (event, ...args) => {win.webContents.send("alert", ...args)}) + ipcMain.on("guigetmods", (event, ...args) => {win.webContents.send("mods", utils.mods.list())}) + + win.webContents.once("dom-ready", () => { + win.webContents.send("mods", utils.mods.list()); + }); if (utils.settings.autoupdate) {utils.updatevp(false)} @@ -48,9 +54,21 @@ function start() { ipcMain.on("updatenow", () => { autoUpdater.quitAndInstall(); }) - } +ipcMain.on("installmod", () => { + if (cli.hasArgs()) { + utils.mods.install(cli.param("installmod")) + } else { + dialog.showOpenDialog({properties: ["openFile"]}).then(res => { + utils.mods.install(res.filePaths[0]); + }).catch(err => {console.error(err)}) + } +}) + +ipcMain.on("removemod", (event, mod) => {utils.mods.remove(mod)}) +ipcMain.on("togglemod", (event, mod) => {utils.mods.toggle(mod)}) + ipcMain.on("launch", (event) => {utils.launch()}) ipcMain.on("setlang", (event, lang) => {utils.setlang(lang)}) ipcMain.on("launchVanilla", (event) => {utils.launch("vanilla")}) @@ -64,6 +82,7 @@ ipcMain.on("setpath", (event, value) => { win.show(); } }); + ipcMain.on("newpath", (event, newpath) => { if (newpath === false && !win.isVisible()) { win.webContents.send("nopathselected"); @@ -94,6 +113,28 @@ ipcMain.on("versioncli", () => { cli.exit(); }) +ipcMain.on("getmods", (event) => { + let mods = utils.mods.list(); + if (mods.all.length > 0) { + console.log(`${utils.lang("general.mods.installed")} ${mods.all.length}`) + console.log(`${utils.lang("general.mods.enabled")} ${mods.enabled.length}`) + for (let i = 0; i < mods.enabled.length; i++) { + console.log(` ${mods.enabled[i].Name} ${mods.enabled[i].Version}`) + } + + if (mods.disabled.length > 0) { + console.log(`${utils.lang("general.mods.disabled")} ${mods.disabled.length}`) + for (let i = 0; i < mods.disabled.length; i++) { + console.log(` ${mods.disabled[i].Name} ${mods.disabled[i].Version}`) + } + } + cli.exit(0); + } else { + console.log("No mods installed"); + cli.exit(0); + } +}) + process.chdir(app.getPath("appData")); if (cli.hasArgs()) { diff --git a/src/lang/en.json b/src/lang/en.json index 10f03cd..61a8545 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -6,6 +6,9 @@ "cli.help.update": "updates Northstar from your set game path", "cli.help.setpath": "sets your game path", "cli.help.updatevp": "updates Viper itself, if supported.", + "cli.help.installmod": "installs a mod, folder or zip", + "cli.help.removemod": "removes a mod", + "cli.help.togglemod": "toggles a mod", "cli.setpath.noarg": "No argument provided for --setpath", @@ -18,6 +21,15 @@ "cli.launch.linuxerror": "Launching the game is not currently supported on Linux", + "cli.mods.failed": "Failed to install mod!", + "cli.mods.removed": "Successfully removed mod!", + "cli.mods.toggled": "Successfully toggled mod", + "cli.mods.installed": "Successfully installed mod!", + "cli.mods.cantfind": "Can't find a mod with that name!", + "cli.mods.notamod": "Selected folder/file is not a mod", + "cli.mods.toggledall": "Successfully toggled all mods", + "cli.mods.improperjson": "%s's mod.json has formatting errors", + "gui.welcome": "Welcome to Viper!", "gui.versions.viper": "Viper version", "gui.versions.northstar": "Northstar version", @@ -25,6 +37,21 @@ "gui.update": "Update", "gui.setpath": "Game Path", + "gui.mods": "Mods", + "gui.mods.count": "Mods Installed:", + "gui.mods.disabledtag": "Disabled", + "gui.mods.install": "Install Mod", + "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.removeall.confirm": "Removing all mods will usually require you to reinstall Northstar, are you sure?", + "gui.mods.notamod": "Not a mod!", + "gui.mods.extracting": "Extracting mod...", + "gui.mods.installing": "Installing mod...", + "gui.mods.installedmod": "Installed mod!", + "gui.update.downloading": "Downloading...", "gui.update.extracting": "Extracting update...", "gui.update.finished": "Done! Ready to play!", @@ -41,5 +68,9 @@ "general.launching": "Launching", - "general.missingpath": "Game path is not set!" + "general.mods.enabled": "Enabled mods:", + "general.mods.disabled": "Disabled mods:", + "general.mods.installed": "Installed mods:", + "general.missingpath": "Game path is not set!", + "general.notinstalled": "Northstar is not installed!" } diff --git a/src/lang/fr.json b/src/lang/fr.json index 0cd9bfb..58302e9 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -6,6 +6,9 @@ "cli.help.update": "met à jour Northstar sur le chemin du jeu précisé", "cli.help.setpath": "enregistre le chemin du client de jeu", "cli.help.updatevp": "met à jour le client Viper, si le format actuel le permet.", + "cli.help.installmod": "Installe un mod, dossier ou zip", + "cli.help.removemod": "Supprime un mod", + "cli.help.togglemod": "Active/désactive un mod", "cli.setpath.noarg": "Aucun argument donné à --setpath", @@ -18,6 +21,15 @@ "cli.launch.linuxerror": "Le support du jeu sur Linux n'est pas encore implémenté.", + "cli.mods.failed": "L'installation du mod a échoué.", + "cli.mods.removed": "Le mod a bien été supprimé.", + "cli.mods.toggled": "Le mod a bien été activé/désactivé.", + "cli.mods.installed": "Mod installé !", + "cli.mods.cantfind": "Aucun mod avec ce nom n'a pu être trouvé.", + "cli.mods.notamod": "Le fichier/dossier sélectionné n'est pas un mod.", + "cli.mods.toggledall": "Tous les mods ont bien été activés/désactivés.", + "cli.mods.improperjson": "Le mod.json de %s présente des erreurs de formatage.", + "gui.welcome": "Bienvenue sur Viper !", "gui.versions.viper": "Version de Viper", "gui.versions.northstar": "Version de Northstar", @@ -25,6 +37,21 @@ "gui.update": "Mise à jour", "gui.setpath": "Chemin du jeu", + "gui.mods": "Mods", + "gui.mods.count": "Mods installés :", + "gui.mods.disabledtag": "Désactivés", + "gui.mods.install": "Installer le mod", + "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.removeall.confirm": "Supprimer tous les mods vous forcera à réinstaller Northstar, souhaitez-vous faire cela ?", + "gui.mods.notamod": "Ceci n'est pas un mod !", + "gui.mods.extracting": "Extraction du mod...", + "gui.mods.installing": "Installation du mod...", + "gui.mods.installedmod": "Mod installé !", + "gui.update.downloading": "Téléchargement de la mise à jour...", "gui.update.extracting": "Extraction des fichiers...", "gui.update.finished": "Terminé, vous pouvez jouer !", @@ -41,5 +68,9 @@ "general.launching": "Lancement", - "general.missingpath": "Le chemin du client n'est pas défini !" + "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.notinstalled": "Northstar n'est pas installé !" } diff --git a/src/utils.js b/src/utils.js index 225de47..68cd6c1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,6 @@ -const fs = require("fs"); const path = require("path"); +const fs = require("fs-extra"); +const copy = require("copy-dir"); const { app, dialog, ipcMain } = require("electron"); const Emitter = require("events"); @@ -128,11 +129,12 @@ async function update() { } } - ipcMain.emit("ns-updated"); - winLog(lang("gui.update.finished")); - console.log(lang("cli.update.finished")); - cli.exit(); - }); + ipcMain.emit("guigetmods"); + ipcMain.emit("ns-updated"); + winLog(lang("gui.update.finished")); + console.log(lang("cli.update.finished")); + cli.exit(); + }) }) }) } @@ -175,7 +177,248 @@ function winLog(msg) { ipcMain.emit("winLog", msg, msg); } +function winAlert(msg) { + ipcMain.emit("winAlert", msg, msg); +} + +let modpath = path.join(settings.gamepath, "R2Northstar/mods"); +const mods = { + list: () => { + if (getNSVersion() == "unknown") { + winLog(lang("general.notinstalled")) + console.log("error: " + lang("general.notinstalled")) + cli.exit(1); + return false; + } + + let mods = []; + let disabled = []; + + files = fs.readdirSync(modpath) + files.forEach((file) => { + if (fs.statSync(path.join(modpath, file)).isDirectory()) { + if (fs.existsSync(path.join(modpath, file, "mod.json"))) { + try { + mods.push({...require(path.join(modpath, file, "mod.json")), FolderName: file, Disabled: false}) + }catch(err) { + console.log("error: " + lang("cli.mods.improperjson"), file) + mods.push({Name: file, FolderName: file, Version: "unknown", Disabled: false}) + } + } + } + }) + + let disabledPath = path.join(modpath, "disabled") + if (! fs.existsSync(disabledPath)) { + fs.mkdirSync(disabledPath) + } + + files = fs.readdirSync(disabledPath) + files.forEach((file) => { + if (fs.statSync(path.join(disabledPath, file)).isDirectory()) { + if (fs.existsSync(path.join(disabledPath, file, "mod.json"))) { + try { + disabled.push({...require(path.join(disabledPath, file, "mod.json")), FolderName: file, Disabled: true}) + }catch(err) { + console.log("error: " + lang("cli.mods.improperjson"), file) + disabled.push({Name: file, FolderName: file, Version: "unknown", Disabled: true}) + } + } + } + }) + + return { + enabled: mods, + disabled: disabled, + all: [...mods, ...disabled] + }; + }, + get: (mod) => { + 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; + }, + install: (mod) => { + 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")) + ipcMain.emit("guigetmods"); + return true; + } + + if (! fs.existsSync(mod)) {return notamod()} + + if (fs.statSync(mod).isDirectory()) { + winLog(lang("gui.mods.installing")) + 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, + }); + + 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()) { + + if (mods.install(path.join(mod, files[i]))) {return true}; + } + } + } + + notamod(); + return false; + } + } 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); + } else { + fs.mkdirSync(cache); + } + + try { + fs.createReadStream(mod).pipe(unzip.Extract({path: cache})) + .on("finish", () => { + if (mods.install(cache)) { + installed(); + } else {return notamod()} + }); + }catch(err) {return notamod()} + } + }, + remove: (mod) => { + 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()) { + fs.rmSync(modPath, {recursive: true}); + console.log(lang("cli.mods.removed")); + cli.exit(); + ipcMain.emit("guigetmods"); + } else { + cli.exit(1); + } + }, + 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 + } + + 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); + let dest = path.join(disabled, modName); + + if (mods.get(mod).Disabled) { + modPath = path.join(disabled, modName); + dest = path.join(modpath, modName); + } + + fs.moveSync(modPath, dest) + if (! fork) { + console.log(lang("cli.mods.toggled")); + cli.exit(); + } + ipcMain.emit("guigetmods"); + } +}; + module.exports = { + mods, + lang, winLog, launch, update, |