Skip to main content

minecraft_java_rs_core/loader/
mod.rs

1pub mod fabric;
2pub mod forge;
3pub mod forge_patcher;
4pub mod neoforge;
5pub mod quilt;
6pub mod types;
7
8use async_trait::async_trait;
9use tokio::sync::mpsc::Sender;
10
11use crate::error::LoaderError;
12use crate::launcher::events::LaunchEvent;
13use crate::launcher::options::LaunchOptions;
14use crate::models::loader::LoaderType;
15
16// ── Helpers ───────────────────────────────────────────────────────────────────
17
18/// Persist a loader's version JSON to `<loader_dir>/versions/<id>/<id>.json`.
19/// Used by Fabric/Quilt where we fetch the profile JSON ourselves; Forge and
20/// NeoForge let the installer write its own version JSON.
21async fn save_loader_version(
22    loader_dir: &std::path::Path,
23    id: &str,
24    json: &impl serde::Serialize,
25) -> Result<(), LoaderError> {
26    let dir = loader_dir.join("versions").join(id);
27    tokio::fs::create_dir_all(&dir).await?;
28    let content = serde_json::to_string(json)?;
29    tokio::fs::write(dir.join(format!("{id}.json")), content).await?;
30    Ok(())
31}
32
33use self::fabric::{FabricMC, FabricVariant};
34use self::forge::ForgeMC;
35use self::neoforge::NeoForgeMC;
36use self::quilt::QuiltMC;
37use self::types::{LoaderInstallInput, LoaderResult};
38
39// ── Trait ─────────────────────────────────────────────────────────────────────
40
41#[async_trait]
42pub trait ModLoader: Send + Sync {
43    async fn install(
44        &self,
45        options: &LaunchOptions,
46        input: &LoaderInstallInput,
47        client: &reqwest::Client,
48        event_tx: &Sender<LaunchEvent>,
49    ) -> Result<LoaderResult, LoaderError>;
50}
51
52// ── Dispatcher ────────────────────────────────────────────────────────────────
53
54pub fn create_loader(loader_type: LoaderType) -> Box<dyn ModLoader> {
55    match loader_type {
56        LoaderType::Forge => Box::new(ForgeMC::new()),
57        LoaderType::NeoForge => Box::new(NeoForgeMC::new()),
58        LoaderType::Fabric => Box::new(FabricMC::new(FabricVariant::Modern)),
59        LoaderType::LegacyFabric => Box::new(FabricMC::new(FabricVariant::Legacy)),
60        LoaderType::Quilt => Box::new(QuiltMC::new()),
61    }
62}
63
64// ── ModLoader impls ───────────────────────────────────────────────────────────
65
66#[async_trait]
67impl ModLoader for FabricMC {
68    async fn install(
69        &self,
70        options: &LaunchOptions,
71        input: &LoaderInstallInput,
72        client: &reqwest::Client,
73        event_tx: &Sender<LaunchEvent>,
74    ) -> Result<LoaderResult, LoaderError> {
75        let loader_name = match self.loader_type() {
76            LoaderType::LegacyFabric => "legacyfabric",
77            _ => "fabric",
78        };
79        let json = self
80            .download_json(&input.mc_version, &options.loader.build, client)
81            .await?;
82        let libraries = self
83            .download_libraries(options, &json, client, event_tx)
84            .await?;
85        save_loader_version(&options.loader_dir(loader_name), &json.id, &json).await?;
86        let extra_game_args = json
87            .minecraft_arguments
88            .as_deref()
89            .map(|s| s.split_whitespace().map(str::to_owned).collect())
90            .unwrap_or_default();
91        Ok(LoaderResult {
92            libraries,
93            main_class: json.main_class,
94            loader_version: json.id,
95            loader_type: self.loader_type(),
96            extra_game_args,
97            extra_jvm_args: vec![],
98        })
99    }
100}
101
102#[async_trait]
103impl ModLoader for QuiltMC {
104    async fn install(
105        &self,
106        options: &LaunchOptions,
107        input: &LoaderInstallInput,
108        client: &reqwest::Client,
109        event_tx: &Sender<LaunchEvent>,
110    ) -> Result<LoaderResult, LoaderError> {
111        let json = self
112            .download_json(&input.mc_version, &options.loader.build, client)
113            .await?;
114        let libraries = self
115            .download_libraries(options, &json, client, event_tx)
116            .await?;
117        save_loader_version(&options.loader_dir("quilt"), &json.id, &json).await?;
118        let extra_game_args = json
119            .minecraft_arguments
120            .as_deref()
121            .map(|s| s.split_whitespace().map(str::to_owned).collect())
122            .unwrap_or_default();
123        Ok(LoaderResult {
124            libraries,
125            main_class: json.main_class,
126            loader_version: json.id,
127            loader_type: LoaderType::Quilt,
128            extra_game_args,
129            extra_jvm_args: vec![],
130        })
131    }
132}
133
134#[async_trait]
135impl ModLoader for ForgeMC {
136    async fn install(
137        &self,
138        options: &LaunchOptions,
139        input: &LoaderInstallInput,
140        client: &reqwest::Client,
141        event_tx: &Sender<LaunchEvent>,
142    ) -> Result<LoaderResult, LoaderError> {
143        let (version_id, main_class, libraries, extra_game_args, extra_jvm_args) = self
144            .install(
145                options,
146                &input.mc_version,
147                &input.java_path,
148                &input.mc_jar,
149                &input.mc_json,
150                &options.loader.build,
151                client,
152                event_tx,
153            )
154            .await?;
155
156        Ok(LoaderResult {
157            libraries,
158            main_class,
159            loader_version: version_id,
160            loader_type: LoaderType::Forge,
161            extra_game_args,
162            extra_jvm_args,
163        })
164    }
165}
166
167#[async_trait]
168impl ModLoader for NeoForgeMC {
169    async fn install(
170        &self,
171        options: &LaunchOptions,
172        input: &LoaderInstallInput,
173        client: &reqwest::Client,
174        event_tx: &Sender<LaunchEvent>,
175    ) -> Result<LoaderResult, LoaderError> {
176        let (version_id, main_class, libraries, extra_game_args, extra_jvm_args) = self
177            .install(
178                options,
179                &input.mc_version,
180                &input.java_path,
181                &input.mc_jar,
182                &input.mc_json,
183                &options.loader.build,
184                client,
185                event_tx,
186            )
187            .await?;
188
189        Ok(LoaderResult {
190            libraries,
191            main_class,
192            loader_version: version_id,
193            loader_type: LoaderType::NeoForge,
194            extra_game_args,
195            extra_jvm_args,
196        })
197    }
198}
199
200// ── Tests ─────────────────────────────────────────────────────────────────────
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn create_loader_forge_is_dyn() {
208        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Forge);
209    }
210
211    #[test]
212    fn create_loader_neoforge_is_dyn() {
213        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::NeoForge);
214    }
215
216    #[test]
217    fn create_loader_fabric_is_dyn() {
218        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Fabric);
219    }
220
221    #[test]
222    fn create_loader_legacy_fabric_is_dyn() {
223        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::LegacyFabric);
224    }
225
226    #[test]
227    fn create_loader_quilt_is_dyn() {
228        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Quilt);
229    }
230
231    #[test]
232    fn all_loader_types_are_dispatchable() {
233        let types = [
234            LoaderType::Forge,
235            LoaderType::NeoForge,
236            LoaderType::Fabric,
237            LoaderType::LegacyFabric,
238            LoaderType::Quilt,
239        ];
240        for t in types {
241            let _loader = create_loader(t);
242        }
243    }
244}