interpulse/api/
modded.rs

1use crate::api::minecraft::{Argument, ArgumentType, Library, VersionInfo, VersionType};
2use crate::utils::prelude::*;
3
4/// The latest version of the format the fabric model structs deserialize to
5pub const CURRENT_FABRIC_FORMAT_VERSION: usize = 0;
6/// The latest version of the format the fabric model structs deserialize to
7pub const CURRENT_FORGE_FORMAT_VERSION: usize = 0;
8/// The latest version of the format the quilt model structs deserialize to
9pub const CURRENT_QUILT_FORMAT_VERSION: usize = 0;
10/// The latest version of the format the neoforge model structs deserialize to
11pub const CURRENT_NEOFORGE_FORMAT_VERSION: usize = 0;
12/// The latest version of the format the legacy fabric model structs deserialize to
13pub const CURRENT_LEGACY_FABRIC_FORMAT_VERSION: usize = 0;
14/// The latest version of the format the cleanroom model structs deserialize to
15pub const CURRENT_CLEANROOM_FORMAT_VERSION: usize = 0;
16
17/// The dummy replace string library names, inheritsFrom, and version names should be replaced with
18pub const DUMMY_REPLACE_STRING: &str = "${interpulse.gameVersion}";
19
20/// A data variable entry that depends on the side of the installation
21#[cfg_attr(feature = "specta", derive(specta::Type))]
22#[derive(Serialize, Deserialize, Debug, Clone)]
23pub struct SidedDataEntry {
24	/// The value on the client
25	pub client: String,
26	/// The value on the server
27	pub server: String,
28}
29
30fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
31where
32	D: Deserializer<'de>,
33{
34	let s = String::deserialize(deserializer)?;
35	#[allow(deprecated)]
36	serde_json::from_str::<DateTime<Utc>>(&format!("\"{s}\""))
37		.or_else(|_| Utc.datetime_from_str(&s, "%Y-%m-%dT%H:%M:%S%.9f"))
38		.map_err(serde::de::Error::custom)
39}
40
41#[cfg_attr(feature = "specta", derive(specta::Type))]
42#[derive(Serialize, Deserialize, Debug, Clone)]
43#[serde(rename_all = "camelCase")]
44/// A partial version returned by fabric meta
45pub struct PartialVersionInfo {
46	/// The version ID of the version
47	pub id: String,
48	/// The version ID this partial version inherits from
49	pub inherits_from: String,
50	/// The time that the version was released
51	#[serde(deserialize_with = "deserialize_date")]
52	pub release_time: DateTime<Utc>,
53	/// The latest time a file in this version was updated
54	#[serde(deserialize_with = "deserialize_date")]
55	pub time: DateTime<Utc>,
56	#[serde(skip_serializing_if = "Option::is_none")]
57	/// The classpath to the main class to launch the game
58	pub main_class: Option<String>,
59	#[serde(skip_serializing_if = "Option::is_none")]
60	/// (Legacy) Arguments passed to the game
61	pub minecraft_arguments: Option<String>,
62	#[serde(skip_serializing_if = "Option::is_none")]
63	/// Arguments passed to the game or JVM
64	pub arguments: Option<HashMap<ArgumentType, Vec<Argument>>>,
65	/// Libraries that the version depends on
66	pub libraries: Vec<Library>,
67	#[serde(rename = "type")]
68	/// The type of version
69	pub type_: VersionType,
70	#[serde(skip_serializing_if = "Option::is_none")]
71	/// (Forge-only)
72	pub data: Option<HashMap<String, SidedDataEntry>>,
73	#[serde(skip_serializing_if = "Option::is_none")]
74	/// (Forge-only) The list of processors to run after downloading the files
75	pub processors: Option<Vec<Processor>>,
76}
77
78/// A processor to be ran after downloading the files
79#[cfg_attr(feature = "specta", derive(specta::Type))]
80#[derive(Serialize, Deserialize, Debug, Clone)]
81pub struct Processor {
82	/// Maven coordinates for the JAR library of this processor.
83	pub jar: String,
84	/// Maven coordinates for all the libraries that must be included in classpath when running this processor.
85	pub classpath: Vec<String>,
86	/// Arguments for this processor.
87	pub args: Vec<String>,
88	#[serde(skip_serializing_if = "Option::is_none")]
89	/// Represents a map of outputs. Keys and values can be data values
90	pub outputs: Option<HashMap<String, String>>,
91	#[serde(skip_serializing_if = "Option::is_none")]
92	/// Which sides this processor shall be ran on.
93	/// Valid values: client, server, extract
94	pub sides: Option<Vec<String>>,
95}
96
97/// Merges a partial version into a complete one
98#[must_use]
99pub fn merge_partial_version(partial: PartialVersionInfo, merge: VersionInfo) -> VersionInfo {
100	let merge_id = merge.id.clone();
101	let mut libraries = vec![];
102
103	for mut lib in merge.libraries {
104		let lib_artifact = lib.name.rsplit_once(':').map(|x| x.0);
105		if let Some(lib_artifact) = lib_artifact {
106			if partial.libraries.iter().any(|x| {
107				let target_artifact = x.name.rsplit_once(':').map(|x| x.0);
108				target_artifact == Some(lib_artifact) && x.include_in_classpath
109			}) {
110				lib.include_in_classpath = false;
111			} else {
112				libraries.push(lib);
113			}
114		} else {
115			libraries.push(lib);
116		}
117	}
118
119	VersionInfo {
120		arguments: if let Some(partial_args) = partial.arguments {
121			if let Some(merge_args) = merge.arguments {
122				fn add_keys(
123					new_map: &mut HashMap<ArgumentType, Vec<Argument>>,
124					args: HashMap<ArgumentType, Vec<Argument>>,
125				) {
126					for (type_, arguments) in args {
127						for arg in arguments {
128							if let Some(vec) = new_map.get_mut(&type_) {
129								vec.push(arg);
130							} else {
131								new_map.insert(type_, vec![arg]);
132							}
133						}
134					}
135				}
136
137				let mut new_map = HashMap::new();
138				add_keys(&mut new_map, merge_args);
139				add_keys(&mut new_map, partial_args);
140
141				Some(new_map)
142			} else {
143				Some(partial_args)
144			}
145		} else {
146			merge.arguments
147		},
148		asset_index: merge.asset_index,
149		assets: merge.assets,
150		downloads: merge.downloads,
151		id: partial.id.replace(DUMMY_REPLACE_STRING, &merge_id),
152		java_version: merge.java_version,
153		libraries: libraries
154			.into_iter()
155			.chain(partial.libraries)
156			.map(|mut x| {
157				x.name = x.name.replace(DUMMY_REPLACE_STRING, &merge_id);
158				x
159			})
160			.collect::<Vec<_>>(),
161		main_class: if let Some(main_class) = partial.main_class {
162			main_class
163		} else {
164			merge.main_class
165		},
166		logging: merge.logging,
167		minecraft_arguments: partial.minecraft_arguments,
168		minimum_launcher_version: merge.minimum_launcher_version,
169		release_time: partial.release_time,
170		time: partial.time,
171		type_: partial.type_,
172		data: partial.data,
173		processors: partial.processors,
174	}
175}
176
177#[cfg_attr(feature = "specta", derive(specta::Type))]
178#[derive(Serialize, Deserialize, Debug, Clone)]
179#[serde(rename_all = "camelCase")]
180/// A manifest containing information about a mod loader's versions
181pub struct Manifest {
182	/// The game versions the mod loader supports
183	pub game_versions: Vec<Version>,
184}
185
186#[cfg_attr(feature = "specta", derive(specta::Type))]
187#[derive(Serialize, Deserialize, Debug, Clone)]
188///  A game version of Minecraft
189pub struct Version {
190	/// The minecraft version ID
191	pub id: String,
192	/// Whether the release is stable or not
193	pub stable: bool,
194	/// A map that contains loader versions for the game version
195	pub loaders: Vec<LoaderVersion>,
196}
197
198#[cfg_attr(feature = "specta", derive(specta::Type))]
199#[derive(Serialize, Deserialize, Debug, Clone)]
200/// A version of a Minecraft mod loader
201pub struct LoaderVersion {
202	/// The version ID of the loader
203	pub id: String,
204	/// The URL of the version's manifest
205	pub url: String,
206	/// Whether the loader is stable or not
207	pub stable: bool,
208}