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.minecraft_arguments
87            .as_deref()
88            .map(|s| s.split_whitespace().map(str::to_owned).collect())
89            .unwrap_or_default();
90        Ok(LoaderResult {
91            libraries,
92            main_class: json.main_class,
93            loader_version: json.id,
94            loader_type: self.loader_type(),
95            extra_game_args,
96            extra_jvm_args: vec![],
97        })
98    }
99}
100
101#[async_trait]
102impl ModLoader for QuiltMC {
103    async fn install(
104        &self,
105        options: &LaunchOptions,
106        input: &LoaderInstallInput,
107        client: &reqwest::Client,
108        event_tx: &Sender<LaunchEvent>,
109    ) -> Result<LoaderResult, LoaderError> {
110        let json = self
111            .download_json(&input.mc_version, &options.loader.build, client)
112            .await?;
113        let libraries = self
114            .download_libraries(options, &json, client, event_tx)
115            .await?;
116        save_loader_version(&options.loader_dir("quilt"), &json.id, &json).await?;
117        let extra_game_args = json.minecraft_arguments
118            .as_deref()
119            .map(|s| s.split_whitespace().map(str::to_owned).collect())
120            .unwrap_or_default();
121        Ok(LoaderResult {
122            libraries,
123            main_class: json.main_class,
124            loader_version: json.id,
125            loader_type: LoaderType::Quilt,
126            extra_game_args,
127            extra_jvm_args: vec![],
128        })
129    }
130}
131
132#[async_trait]
133impl ModLoader for ForgeMC {
134    async fn install(
135        &self,
136        options: &LaunchOptions,
137        input: &LoaderInstallInput,
138        client: &reqwest::Client,
139        event_tx: &Sender<LaunchEvent>,
140    ) -> Result<LoaderResult, LoaderError> {
141        let (version_id, main_class, libraries, extra_game_args, extra_jvm_args) = self
142            .install(
143                options,
144                &input.mc_version,
145                &input.java_path,
146                &input.mc_jar,
147                &input.mc_json,
148                &options.loader.build,
149                client,
150                event_tx,
151            )
152            .await?;
153
154        Ok(LoaderResult {
155            libraries,
156            main_class,
157            loader_version: version_id,
158            loader_type: LoaderType::Forge,
159            extra_game_args,
160            extra_jvm_args,
161        })
162    }
163}
164
165#[async_trait]
166impl ModLoader for NeoForgeMC {
167    async fn install(
168        &self,
169        options: &LaunchOptions,
170        input: &LoaderInstallInput,
171        client: &reqwest::Client,
172        event_tx: &Sender<LaunchEvent>,
173    ) -> Result<LoaderResult, LoaderError> {
174        let (version_id, main_class, libraries, extra_game_args, extra_jvm_args) = self
175            .install(
176                options,
177                &input.mc_version,
178                &input.java_path,
179                &input.mc_jar,
180                &input.mc_json,
181                &options.loader.build,
182                client,
183                event_tx,
184            )
185            .await?;
186
187        Ok(LoaderResult {
188            libraries,
189            main_class,
190            loader_version: version_id,
191            loader_type: LoaderType::NeoForge,
192            extra_game_args,
193            extra_jvm_args,
194        })
195    }
196}
197
198// ── Tests ─────────────────────────────────────────────────────────────────────
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn create_loader_forge_is_dyn() {
206        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Forge);
207    }
208
209    #[test]
210    fn create_loader_neoforge_is_dyn() {
211        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::NeoForge);
212    }
213
214    #[test]
215    fn create_loader_fabric_is_dyn() {
216        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Fabric);
217    }
218
219    #[test]
220    fn create_loader_legacy_fabric_is_dyn() {
221        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::LegacyFabric);
222    }
223
224    #[test]
225    fn create_loader_quilt_is_dyn() {
226        let _loader: Box<dyn ModLoader> = create_loader(LoaderType::Quilt);
227    }
228
229    #[test]
230    fn all_loader_types_are_dispatchable() {
231        let types = [
232            LoaderType::Forge,
233            LoaderType::NeoForge,
234            LoaderType::Fabric,
235            LoaderType::LegacyFabric,
236            LoaderType::Quilt,
237        ];
238        for t in types {
239            let _loader = create_loader(t);
240        }
241    }
242}