mcvm/config/
profile.rs

1use std::collections::HashMap;
2
3use anyhow::bail;
4use mcvm_shared::id::ProfileID;
5use mcvm_shared::Side;
6#[cfg(feature = "schema")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use mcvm_shared::modifications::{ClientType, Modloader, Proxy, ServerType};
11
12use super::instance::{merge_instance_configs, InstanceConfig};
13use super::package::PackageConfigDeser;
14
15/// Configuration for a profile
16#[derive(Deserialize, Serialize, Clone)]
17#[cfg_attr(feature = "schema", derive(JsonSchema))]
18pub struct ProfileConfig {
19	/// The configuration for the instance
20	#[serde(flatten)]
21	pub instance: InstanceConfig,
22	/// Package configuration
23	#[serde(default)]
24	pub packages: ProfilePackageConfiguration,
25}
26
27impl ProfileConfig {
28	/// Merge this profile with another one
29	pub fn merge(&mut self, other: Self) {
30		self.instance = merge_instance_configs(&self.instance, other.instance);
31	}
32}
33
34/// Different representations of package configuration on a profile
35#[derive(Deserialize, Serialize, Debug, Clone)]
36#[cfg_attr(feature = "schema", derive(JsonSchema))]
37#[serde(untagged)]
38pub enum ProfilePackageConfiguration {
39	/// Is just a list of packages for every instance
40	Simple(Vec<PackageConfigDeser>),
41	/// Full configuration
42	Full {
43		/// Packages to apply to every instance
44		#[serde(default)]
45		global: Vec<PackageConfigDeser>,
46		/// Packages to apply to only clients
47		#[serde(default)]
48		client: Vec<PackageConfigDeser>,
49		/// Packages to apply to only servers
50		#[serde(default)]
51		server: Vec<PackageConfigDeser>,
52	},
53}
54
55impl Default for ProfilePackageConfiguration {
56	fn default() -> Self {
57		Self::Simple(Vec::new())
58	}
59}
60
61impl ProfilePackageConfiguration {
62	/// Validate all the configured packages
63	pub fn validate(&self) -> anyhow::Result<()> {
64		match &self {
65			Self::Simple(global) => {
66				for pkg in global {
67					pkg.validate()?;
68				}
69			}
70			Self::Full {
71				global,
72				client,
73				server,
74			} => {
75				for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
76					pkg.validate()?;
77				}
78			}
79		}
80
81		Ok(())
82	}
83
84	/// Iterate over all of the packages
85	pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
86		match &self {
87			Self::Simple(global) => Box::new(global.iter()),
88			Self::Full {
89				global,
90				client,
91				server,
92			} => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
93		}
94	}
95
96	/// Iterate over the global package list
97	pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
98		match &self {
99			Self::Simple(global) => global,
100			Self::Full { global, .. } => global,
101		}
102		.iter()
103	}
104
105	/// Iterate over the package list for a specific side
106	pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
107		match &self {
108			Self::Simple(..) => [].iter(),
109			Self::Full { client, server, .. } => match side {
110				Side::Client => client.iter(),
111				Side::Server => server.iter(),
112			},
113		}
114	}
115
116	/// Adds a package to the global list
117	pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
118		match self {
119			Self::Simple(global) => global.push(pkg),
120			Self::Full { global, .. } => global.push(pkg),
121		}
122	}
123
124	/// Adds a package to the client list
125	pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
126		match self {
127			Self::Simple(global) => {
128				*self = Self::Full {
129					global: global.clone(),
130					client: vec![pkg],
131					server: Vec::new(),
132				}
133			}
134			Self::Full { client, .. } => client.push(pkg),
135		}
136	}
137
138	/// Adds a package to the server list
139	pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
140		match self {
141			Self::Simple(global) => {
142				*self = Self::Full {
143					global: global.clone(),
144					client: Vec::new(),
145					server: vec![pkg],
146				}
147			}
148			Self::Full { server, .. } => server.push(pkg),
149		}
150	}
151}
152
153/// Consolidates profile configs into the full profiles
154pub fn consolidate_profile_configs(
155	profiles: HashMap<ProfileID, ProfileConfig>,
156	global_profile: Option<&ProfileConfig>,
157) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
158	let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
159
160	let max_iterations = 10000;
161
162	// We do this by repeatedly finding a profile with an already resolved ancenstor
163	let mut i = 0;
164	while out.len() != profiles.len() {
165		for (id, profile) in &profiles {
166			// Don't redo profiles that are already done
167			if out.contains_key(id) {
168				continue;
169			}
170
171			if profile.instance.common.from.is_empty() {
172				// Profiles with no ancestor can just be added directly to the output, after deriving from the global profile
173				let mut profile = profile.clone();
174				if let Some(global_profile) = global_profile {
175					let overlay = profile;
176					profile = global_profile.clone();
177					profile.merge(overlay);
178				}
179				out.insert(id.clone(), profile);
180			} else {
181				for parent in profile.instance.common.from.iter() {
182					// If the parent is already in the map (already consolidated) then we can derive from it and add to the map
183					if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
184						let mut new = parent.clone();
185						new.merge(profile.clone());
186						out.insert(id.clone(), new);
187					} else {
188						bail!("Parent profile '{parent}' does not exist");
189					}
190				}
191			}
192		}
193
194		i += 1;
195		if i > max_iterations {
196			panic!("Max iterations exceeded while resolving profiles. This is a bug in MCVM.");
197		}
198	}
199
200	Ok(out)
201}
202
203/// Game modifications
204#[derive(Clone, Debug)]
205pub struct GameModifications {
206	modloader: Modloader,
207	/// Type of the client
208	client_type: ClientType,
209	/// Type of the server
210	server_type: ServerType,
211}
212
213impl GameModifications {
214	/// Create a new GameModifications
215	pub fn new(modloader: Modloader, client_type: ClientType, server_type: ServerType) -> Self {
216		Self {
217			modloader,
218			client_type,
219			server_type,
220		}
221	}
222
223	/// Gets the client type
224	pub fn client_type(&self) -> ClientType {
225		if let ClientType::None = self.client_type {
226			match &self.modloader {
227				Modloader::Vanilla => ClientType::Vanilla,
228				Modloader::Forge => ClientType::Forge,
229				Modloader::NeoForged => ClientType::NeoForged,
230				Modloader::Fabric => ClientType::Fabric,
231				Modloader::Quilt => ClientType::Quilt,
232				Modloader::LiteLoader => ClientType::LiteLoader,
233				Modloader::Risugamis => ClientType::Risugamis,
234				Modloader::Rift => ClientType::Rift,
235				Modloader::Unknown(modloader) => ClientType::Unknown(modloader.clone()),
236			}
237		} else {
238			self.client_type.clone()
239		}
240	}
241
242	/// Gets the server type
243	pub fn server_type(&self) -> ServerType {
244		if let ServerType::None = self.server_type {
245			match &self.modloader {
246				Modloader::Vanilla => ServerType::Vanilla,
247				Modloader::Forge => ServerType::Forge,
248				Modloader::NeoForged => ServerType::NeoForged,
249				Modloader::Fabric => ServerType::Fabric,
250				Modloader::Quilt => ServerType::Quilt,
251				Modloader::LiteLoader => ServerType::Unknown("liteloader".into()),
252				Modloader::Risugamis => ServerType::Risugamis,
253				Modloader::Rift => ServerType::Rift,
254				Modloader::Unknown(modloader) => ServerType::Unknown(modloader.clone()),
255			}
256		} else {
257			self.server_type.clone()
258		}
259	}
260
261	/// Gets the modloader of a side
262	pub fn get_modloader(&self, side: Side) -> Modloader {
263		match side {
264			Side::Client => match self.client_type {
265				ClientType::None => self.modloader.clone(),
266				ClientType::Vanilla => Modloader::Vanilla,
267				ClientType::Forge => Modloader::Forge,
268				ClientType::NeoForged => Modloader::NeoForged,
269				ClientType::Fabric => Modloader::Fabric,
270				ClientType::Quilt => Modloader::Quilt,
271				ClientType::LiteLoader => Modloader::LiteLoader,
272				ClientType::Risugamis => Modloader::Risugamis,
273				ClientType::Rift => Modloader::Rift,
274				_ => Modloader::Vanilla,
275			},
276			Side::Server => match self.server_type {
277				ServerType::None => self.modloader.clone(),
278				ServerType::Forge | ServerType::SpongeForge => Modloader::Forge,
279				ServerType::NeoForged => Modloader::NeoForged,
280				ServerType::Fabric => Modloader::Fabric,
281				ServerType::Quilt => Modloader::Quilt,
282				ServerType::Risugamis => Modloader::Risugamis,
283				ServerType::Rift => Modloader::Rift,
284				_ => Modloader::Vanilla,
285			},
286		}
287	}
288
289	/// Gets whether both client and server have the same modloader
290	pub fn common_modloader(&self) -> bool {
291		matches!(
292			(&self.client_type, &self.server_type),
293			(ClientType::None, ServerType::None)
294				| (ClientType::Vanilla, ServerType::Vanilla)
295				| (ClientType::Forge, ServerType::Forge)
296				| (ClientType::NeoForged, ServerType::NeoForged)
297				| (ClientType::Fabric, ServerType::Fabric)
298				| (ClientType::Quilt, ServerType::Quilt)
299				| (ClientType::Risugamis, ServerType::Risugamis)
300				| (ClientType::Rift, ServerType::Rift)
301		)
302	}
303}
304
305/// Check if a client type can be installed by MCVM
306pub fn can_install_client_type(client_type: &ClientType) -> bool {
307	matches!(client_type, ClientType::None | ClientType::Vanilla)
308}
309
310/// Check if a server type can be installed by MCVM
311pub fn can_install_server_type(server_type: &ServerType) -> bool {
312	matches!(
313		server_type,
314		ServerType::None
315			| ServerType::Vanilla
316			| ServerType::Paper
317			| ServerType::Folia
318			| ServerType::Sponge
319	)
320}
321
322/// Check if a proxy can be installed by MCVM
323pub fn can_install_proxy(proxy: Proxy) -> bool {
324	// TODO: Support Velocity
325	matches!(proxy, Proxy::None)
326}