conduit_cli/inspector/
mod.rs1use 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}