Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more contextual informations to actions #242

Closed
T3sT3ro opened this issue Dec 3, 2022 · 11 comments
Closed

Add more contextual informations to actions #242

T3sT3ro opened this issue Dec 3, 2022 · 11 comments
Labels
enhancement Improvements to the project

Comments

@T3sT3ro
Copy link

T3sT3ro commented Dec 3, 2022

The problem

There are some actions that produce output on terminal, which would be a lot more helpful if it included some additional info.

My solutions

Moved to #250
  • When ferium add results in "project/repository is not compatible":
  • ferium profile should print current profile first, then a simple text saying "Use ferium profile -h for more info about this command"
  • ferium list should print first a header with profile name, loader and MC version and only then list of mods.
  • ferium modpack should show current modpack, then a simple prompt with "Use ferium modpack -h for more info about this command"
  • ferium profile switch should print in a row profile name, mc version, loader and number of mods
  • ferium profiles should be an alias to ferium profile list
  • ferium modpacks should be an alias to ferium modpack list
  • ferium mods should be an alias to ferium list (because the list is the only unscoped subcommand which doesn't align with profiles list and modpacks list).
  • ferium info should be added and show currently set profile and modpack
  • ferium remove should print removed <Mod name> <projectID>
  • There should be some kind of dependency listing showing which mods depend on which, for example ferium dependencies would print which mods were pulled as required dependencies of other mods

  • The mod list should include a version of the mod

@T3sT3ro T3sT3ro added the enhancement Improvements to the project label Dec 3, 2022
@theRookieCoder
Copy link
Collaborator

theRookieCoder commented Dec 4, 2022

print WHY: minecraft version? Mod loader? License or 3rd party tool access disabled by moderators?

#76

The mod list should also include a version of the mod

Does this mean the current version installed, or the latest compatible version? Unfortunately both are not feasible, I've discussed the former in #138 and the latter is just way too slow.

These are great suggestions! I will have to think about some of these, and some features are just not worth the effort (e.g. dependency listing). They are waaay more complicated to implement than it might seem, and it might not be worth it for such a small improvement.

@T3sT3ro
Copy link
Author

T3sT3ro commented Dec 4, 2022

Does this mean the current version installed, or the latest compatible version?

I was thinking about currently installed version, which is almost always present in the filename. Currently the output is of the form:

$ ferium list
Cloth Config API (Fabric/Forge)               CurseForge 348521
Fabric API                                    Modrinth   P7dR8mSH

but the ferium upgrade update already contains this info in the form:

$ ferium upgrade
...
✓ Cloth Config API (Fabric/Forge)             cloth-config-8.2.88-fabric.jar
✓ Fabric API                                  fabric-api-0.68.0+1.19.2.jar
...

So I think it would be beneficial to print just that - the filename of the latest mod, which helps in identifying the version easily:

Cloth Config API (Fabric/Forge)     CurseForge 348521   cloth-config-8.2.88-fabric.jar
Fabric API                          Modrinth   P7dR8mSH fabric-api-0.68.0+1.19.2.jar

This is the most straightforward way, in my opinion, but additional work could be done to provide a RegExp for extracting the version from the mod name using some most common patterns. This would on the other hand require you to sometimes update the regex if some new format is reported. Some example formats from the top of my head could fit the following regex: [^\d]*(\d+(\.\d+(.\d+(.\d+([^\d]?.*)?)?)?)?)

Which should match any of the following:

  • a.b.c.d with each part optional
  • with some kind of prefix like v, V, ver...
  • with suffix (-fabric, +quilt, -rc, letter after numbers)
    etc

@theRookieCoder
Copy link
Collaborator

theRookieCoder commented Dec 5, 2022

What you're describing is actually a huge problem that this program has had to deal with since the very beginning. (see #17, yes that old) The main issue is that using the filename to determine the mod version is unreliable because naming conventions differ quite a bit between authors, mod loaders, etc.

@T3sT3ro
Copy link
Author

T3sT3ro commented Dec 5, 2022

Note, that my main proposal is to simply display the filename, and depend on human eyes to read the version number.

Using the filename to determine the mod version, on the other hand, would be problematic, and it probably would be a dirty solution with a long cycle of proposing another pattern matcher and adapting it, while also never being sure, that ALL versioning schemes used in the wild are handled.

While thinking about that I came up with yet another solution. Mods written for forge, fabric or quilt all have some kind of file, which has some mod metadata inside of them:

  • fabric and quilt have fabric.mod.json or quilt.mod.json inside the mod's *.jar file. Inside there is a version field.
  • forge has META-INF/mods.toml (takes priority) and META-INF/MANIFEST.mf, but there are 2 options:
    • version field inside contains verbatim version string like "1.2.7"
    • version field contains ${file.jarVersion} which pulls version info from jar's manifest file.

Commands I use to extract version information:

  • fabric: unzip -p <mod>.jar fabric.mod.json | jq '.version'
  • forge:
    1. first read the version string from toml: unzip -p <mod>.jar META-INF/mods.toml | wildq -i toml ".mods[].version"
    2. Read META-INF/MANIFEST.mf from inside the jar: unzip -p forge/AdvancedCompas-forge-1.19.2-1.2.13.jar pack.mcmeta META-INF/MANIFEST.MF | grep Implementation-Version | cut -d" " -f2

This approach would be the most correct one, as it depends on a common, standardized by mod-loaders mod structure. Versions would have to be extracted using appropriate json and toml parsers for fabric/quilt and forge respectively.

It would be that simple, because jar files are just zip files with different extension name, so any tool and library that can peek inside the zip file could read mod's version. What's more, this approach could be useful to parse additional mod information that is included in the mod's config files, such as description, author, dependencies (as listed in fabric.mod.json, author, URL etc.). What's great about that is that the layout of configuration file is specified in the corresponding forge/fabric/quilt documentation, so there is no quessing, and if the mod does not adhere to standard, then bugs would be reported to corresponding mod developers to fix them.

@theRookieCoder
Copy link
Collaborator

theRookieCoder commented Dec 5, 2022

Yes that would be a solution, I think PrismMC uses this. I was thinking that getting the files, unzipping them, and reading the manifests would be too slow, but I guess I should try it out one day.

@T3sT3ro
Copy link
Author

T3sT3ro commented Dec 5, 2022

I had to check to be sure, but according to wikipedia the zip file doesn't need to be decompressed as a whole to access separate file. Zip file is capable of storing multiple files each compressed using different method, as well as decompressing a part of the whole archive — in this case, the fabric.mod.json/quilt.mod.json/META-INF/mods.toml/META-INF/MANIFEST.mf. The key would be to find a library (or to write a utility function yourself) that extracts required files. This method would have to read the list of the files in zip archive (called central directory and stored at the end of the file, as described on wiki), offset into the file and decompress a single file.

There is a zip crate probably capable of doing that.

@T3sT3ro
Copy link
Author

T3sT3ro commented Dec 5, 2022

Here is a concept that solves just that, written with my very basic knowledge of Rust. First, dependencies:

# Cargo.toml
[dependencies]
zip = "0.6"
serde = "1.0"
serde_json = "1.0"
toml = "0.5"
regex = "1.7"

and here is the script:

use serde_json::{Result as JsonRes, Value as JsonVal};
use std::{fs::File, io::Read};
use toml::Value as TomlVal;
use zip::{read::ZipFile, ZipArchive};
use regex::Regex;

fn main() {
    let jar_path = std::env::args().nth(1).expect("pass *.jar file path");
    let t = get_version(jar_path);
    println!("{}", t);
}

fn get_version(jar_path: String) -> String {
    let jar_file = File::open(jar_path).expect("File not found");
    let mut jar_archive = ZipArchive::new(jar_file).expect("Can't open .jar file");

    match jar_archive
        .by_name("fabric.mod.json")
        .map(get_version_string_from_json)
    {
        Ok(ver) => return ver,
        Err(e) => e,
    };

    match jar_archive
        .by_name("quilt.mod.json")
        .map(get_version_string_from_json)
    {
        Ok(ver) => return ver,
        Err(e) => e,
    };

    match jar_archive
        .by_name("META-INF/mods.toml")
        .map(get_version_string_from_toml)
    {
        Ok(ver) => {
            return if ver != "${file.jarVersion}" {
                ver
            } else {
                jar_archive
                    .by_name("META-INF/MANIFEST.MF")
                    .map(get_version_string_from_manifest)
                    .expect("cannot determine version")
            }
        }
        Err(_) => panic!("cannot determine version"),
    };
}

fn read_zip_file_content(mut zip_file: ZipFile<'_>) -> String {
    let mut content = String::new();
    let _n = zip_file
        .read_to_string(&mut content)
        .expect("couldn't read file");
    return content;
}

fn get_version_string_from_json(zip_file: ZipFile<'_>) -> String {
    let content = read_zip_file_content(zip_file);
    let config: JsonRes<JsonVal> = serde_json::from_str(content.as_str());

    return String::from(
        config.expect("couldn't read json")["version"]
            .as_str()
            .expect("missing version string in json"),
    );
}

fn get_version_string_from_toml(zip_file: ZipFile<'_>) -> String {
    let content = read_zip_file_content(zip_file);
    let config: TomlVal = toml::from_str(content.as_str()).expect("can't read toml");
    // println!("{:?}", config["mods"][0]["version"].as_str());
    return String::from(config["mods"][0]["version"].as_str().expect("missing version string in toml"));
}

fn get_version_string_from_manifest(zip_file: ZipFile<'_>) -> String {
    let content = read_zip_file_content(zip_file);
    let re  = Regex::new(r"Implementation-Version: (?P<version>.*)").unwrap();
    let caps = re.captures(content.as_str());
    return String::from(&caps.unwrap()["version"]);
}

It requires a thorough check, of course, but it is capable of extracting version info from fabric, quilt(probably, untested) and forge mods when given mod *.jar file. It doesn't decompress whole file, just extracts a single file from the zip.

The important part is using ZipArchive::new(jar_file).by_name("some.jar").read_to_string(s)

I wanted it to be more concise, but I for the love of god don't know how to chain results and deal with lifetime problems of ZipArchive.

@theRookieCoder
Copy link
Collaborator

Oh yeah I do remember now, it would be possible to access only the manifest. In fact I do already use the zip crate in libium to unzip modpacks.

@theRookieCoder
Copy link
Collaborator

theRookieCoder commented Dec 26, 2022

Alright well there is still the issue of linking the mod file and the mod entry within ferium, how can that be done? This is the reason I had refused to implement #138

@theRookieCoder
Copy link
Collaborator

theRookieCoder commented Dec 26, 2022

I've moved your recommendations about the CLI to #250. I've updated your initial comment to collapse those and link to the new issue. The 2 points we discussed here are left outside, which are unfortunately not feasible at the moment.

@theRookieCoder theRookieCoder closed this as not planned Won't fix, can't repro, duplicate, stale Dec 26, 2022
@T3sT3ro
Copy link
Author

T3sT3ro commented Dec 26, 2022

Alright well there is still the issue of linking the mod file and the mod entry within ferium, how can that be done? This is the reason I had refused to implement #138

I would need some more context. For now, the most straightforward I can imagine is storing the path to the file jar along with the SHA256 hash of a mod jar in the profile for the project ID. This way you can have both quick access to jar file, as well as validation that the file you point to is the actual file in question. If the file doesn't exist OR the SHA256 of jar is invalid, appropriate message should be displayed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvements to the project
Development

No branches or pull requests

2 participants