Skip to main content

conduit_cli/loaders/
neoforge.rs

1use crate::core::{
2    events::{CoreCallbacks, CoreEvent},
3    installer::download::download_to_path,
4};
5use regex::Regex;
6use std::path::{Path, PathBuf};
7
8pub async fn get_latest_neoforge_version(
9    mc_version: &str,
10    callbacks: &mut dyn CoreCallbacks,
11) -> Result<String, Box<dyn std::error::Error>> {
12    callbacks.on_event(CoreEvent::Info(
13        "Fetching latest NeoForge version".to_string(),
14    ));
15
16    let metadata_url =
17        "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml";
18    let response = reqwest::get(metadata_url).await?.text().await?;
19
20    let parts: Vec<&str> = mc_version.split('.').collect();
21
22    let major = if parts[0] == "1" {
23        parts.get(1).copied().unwrap_or("")
24    } else {
25        parts[0]
26    };
27
28    let minor = if parts[0] == "1" {
29        parts.get(2).copied().unwrap_or("0")
30    } else {
31        parts.get(1).copied().unwrap_or("0")
32    };
33
34    let prefix = format!("{}.{}.", major, minor);
35
36    let pattern = format!(r"<version>({}.*?)</version>", regex::escape(&prefix));
37    let re = Regex::new(&pattern)?;
38
39    let versions: Vec<String> = re
40        .captures_iter(&response)
41        .map(|cap| cap[1].to_string())
42        .collect();
43
44    let version = versions
45        .last()
46        .cloned()
47        .ok_or_else(|| format!("No compatible NeoForge version found for MC {}", mc_version))?;
48
49    callbacks.on_event(CoreEvent::Info(format!(
50        "Found NeoForge version: {}",
51        version
52    )));
53
54    Ok(version)
55}
56
57pub async fn download_neoforge_installer(
58    mc_version: &str,
59    loader_version: &str,
60    install_path: &Path,
61    callbacks: &mut dyn CoreCallbacks,
62) -> Result<PathBuf, Box<dyn std::error::Error>> {
63    let version = if loader_version == "latest" {
64        get_latest_neoforge_version(mc_version, callbacks).await?
65    } else {
66        loader_version.to_string()
67    };
68
69    let url = format!(
70        "https://maven.neoforged.net/releases/net/neoforged/neoforge/{v}/neoforge-{v}-installer.jar",
71        v = version
72    );
73
74    let filename = "neoforge-installer.jar";
75    let installer_path = install_path.join(filename);
76
77    let display_name = format!("neoforge-{}-installer.jar", version);
78
79    download_to_path(&url, &installer_path, &display_name, callbacks).await?;
80
81    Ok(installer_path)
82}
83
84pub async fn execute_neoforge_installer(
85    installer_path: &Path,
86    callbacks: &mut dyn CoreCallbacks,
87) -> Result<PathBuf, Box<dyn std::error::Error>> {
88    callbacks.on_event(CoreEvent::TaskStarted(format!(
89        "Executing NeoForge installer: {}",
90        installer_path.display()
91    )));
92
93    let status = tokio::process::Command::new("java")
94        .arg("-jar")
95        .arg(installer_path)
96        .arg("--installServer")
97        .current_dir(installer_path.parent().unwrap_or(Path::new(".")))
98        .stdout(std::process::Stdio::null())
99        .status()
100        .await?;
101
102    callbacks.on_event(CoreEvent::TaskFinished);
103
104    std::fs::remove_file(installer_path).ok();
105
106    if !status.success() {
107        return Err(format!("Installer exited with code: {}", status).into());
108    }
109
110    Ok(installer_path.to_path_buf())
111}
112
113pub async fn post_install_neoforge(
114    installer_path: &std::path::Path,
115    install_path: &Path,
116    callbacks: &mut dyn CoreCallbacks,
117) -> Result<(), Box<dyn std::error::Error>> {
118    callbacks.on_event(CoreEvent::TaskStarted(format!(
119        "Executing Post-Install: {}",
120        installer_path.display()
121    )));
122
123    let eula_path = install_path.join("eula.txt");
124    std::fs::write(eula_path, "eula=true")?;
125
126    let log_names = ["neoforge-installer.jar.log", "installer.log"];
127
128    for log_name in log_names {
129        let log_path = install_path.join(log_name);
130        if log_path.exists() {
131            let _ = std::fs::remove_file(log_path);
132        }
133    }
134
135    #[cfg(unix)]
136    {
137        use std::os::unix::fs::PermissionsExt;
138        let run_sh = install_path.join("run.sh");
139        if let Ok(metadata) = std::fs::metadata(&run_sh) {
140            let mut perms = metadata.permissions();
141            perms.set_mode(0o755); // rwxr-xr-x
142            let _ = std::fs::set_permissions(&run_sh, perms);
143        }
144    }
145
146    callbacks.on_event(CoreEvent::TaskFinished);
147
148    Ok(())
149}