aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/core/modpack/formats/multimc.rs
blob: 25449ae158f3ea507c838c7371853905551442ef (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use super::super::{
    archive::{Archive, read_entry, read_json},
    types::{ModpackInfo, ParsedModpack},
};

pub(crate) fn parse(archive: &mut Archive) -> Result<ParsedModpack, String> {
    let root = find_root(archive).ok_or("not multimc")?;
    let cfg = read_entry(archive, &format!("{root}instance.cfg")).ok_or("not multimc")?;
    let name = cfg_value(&cfg, "name").unwrap_or_else(|| "MultiMC Modpack".into());

    let (minecraft_version, mod_loader, mod_loader_version) =
        read_json(archive, &format!("{root}mmc-pack.json"))
            .map(|json| parse_components(&json))
            .unwrap_or_default();
    let minecraft_version = minecraft_version.or_else(|| cfg_value(&cfg, "IntendedVersion"));

    Ok(ParsedModpack {
        info: ModpackInfo {
            name,
            minecraft_version,
            mod_loader,
            mod_loader_version,
            modpack_type: "multimc".into(),
            instance_id: None,
        },
        files: Vec::new(),
        override_prefixes: vec![format!("{root}.minecraft/"), format!("{root}minecraft/")],
    })
}

fn cfg_value(content: &str, key: &str) -> Option<String> {
    let prefix = format!("{key}=");
    content
        .lines()
        .find_map(|line| Some(line.strip_prefix(&prefix)?.trim().to_string()))
}

fn find_root(archive: &mut Archive) -> Option<String> {
    for index in 0..archive.len() {
        let name = archive.by_index_raw(index).ok()?.name().to_string();
        if name == "instance.cfg" {
            return Some(String::new());
        }
        if name.ends_with("/instance.cfg") && name.matches('/').count() == 1 {
            return Some(name.strip_suffix("instance.cfg")?.to_string());
        }
    }
    None
}

fn parse_components(json: &serde_json::Value) -> (Option<String>, Option<String>, Option<String>) {
    let (mut minecraft_version, mut mod_loader, mut mod_loader_version) = (None, None, None);

    for component in json["components"].as_array().into_iter().flatten() {
        let version = component["version"].as_str().map(String::from);
        match component["uid"].as_str().unwrap_or("") {
            "net.minecraft" => minecraft_version = version,
            "net.minecraftforge" => {
                mod_loader = Some("forge".into());
                mod_loader_version = version;
            }
            "net.neoforged" => {
                mod_loader = Some("neoforge".into());
                mod_loader_version = version;
            }
            "net.fabricmc.fabric-loader" => {
                mod_loader = Some("fabric".into());
                mod_loader_version = version;
            }
            "org.quiltmc.quilt-loader" => {
                mod_loader = Some("quilt".into());
                mod_loader_version = version;
            }
            "com.mumfrey.liteloader" => {
                mod_loader = Some("liteloader".into());
                mod_loader_version = version;
            }
            _ => {}
        }
    }

    (minecraft_version, mod_loader, mod_loader_version)
}