Closed T3sT3ro closed 1 year ago
print WHY: minecraft version? Mod loader? License or 3rd party tool access disabled by moderators?
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.
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:
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.
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.mod.json
or quilt.mod.json
inside the mod's *.jar
file. Inside there is a version
field.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:
unzip -p <mod>.jar fabric.mod.json | jq '.version'
unzip -p <mod>.jar META-INF/mods.toml | wildq -i toml ".mods[].version"
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.
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.
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.
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.
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.
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'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.
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.
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": - print WHY: minecraft version? Mod loader? License or 3rd party tool access disabled by moderators? - Link to info about overrides as part of the error messageThere 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 modsThe mod list should include a version of the mod