1use crate::api::minecraft::{Argument, ArgumentType, Library, VersionInfo, VersionType};
2use crate::utils::prelude::*;
3
4pub const CURRENT_FABRIC_FORMAT_VERSION: usize = 0;
6pub const CURRENT_FORGE_FORMAT_VERSION: usize = 0;
8pub const CURRENT_QUILT_FORMAT_VERSION: usize = 0;
10pub const CURRENT_NEOFORGE_FORMAT_VERSION: usize = 0;
12pub const CURRENT_LEGACY_FABRIC_FORMAT_VERSION: usize = 0;
14pub const CURRENT_CLEANROOM_FORMAT_VERSION: usize = 0;
16
17pub const DUMMY_REPLACE_STRING: &str = "${interpulse.gameVersion}";
19
20#[cfg_attr(feature = "specta", derive(specta::Type))]
22#[derive(Serialize, Deserialize, Debug, Clone)]
23pub struct SidedDataEntry {
24 pub client: String,
26 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")]
44pub struct PartialVersionInfo {
46 pub id: String,
48 pub inherits_from: String,
50 #[serde(deserialize_with = "deserialize_date")]
52 pub release_time: DateTime<Utc>,
53 #[serde(deserialize_with = "deserialize_date")]
55 pub time: DateTime<Utc>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub main_class: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub minecraft_arguments: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub arguments: Option<HashMap<ArgumentType, Vec<Argument>>>,
65 pub libraries: Vec<Library>,
67 #[serde(rename = "type")]
68 pub type_: VersionType,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub data: Option<HashMap<String, SidedDataEntry>>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub processors: Option<Vec<Processor>>,
76}
77
78#[cfg_attr(feature = "specta", derive(specta::Type))]
80#[derive(Serialize, Deserialize, Debug, Clone)]
81pub struct Processor {
82 pub jar: String,
84 pub classpath: Vec<String>,
86 pub args: Vec<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub outputs: Option<HashMap<String, String>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub sides: Option<Vec<String>>,
95}
96
97#[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")]
180pub struct Manifest {
182 pub game_versions: Vec<Version>,
184}
185
186#[cfg_attr(feature = "specta", derive(specta::Type))]
187#[derive(Serialize, Deserialize, Debug, Clone)]
188pub struct Version {
190 pub id: String,
192 pub stable: bool,
194 pub loaders: Vec<LoaderVersion>,
196}
197
198#[cfg_attr(feature = "specta", derive(specta::Type))]
199#[derive(Serialize, Deserialize, Debug, Clone)]
200pub struct LoaderVersion {
202 pub id: String,
204 pub url: String,
206 pub stable: bool,
208}