Skip to content

Commit ae0a6d0

Browse files
committedAug 6, 2024·
feat: add Forge support for old versions
1 parent 6b85abb commit ae0a6d0

File tree

7 files changed

+225
-70
lines changed

7 files changed

+225
-70
lines changed
 

‎crates/nomi-core/src/instance/launch.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::{
1818
markers::Undefined,
1919
repository::{
2020
java_runner::JavaRunner,
21-
manifest::{Manifest, VersionType},
21+
manifest::{Arguments, Manifest, VersionType},
2222
},
2323
};
2424

@@ -160,26 +160,37 @@ impl LaunchInstance {
160160
let manifest_jvm_arguments = arguments_builder.manifest_jvm_arguments();
161161
let manifest_game_arguments = arguments_builder.manifest_game_arguments();
162162

163+
dbg!(arguments_builder.classpath_as_slice());
164+
163165
let main_class = arguments_builder.get_main_class();
164166

165167
let loader_arguments = arguments_builder.loader_arguments();
166168

167169
let loader_jvm_arguments = loader_arguments.jvm_arguments();
168170
let loader_game_arguments = loader_arguments.game_arguments();
169171

170-
let mut child = Command::new(java_runner.get())
172+
let mut command = Command::new(java_runner.get());
173+
let command = command
171174
.args(custom_jvm_arguments)
172175
.args(loader_jvm_arguments)
173176
.args(manifest_jvm_arguments)
174177
.arg(main_class)
175178
.args(manifest_game_arguments)
176179
.args(loader_game_arguments)
177180
.stdout(Stdio::piped())
178-
.stderr(Stdio::piped())
179-
// Works incorrectly so let's ignore it for now.
180-
// It will work when the instances are implemented.
181-
// .current_dir(std::fs::canonicalize(MINECRAFT_DIR)?)
182-
.spawn()?;
181+
.stderr(Stdio::piped());
182+
// Works incorrectly so let's ignore it for now.
183+
// It will work when the instances are implemented.
184+
// .current_dir(std::fs::canonicalize(MINECRAFT_DIR)?)
185+
186+
// if matches!(manifest.arguments, Arguments::Old(_)) {
187+
// let mut cp = arguments_builder.classpath_as_str().to_string();
188+
// cp.push_str(CLASSPATH_SEPARATOR);
189+
// cp.push_str("./.nomi/launchwrapper-1.12.jar");
190+
// command.env("CLASSPATH", cp);
191+
// }
192+
193+
let mut child = command.spawn()?;
183194

184195
let stdout = child.stdout.take().expect("child did not have a handle to stdout");
185196
let stderr = child.stderr.take().expect("child did not have a handle to stdout");

‎crates/nomi-core/src/instance/launch/arguments.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use std::{marker::PhantomData, path::PathBuf};
22

33
use itertools::Itertools;
4+
use tracing::info;
45

56
use crate::{
67
instance::{
78
launch::{macros::replace, rules::is_library_passes},
89
profile::LoaderProfile,
910
},
1011
markers::Undefined,
12+
maven_data::MavenArtifact,
1113
repository::{
1214
manifest::{Argument, Arguments, Classifiers, DownloadFile, Manifest, Value},
1315
username::Username,
@@ -245,10 +247,26 @@ impl<'a, S, U> ArgumentsBuilder<'a, S, U> {
245247
.iter()
246248
.filter(|lib| is_library_passes(lib))
247249
.map(|lib| {
250+
let name = lib.name.as_str();
248251
(
249252
lib.downloads
250253
.artifact
251254
.as_ref()
255+
.filter(|_| {
256+
let Some(loader_profile) = self.instance.loader_profile() else {
257+
return true;
258+
};
259+
260+
!loader_profile.libraries.iter().any(|lib| {
261+
let value = lib.artifact.group == MavenArtifact::new(name).group;
262+
263+
if value {
264+
info!(vanilla = name, loader = %lib.artifact, "Found overlapping library. Using the one loader provides.");
265+
}
266+
267+
value
268+
})
269+
})
252270
.and_then(|artifact| artifact.path.as_ref())
253271
.map(|path| self.instance.settings.libraries_dir.join(path)),
254272
lib.downloads

‎crates/nomi-core/src/loaders/fabric.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
fs::write_to_file,
1717
game_paths::GamePaths,
1818
instance::profile::LoaderProfile,
19-
maven_data::MavenData,
19+
maven_data::{MavenArtifact, MavenData},
2020
repository::{
2121
fabric_meta::FabricVersions,
2222
fabric_profile::{FabricLibrary, FabricProfile},
@@ -99,7 +99,7 @@ impl Fabric {
9999
.profile
100100
.libraries
101101
.iter()
102-
.map(|l| MavenData::new(&l.name))
102+
.map(|l| MavenArtifact::new(&l.name))
103103
.map(SimpleLib::from)
104104
.collect_vec(),
105105
}

‎crates/nomi-core/src/loaders/forge.rs

+80-42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
collections::HashMap,
33
fmt::Debug,
4+
fs::File,
45
io::Read,
56
marker::PhantomData,
67
path::{Path, PathBuf},
@@ -10,7 +11,8 @@ use std::{
1011
use anyhow::{anyhow, bail};
1112
use itertools::Itertools;
1213
use serde::{Deserialize, Serialize};
13-
use tracing::{error, warn};
14+
use tokio::io::AsyncWriteExt;
15+
use tracing::{error, info, warn};
1416

1517
use crate::{
1618
configs::profile::Loader,
@@ -24,7 +26,7 @@ use crate::{
2426
instance::profile::LoaderProfile,
2527
loaders::vanilla::VanillaLibrariesMapper,
2628
markers::Undefined,
27-
maven_data::MavenData,
29+
maven_data::{MavenArtifact, MavenData},
2830
repository::{
2931
manifest::{Argument, Arguments, Library},
3032
simple_args::SimpleArgs,
@@ -34,12 +36,8 @@ use crate::{
3436
};
3537

3638
const FORGE_REPO_URL: &str = "https://maven.minecraftforge.net";
37-
const FORGE_GROUP: &str = "net.minecraftforge";
38-
const FORGE_ARTIFACT: &str = "forge";
3939

4040
const NEO_FORGE_REPO_URL: &str = "https://maven.neoforged.net/releases/";
41-
const NEO_FORGE_GROUP: &str = "net.neoforged";
42-
const NEO_FORGE_ARTIFACT: &str = "neoforge";
4341

4442
/// Some versions require to have a suffix
4543
const FORGE_SUFFIXES: &[(&str, &[&str])] = &[
@@ -61,6 +59,7 @@ pub struct Forge {
6159
downloader: LibrariesDownloader,
6260
game_version: String,
6361
forge_version: String,
62+
libraries_dir: PathBuf,
6463
}
6564

6665
impl Forge {
@@ -221,15 +220,14 @@ impl Forge {
221220

222221
impl LibrariesMapper<ForgeOldLibrary> for ForgeOldLibrariesMapper<'_> {
223222
fn proceed(&self, library: &ForgeOldLibrary) -> Option<FileDownloader> {
224-
let (name, url, is_required) = (library.name.as_str(), library.url.as_deref(), library.clientreq);
225-
226-
is_required
227-
.filter(|x| *x)
228-
.map(|_| name)
229-
.map(MavenData::new)
230-
.map(|m| (m.url, m.path))
231-
.and_then(|(url_part, path)| url.map(|u| format!("{u}{url_part}")).map(|url| (url, path)))
232-
.map(|(url, path)| FileDownloader::new(url, self.path.join(path)))
223+
let (name, url) = (library.name.as_str(), library.url.as_deref());
224+
225+
let maven_data = MavenData::new(name);
226+
let url = url.map_or(format!("https://libraries.minecraft.net/{}", maven_data.url), |url| {
227+
format!("{url}{}", &maven_data.url)
228+
});
229+
230+
Some(FileDownloader::new(url, self.path.join(&maven_data.path)))
233231
}
234232
}
235233

@@ -243,6 +241,7 @@ impl Forge {
243241
downloader,
244242
game_version,
245243
forge_version,
244+
libraries_dir: game_paths.libraries.clone(),
246245
})
247246
}
248247
}
@@ -264,7 +263,48 @@ impl Downloader for Forge {
264263
}
265264

266265
fn io(&self) -> PinnedFutureWithBounds<anyhow::Result<()>> {
267-
Box::pin(async { Ok(()) })
266+
struct ForgeLibraryExtractionData {
267+
library_path: String,
268+
target_path: PathBuf,
269+
}
270+
271+
async fn inner(installer_path: PathBuf, libraries_dir: PathBuf, lib_data: Option<ForgeLibraryExtractionData>) -> anyhow::Result<()> {
272+
info!("Applying Forge IO");
273+
if let Some(data) = lib_data {
274+
info!("Extracting {}", &data.library_path);
275+
let file = tokio::fs::File::open(installer_path).await?;
276+
let mut archive = zip::ZipArchive::new(file.into_std().await)?;
277+
let mut library_bytes = Vec::new();
278+
279+
// If it's not in it's own scope then the future cannot be send between thread safely
280+
{
281+
let mut library = archive.by_name(&data.library_path)?;
282+
library.read_to_end(&mut library_bytes)?;
283+
}
284+
285+
let target_path = libraries_dir.join(data.target_path);
286+
287+
if let Some(parent) = target_path.parent().filter(|p| !p.exists()) {
288+
tokio::fs::create_dir_all(parent).await?;
289+
}
290+
291+
let mut target = tokio::fs::File::create(target_path).await?;
292+
target.write_all(library_bytes.as_slice()).await?;
293+
}
294+
295+
Ok(())
296+
}
297+
298+
let extraction = match &self.profile {
299+
ForgeProfile::Old(old) => Some(ForgeLibraryExtractionData {
300+
library_path: old.install.file_path.clone(),
301+
target_path: MavenData::new(&old.install.path).path,
302+
}),
303+
ForgeProfile::New(_) => None,
304+
};
305+
306+
let (installer_path, libraries_dir) = (self.installer_path(), self.libraries_dir.clone());
307+
Box::pin(inner(installer_path, libraries_dir, extraction))
268308
}
269309
}
270310

@@ -324,7 +364,7 @@ impl ForgeProfile {
324364
args.iter()
325365
.filter_map(|a| match a {
326366
Argument::String(s) => Some(s),
327-
_ => None,
367+
Argument::Struct { .. } => None,
328368
})
329369
.cloned()
330370
.collect_vec()
@@ -341,7 +381,7 @@ impl ForgeProfile {
341381
warn!("Cannot find `--tweakClass` parameter in the Forge arguments list. Game might not launch.");
342382
Vec::new()
343383
},
344-
|(_, val)| vec!["--tweakClass", val].into_iter().map(String::from).collect_vec(),
384+
|(_, val)| vec!["--tweakClass", val.trim()].into_iter().map(String::from).collect_vec(),
345385
),
346386
jvm: Vec::new(),
347387
},
@@ -354,25 +394,23 @@ impl ForgeProfile {
354394
.libraries
355395
.iter()
356396
.map(|lib| SimpleLib {
357-
jar: lib
358-
.downloads
359-
.artifact
360-
.as_ref()
361-
.and_then(|a| a.path.as_ref())
362-
.map(PathBuf::from)
363-
.unwrap_or_else(|| {
397+
jar: lib.downloads.artifact.as_ref().and_then(|a| a.path.as_ref()).map_or_else(
398+
|| {
364399
warn!("Forge library does not have path. Game might not launch.");
365400
PathBuf::new()
366-
}),
401+
},
402+
PathBuf::from,
403+
),
404+
artifact: MavenArtifact::new(&lib.name),
367405
})
368406
.collect_vec(),
369407
ForgeProfile::Old(old) => old
370408
.version_info
371409
.libraries
372410
.iter()
373-
.filter(|lib| lib.clientreq.is_some_and(|required| required))
411+
// .filter(|lib| lib.clientreq.is_some_and(|required| required))
374412
.map(|lib| lib.name.as_str())
375-
.map(MavenData::new)
413+
.map(MavenArtifact::new)
376414
.map(SimpleLib::from)
377415
.collect_vec(),
378416
}
@@ -402,23 +440,23 @@ pub struct Logging {}
402440
#[derive(Serialize, Deserialize, Debug)]
403441
#[serde(rename_all = "camelCase")]
404442
pub struct ForgeProfileOld {
405-
// pub install: Install,
443+
pub install: Install,
406444
pub version_info: VersionInfo,
407445
}
408446

409-
// #[derive(Serialize, Deserialize, Debug)]
410-
// #[serde(rename_all = "camelCase")]
411-
// pub struct Install {
412-
// pub profile_name: String,
413-
// pub target: String,
414-
// pub path: String,
415-
// pub version: String,
416-
// pub file_path: String,
417-
// pub welcome: String,
418-
// pub minecraft: String,
419-
// pub mirror_list: String,
420-
// pub logo: String,
421-
// }
447+
#[derive(Serialize, Deserialize, Debug)]
448+
#[serde(rename_all = "camelCase")]
449+
pub struct Install {
450+
// pub profile_name: String,
451+
// pub target: String,
452+
pub path: String,
453+
// pub version: String,
454+
pub file_path: String,
455+
// pub welcome: String,
456+
// pub minecraft: String,
457+
// pub mirror_list: String,
458+
// pub logo: String,
459+
}
422460

423461
#[derive(Serialize, Deserialize, Debug)]
424462
#[serde(rename_all = "camelCase")]

‎crates/nomi-core/src/maven_data.rs

+77-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
use std::path::PathBuf;
1+
use std::{fmt::Display, path::PathBuf};
22

33
use itertools::Itertools;
4+
use regex::Regex;
5+
use serde::{Deserialize, Serialize};
6+
use tracing::{error, warn};
47

58
#[derive(Debug, Default)]
69
pub struct MavenData {
710
pub url: String,
811
pub path: PathBuf,
9-
pub file: String,
12+
pub file_name: String,
1013
}
1114

1215
impl MavenData {
@@ -27,9 +30,69 @@ impl MavenData {
2730
Self {
2831
path: PathBuf::from(&path),
2932
url: urlencoding::encode(&path).into_owned(),
30-
file: name,
33+
file_name: name,
3134
}
3235
}
36+
37+
#[must_use]
38+
pub fn from_artifact_data(artifact: &MavenArtifact) -> Self {
39+
let group_parts = artifact.group.split('.').collect_vec();
40+
let file_name = format!("{}-{}.jar", &artifact.artifact, &artifact.version);
41+
42+
let path = group_parts
43+
.into_iter()
44+
.chain([artifact.artifact.as_str(), artifact.version.as_str(), file_name.as_str()])
45+
.join("/");
46+
47+
let url = urlencoding::encode(&path).into_owned();
48+
49+
Self {
50+
url,
51+
path: PathBuf::from(path),
52+
file_name,
53+
}
54+
}
55+
}
56+
57+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
58+
pub struct MavenArtifact {
59+
pub group: String,
60+
pub artifact: String,
61+
pub version: String,
62+
}
63+
64+
impl MavenArtifact {
65+
#[must_use]
66+
#[allow(clippy::missing_panics_doc)]
67+
pub fn new(artifact: &str) -> Self {
68+
// PANICS: This will never panic because the pattern is valid.
69+
let regex = Regex::new(r"(?P<group>.*):(?P<artifact>.*):(?P<version>.*)").unwrap();
70+
regex.captures(artifact).map_or_else(
71+
|| {
72+
error!(artifact, "No values captured. Using provided artifact as a group");
73+
MavenArtifact {
74+
group: artifact.to_string(),
75+
artifact: String::new(),
76+
version: String::new(),
77+
}
78+
},
79+
|captures| {
80+
let get_group = |name| captures.name(name).map_or(String::default(), |v| String::from(v.as_str()));
81+
82+
let group = get_group("group");
83+
let artifact = get_group("artifact");
84+
let version = get_group("version");
85+
86+
MavenArtifact { group, artifact, version }
87+
},
88+
)
89+
}
90+
}
91+
92+
impl Display for MavenArtifact {
93+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94+
f.write_fmt(format_args!("{}:{}:{}", self.group, self.artifact, self.version))
95+
}
3396
}
3497

3598
#[cfg(test)]
@@ -40,11 +103,21 @@ mod tests {
40103

41104
use super::*;
42105

106+
#[test]
107+
fn maven_artifact_parse_test() {
108+
let artifact = MavenArtifact::new("net.fabricmc:fabric-loader:0.14.22");
109+
110+
println!("{:#?}", artifact);
111+
}
112+
43113
#[test]
44114
fn parse_test() {
45115
let artifact = "net.fabricmc:fabric-loader:0.14.22";
46116

47-
let maven = MavenData::new(artifact);
117+
let maven = MavenData::from_artifact_data(&MavenArtifact::new(artifact));
118+
119+
assert_eq!(maven.path, PathBuf::from("net/fabricmc/fabric-loader/0.14.22/fabric-loader-0.14.22.jar"));
120+
48121
println!("{maven:#?}");
49122
}
50123

‎crates/nomi-core/src/repository/simple_lib.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ use std::path::PathBuf;
22

33
use serde::{Deserialize, Serialize};
44

5-
use crate::maven_data::MavenData;
5+
use crate::maven_data::{MavenArtifact, MavenData};
66

77
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
88
pub struct SimpleLib {
9+
pub artifact: MavenArtifact,
910
pub jar: PathBuf,
1011
}
1112

12-
impl From<MavenData> for SimpleLib {
13-
fn from(value: MavenData) -> Self {
14-
Self { jar: value.path }
13+
impl From<MavenArtifact> for SimpleLib {
14+
fn from(value: MavenArtifact) -> Self {
15+
let maven_data = MavenData::from_artifact_data(&value);
16+
Self {
17+
jar: maven_data.path,
18+
artifact: value,
19+
}
1520
}
1621
}

‎crates/nomi-core/tests/forge_test.rs

+21-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use nomi_core::{
1818
};
1919

2020
#[tokio::test]
21-
async fn full_fabric_test() {
21+
async fn forge_test() {
2222
let _guard = tracing::subscriber::set_default(tracing_subscriber::fmt().finish());
2323

2424
let current = std::env::current_dir().unwrap();
@@ -32,14 +32,15 @@ async fn full_fabric_test() {
3232

3333
let instance = Instance::builder()
3434
.name("forge-test".into())
35-
.version("1.20.1".into())
35+
.version("1.7.10".into())
3636
.game_paths(game_paths.clone())
37-
.instance(Box::new(Forge::new("1.20.1", ForgeVersion::Recommended, &game_paths).await.unwrap()))
37+
.instance(Box::new(Forge::new("1.7.10", ForgeVersion::Recommended, &game_paths).await.unwrap()))
38+
// .instance(Box::new(Vanilla::new("1.7.10", game_paths.clone()).await.unwrap()))
3839
.build();
3940

4041
let mc_dir = current.join("minecraft");
4142

42-
// let vanilla = Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap());
43+
// let vanilla = Box::new(Vanilla::new("1.7.10", game_paths.clone()).await.unwrap());
4344
// let io = vanilla.io();
4445

4546
// vanilla.download(&tx).await;
@@ -51,23 +52,26 @@ async fn full_fabric_test() {
5152
game_dir: mc_dir.clone(),
5253
java_bin: JavaRunner::default(),
5354
libraries_dir: mc_dir.clone().join("libraries"),
54-
manifest_file: mc_dir.clone().join("versions/forge-test/1.20.1.json"),
55+
manifest_file: mc_dir.clone().join("versions/forge-test/1.7.10.json"),
5556
natives_dir: mc_dir.clone().join("versions/forge-test/natives"),
56-
version_jar_file: mc_dir.join("versions/forge-test/1.20.1.jar"),
57-
version: "1.20.1".to_string(),
57+
version_jar_file: mc_dir.join("versions/forge-test/1.7.10.jar"),
58+
version: "1.7.10".to_string(),
5859
version_type: nomi_core::repository::manifest::VersionType::Release,
5960
};
6061

6162
let launch = instance.launch_instance(settings, None);
6263

63-
Box::new(instance.assets().await.unwrap()).download(&tx).await;
64+
let assets = instance.assets().await.unwrap();
65+
let assets_io = assets.io();
66+
Box::new(assets).download(&tx).await;
67+
assets_io.await.unwrap();
6468

6569
let instance = instance.instance();
66-
let ui_fut = instance.io();
70+
let io_fut = instance.io();
6771

6872
instance.download(&tx).await;
6973

70-
ui_fut.await.unwrap();
74+
io_fut.await.unwrap();
7175

7276
let profile = VersionProfile::builder()
7377
.id(1)
@@ -76,7 +80,13 @@ async fn full_fabric_test() {
7680
.build();
7781

7882
dbg!(profile)
79-
.launch(UserData::default(), &JavaRunner::default(), &PrintLogs)
83+
.launch(
84+
UserData::default(),
85+
&JavaRunner::path(PathBuf::from(
86+
"E:/programming/code/nomi/crates/nomi-core/.nomi/java/jdk8u422-b05/bin/javaw.exe",
87+
)),
88+
&PrintLogs,
89+
)
8090
.await
8191
.unwrap();
8292
}

0 commit comments

Comments
 (0)
Please sign in to comment.