From d61a66893f6212f18da1eba3d874cf23594fa75e Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sun, 16 Jul 2023 17:42:36 +0200 Subject: initial support for packages dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is missing a lot of things, notably support for detecting and installing dependencies, it also is actually being used by the frontend, that still uses `mods.js`, this'll change soon™️ It should also when done, be capable of removing mods installed in the old `mods/` folder when updating those mods, and then installing in the new `packages/` folder, making the upgrade a smooth ride. --- src/index.js | 2 + src/modules/packages.js | 338 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 src/modules/packages.js diff --git a/src/index.js b/src/index.js index 3d2f65b..99ed3c1 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,8 @@ const settings = require("./modules/settings"); const requests = require("./modules/requests"); const is_running = require("./modules/is_running"); +const plugins = require("./modules/packages"); + var log = console.log; // Starts the actual BrowserWindow, which is only run when using the diff --git a/src/modules/packages.js b/src/modules/packages.js new file mode 100644 index 0000000..9e5b08f --- /dev/null +++ b/src/modules/packages.js @@ -0,0 +1,338 @@ +const path = require("path"); +const fs = require("fs-extra"); +const unzip = require("unzipper"); +const app = require("electron").app; +const https = require("follow-redirects").https; + +const json = require("./json"); +const win = require("./window"); +const settings = require("./settings"); + +var packages = {}; + +function update_path() { + packages.path = path.join(settings.gamepath, "R2Northstar/packages"); + + // make sure the `packages` folder exists + if (fs.existsSync(packages.path)) { + // if it does, but it's a file, remove it + if (fs.lstatSync(packages.path).isFile()) { + fs.rmSync(packages.path); + } else {return} + } + + // create the folder, in case it doesn't already exist + fs.mkdirSync(packages.path); +}; update_path(); + +packages.format_name = (author, package_name, version) => { + return author + "-" + package_name + "-" + version; +} + +// splits the package name into it's individual parts +packages.split_name = (name) => { + let split = name.split("-"); + + // make sure there are only 3 parts + if (split.length !== 3) { + return false; + } + + // return parts + return { + author: split[0], + version: split[2], + package_name: split[1] + } +} + +packages.list = (dir = packages.path) => { + let files = fs.readdirSync(dir); + let package_list = {}; + + for (let i = 0; i < files.length; i++) { + let package_path = path.join(dir, files[i]); + let verification = packages.verify(package_path); + + let split_name = packages.split_name(files[i]); + + if (! split_name) {continue} + + // make sure the package is actually package + switch(verification) { + case true: + case "has-plugins": + package_list[files[i]] = { + // adds `author`, `package_name` and `version` + ...split_name, + + icon: false, // will be set later + package_path: package_path, // path to package + + // this is whether or not the package has plugins + has_plugins: (verification == "has-plugins"), + + // contents of `manifest.json` or `false` if it can + // be parsed correctly + manifest: json( + path.join(package_path, "manifest.json") + ), + } + + // add `.remove()` function, mostly just a shorthand + package_list[files[i]].remove = () => { + return packages.remove( + split_name.author, + split_name.package_name, + split_name.version, + ) + } + + // set the `.icon` property + let icon_file = path.join(package_path, "icon.png"); + if (fs.existsSync(icon_file) && + fs.lstatSync(icon_file).isFile()) { + + package_list[files[i]].icon = icon_file; + } + break; + } + } + + return package_list; +} + +packages.remove = (author, package_name, version) => { + // if `version` is not set, we'll search for a package with the same + // `author` and `package_name` and use the version from that, + // this'll be useful when updating, of course this assumes that + // nobody has two versions of the same package installed + // + // TODO: perhaps we should remove duplicate packages? + if (! version) { + // get list of packages + let list = packages.list(); + + // iterate through them + for (let i in list) { + // check for `author` and `package_name` being the same + if (list[i].author == author && + list[i].package_name == package_name) { + + // set `version` to the found package + version = list[i].version; + break; + } + } + } + + let name = packages.format_name(author, package_name, version); + let package_path = path.join(packages.path, name); + + // make sure the package even exists to begin with + if (! fs.existsSync(package_path)) { + return false; + } + + fs.rmSync(package_path, {recursive: true}); + + // return the inverse of whether the package still exists, this'll + // be equivalent to whether or not the removal was successful + return !! fs.existsSync(package_path); +} + +packages.install = async (url, author, package_name, version) => { + update_path(); + + let name = packages.format_name(author, package_name, version); + + console.log("Downloading package:", name); + // download `url` to a temporary dir, and return the path to it + let zip_path = await packages.download(url, name); + + console.log("Extracting package:", name); + // extract the zip file we downloaded before, and return the path of + // the folder that we extracted it to + let package_path = await packages.extract(zip_path, name); + + + console.log("Verifying package:", name); + let verification = packages.verify(package_path); + + switch(verification) { + case "has-plugins": + // if the package has plugins, then we want to prompt the + // user, and make absolutely certain that they do want to + // install this package, as plugins have security concerns + let confirmation = await win.confirm( + `The following package has native plugins: ${name} \n\n` + + + + + "Native plugins have far more system access than a " + + "regular mod, and because of this they're inherently " + + "less secure to have installed, as a malicious plugin" + + " could do far more harm this way. If this plugin is " + + "one from a trusted developer or similar or you know " + + "what you're doing, then you can disregard this " + + "message completely." + ) + + // check whether the user cancelled or confirmed the + // installation, and act accordingly + if (! confirmation) { + return console.log("Cancelled package installation:", name); + } + break; + default: + // other unhandled error + return console.log( + "Verification of package failed:", name, + ", reason:", verification + ); + } + + console.log("Verified package:", name); + + console.log("Deleting older version, if it exists:", name); + packages.remove(author, package_name, version); + + console.log("Moving package:", name); + let moved = packages.move(package_path); + + if (moved) { + console.log("Installed package:", name); + } else { + console.log("Moving package failed:", name); + } +} + +packages.download = async (url, name) => { + update_path(); + + return new Promise((resolve) => { + // download mod to a temporary location + https.get(url, (res) => { + let tmp = path.join(app.getPath("cache"), "vipertmp"); + + let zip_name = name || "package"; + let zip_path = path.join(tmp, `/${zip_name}.zip`); + + // make sure the temporary folder exists + if (fs.existsSync(tmp)) { + // if it's not a folder, then delete it + if (! fs.statSync(tmp).isDirectory()) { + fs.rmSync(tmp); + } + } else { + // create the folder + fs.mkdirSync(tmp); + + // if there's already a zip file at `zip_path`, then we + // simple remove it, otherwise problems will occur + if (fs.existsSync(zip_path)) { + fs.rmSync(zip_path); + } + } + + // write out the file to the temporary location + let stream = fs.createWriteStream(zip_path); + res.pipe(stream); + + stream.on("finish", () => { + stream.close(); + + // return the path of the downloaded zip file + resolve(zip_path); + }) + }) + }) +} + +packages.extract = async (zip_path, name) => { + // this is where everything from `zip_path` will be extracted + let extract_dir = path.join(path.dirname(zip_path), name); + + // delete `extract_dir` if it does exist + if (fs.existsSync(extract_dir)) { + fs.rmSync(extract_dir, {recursive: true}); + } + + // make an empty folder at `extract_dir` + fs.mkdirSync(extract_dir); + + return new Promise((resolve) => { + fs.createReadStream(zip_path).pipe( + unzip.Extract({ + path: extract_dir + } + )).on("finish", () => { + resolve(extract_dir); + }); + }) +} + +packages.verify = (package_path) => { + // make sure `package_path` is even exists + if (! fs.existsSync(package_path)) { + return "does-not-exist"; + } + + // make sure `package_path` is not a folder + if (fs.lstatSync(package_path).isFile()) { + return "is-file"; + } + + // make sure a manifest file exists, this is required for + // Thunderstore packages, and is therefore also assumed to be + // required here + let manifest = path.join(package_path, "manifest.json"); + if (! fs.existsSync(manifest) || + fs.lstatSync(manifest).isDirectory()) { + + return "missing-manifest"; + } + + // check if there are any plugins in the package + let mods = path.join(package_path, "mods"); + let plugins = path.join(package_path, "plugins"); + if (fs.existsSync(plugins) && fs.lstatSync(plugins).isDirectory()) { + // package has plugins, the function calling `packages.verify()` + // will have to handle this at their own discretion + return "has-plugins"; + } else if (! fs.existsSync(mods) || fs.lstatSync(mods).isFile()) { + // if there are no plugins, then we check if there are any mods, + // if not, then it means there are both no plugins and mods, so + // we signal that back + return "no-mods"; + } + + // all files exist, and everything is just fine + return true; +} + +// moves `package_path` to the packages folder +packages.move = (package_path) => { + update_path(); + + // make sure we're actually dealing with a real folder + if (! fs.existsSync(package_path) || + ! fs.lstatSync(package_path).isDirectory()) { + + return false; + } + + // get path to the package's destination + let new_path = path.join( + packages.path, path.basename(package_path) + ) + + // attempt to move `package_path` to the packages folder + try { + fs.moveSync(package_path, new_path); + }catch(err) {return false} + + return true; +} + +module.exports = packages; -- cgit v1.2.3 From fb177137413d9fa8eb12364a76dd74b0b4bfc1e6 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sun, 16 Jul 2023 18:40:32 +0200 Subject: fixed packages without plugins not installing --- src/modules/packages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/packages.js b/src/modules/packages.js index 9e5b08f..f515dde 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -160,6 +160,7 @@ packages.install = async (url, author, package_name, version) => { let verification = packages.verify(package_path); switch(verification) { + case true: break; case "has-plugins": // if the package has plugins, then we want to prompt the // user, and make absolutely certain that they do want to -- cgit v1.2.3 From 4434c1d4c8b24db2a3ca8ec2aaf629ce66956034 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sun, 16 Jul 2023 18:46:32 +0200 Subject: fixed unzipper pre-maturily thinking it's finished This caused errors where the folder would already be moved even though it's not entirely done unzipping. --- src/modules/packages.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index f515dde..8d40de1 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -268,7 +268,9 @@ packages.extract = async (zip_path, name) => { path: extract_dir } )).on("finish", () => { - resolve(extract_dir); + setInterval(() => { + resolve(extract_dir); + }, 1000) }); }) } -- cgit v1.2.3 From 241f5528c749772b62095bc9b025906f84a68810 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sun, 16 Jul 2023 18:58:10 +0200 Subject: remove mods in old folder if installing to new one If you're now installing a mod that's currently in the `mods/` folder it'll remove that, then install the new one to `packages/` with the new naming scheme and everything. --- src/modules/packages.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index 8d40de1..4410192 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -4,6 +4,7 @@ const unzip = require("unzipper"); const app = require("electron").app; const https = require("follow-redirects").https; +const mods = require("./mods"); const json = require("./json"); const win = require("./window"); const settings = require("./settings"); @@ -195,7 +196,19 @@ packages.install = async (url, author, package_name, version) => { console.log("Verified package:", name); - console.log("Deleting older version, if it exists:", name); + console.log("Deleting older version(s), if it exists:", name); + // check and delete any mod with the name package details in the old + // `mods` folder, if there are any at all + let mods_list = mods.list().all; + for (let i = 0; i < mods_list.length; i++) { + let mod = mods_list[i]; + + if (mod.ManifestName == package_name) { + mods.remove(mod.Name); + continue; + } + } + packages.remove(author, package_name, version); console.log("Moving package:", name); @@ -217,7 +230,7 @@ packages.download = async (url, name) => { let tmp = path.join(app.getPath("cache"), "vipertmp"); let zip_name = name || "package"; - let zip_path = path.join(tmp, `/${zip_name}.zip`); + let zip_path = path.join(tmp, `${zip_name}.zip`); // make sure the temporary folder exists if (fs.existsSync(tmp)) { -- cgit v1.2.3 From cf83f8fd648dab4eb905ea81dd258160f8b95d33 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sun, 16 Jul 2023 19:11:26 +0200 Subject: minor changes to packages.install() --- src/modules/packages.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index 4410192..bb610d5 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -209,16 +209,19 @@ packages.install = async (url, author, package_name, version) => { } } + // removes older version of package inside the `packages` folder packages.remove(author, package_name, version); console.log("Moving package:", name); let moved = packages.move(package_path); - if (moved) { - console.log("Installed package:", name); - } else { + if (! moved) { console.log("Moving package failed:", name); + return false; } + + console.log("Installed package:", name); + return true; } packages.download = async (url, name) => { -- cgit v1.2.3 From 928333b53e07809bef1897813a4f6588f37c677c Mon Sep 17 00:00:00 2001 From: 0neGal Date: Fri, 21 Jul 2023 23:10:26 +0200 Subject: basic support for showing packages in frontend --- src/modules/mods.js | 139 +++++++++++++++++++++++++++++++++--------------- src/modules/packages.js | 20 +++++-- 2 files changed, 112 insertions(+), 47 deletions(-) diff --git a/src/modules/mods.js b/src/modules/mods.js index 095e501..62b6184 100644 --- a/src/modules/mods.js +++ b/src/modules/mods.js @@ -53,57 +53,108 @@ mods.list = () => { }; } - let files = fs.readdirSync(mods.path); - files.forEach((file) => { - // return early if `file` isn't a folder - if (! fs.statSync(path.join(mods.path, file)).isDirectory()) { - return; - } + let get_in_dir = (dir, package_obj) => { + let files = fs.readdirSync(dir); + files.forEach((file) => { + // return early if `file` isn't a folder + if (! fs.statSync(path.join(dir, file)).isDirectory()) { + return; + } - let modjson = path.join(mods.path, file, "mod.json"); - - // return early if mod.json doesn't exist or isn't a file - if (! fs.existsSync(modjson) || ! fs.statSync(modjson).isFile()) { - return; - } + let modjson = path.join(dir, file, "mod.json"); + + // return early if mod.json doesn't exist or isn't a file + if (! fs.existsSync(modjson) || ! fs.statSync(modjson).isFile()) { + return; + } + + let mod = json(modjson); + if (! mod) {return} + + let obj = { + Author: mod.Author || false, + Version: mod.Version || "unknown", + Name: mod.Name || "unknown", + Description: mod.Description || "", + + FolderName: file, + FolderPath: path.join(dir, file), + Package: package_obj || false + } + + // Electron's serializer for + // BrowserWindow.webContents.send() is quite broken, and for + // some reason cant serialize the output when `package_obj` + // is anywhere inside, trying to find a solution I + // discovered that simply `JSON.stringify()`'ing + // `package_obj` does let it be able to be parsed in + // `index.js` however, of course now it is a string. + // + // wanting to not have to add extra code to the renderer + // just to fix this, I had to try and look for a better + // solution, and apparently this just somehow works, I have + // literally 0% fucking clue why or how it works and fixes + // it, but I will quite frankly not complain. + // + // whether `obj` has been serialized here or not, the actual + // output is the exact same, but somehow Electron just cant + // handle sending it to the renderer, failing to serialize + // it, by itself... + // + // anyway, you can move along now. :') + obj = JSON.stringify(obj); + obj = JSON.parse(obj); + + if (obj.Package) { + obj.Author = obj.Package.author; + } + + obj.Disabled = ! mods.modfile.get(obj.Name); - 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); - - // add manifest data from manifest.json, if it exists - 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; + // add manifest data from manifest.json, if it exists + let manifest_file = path.join(dir, 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; + } } } - } - // add author data from author file, if it exists - let author_file = path.join(mods.path, file, "thunderstore_author.txt"); - if (fs.existsSync(author_file)) { - obj.Author = fs.readFileSync(author_file, "utf8"); - } + // add author data from author file, if it exists + let author_file = path.join(dir, file, "thunderstore_author.txt"); + if (fs.existsSync(author_file)) { + obj.Author = fs.readFileSync(author_file, "utf8"); + } - // add mod to their respective disabled or enabled Array - if (obj.Disabled) { - disabled.push(obj); - } else { - enabled.push(obj); + // add mod to their respective disabled or enabled Array + if (obj.Disabled) { + disabled.push(obj); + } else { + enabled.push(obj); + } + }) + } + + // get mods in `mods` folder + get_in_dir(mods.path); + + // get mods in `packages` folder + let package_list = require("./packages").list(); + for (let i in package_list) { + // make sure the package actually has mods + if (! package_list[i].has_mods) { + continue; } - }) + + // search the package's `mods` folder + get_in_dir( + path.join(package_list[i].package_path, "mods"), + package_list[i] + ) + } return { enabled: enabled, diff --git a/src/modules/packages.js b/src/modules/packages.js index bb610d5..62ef21e 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -4,7 +4,6 @@ const unzip = require("unzipper"); const app = require("electron").app; const https = require("follow-redirects").https; -const mods = require("./mods"); const json = require("./json"); const win = require("./window"); const settings = require("./settings"); @@ -73,6 +72,9 @@ packages.list = (dir = packages.path) => { // this is whether or not the package has plugins has_plugins: (verification == "has-plugins"), + // this will be set later on + has_mods: false, + // contents of `manifest.json` or `false` if it can // be parsed correctly manifest: json( @@ -80,6 +82,17 @@ packages.list = (dir = packages.path) => { ), } + // if the package has a `mods` folder, and it's not + // empty, then we can assume that the package does + // indeed have mods + let mods_dir = path.join(package_path, "mods"); + if (fs.existsSync(mods_dir) && + fs.lstatSync(mods_dir).isDirectory() && + fs.readdirSync(mods_dir).length >= 1) { + + package_list[files[i]].has_mods = true; + } + // add `.remove()` function, mostly just a shorthand package_list[files[i]].remove = () => { return packages.remove( @@ -199,6 +212,7 @@ packages.install = async (url, author, package_name, version) => { console.log("Deleting older version(s), if it exists:", name); // check and delete any mod with the name package details in the old // `mods` folder, if there are any at all + let mods = require("./mods"); let mods_list = mods.list().all; for (let i = 0; i < mods_list.length; i++) { let mod = mods_list[i]; @@ -313,13 +327,13 @@ packages.verify = (package_path) => { } // check if there are any plugins in the package - let mods = path.join(package_path, "mods"); + let mods_path = path.join(package_path, "mods"); let plugins = path.join(package_path, "plugins"); if (fs.existsSync(plugins) && fs.lstatSync(plugins).isDirectory()) { // package has plugins, the function calling `packages.verify()` // will have to handle this at their own discretion return "has-plugins"; - } else if (! fs.existsSync(mods) || fs.lstatSync(mods).isFile()) { + } else if (! fs.existsSync(mods_path) || fs.lstatSync(mods_path).isFile()) { // if there are no plugins, then we check if there are any mods, // if not, then it means there are both no plugins and mods, so // we signal that back -- cgit v1.2.3 From b08e4d193a2c6dea82a6f00432203cfad097e708 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Fri, 21 Jul 2023 23:22:44 +0200 Subject: rename mods.list() properties to use snake case I may or may not have missed some properties or something somewhere, perhaps we'll see if something ends up broken in the future... --- src/app/js/browser.js | 20 ++++++++++---------- src/app/js/mods.js | 18 +++++++++--------- src/app/main.js | 13 +++++++++---- src/index.js | 4 ++-- src/modules/mods.js | 49 +++++++++++++++++++++++++------------------------ src/modules/packages.js | 4 ++-- 6 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/app/js/browser.js b/src/app/js/browser.js index 206b80e..3c3786f 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -133,9 +133,9 @@ var Browser = { for (let ii = 0; ii < modsobj.all.length; ii++) { let mod = modsobj.all[ii]; - if (normalize(mod.Name) === normalized) { - local_name = mod.Name; - local_version = version.format(mod.Version); + if (normalize(mod.name) === normalized) { + local_name = mod.name; + local_version = version.format(mod.version); if (version.is_newer(remote_version, local_version)) { has_update = true; } @@ -270,13 +270,13 @@ var Browser = { setTimeout(() => { for (let i = 0; i < modsobj.all.length; i++) { - let modname = normalize(modsobj.all[i].Name); - let modfolder = normalize(modsobj.all[i].FolderName); + let modname = normalize(modsobj.all[i].name); + let modfolder = normalize(modsobj.all[i].folder_name); if (mod.includes(modname)) { if (! make(modname)) { - if (modsobj.all[i].ManifestName) { - make(normalize(modsobj.all[i].ManifestName)); + if (modsobj.all[i].manifest_name) { + make(normalize(modsobj.all[i].manifest_name)); } } } @@ -386,7 +386,7 @@ function BrowserEl(properties) { let normalized_mods = []; for (let i = 0; i < modsobj.all; i++) { - normalized_mods.push(normalize(mods_list[i].Name)); + normalized_mods.push(normalize(mods_list[i].name)); } if (properties.pkg.local_version) { @@ -445,8 +445,8 @@ function add_recent_toast(name, timeout = 3000) { ipcRenderer.on("removed-mod", (event, mod) => { setButtons(true); Browser.setbutton(mod.name, lang("gui.browser.install")); - if (mod.manifestname) { - Browser.setbutton(mod.manifestname, lang("gui.browser.install")); + if (mod.manifest_name) { + Browser.setbutton(mod.manifest_name, lang("gui.browser.install")); } }) diff --git a/src/app/js/mods.js b/src/app/js/mods.js index d4e8043..af430e5 100644 --- a/src/app/js/mods.js +++ b/src/app/js/mods.js @@ -6,13 +6,13 @@ mods.load = (mods_obj) => { let normalized_names = []; let set_mod = (mod) => { - let normalized_name = "mod-list-" + normalize(mod.Name); + let normalized_name = "mod-list-" + normalize(mod.name); normalized_names.push(normalized_name); let el = document.getElementById(normalized_name); if (el) { - if (mod.Disabled) { + if (mod.disabled) { el.querySelector(".switch").classList.remove("on"); } else { el.querySelector(".switch").classList.add("on"); @@ -31,30 +31,30 @@ mods.load = (mods_obj) => {
-
${mod.Name}
-
${mod.Description}
+
${mod.name}
+
${mod.description}
- - +
`; - if (mod.Disabled) { + if (mod.disabled) { div.querySelector(".switch").classList.remove("on"); } div.querySelector(".switch").addEventListener("click", () => { - mods.toggle(mod.Name); + mods.toggle(mod.name); }) div.querySelector(".image").style.display = "none"; diff --git a/src/app/main.js b/src/app/main.js index 1dbff40..26410cc 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -3,7 +3,12 @@ const path = require("path"); const { app, ipcRenderer, shell } = require("electron"); const lang = require("../lang"); -var modsobj = {}; +var modsobj = { + all: [], + enabled: [], + disabled: [] +} + let shouldInstallNorthstar = false; // Base settings @@ -212,11 +217,11 @@ function installFromURL(url, dependencies, clearqueue, author) { function isModInstalled(modname) { for (let i = 0; i < modsobj.all.length; i++) { let mod = modsobj.all[i]; - if (mod.ManifestName) { - if (mod.ManifestName.match(modname)) { + if (mod.manifest_name) { + if (mod.manifest_name.match(modname)) { return true; } - } else if (mod.Name.match(modname)) { + } else if (mod.name.match(modname)) { return true; } } diff --git a/src/index.js b/src/index.js index 99ed3c1..eb258a5 100644 --- a/src/index.js +++ b/src/index.js @@ -239,13 +239,13 @@ ipcMain.on("getmods", () => { log(`${lang("general.mods.installed")} ${mods.all.length}`); log(`${lang("general.mods.enabled")} ${mods.enabled.length}`); for (let i = 0; i < mods.enabled.length; i++) { - log(` ${mods.enabled[i].Name} ${mods.enabled[i].Version}`); + log(` ${mods.enabled[i].name} ${mods.enabled[i].version}`); } if (mods.disabled.length > 0) { log(`${lang("general.mods.disabled")} ${mods.disabled.length}`); for (let i = 0; i < mods.disabled.length; i++) { - log(` ${mods.disabled[i].Name} ${mods.disabled[i].Version}`); + log(` ${mods.disabled[i].name} ${mods.disabled[i].version}`); } } cli.exit(0); diff --git a/src/modules/mods.js b/src/modules/mods.js index 62b6184..5ba1fab 100644 --- a/src/modules/mods.js +++ b/src/modules/mods.js @@ -72,14 +72,15 @@ mods.list = () => { if (! mod) {return} let obj = { - Author: mod.Author || false, - Version: mod.Version || "unknown", - Name: mod.Name || "unknown", - Description: mod.Description || "", - - FolderName: file, - FolderPath: path.join(dir, file), - Package: package_obj || false + author: mod.Author || false, + version: mod.Version || "unknown", + name: mod.Name || "unknown", + description: mod.Description || "", + + folder_name: file, + folder_path: path.join(dir, file), + + package: package_obj || false } // Electron's serializer for @@ -105,20 +106,20 @@ mods.list = () => { obj = JSON.stringify(obj); obj = JSON.parse(obj); - if (obj.Package) { - obj.Author = obj.Package.author; + if (obj.package) { + obj.author = obj.package.author; } - obj.Disabled = ! mods.modfile.get(obj.Name); + obj.disabled = ! mods.modfile.get(obj.name); // add manifest data from manifest.json, if it exists let manifest_file = path.join(dir, 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; + obj.manifest_name = manifest.name; + if (obj.version == "unknown") { + obj.version = manifest.version_number; } } } @@ -126,11 +127,11 @@ mods.list = () => { // add author data from author file, if it exists let author_file = path.join(dir, file, "thunderstore_author.txt"); if (fs.existsSync(author_file)) { - obj.Author = fs.readFileSync(author_file, "utf8"); + obj.author = fs.readFileSync(author_file, "utf8"); } // add mod to their respective disabled or enabled Array - if (obj.Disabled) { + if (obj.disabled) { disabled.push(obj); } else { enabled.push(obj); @@ -185,7 +186,7 @@ mods.get = (mod) => { // search for mod in list for (let i = 0; i < list.length; i++) { - if (list[i].Name == mod) { + if (list[i].name == mod) { // found mod, return data return list[i]; } else {continue} @@ -225,7 +226,7 @@ mods.modfile.gen = () => { let list = mods.list().all; // get list of all mods for (let i = 0; i < list.length; i++) { // add every mod to the list - names[list[i].Name] = true + names[list[i].name] = true } // write the actual file @@ -565,12 +566,12 @@ mods.remove = (mod) => { if (mod == "allmods") { let modlist = mods.list().all; for (let i = 0; i < modlist.length; i++) { - mods.remove(modlist[i].Name); + mods.remove(modlist[i].name); } return } - let mod_name = mods.get(mod).FolderName; + let mod_name = mods.get(mod).folder_name; if (! mod_name) { console.log("error: " + lang("cli.mods.cantfind")); cli.exit(1); @@ -584,12 +585,12 @@ mods.remove = (mod) => { return cli.exit(1); } - let manifestname = null; + let manifest_name = null; // if the mod has a manifest.json we want to save it now so we can // send it later when telling the renderer about the deleted mod if (fs.existsSync(path.join(path_to_mod, "manifest.json"))) { - manifestname = require(path.join(path_to_mod, "manifest.json")).name; + manifest_name = require(path.join(path_to_mod, "manifest.json")).name; } // actually remove the mod itself @@ -604,7 +605,7 @@ mods.remove = (mod) => { // relevant info for it to properly update everything graphically ipcMain.emit("removed-mod", "", { name: mod.replace(/^.*(\\|\/|\:)/, ""), - manifestname: manifestname + manifest_name: manifest_name }); } @@ -629,7 +630,7 @@ mods.toggle = (mod, fork) => { if (mod == "allmods") { let modlist = mods.list().all; // get list of all mods for (let i = 0; i < modlist.length; i++) { // run through list - mods.toggle(modlist[i].Name, true); // enable mod + mods.toggle(modlist[i].name, true); // enable mod } console.log(lang("cli.mods.toggledall")); diff --git a/src/modules/packages.js b/src/modules/packages.js index 62ef21e..25416c4 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -217,8 +217,8 @@ packages.install = async (url, author, package_name, version) => { for (let i = 0; i < mods_list.length; i++) { let mod = mods_list[i]; - if (mod.ManifestName == package_name) { - mods.remove(mod.Name); + if (mod.manifest_name == package_name) { + mods.remove(mod.name); continue; } } -- cgit v1.2.3 From a8174ea74b81db2ada3d0a519cd58de4939a67a8 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Fri, 21 Jul 2023 23:35:34 +0200 Subject: mods from packages can be removed in the frontend I also love how I spent a very long time trying to figure out why Electron's serializer was failing, turns out, it just throws errors when it encounters functions, instead of stripping them out, like `JSON.stringify()` does Oh well... --- src/modules/mods.js | 48 +++++++++++++++++------------------------------- src/modules/packages.js | 19 +++++++++++-------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/modules/mods.js b/src/modules/mods.js index 5ba1fab..e3ff990 100644 --- a/src/modules/mods.js +++ b/src/modules/mods.js @@ -83,29 +83,6 @@ mods.list = () => { package: package_obj || false } - // Electron's serializer for - // BrowserWindow.webContents.send() is quite broken, and for - // some reason cant serialize the output when `package_obj` - // is anywhere inside, trying to find a solution I - // discovered that simply `JSON.stringify()`'ing - // `package_obj` does let it be able to be parsed in - // `index.js` however, of course now it is a string. - // - // wanting to not have to add extra code to the renderer - // just to fix this, I had to try and look for a better - // solution, and apparently this just somehow works, I have - // literally 0% fucking clue why or how it works and fixes - // it, but I will quite frankly not complain. - // - // whether `obj` has been serialized here or not, the actual - // output is the exact same, but somehow Electron just cant - // handle sending it to the renderer, failing to serialize - // it, by itself... - // - // anyway, you can move along now. :') - obj = JSON.stringify(obj); - obj = JSON.parse(obj); - if (obj.package) { obj.author = obj.package.author; } @@ -143,7 +120,8 @@ mods.list = () => { get_in_dir(mods.path); // get mods in `packages` folder - let package_list = require("./packages").list(); + let packages = require("./packages"); + let package_list = require("./packages").list(packages.path, true); for (let i in package_list) { // make sure the package actually has mods if (! package_list[i].has_mods) { @@ -571,17 +549,25 @@ mods.remove = (mod) => { return } - let mod_name = mods.get(mod).folder_name; + let mod_data = mods.get(mod); + let mod_name = mod_data.folder_name; + if (! mod_name) { console.log("error: " + lang("cli.mods.cantfind")); cli.exit(1); return; } - let path_to_mod = path.join(mods.path, mod_name); + let mod_path = mod_data.folder_path; + + // if the mod comes from a package, we'll want to set `mod_path` to + // the package's folder, that way everything gets removed cleanly + if (mod_data.package) { + mod_path = mod_data.package.package_path; + } - // return early if path_to_mod isn't a folder - if (! fs.statSync(path_to_mod).isDirectory()) { + // return early if `mod_path` isn't a folder + if (! fs.statSync(mod_path).isDirectory()) { return cli.exit(1); } @@ -589,12 +575,12 @@ mods.remove = (mod) => { // if the mod has a manifest.json we want to save it now so we can // send it later when telling the renderer about the deleted mod - if (fs.existsSync(path.join(path_to_mod, "manifest.json"))) { - manifest_name = require(path.join(path_to_mod, "manifest.json")).name; + if (fs.existsSync(path.join(mod_path, "manifest.json"))) { + manifest_name = require(path.join(mod_path, "manifest.json")).name; } // actually remove the mod itself - fs.rmSync(path_to_mod, {recursive: true}); + fs.rmSync(mod_path, {recursive: true}); console.log(lang("cli.mods.removed")); cli.exit(); diff --git a/src/modules/packages.js b/src/modules/packages.js index 25416c4..12659fe 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -46,7 +46,7 @@ packages.split_name = (name) => { } } -packages.list = (dir = packages.path) => { +packages.list = (dir = packages.path, no_functions) => { let files = fs.readdirSync(dir); let package_list = {}; @@ -93,13 +93,16 @@ packages.list = (dir = packages.path) => { package_list[files[i]].has_mods = true; } - // add `.remove()` function, mostly just a shorthand - package_list[files[i]].remove = () => { - return packages.remove( - split_name.author, - split_name.package_name, - split_name.version, - ) + // add `.remove()` function, mostly just a shorthand, + // unless `no_functions` is `true` + if (! no_functions) { + package_list[files[i]].remove = () => { + return packages.remove( + split_name.author, + split_name.package_name, + split_name.version, + ) + } } // set the `.icon` property -- cgit v1.2.3 From 73a651b63c29e24774c1ba3952a2d6c977101047 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sat, 22 Jul 2023 00:29:53 +0200 Subject: unify all mods of a package into one This means instead of showing the invididual mods inside a package, we simply show 1 mod/package, we then use the name, description, version and so forth from the `manifest.json` inside the package folder. Further we now also support a local `icon.png` file, instead of relying on remotely stored icons and trying to match names against each other. --- src/app/js/mods.js | 74 +++++++++++++++++++++++++++++++++++++++++------------ src/modules/mods.js | 24 +++++++++++++++++ 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/app/js/mods.js b/src/app/js/mods.js index af430e5..2df7205 100644 --- a/src/app/js/mods.js +++ b/src/app/js/mods.js @@ -6,7 +6,12 @@ mods.load = (mods_obj) => { let normalized_names = []; let set_mod = (mod) => { - let normalized_name = "mod-list-" + normalize(mod.name); + let name = mod.name; + if (mod.package) { + name = mod.package.package_name; + } + + let normalized_name = "mod-list-" + normalize(name); normalized_names.push(normalized_name); @@ -25,23 +30,38 @@ mods.load = (mods_obj) => { div.classList.add("el"); div.id = normalized_name; + let mod_details = { + name: mod.name, + version: mod.version, + description: mod.description + } + + if (mod.package) { + mod_details = { + image: mod.package.icon, + name: mod.package.manifest.name, + version: mod.package.manifest.version_number, + description: mod.package.manifest.description + } + } + div.innerHTML += `
- +
-
${mod.name}
-
${mod.description}
+
${mod_details.name}
+
${mod_details.description}
- - +
`; + div.querySelector(".remove").addEventListener("click", () => { + if (! mod.package) { + return mods.remove(mod.name); + } + + for (let i = 0; i < mod.packaged_mods.length; i++) { + mods.remove(mod.packaged_mods[i]); + } + }) + if (mod.disabled) { div.querySelector(".switch").classList.remove("on"); } div.querySelector(".switch").addEventListener("click", () => { - mods.toggle(mod.name); + if (! mod.package) { + return mods.toggle(mod.name); + } + + for (let i = 0; i < mod.packaged_mods.length; i++) { + mods.toggle(mod.packaged_mods[i]); + } }) div.querySelector(".image").style.display = "none"; @@ -87,20 +123,24 @@ mods.load = (mods_obj) => { return; } + + let image_container = mod_els[i].querySelector(".image"); + let image_el = image_container.querySelector("img") + let image_blur_el = image_container.querySelector("img.blur") + if (mod_versions[mod]) { - let image_url = mod_versions[mod].package.versions[0].icon; + image_el.src = mod_versions[mod].package.versions[0].icon; + } - let image_container = mod_els[i].querySelector(".image"); - let image_el = image_container.querySelector("img") - let image_blur_el = image_container.querySelector("img.blur") + if (image_el.getAttribute("src") && + ! image_container.parentElement.classList.contains("has-icon")) { - if (image_url && ! image_el.getAttribute("src")) { - image_container.style.display = null; - image_el.src = image_url; - image_blur_el.src = image_url; + let image_src = image_el.getAttribute("src"); - image_container.parentElement.classList.add("has-icon"); - } + image_blur_el.src = image_src; + image_container.style.display = null; + + image_container.parentElement.classList.add("has-icon"); } if (mod_versions[mod] diff --git a/src/modules/mods.js b/src/modules/mods.js index e3ff990..86c78f1 100644 --- a/src/modules/mods.js +++ b/src/modules/mods.js @@ -54,7 +54,9 @@ mods.list = () => { } let get_in_dir = (dir, package_obj) => { + let packaged_mods = []; let files = fs.readdirSync(dir); + files.forEach((file) => { // return early if `file` isn't a folder if (! fs.statSync(path.join(dir, file)).isDirectory()) { @@ -84,6 +86,7 @@ mods.list = () => { } if (obj.package) { + packaged_mods.push(obj.name); obj.author = obj.package.author; } @@ -114,6 +117,27 @@ mods.list = () => { enabled.push(obj); } }) + + if (packaged_mods.length == 0) { + return; + } + + let add_packaged_mods = (mods_array) => { + for (let i = 0; i < mods_array.length; i++) { + if (mods_array[i].package.package_name !== + package_obj.package_name) { + + continue; + } + + mods_array[i].packaged_mods = packaged_mods; + } + + return mods_array; + } + + enabled = add_packaged_mods(enabled); + disbled = add_packaged_mods(disabled); } // get mods in `mods` folder -- cgit v1.2.3 From c3652b72b7bb3bcce207139ebc718e15f6746e43 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sat, 22 Jul 2023 01:23:04 +0200 Subject: mostly support installing packages through GUI Install toasts, installing overall, dependencies and so forth, all seem to be fully functional, however more bug testing is probably required to concluce whether that is actually the case or not... This also doesn't break `src/modules/mods.js`, i.e dragging mods in to manually install them still functions the same as always. --- src/app/js/browser.js | 18 ++++++++++++------ src/app/main.js | 8 +++++--- src/index.js | 6 ++++-- src/modules/packages.js | 10 +++++++++- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/app/js/browser.js b/src/app/js/browser.js index 3c3786f..b95b0a7 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -115,7 +115,11 @@ var Browser = { return installFromURL( package_obj.download || package_obj.versions[0].download_url, package_obj.dependencies || package_obj.versions[0].dependencies, - clear_queue, package_obj.owner + clear_queue, + + package_obj.author || package_obj.owner, + package_obj.name || package_obj.pkg.name, + package_obj.version || package_obj.versions[0].version_number ) }, add_pkg_properties: () => { @@ -463,7 +467,7 @@ ipcRenderer.on("failed-mod", (event, modname) => { }) }) -ipcRenderer.on("duped-mod", (event, modname) => { +ipcRenderer.on("legacy-duped-mod", (event, modname) => { if (recent_toasts["duped" + modname]) {return} add_recent_toast("duped" + modname); @@ -490,28 +494,30 @@ ipcRenderer.on("installed-mod", (event, mod) => { if (recent_toasts["installed" + mod.name]) {return} add_recent_toast("installed" + mod.name); + let name = mod.fancy_name || mod.name; + setButtons(true); - Browser.setbutton(mod.name, lang("gui.browser.reinstall")); + Browser.setbutton(name, lang("gui.browser.reinstall")); if (mod.malformed) { new Toast({ timeout: 8000, scheme: "warning", title: lang("gui.toast.title.malformed"), - description: mod.name + " " + lang("gui.toast.desc.malformed") + description: name + " " + lang("gui.toast.desc.malformed") }) } new Toast({ scheme: "success", title: lang("gui.toast.title.installed"), - description: mod.name + " " + lang("gui.toast.desc.installed") + description: name + " " + lang("gui.toast.desc.installed") }) if (installqueue.length != 0) { installFromURL( "https://thunderstore.io/package/download/" + installqueue[0].pkg, - false, false, installqueue[0].author + false, false, installqueue[0].author, installqueue[0].package_name, installqueue[0].version ) installqueue.shift(); diff --git a/src/app/main.js b/src/app/main.js index 26410cc..51d9e73 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -173,7 +173,7 @@ function installFromPath(path) { } // Tells the main process to install a mod from a URL -function installFromURL(url, dependencies, clearqueue, author) { +function installFromURL(url, dependencies, clearqueue, author, package_name, version) { if (clearqueue) {installqueue = []}; let prettydepends = []; @@ -188,7 +188,9 @@ function installFromURL(url, dependencies, clearqueue, author) { if (! isModInstalled(pkg[1])) { newdepends.push({ pkg: depend, - author: pkg[0] + author: pkg[0], + version: pkg[2], + package_name: pkg[1] }); prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.madeby")} ${pkg[0]}`); @@ -207,7 +209,7 @@ function installFromURL(url, dependencies, clearqueue, author) { } setButtons(false); - ipcRenderer.send("install-from-url", url, author); + ipcRenderer.send("install-from-url", url, author, package_name, version); if (dependencies) { installqueue = dependencies; diff --git a/src/index.js b/src/index.js index eb258a5..9e2f31f 100644 --- a/src/index.js +++ b/src/index.js @@ -19,9 +19,9 @@ const version = require("./modules/version"); const gamepath = require("./modules/gamepath"); const settings = require("./modules/settings"); const requests = require("./modules/requests"); +const packages = require("./modules/packages"); const is_running = require("./modules/is_running"); -const plugins = require("./modules/packages"); var log = console.log; @@ -113,7 +113,9 @@ function start() { // install calls ipcMain.on("install-from-path", (event, path) => {mods.install(path)}); - ipcMain.on("install-from-url", (event, url, author) => {mods.installFromURL(url, author)}); + ipcMain.on("install-from-url", (event, url, author, package_name, version) => { + packages.install(url, author, package_name, version); + }); win.webContents.on("dom-ready", () => { send("mods", mods.list()); diff --git a/src/modules/packages.js b/src/modules/packages.js index 12659fe..317f0b0 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -1,7 +1,7 @@ const path = require("path"); const fs = require("fs-extra"); const unzip = require("unzipper"); -const app = require("electron").app; +const { app, ipcMain } = require("electron"); const https = require("follow-redirects").https; const json = require("./json"); @@ -203,6 +203,8 @@ packages.install = async (url, author, package_name, version) => { } break; default: + ipcMain.emit("failed-mod", name); + // other unhandled error return console.log( "Verification of package failed:", name, @@ -233,10 +235,16 @@ packages.install = async (url, author, package_name, version) => { let moved = packages.move(package_path); if (! moved) { + ipcMain.emit("failed-mod", name); console.log("Moving package failed:", name); return false; } + ipcMain.emit("installed-mod", "", { + name: name, + fancy_name: package_name + }) + console.log("Installed package:", name); return true; } -- cgit v1.2.3 From a93dd1170d71ad3cb687a585034c2225aae0ef95 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Sat, 22 Jul 2023 02:05:23 +0200 Subject: added English strings for plugin install prompt --- src/lang/en.json | 2 ++ src/modules/packages.js | 13 ++----------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index bd95a53..5ce9407 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -69,6 +69,8 @@ "gui.mods.installedmod": "Installed mod!", "gui.mods.dragdrop": "Drag and drop a mod to install", "gui.mods.confirmdependencies": "This package has dependencies, shown below, clicking \"Ok\" will install the package and the dependencies.\n\n", + "gui.mods.confirm_plugins_title": "The following package has native plugins:", + "gui.mods.confirm_plugins_description": "Native plugins have far more system access than a regular mod, and because of this they're inherently less secure to have installed, as a malicious plugin could do far more harm this way. If this plugin is one from a trusted developer or similar or you know what you're doing, then you can disregard this message completely.", "gui.browser.info": "Info", "gui.browser.view": "View", diff --git a/src/modules/packages.js b/src/modules/packages.js index 317f0b0..e6eb4a1 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -183,17 +183,8 @@ packages.install = async (url, author, package_name, version) => { // user, and make absolutely certain that they do want to // install this package, as plugins have security concerns let confirmation = await win.confirm( - `The following package has native plugins: ${name} \n\n` - - + - - "Native plugins have far more system access than a " + - "regular mod, and because of this they're inherently " + - "less secure to have installed, as a malicious plugin" + - " could do far more harm this way. If this plugin is " + - "one from a trusted developer or similar or you know " + - "what you're doing, then you can disregard this " + - "message completely." + `${lang("gui.mods.confirm_plugins_title")} ${name} \n\n` + + lang("gui.mods.confirm_plugins_description") ) // check whether the user cancelled or confirmed the -- cgit v1.2.3 From 3df421d077adefd86a1ae84f9cf64f71e2c1dd5f Mon Sep 17 00:00:00 2001 From: Delta <31860825+AA-Delta@users.noreply.github.com> Date: Fri, 21 Jul 2023 19:22:11 -0500 Subject: Added Spanish strings for plugin install prompt #191 --- src/lang/es.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lang/es.json b/src/lang/es.json index d0bc189..863a6e9 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -69,6 +69,8 @@ "gui.mods.confirmdependencies": "Este paquete tiene dependencias, se muestran abajo. Presionar \"Ok\" instalará el paquete y las dependencias.\n\n", "gui.mods.remove": "Remover", "gui.mods.unknown_author": "Desconocido", + "gui.mods.confirm_plugins_title": "El siguiente paquete tiene complementos nativos:", + "gui.mods.confirm_plugins_description": "Los complementos nativos tienen mucho más acceso al sistema que un mod regular y, por lo tanto, son inherentemente menos seguros de instalar, ya que un complemento malicioso podría causar mucho más daño de esta manera. Si este complemento es de un desarrollador confiable o similar o si sabe lo que está haciendo, entonces puede ignorar completamente este mensaje.", "gui.browser.info": "Información", "gui.browser.madeby": "por", -- cgit v1.2.3 From a1b4d431bc2d038bb0026bfd07bbdf5003b46299 Mon Sep 17 00:00:00 2001 From: Remy Raes Date: Sat, 22 Jul 2023 23:50:59 +0200 Subject: translate --- src/lang/fr.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lang/fr.json b/src/lang/fr.json index 3ae7272..4eef1cf 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -69,6 +69,8 @@ "gui.mods.installedmod": "Mod installé !", "gui.mods.dragdrop": "Glissez/déposez un mod pour l'installer", "gui.mods.confirmdependencies": "Ce mod a des dépendances (affichées ci-dessous), cliquer \"Ok\" les installera en même temps que le mod.\n\n", + "gui.mods.confirm_plugins_title": "Ce mod contient des plugins :", + "gui.mods.confirm_plugins_description": "Les plugins ont des accès à votre système, comparés aux mods classiques, et sont de fait plus dangereux à l'installation, comme pourrait l'être un plugin contenant un malware. Si ce plugin provient d'un tiers de confiance ou si vous savez ce que vous faites, ne tenez pas compte de ce message.", "gui.browser.info": "Info", "gui.browser.view": "Voir", -- cgit v1.2.3 From 7c0c8d2d36bde363e56a2b12f6a965f844c1bc12 Mon Sep 17 00:00:00 2001 From: Presti Date: Mon, 24 Jul 2023 01:16:05 +0200 Subject: German translation. --- src/lang/de.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lang/de.json b/src/lang/de.json index 1662bef..f79ed05 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -69,6 +69,8 @@ "gui.mods.installedmod": "Mod installiert!", "gui.mods.dragdrop": "Drag and drop den Mod um ihn zu installieren!", "gui.mods.confirmdependencies": "Dieser Mod benötigt weitere Mods, diese werden unter dieser Nachricht angezeigt. Beim drücken auf \"Ok\" stimmst du zu da diese Installiert werden.\n\n", + "gui.mods.confirm_plugins_title": "Das folgende Packet hat native plugins::", + "gui.mods.confirm_plugins_description": "Native plugins haben sehr viel mehr Rechte als reguläre Mods, da durch ist das nutzen dieser um einiges unsicherer denn es ist einfacher ihnen zuschaden! Bitte installieren sie nur native plugins von vertrauten Entwicklern oder ähnliches, falls dir bewusst ist was du machst kannst du diese Nachricht ignorieren.", "gui.browser.info": "Info", "gui.browser.view": "Anschauen", -- cgit v1.2.3 From 9d33c21f489c2d3d3e4bfd4fc0cf4ff3d613b9fd Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:09:32 +0200 Subject: use prettier console logging for packages.js --- src/modules/packages.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index e6eb4a1..27df20a 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -8,6 +8,8 @@ const json = require("./json"); const win = require("./window"); const settings = require("./settings"); +console = require("./console"); + var packages = {}; function update_path() { @@ -163,17 +165,17 @@ packages.install = async (url, author, package_name, version) => { let name = packages.format_name(author, package_name, version); - console.log("Downloading package:", name); + console.info("Downloading package:", name); // download `url` to a temporary dir, and return the path to it let zip_path = await packages.download(url, name); - console.log("Extracting package:", name); + console.info("Extracting package:", name); // extract the zip file we downloaded before, and return the path of // the folder that we extracted it to let package_path = await packages.extract(zip_path, name); - console.log("Verifying package:", name); + console.info("Verifying package:", name); let verification = packages.verify(package_path); switch(verification) { @@ -190,22 +192,22 @@ packages.install = async (url, author, package_name, version) => { // check whether the user cancelled or confirmed the // installation, and act accordingly if (! confirmation) { - return console.log("Cancelled package installation:", name); + return console.ok("Cancelled package installation:", name); } break; default: ipcMain.emit("failed-mod", name); // other unhandled error - return console.log( + return console.error( "Verification of package failed:", name, ", reason:", verification ); } - console.log("Verified package:", name); + console.ok("Verified package:", name); - console.log("Deleting older version(s), if it exists:", name); + console.info("Deleting older version(s), if it exists:", name); // check and delete any mod with the name package details in the old // `mods` folder, if there are any at all let mods = require("./mods"); @@ -222,12 +224,12 @@ packages.install = async (url, author, package_name, version) => { // removes older version of package inside the `packages` folder packages.remove(author, package_name, version); - console.log("Moving package:", name); + console.info("Moving package:", name); let moved = packages.move(package_path); if (! moved) { ipcMain.emit("failed-mod", name); - console.log("Moving package failed:", name); + console.error("Moving package failed:", name); return false; } @@ -236,7 +238,7 @@ packages.install = async (url, author, package_name, version) => { fancy_name: package_name }) - console.log("Installed package:", name); + console.ok("Installed package:", name); return true; } -- cgit v1.2.3 From a01a44e75837a54b4bd9c3ba0a8171364c0d654d Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:13:44 +0200 Subject: cleanup leftover files when installing packages --- src/modules/packages.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index 27df20a..a20cb5a 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -165,6 +165,22 @@ packages.install = async (url, author, package_name, version) => { let name = packages.format_name(author, package_name, version); + // removes zip's and folders + let cleanup = () => { + console.info("Cleaning up cache folder of mod:", name); + if (zip_path && fs.existsSync(zip_path)) { + fs.rm(zip_path, {recursive: true}); + console.ok("Cleaned archive of mod:", name); + } + + if (package_path && fs.existsSync(package_path)) { + fs.rm(zip_path, {recursive: true}); + console.ok("Cleaned mod folder:", name); + } + + console.ok("Cleaned up cache folder of mod:", name); + } + console.info("Downloading package:", name); // download `url` to a temporary dir, and return the path to it let zip_path = await packages.download(url, name); @@ -199,10 +215,12 @@ packages.install = async (url, author, package_name, version) => { ipcMain.emit("failed-mod", name); // other unhandled error - return console.error( + console.error( "Verification of package failed:", name, ", reason:", verification ); + + return cleanup(); } console.ok("Verified package:", name); @@ -230,6 +248,9 @@ packages.install = async (url, author, package_name, version) => { if (! moved) { ipcMain.emit("failed-mod", name); console.error("Moving package failed:", name); + + cleanup(); + return false; } @@ -239,6 +260,8 @@ packages.install = async (url, author, package_name, version) => { }) console.ok("Installed package:", name); + cleanup(); + return true; } -- cgit v1.2.3 From 3b3173e0454605c8181f037fff49c7ab8a353cf8 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:26:15 +0200 Subject: fixed removing mods breaking when reading manifest --- src/modules/mods.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/mods.js b/src/modules/mods.js index d3d3a50..3c469c3 100644 --- a/src/modules/mods.js +++ b/src/modules/mods.js @@ -602,7 +602,7 @@ mods.remove = (mod) => { // if the mod has a manifest.json we want to save it now so we can // send it later when telling the renderer about the deleted mod if (fs.existsSync(path.join(mod_path, "manifest.json"))) { - manifest_name = require(path.join(mod_path, "manifest.json")).name; + manifest_name = json(path.join(mod_path, "manifest.json")).name; } // actually remove the mod itself -- cgit v1.2.3 From 116fceaed48b4aace01290cee3ddfa007b2550f5 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:27:49 +0200 Subject: fixed updating not removing older packages --- src/modules/packages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/packages.js b/src/modules/packages.js index a20cb5a..d1ea46e 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -240,6 +240,7 @@ packages.install = async (url, author, package_name, version) => { } // removes older version of package inside the `packages` folder + packages.remove(author, package_name); packages.remove(author, package_name, version); console.info("Moving package:", name); -- cgit v1.2.3 From 8a47471ae6263aa78ed2a5c4e7a545c2e21c8bcf Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:42:47 +0200 Subject: fixed updateable packages not being removeable --- src/app/js/mods.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/js/mods.js b/src/app/js/mods.js index 2df7205..0d516d9 100644 --- a/src/app/js/mods.js +++ b/src/app/js/mods.js @@ -69,7 +69,7 @@ mods.load = (mods_obj) => { `; - div.querySelector(".remove").addEventListener("click", () => { + div.querySelector(".remove").onclick = () => { if (! mod.package) { return mods.remove(mod.name); } @@ -77,7 +77,7 @@ mods.load = (mods_obj) => { for (let i = 0; i < mod.packaged_mods.length; i++) { mods.remove(mod.packaged_mods[i]); } - }) + } if (mod.disabled) { div.querySelector(".switch").classList.remove("on"); @@ -123,7 +123,6 @@ mods.load = (mods_obj) => { return; } - let image_container = mod_els[i].querySelector(".image"); let image_el = image_container.querySelector("img") let image_blur_el = image_container.querySelector("img.blur") @@ -158,6 +157,10 @@ mods.load = (mods_obj) => { let mod_el = mod_els[i].cloneNode(true); + // copy click event of the remove button to the new button + mod_el.querySelector(".remove").onclick = + mod_els[i].querySelector(".remove").onclick; + mod_el.classList.add("no-animation"); mod_el.querySelector(".switch").addEventListener("click", () => { -- cgit v1.2.3 From c7a3316b1fe29b3b0a4ab053f0a1b6a9b4069f24 Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 18:50:34 +0200 Subject: properly detect if a package has mods --- src/modules/packages.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/modules/packages.js b/src/modules/packages.js index d1ea46e..cb0b800 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -368,6 +368,32 @@ packages.verify = (package_path) => { return "no-mods"; } + // make sure files in the `mods` folder actually are mods, and if + // none of them are, then we make sure to return back that are no + // mods installed + let found_mod = false; + let mods = fs.readdirSync(mods_path); + for (let i = 0; i < mods.length; i++) { + let mod = mods[i]; + let mod_file = path.join(mod, "mod.json"); + + // make sure mod.json exists, and is a file, otherwise, this + // is unlikely to be a mod folder + if (! fs.existsSync(mod_file) + || ! fs.statSync(mod_file).isFile()) { + continue; + } + + // attempt to read the mod.json file, and if it succeeds, then + // this is likely to be a mod + let json_data = json(mod_file); + if (json_data) { + found_mod = true; + } + } + + if (! found_mod) {return "no-mods"} + // all files exist, and everything is just fine return true; } -- cgit v1.2.3 From 67f12f53c24ee3b5742aa28a7e3da8a56ddacf2a Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 19:14:41 +0200 Subject: largely improve how we convert mods to packages Convert is a strong word, in reality, when a user installs (and thereby also updates) a package, we attempt to search for a mod that we can with pretty high confidence, we could theoretically make this even better, but at some point you'll accidentally delete mods that are manually installed and weren't actually the right ones. --- src/modules/packages.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index cb0b800..8a5af76 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -237,6 +237,36 @@ packages.install = async (url, author, package_name, version) => { mods.remove(mod.name); continue; } + + // normalizes a string, i.e attempt to make two strings + // identical, that simply have slightly different formatting, as + // an example, these strings: + // + // "Mod_Name" and "Mod name" + // + // will just become: + // + // "modname" + let normalize = (string) => { + return string.toLowerCase() + .replaceAll("_", "") + .replaceAll(".", "") + .replaceAll(" ", ""); + } + + // check if the mod's name from it's `mod.json` file when + // normalized, is the same as the normalized name of the package + if (normalize(mod.name) == normalize(package_name)) { + mods.remove(mod.name); + continue; + } + + // check if the name of the mod's folder when normalized, is the + // same as the normalized name of the package + if (normalize(mod.folder_name) == normalize(package_name)) { + mods.remove(mod.name); + continue; + } } // removes older version of package inside the `packages` folder @@ -374,8 +404,7 @@ packages.verify = (package_path) => { let found_mod = false; let mods = fs.readdirSync(mods_path); for (let i = 0; i < mods.length; i++) { - let mod = mods[i]; - let mod_file = path.join(mod, "mod.json"); + let mod_file = path.join(mods_path, mods[i], "mod.json"); // make sure mod.json exists, and is a file, otherwise, this // is unlikely to be a mod folder -- cgit v1.2.3 From 54d6254c1a502aa77f21ac78f55d1e5d127bb20c Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 19:51:32 +0200 Subject: remove debug console.log() --- src/app/js/browser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/js/browser.js b/src/app/js/browser.js index b95b0a7..06d3dd9 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -111,7 +111,6 @@ var Browser = { browser.classList.toggle("shown"); }, install: (package_obj, clear_queue = false) => { - console.log(package_obj) return installFromURL( package_obj.download || package_obj.versions[0].download_url, package_obj.dependencies || package_obj.versions[0].dependencies, -- cgit v1.2.3 From 55040f6808f4aef3cd7ba86a45290d03963c37bd Mon Sep 17 00:00:00 2001 From: 0neGal Date: Mon, 24 Jul 2023 20:05:10 +0200 Subject: make sure profile exists, then create packages dir If the gamepath isn't found or unmounted or similar, then the profile doesn't exist either, but we previously just assumed that it did, now we refuse to create the `packages` folder, fixing an error. --- src/modules/packages.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/packages.js b/src/modules/packages.js index 8a5af76..c9fb1c4 100644 --- a/src/modules/packages.js +++ b/src/modules/packages.js @@ -23,8 +23,12 @@ function update_path() { } else {return} } - // create the folder, in case it doesn't already exist - fs.mkdirSync(packages.path); + // only create folder if the profile folder exists + if (fs.existsSync(path.dirname(packages.path))) { + // create the folder, in case it doesn't already exist + fs.mkdirSync(packages.path); + } + }; update_path(); packages.format_name = (author, package_name, version) => { -- cgit v1.2.3