Skip to main content

minecraft_java_rs_core/launcher/
options.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::models::{loader::LoaderType, minecraft::Authenticator};
6
7/// Complete configuration for a launcher session.
8///
9/// Pass to `Launcher::new()`. Every field except `path`, `version`, and
10/// `authenticator` has a sensible default so callers only need to set what
11/// differs from the defaults.
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct LaunchOptions {
14    /// Absolute base path for all launcher data
15    /// (libraries/, assets/, versions/, runtime/, …).
16    pub path: PathBuf,
17
18    /// Minecraft version: concrete (`"1.20.4"`) or alias
19    /// (`"latest_release"` / `"r"` / `"lr"` / `"latest_snapshot"` / `"s"` / `"ls"`).
20    pub version: String,
21
22    /// Authentication credentials — required.
23    pub authenticator: Authenticator,
24
25    /// HTTP request timeout in seconds (default: 10).
26    #[serde(default = "defaults::timeout_secs")]
27    pub timeout_secs: u64,
28
29    /// Concurrent download workers, clamped to 1–30 (default: 5).
30    #[serde(default = "defaults::download_concurrency")]
31    pub download_concurrency: u32,
32
33    /// Concurrent SHA-1 verify workers, clamped to 1–16 (default: 4).
34    /// Lower than `download_concurrency` to avoid disk seek thrashing on HDDs.
35    #[serde(default = "defaults::verify_concurrency")]
36    pub verify_concurrency: u32,
37
38    #[serde(default)]
39    pub memory: MemoryConfig,
40
41    #[serde(default)]
42    pub java: JavaOptions,
43
44    #[serde(default)]
45    pub loader: LoaderConfig,
46
47    #[serde(default)]
48    pub screen: ScreenConfig,
49
50    /// Re-verify SHA-1 integrity of every file after download (default: false).
51    #[serde(default)]
52    pub verify: bool,
53
54    /// Extra arguments appended after the vanilla game arg list.
55    #[serde(default)]
56    pub game_args: Vec<String>,
57
58    /// Extra arguments prepended to the JVM arg list.
59    #[serde(default)]
60    pub jvm_args: Vec<String>,
61
62    /// Named instance for multi-instance support.
63    /// When set, data lives under `<path>/instances/<instance>/`.
64    #[serde(default)]
65    pub instance: Option<String>,
66
67    /// URL for custom additional assets (optional).
68    #[serde(default)]
69    pub url: Option<String>,
70
71    /// Path to a custom Minecraft JAR (mod compatibility parameter).
72    #[serde(default)]
73    pub mcp: Option<String>,
74
75    /// macOS only: force x64 Java even on Apple Silicon (Rosetta 2).
76    #[serde(default)]
77    pub intel_enabled_mac: bool,
78
79    /// Redirect Mojang auth endpoints to an invalid domain so offline
80    /// multiplayer works without a valid session (default: false).
81    #[serde(default)]
82    pub bypass_offline: bool,
83
84    /// When `true` and `gameData.json` already exists on disk, skip the
85    /// bundle integrity check and load directly from cache (fast launch).
86    /// Falls through to the normal download path when the cache is absent.
87    /// Default: `false` (always verify — current behaviour preserved).
88    #[serde(default)]
89    pub skip_bundle_check: bool,
90}
91
92impl LaunchOptions {
93    /// Directory where `gameData.json` is stored.
94    /// Returns `<path>/instances/<instance>` when instanced, otherwise `<path>`.
95    pub fn save_dir(&self) -> PathBuf {
96        match &self.instance {
97            Some(inst) => self.path.join("instances").join(inst),
98            None => self.path.clone(),
99        }
100    }
101
102    /// Root directory for a specific mod loader's files.
103    ///
104    /// Returns `<path>/loader/<name>` unless `loader.path` is set explicitly.
105    pub fn loader_dir(&self, name: &str) -> PathBuf {
106        match &self.loader.path {
107            Some(p) => PathBuf::from(p),
108            None => self.path.join("loader").join(name),
109        }
110    }
111
112    /// `download_concurrency` clamped to the valid range 1–30.
113    pub fn clamped_concurrency(&self) -> u32 {
114        self.download_concurrency.clamp(1, 30)
115    }
116
117    /// `verify_concurrency` clamped to the valid range 1–16.
118    pub fn clamped_verify_concurrency(&self) -> u32 {
119        self.verify_concurrency.clamp(1, 16)
120    }
121}
122
123// ── Memory ───────────────────────────────────────────────────────────────────
124
125#[derive(Debug, Clone, Deserialize, Serialize)]
126pub struct MemoryConfig {
127    /// JVM minimum heap (`-Xms`), e.g. `"1G"`, `"512M"` (default: `"1G"`).
128    #[serde(default = "defaults::memory_min")]
129    pub min: String,
130    /// JVM maximum heap (`-Xmx`), e.g. `"2G"` (default: `"2G"`).
131    #[serde(default = "defaults::memory_max")]
132    pub max: String,
133}
134
135impl Default for MemoryConfig {
136    fn default() -> Self {
137        Self {
138            min: defaults::memory_min(),
139            max: defaults::memory_max(),
140        }
141    }
142}
143
144// ── Screen ───────────────────────────────────────────────────────────────────
145
146#[derive(Debug, Clone, Deserialize, Serialize, Default)]
147pub struct ScreenConfig {
148    pub width: Option<u32>,
149    pub height: Option<u32>,
150    /// Launch in fullscreen mode (default: false).
151    #[serde(default)]
152    pub fullscreen: bool,
153}
154
155// ── Java ─────────────────────────────────────────────────────────────────────
156
157#[derive(Debug, Clone, Deserialize, Serialize)]
158pub struct JavaOptions {
159    /// Path to a pre-installed `java` executable — skips automatic download.
160    #[serde(default)]
161    pub path: Option<PathBuf>,
162
163    /// Force a specific Java major version, e.g. `"21"`.
164    #[serde(default)]
165    pub version: Option<String>,
166
167    /// Adoptium image type: `"jre"` or `"jdk"` (default: `"jre"`).
168    #[serde(default = "defaults::java_image_type")]
169    pub image_type: String,
170}
171
172impl Default for JavaOptions {
173    fn default() -> Self {
174        Self {
175            path: None,
176            version: None,
177            image_type: defaults::java_image_type(),
178        }
179    }
180}
181
182// ── Loader ───────────────────────────────────────────────────────────────────
183
184#[derive(Debug, Clone, Deserialize, Serialize, Default)]
185pub struct LoaderConfig {
186    /// Which mod loader to install (`None` = no loader).
187    pub loader_type: Option<LoaderType>,
188
189    /// Build selector: `"latest"`, `"recommended"`, or an exact version string
190    /// (default: `"latest"`).
191    #[serde(default = "defaults::loader_build")]
192    pub build: String,
193
194    /// Whether to run the loader installer (default: false).
195    #[serde(default)]
196    pub enable: bool,
197
198    /// Loader-local directory prefix, e.g. `"./loader/forge"`.
199    /// Auto-set to `"./loader/<type>"` if not provided.
200    #[serde(default)]
201    pub path: Option<String>,
202
203    /// Paths populated by the installer after a successful install.
204    /// Passed back to the argument builder.
205    #[serde(default)]
206    pub config: Option<LoaderInnerConfig>,
207}
208
209/// File paths set by the mod loader installer.
210#[derive(Debug, Clone, Deserialize, Serialize)]
211pub struct LoaderInnerConfig {
212    pub java_path: String,
213    pub minecraft_jar: String,
214    pub minecraft_json: String,
215}
216
217// ── Defaults (free functions required by serde's `default = "..."`) ─────────
218
219mod defaults {
220    pub fn timeout_secs() -> u64 { 10 }
221    pub fn download_concurrency() -> u32 { 5 }
222    pub fn verify_concurrency() -> u32 { 4 }
223    pub fn memory_min() -> String { "1G".into() }
224    pub fn memory_max() -> String { "2G".into() }
225    pub fn java_image_type() -> String { "jre".into() }
226    pub fn loader_build() -> String { "latest".into() }
227}
228
229// ── Tests ────────────────────────────────────────────────────────────────────
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn save_dir_without_instance() {
237        let opts = make_opts(None);
238        assert_eq!(opts.save_dir(), PathBuf::from("/mc"));
239    }
240
241    #[test]
242    fn save_dir_with_instance() {
243        let opts = make_opts(Some("test-world".into()));
244        assert_eq!(opts.save_dir(), PathBuf::from("/mc/instances/test-world"));
245    }
246
247    #[test]
248    fn concurrency_clamp() {
249        let mut opts = make_opts(None);
250        opts.download_concurrency = 0;
251        assert_eq!(opts.clamped_concurrency(), 1);
252        opts.download_concurrency = 99;
253        assert_eq!(opts.clamped_concurrency(), 30);
254        opts.download_concurrency = 5;
255        assert_eq!(opts.clamped_concurrency(), 5);
256    }
257
258    #[test]
259    fn verify_concurrency_clamp() {
260        let mut opts = make_opts(None);
261        opts.verify_concurrency = 0;
262        assert_eq!(opts.clamped_verify_concurrency(), 1);
263        opts.verify_concurrency = 99;
264        assert_eq!(opts.clamped_verify_concurrency(), 16);
265        opts.verify_concurrency = 4;
266        assert_eq!(opts.clamped_verify_concurrency(), 4);
267    }
268
269    #[test]
270    fn memory_defaults() {
271        let m = MemoryConfig::default();
272        assert_eq!(m.min, "1G");
273        assert_eq!(m.max, "2G");
274    }
275
276    fn make_opts(instance: Option<String>) -> LaunchOptions {
277        use crate::models::minecraft::Authenticator;
278        LaunchOptions {
279            path: PathBuf::from("/mc"),
280            version: "1.20.4".into(),
281            authenticator: Authenticator {
282                access_token: "tok".into(),
283                name: "Player".into(),
284                uuid: "uuid".into(),
285                xbox_account: None,
286                user_properties: None,
287                client_id: None,
288                client_token: None,
289            },
290            timeout_secs: 10,
291            download_concurrency: 5,
292            verify_concurrency: 4,
293            memory: MemoryConfig::default(),
294            java: JavaOptions::default(),
295            loader: LoaderConfig::default(),
296            screen: ScreenConfig::default(),
297            verify: false,
298            game_args: vec![],
299            jvm_args: vec![],
300            instance,
301            url: None,
302            mcp: None,
303            intel_enabled_mac: false,
304            bypass_offline: false,
305            skip_bundle_check: false,
306        }
307    }
308}