use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::models::{loader::LoaderType, minecraft::Authenticator};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LaunchOptions {
pub path: PathBuf,
pub version: String,
pub authenticator: Authenticator,
#[serde(default = "defaults::timeout_secs")]
pub timeout_secs: u64,
#[serde(default = "defaults::download_concurrency")]
pub download_concurrency: u32,
#[serde(default = "defaults::verify_concurrency")]
pub verify_concurrency: u32,
#[serde(default)]
pub memory: MemoryConfig,
#[serde(default)]
pub java: JavaOptions,
#[serde(default)]
pub loader: LoaderConfig,
#[serde(default)]
pub screen: ScreenConfig,
#[serde(default)]
pub verify: bool,
#[serde(default)]
pub game_args: Vec<String>,
#[serde(default)]
pub jvm_args: Vec<String>,
#[serde(default)]
pub instance: Option<String>,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub mcp: Option<String>,
#[serde(default)]
pub intel_enabled_mac: bool,
#[serde(default)]
pub bypass_offline: bool,
}
impl LaunchOptions {
pub fn save_dir(&self) -> PathBuf {
match &self.instance {
Some(inst) => self.path.join("instances").join(inst),
None => self.path.clone(),
}
}
pub fn loader_dir(&self, name: &str) -> PathBuf {
match &self.loader.path {
Some(p) => PathBuf::from(p),
None => self.path.join("loader").join(name),
}
}
pub fn clamped_concurrency(&self) -> u32 {
self.download_concurrency.clamp(1, 30)
}
pub fn clamped_verify_concurrency(&self) -> u32 {
self.verify_concurrency.clamp(1, 16)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MemoryConfig {
#[serde(default = "defaults::memory_min")]
pub min: String,
#[serde(default = "defaults::memory_max")]
pub max: String,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
min: defaults::memory_min(),
max: defaults::memory_max(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct ScreenConfig {
pub width: Option<u32>,
pub height: Option<u32>,
#[serde(default)]
pub fullscreen: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JavaOptions {
#[serde(default)]
pub path: Option<PathBuf>,
#[serde(default)]
pub version: Option<String>,
#[serde(default = "defaults::java_image_type")]
pub image_type: String,
}
impl Default for JavaOptions {
fn default() -> Self {
Self {
path: None,
version: None,
image_type: defaults::java_image_type(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct LoaderConfig {
pub loader_type: Option<LoaderType>,
#[serde(default = "defaults::loader_build")]
pub build: String,
#[serde(default)]
pub enable: bool,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub config: Option<LoaderInnerConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LoaderInnerConfig {
pub java_path: String,
pub minecraft_jar: String,
pub minecraft_json: String,
}
mod defaults {
pub fn timeout_secs() -> u64 { 10 }
pub fn download_concurrency() -> u32 { 5 }
pub fn verify_concurrency() -> u32 { 4 }
pub fn memory_min() -> String { "1G".into() }
pub fn memory_max() -> String { "2G".into() }
pub fn java_image_type() -> String { "jre".into() }
pub fn loader_build() -> String { "latest".into() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn save_dir_without_instance() {
let opts = make_opts(None);
assert_eq!(opts.save_dir(), PathBuf::from("/mc"));
}
#[test]
fn save_dir_with_instance() {
let opts = make_opts(Some("test-world".into()));
assert_eq!(opts.save_dir(), PathBuf::from("/mc/instances/test-world"));
}
#[test]
fn concurrency_clamp() {
let mut opts = make_opts(None);
opts.download_concurrency = 0;
assert_eq!(opts.clamped_concurrency(), 1);
opts.download_concurrency = 99;
assert_eq!(opts.clamped_concurrency(), 30);
opts.download_concurrency = 5;
assert_eq!(opts.clamped_concurrency(), 5);
}
#[test]
fn verify_concurrency_clamp() {
let mut opts = make_opts(None);
opts.verify_concurrency = 0;
assert_eq!(opts.clamped_verify_concurrency(), 1);
opts.verify_concurrency = 99;
assert_eq!(opts.clamped_verify_concurrency(), 16);
opts.verify_concurrency = 4;
assert_eq!(opts.clamped_verify_concurrency(), 4);
}
#[test]
fn memory_defaults() {
let m = MemoryConfig::default();
assert_eq!(m.min, "1G");
assert_eq!(m.max, "2G");
}
fn make_opts(instance: Option<String>) -> LaunchOptions {
use crate::models::minecraft::Authenticator;
LaunchOptions {
path: PathBuf::from("/mc"),
version: "1.20.4".into(),
authenticator: Authenticator {
access_token: "tok".into(),
name: "Player".into(),
uuid: "uuid".into(),
xbox_account: None,
user_properties: None,
client_id: None,
client_token: None,
},
timeout_secs: 10,
download_concurrency: 5,
verify_concurrency: 4,
memory: MemoryConfig::default(),
java: JavaOptions::default(),
loader: LoaderConfig::default(),
screen: ScreenConfig::default(),
verify: false,
game_args: vec![],
jvm_args: vec![],
instance,
url: None,
mcp: None,
intel_enabled_mac: false,
bypass_offline: false,
}
}
}