Skip to main content

conduit_cli/inspector/
mod.rs

1use serde::Deserialize;
2use std::fs::File;
3use std::io::Read;
4use std::path::Path;
5use zip::ZipArchive;
6
7#[derive(Debug, Deserialize)]
8pub struct NeoForgeMetadata {
9    pub dependencies: Option<std::collections::HashMap<String, Vec<Dependency>>>,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct NeoForgeModsList {
14    pub mods: Option<Vec<NeoForgeModEntry>>,
15}
16
17#[derive(Debug, Deserialize)]
18pub struct NeoForgeModEntry {
19    #[serde(rename = "modId")]
20    pub mod_id: String,
21}
22
23#[derive(Debug, Deserialize, Clone)]
24pub struct Dependency {
25    #[serde(rename = "modId")]
26    pub mod_id: String,
27    pub r#type: String,
28}
29
30#[derive(Debug, Deserialize)]
31pub struct JarJarMetadata {
32    pub jars: Vec<JarJarEntry>,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct JarJarEntry {
37    pub identifier: JarJarIdentifier,
38}
39
40#[derive(Debug, Deserialize)]
41pub struct JarJarIdentifier {
42    pub artifact: String,
43}
44
45pub struct JarInspector;
46
47impl JarInspector {
48    pub fn inspect_neoforge<P: AsRef<Path>>(
49        path: P,
50    ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
51        let file = File::open(path)?;
52        let mut archive = ZipArchive::new(file)?;
53
54        let toml_content = {
55            let mut toml_file = archive.by_name("META-INF/neoforge.mods.toml")?;
56            let mut content = String::new();
57            toml_file.read_to_string(&mut content)?;
58            content
59        };
60        
61        let decoded: NeoForgeMetadata = toml::from_str(&toml_content)?;
62
63        let mut embedded_mods = Vec::new();
64        if let Ok(mut jarjar_file) = archive.by_name("META-INF/jarjar/metadata.json") {
65            let mut jarjar_content = String::new();
66            jarjar_file.read_to_string(&mut jarjar_content)?;
67            if let Ok(jarjar_data) = serde_json::from_str::<JarJarMetadata>(&jarjar_content) {
68                for entry in jarjar_data.jars {
69                    let clean_id = entry.identifier.artifact.split('-').next().unwrap_or("").to_string();
70                    embedded_mods.push(clean_id.to_lowercase());
71                    embedded_mods.push(entry.identifier.artifact.to_lowercase());
72                }
73            }
74        }
75
76        let mut required_deps = Vec::new();
77
78        if let Some(all_deps) = decoded.dependencies {
79            for (_, deps_list) in all_deps {
80                for dep in deps_list {
81                    let dep_id_lower = dep.mod_id.to_lowercase();
82                    
83                    if dep.r#type == "required"
84                        && dep_id_lower != "neoforge"
85                        && dep_id_lower != "minecraft"
86                        && !embedded_mods.iter().any(|embedded| dep_id_lower.contains(embedded))
87                    {
88                        required_deps.push(dep.mod_id);
89                    }
90                }
91            }
92        }
93
94        required_deps.sort();
95        required_deps.dedup();
96
97        Ok(required_deps)
98    }
99
100    pub fn extract_primary_mod_id<P: AsRef<Path>>(
101        path: P,
102    ) -> Result<Option<String>, Box<dyn std::error::Error>> {
103        let file = File::open(path)?;
104        let mut archive = ZipArchive::new(file)?;
105
106        let toml_content = if let Ok(mut toml_file) = archive.by_name("META-INF/neoforge.mods.toml") {
107            let mut content = String::new();
108            toml_file.read_to_string(&mut content)?;
109            content
110        } else if let Ok(mut toml_file) = archive.by_name("META-INF/mods.toml") {
111            let mut content = String::new();
112            toml_file.read_to_string(&mut content)?;
113            content
114        } else {
115            return Ok(None);
116        };
117
118        let decoded: NeoForgeModsList = toml::from_str(&toml_content)?;
119        let mod_id = decoded
120            .mods
121            .unwrap_or_default()
122            .into_iter()
123            .next()
124            .map(|m| m.mod_id);
125        Ok(mod_id)
126    }
127}