diff --git a/src/app/icons/linux.png b/src/app/icons/linux.png new file mode 100644 index 00000000..11601563 Binary files /dev/null and b/src/app/icons/linux.png differ diff --git a/src/app/index.html b/src/app/index.html index d9e8e14f..f073cacd 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -51,6 +51,32 @@

%%gui.settings.title.ns%%

+
+ +

%%gui.settings.title.linux%%

+
+
+
+ %%gui.settings.wineprefix.title%% +
+ %%gui.settings.wineprefix.desc%% +
+
+
+ +
+
+
+
+ %%gui.settings.winebin.title%% +
+ %%gui.settings.winebin.desc%% +
+
+
+ +
+

%%gui.settings.title.language%%

diff --git a/src/app/js/settings.js b/src/app/js/settings.js index 63b4b99e..34975fe7 100644 --- a/src/app/js/settings.js +++ b/src/app/js/settings.js @@ -126,6 +126,13 @@ var Settings = { } } + if (process.platform == "win32") { + let linuxopts = document.querySelectorAll(".options .linuxopt"); + for (let i = 0; i < linuxopts.length; i++) { + linuxopts[i].style.display = "none"; + } + } + ipcRenderer.send("can-autoupdate"); ipcRenderer.on("cant-autoupdate", () => { document.querySelector(".option[name=autoupdate]").style.display = "none"; diff --git a/src/app/main.js b/src/app/main.js index 5ac07dea..6120428c 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -10,6 +10,7 @@ let shouldInstallNorthstar = false; var settings = { nsargs: "", gamepath: "", + wineprefix: "", nsupdate: true, autolang: true, forcedlang: "en", @@ -17,6 +18,7 @@ var settings = { originkill: false, zip: "/northstar.zip", lang: navigator.language, + winebin: "/usr/bin/wine64", excludes: [ "ns_startup_args.txt", "ns_startup_args_dedi.txt" @@ -112,6 +114,19 @@ function setButtons(state) { disablearray(document.querySelectorAll("#browser #browserEntries .text button")); } +ipcRenderer.on("gamestate", (event, state) => { + setButtons(! state); + + let string = lang("gui.launch"); + if (state) { + string = lang("gui.running"); + } + + let btns = document.querySelectorAll(".playBtnContainer .playBtn"); + btns[0].innerHTML = string; + btns[1].innerHTML = string; +}) + ipcRenderer.on("set-buttons", (event, state) => { setButtons(state); }) diff --git a/src/index.js b/src/index.js index 638bebbf..2f4285eb 100644 --- a/src/index.js +++ b/src/index.js @@ -69,6 +69,10 @@ function start() { process.exit(0) } }); + + ipcMain.on("gamestarted", (event) => {win.webContents.send("gamestate", true)}); + ipcMain.on("gamestopped", (event) => {win.webContents.send("gamestate", false)}); + ipcMain.on("minimize", () => {win.minimize()}); ipcMain.on("relaunch", () => {app.relaunch(); app.exit()}); diff --git a/src/lang/en.json b/src/lang/en.json index cfb294a9..e2cf29fd 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -87,12 +87,17 @@ "gui.settings.save": "Save", "gui.settings.discard": "Discard", + "gui.settings.title.linux": "Linux", "gui.settings.title.ns": "Northstar", "gui.settings.title.language": "Language", "gui.settings.title.updates": "Updates", "gui.settings.title.misc": "Miscellaneous", "gui.settings.nsargs.title": "Launch options", "gui.settings.nsargs.desc": "Here you can add launch options for Northstar/Titanfall.", + "gui.settings.wineprefix.title": "Wine Prefix", + "gui.settings.wineprefix.desc": "The prefix which Wine/Proton will use, if left empty, it'll try to find your Steam prefix automatically.", + "gui.settings.winebin.title": "Wine Executable", + "gui.settings.winebin.desc": "The version of Wine/Proton to use to run the game, if set to \"/usr/bin/wine64\", Viper will try to find the latest installed version of Proton automatically, if it can't find anything it'll fallback to wine64", "gui.settings.autolang.title": "Auto-Detect Language", "gui.settings.autolang.desc": "When enabled, Viper tries to automatically detect your system language, when disabled you can manually change the language below.", "gui.settings.forcedlang.title": "Language", @@ -121,6 +126,7 @@ "gui.server.offline": "Masterserver is Offline", "gui.launch": "Launch", + "gui.running": "Running", "gui.launchvanilla": "Vanilla", "gui.launchnorthstar": "Northstar", "gui.installnorthstar": "Install", @@ -141,6 +147,10 @@ "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.", + "wine.cantfindprefix": "Viper was unable to automatically find and set your Wine prefix.", + "gui.toast.noInternet.title": "No Internet", "gui.toast.noInternet.desc": "Viper may not work properly.", diff --git a/src/lang/es.json b/src/lang/es.json index 64c3f014..aaee7b9f 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -88,6 +88,7 @@ "gui.settings.save": "Guardar", "gui.settings.discard": "Descartar", "gui.settings.title.ns": "Northstar", + "gui.settings.title.linux": "Linux", "gui.settings.title.updates": "Actualizaciones", "gui.settings.title.misc": "Misceláneos", "gui.settings.nsargs.title": "Opciones de lanzamiento", @@ -105,6 +106,10 @@ "gui.settings.autolang.desc": "Cuando está habilitado, Viper intenta detectar automáticamente el idioma de su sistema, cuando está deshabilitado, puede cambiar manualmente el idioma a continuación.", "gui.settings.forcedlang.title": "Idioma", "gui.settings.forcedlang.desc": "Cuando \"Detectar automáticamente el idioma\" está deshabilitado, ésta opción decidirá el lenguaje. Se necesita reiniciar para que surja efecto.", + "gui.settings.wineprefix.title": "Prefijo de Wine", + "gui.settings.wineprefix.desc": "El prefijo el cual usará Wine/Proton. Si se deja vacío, se tratará de encontrar su prefijo de Steam automáticamente.", + "gui.settings.winebin.title": "Ejecutable de Wine ", + "gui.settings.winebin.desc": "La versión de Wine/Proton que se usará para ejecutar el juego. Si se elige \"/usr/bin/wine64\", Viper tratará de encontrar la ultima versión instalada de Proton automáticamente, si no se encuentra, se usará wine64", "gui.update.downloading": "Descargando...", "gui.update.extracting": "Extrayendo actualización...", @@ -136,6 +141,7 @@ "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", "gui.server.players": "jugadores", "gui.server.servers": "servidores", @@ -160,8 +166,14 @@ "general.mods.enabled": "Modificaciones habilitadas:", "general.mods.disabled": "Modificaciones deshabilitadas:", "general.mods.installed": "Modificiaciones intaladas:", - "general.missingpath": "¡La ruta del jueno no se ha podido encontrar automaticamente! ¡Por favor, elige la ruta manualmente!", + "general.missingpath": "¡La ruta del jueno no se ha podido encontrar automáticamente! ¡Por favor, elige la ruta manualmente!", "general.notinstalled": "¡Northstar no se ha instalado!", + "general.launching": "Ejecutando", + + "wine.invalidprefix": "El prefijo seleccionado de Wine no existe, y por ende es inválido.", + "wine.originnotfound": "Origin no pudo ser encontrado en el prefijo de Wine seleccionado.", + "wine.cantfindprefix": "Viper no logró encontrar y establecer automáticamente el prefijo de Wine", + "general.launching": "Ejecutando", "general.invalidconfig": "Su archivo de configuración está formateado de forma incorrecta, si ha sido editado manualmente, por favor valide que ha sido escrito correctamente. \n\nSi no ha editado manualmente el archivo de configuración, es recomendado que simplemente restaure la configuración. \n\nPara restaurar tu configuración simplemente de click en \"OK\" a continuación. \n\nMas detalles:\n", diff --git a/src/lang/fr.json b/src/lang/fr.json index 94c31c80..48b3a695 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -88,11 +88,16 @@ "gui.settings.save": "Sauvegarder", "gui.settings.discard": "Annuler", "gui.settings.title.ns": "Northstar", + "gui.settings.title.linux": "Linux", "gui.settings.title.language": "Langue", "gui.settings.title.updates": "Mises à jour", "gui.settings.title.misc": "Divers", "gui.settings.nsargs.title": "Options de lancement", "gui.settings.nsargs.desc": "Vous pouvez ajouter ici des options de démarrage pour Northstar/Titanfall.", + "gui.settings.wineprefix.title": "Préfixe Wine", + "gui.settings.wineprefix.desc": "Le préfixe utilisé par Wine/Proton ; si laissé vide, il essaiera de trouver automatiquement votre préfixe Steam.", + "gui.settings.winebin.title": "Exécutable Wine", + "gui.settings.winebin.desc": "La version de Wine/Proton à utiliser pour lancer le jeu, s'il vaut \"/usr/bin/wine64\", Viper essaiera de déterminer automatiquement la dernière version installée de Proton (et utilisera wine64 s'il ne trouve rien).", "gui.settings.autolang.title": "Auto-détection de la langue", "gui.settings.autolang.desc": "Lorsque activée, Viper essaie de déterminer automatiquement la langue de votre système ; désactiver cette option vous permet de sélectionner manuellement la langue utilisée.", "gui.settings.forcedlang.title": "Langue", @@ -121,6 +126,7 @@ "gui.server.offline": "Le serveur maître est hors-ligne", "gui.launch": "Jouer", + "gui.running": "En cours d'exécution", "gui.launchvanilla": "Vanilla", "gui.launchnorthstar": "Northstar", "gui.installnorthstar": "Installer", @@ -141,6 +147,10 @@ "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é.", + "wine.cantfindprefix": "Viper n'a pas pu trouver et configurer votre préfixe Wine.", + "gui.toast.noInternet.title": "Pas de connexion Internet", "gui.toast.noInternet.desc": "Viper ne fonctionnera pas correctement tant que la connexion n'est pas rétablie.", diff --git a/src/modules/find.js b/src/modules/find.js new file mode 100644 index 00000000..8ed565e7 --- /dev/null +++ b/src/modules/find.js @@ -0,0 +1,153 @@ +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); + +let libraries = []; +let home = app.getPath("home"); + +let symdir = ".steam/steam"; +let localdir = ".local/share/Steam"; +let flatpakdir = ".var/app/com.valvesoftware.Steam/"; + +module.exports = { + prefix: () => { + if (process.platform == "win32") {return false} + let compatdir = "/steamapps/compatdata/1237970/pfx"; + let folders = [ + path.join(home, symdir, compatdir), + path.join(home, localdir, compatdir), + path.join(home, flatpakdir, symdir, compatdir), + path.join(home, flatpakdir, localdir, compatdir), + ] + + for (let i = 0; i < folders.length; i++) { + let origin = path.join(folders[i], "drive_c/Program Files (x86)/Origin/Origin.exe"); + if (fs.existsSync(folders[i])) { + if (fs.existsSync(origin)) { + return { + origin: origin, + path: folders[i], + } + + } + } + } + + return false; + }, + proton: () => { + module.exports.game(true); + + let proton = "0.0"; + let protonpath = false; + + for (let i = 0; i < libraries.length; i++) { + if (! fs.existsSync(libraries[i]) + || fs.statSync(libraries[i]).isDirectory()) { + + continue; + } + + let files = fs.readdirSync(libraries[i]); + for (let ii = 0; ii < files.length; ii++) { + if (files[ii].match(/^Proton [0-9]+\.[0-9]+/)) { + if (fs.existsSync(path.join(libraries[i], files[ii], "/dist/bin/wine64"))) { + let version = files[ii].replace(/^Proton /, ""); + if (version > proton) { + proton = version; + protonpath = path.join(libraries[i], files[ii], "/dist/bin/wine64"); + } + } + } + } + } + + return protonpath; + }, + game: async (quiet) => { + 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); + } + + libraries = []; + + // `.length - 1` This is because the last value is `contentstatsid` + for (let i = 0; i < values.length; i++) { + libraries.push(values[i].path + "/steamapps/common"); + + let data_array = Object.values(values[i]) + + if (fs.existsSync(data_array[0] + "/steamapps/common/Titanfall2/Titanfall2.exe")) { + if (! quiet ) {console.log("Found game in:", data_array[0])} + return data_array[0] + "/steamapps/common/Titanfall2"; + } else { + if (! quiet ) {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 vdfdir = "/steamapps/libraryfolders.vdf"; + folders = [ + path.join(home, symdir, vdfdir), + path.join(home, localdir, vdfdir), + path.join(home, flatpakdir, symdir, vdfdir), + path.join(home, flatpakdir, localdir, vdfdir) + ] + break + } + + if (folders.length > 0) { + for (let i = 0; i < folders.length; i++) { + if (! fs.existsSync(folders[i])) {continue} + if (! quiet ) {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/modules/findgame.js b/src/modules/findgame.js deleted file mode 100644 index 615c5b49..00000000 --- a/src/modules/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/utils.js b/src/utils.js index 9beaa3ec..615057fa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -7,10 +7,11 @@ const events = new Emitter(); const cli = require("./cli"); const lang = require("./lang"); + +const find = require("./modules/find"); const json = require("./modules/json"); const settings = require("./modules/settings"); const requests = require("./modules/requests"); -const findgame = require("./modules/findgame"); const unzip = require("unzipper"); const exec = require("child_process").exec; @@ -166,7 +167,7 @@ async function setpath(win, forcedialog) { ipcMain.emit("newpath", null, settings.gamepath); } - let gamepath = await findgame(); + let gamepath = await find.game(); if (gamepath) { setGamepath(gamepath); return; @@ -414,24 +415,71 @@ function updateViper(autoinstall) { // Either Northstar or Vanilla. Linux support is not currently a thing, // however it'll be added at some point. function launch(version) { - if (process.platform == "linux") { - winAlert(lang("cli.launch.linuxerror")); - console.error("error:", lang("cli.launch.linuxerror")); - cli.exit(1); - return; + let cwd = process.cwd(); + let prefix = { + path: settings.wineprefix, + origin: path.join(settings.wineprefix, "/drive_c/Program Files (x86)/Origin/Origin.exe") } process.chdir(settings.gamepath); + + let winebin = settings.winebin; + + if (process.platform != "win32") { + if (winebin == "/usr/bin/wine64") { + let proton = find.proton(); + if (proton) {winebin = proton} + } + + if (prefix.path == "") { + let foundprefix = find.prefix(); + if (foundprefix) { + prefix = foundprefix; + } else { + winAlert(lang("wine.cantfindprefix")); + return false; + } + } else { + + } + + if (! fs.existsSync(prefix.path)) { + winAlert(lang("wine.invalidprefix") + "\n\n" + prefix.path); + return false; + } else { + process.env["WINEPREFIX"] = prefix.path; + } + } + switch(version) { case "vanilla": - console.log(lang("general.launching"), "Vanilla..."); - exec("Titanfall2.exe", {cwd: settings.gamepath}); + console.log(lang("general.launching"), "Vanilla...") + + if (process.platform != "win32") { + run(winebin, [path.join(settings.gamepath + "/Titanfall2.exe")]) + } else { + exec("Titanfall2.exe", {cwd: settings.gamepath}); + } + break; default: - console.log(lang("general.launching"), "Northstar..."); - exec("NorthstarLauncher.exe", {cwd: settings.gamepath}); + console.log(lang("general.launching"), "Northstar...") + + if (process.platform != "win32") { + if (! fs.existsSync(prefix.origin)) { + winAlert(lang("wine.originnotfound") + "\n\n" + prefix.origin); + return false; + } + + run(winebin, [prefix.origin]) + run(winebin, [path.join(settings.gamepath + "/NorthstarLauncher.exe")]) + } else { + exec("NorthstarLauncher.exe", {cwd: settings.gamepath}); + } break; } + + process.chdir(cwd); } // Returns true/false depending on if the gamepath currently exists/is @@ -440,7 +488,7 @@ function gamepathExists() { return fs.existsSync(settings.gamepath); } -setInterval(() => { +setInterval(async () => { if (gamepathExists()) { ipcMain.emit("gui-getmods"); } else { @@ -450,6 +498,12 @@ setInterval(() => { } } } + + if (await isGameRunning()) { + ipcMain.emit("gamestarted"); + } else { + ipcMain.emit("gamestopped"); + } }, 1500) module.exports = {