From f6f0d08bf15b57d7411faaa0cae49e814c46f6f5 Mon Sep 17 00:00:00 2001 From: bsuu Date: Wed, 21 Aug 2024 17:45:20 +0200 Subject: [PATCH 1/4] core changes --- .gitignore | 2 + core/Cargo.toml | 1 + core/bindings/ErrorKind.ts | 2 +- core/bindings/HandlerGameType.ts | 2 +- core/bindings/MinecraftVariant.ts | 2 +- core/bindings/UserPermission.ts | 2 +- core/src/handlers/instance_setup_configs.rs | 4 + .../implementations/minecraft/configurable.rs | 6 + core/src/implementations/minecraft/mod.rs | 60 ++++- .../src/implementations/minecraft/neoforge.rs | 233 ++++++++++++++++++ core/src/implementations/minecraft/server.rs | 37 +++ core/src/implementations/minecraft/util.rs | 59 ++++- .../src/implementations/minecraft/versions.rs | 16 ++ core/src/traits/t_configurable/mod.rs | 4 + core/test.db | Bin 12288 -> 12288 bytes dashboard/src/bindings/MinecraftVariant.ts | 8 +- 16 files changed, 425 insertions(+), 13 deletions(-) create mode 100644 core/src/implementations/minecraft/neoforge.rs diff --git a/.gitignore b/.gitignore index de934ef4..610d3132 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +.idea/ \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index 0e6a3784..8d41029d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -98,6 +98,7 @@ hex = "0.4.3" toml = "0.7.4" which = "5.0.0" bollard = "*" +regex = "1.7.2" [dependencies.uuid] version = "1.1.2" features = [ diff --git a/core/bindings/ErrorKind.ts b/core/bindings/ErrorKind.ts index c32e3fbc..76143660 100644 --- a/core/bindings/ErrorKind.ts +++ b/core/bindings/ErrorKind.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ErrorKind = "NotFound" | "UnsupportedOperation" | "BadRequest" | "PermissionDenied" | "Unauthorized" | "Internal"; \ No newline at end of file +export type ErrorKind = "NotFound" | "UnsupportedOperation" | "BadRequest" | "PermissionDenied" | "Unauthorized" | "External" | "Internal"; \ No newline at end of file diff --git a/core/bindings/HandlerGameType.ts b/core/bindings/HandlerGameType.ts index 1ba3a66e..bf5f7177 100644 --- a/core/bindings/HandlerGameType.ts +++ b/core/bindings/HandlerGameType.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type HandlerGameType = "MinecraftJavaVanilla" | "MinecraftFabric" | "MinecraftForge" | "MinecraftPaper" | "MinecraftBedrock"; \ No newline at end of file +export type HandlerGameType = "MinecraftJavaVanilla" | "MinecraftFabric" | "MinecraftForge" | "MinecraftPaper" | "MinecraftBedrock" | "MinecraftNeoforge"; \ No newline at end of file diff --git a/core/bindings/MinecraftVariant.ts b/core/bindings/MinecraftVariant.ts index a12515ab..96be219d 100644 --- a/core/bindings/MinecraftVariant.ts +++ b/core/bindings/MinecraftVariant.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type MinecraftVariant = { "type": "Vanilla" } | { "type": "Forge" } | { "type": "Fabric" } | { "type": "Paper" } | { "type": "Spigot" } | { "type": "Other", name: string, }; \ No newline at end of file +export type MinecraftVariant = { "type": "Vanilla" } | { "type": "Forge" } | { "type": "Fabric" } | { "type": "Paper" } | { "type": "Spigot" } | { "type": "Neoforge" } | { "type": "Other", name: string, }; \ No newline at end of file diff --git a/core/bindings/UserPermission.ts b/core/bindings/UserPermission.ts index ce7874d1..2c8b0cc4 100644 --- a/core/bindings/UserPermission.ts +++ b/core/bindings/UserPermission.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { InstanceUuid } from "./InstanceUuid"; -export interface UserPermission { can_view_instance: Array, can_start_instance: Array, can_stop_instance: Array, can_access_instance_console: Array, can_access_instance_setting: Array, can_read_instance_resource: Array, can_write_instance_resource: Array, can_access_instance_macro: Array, can_read_instance_file: Array, can_write_instance_file: Array, can_create_instance: boolean, can_delete_instance: boolean, can_read_global_file: boolean, can_write_global_file: boolean, can_manage_permission: boolean, } \ No newline at end of file +export interface UserPermission { can_view_instance: Array, can_start_instance: Array, can_stop_instance: Array, can_access_instance_console: Array, can_access_instance_setting: Array, can_read_instance_resource: Array, can_write_instance_resource: Array, can_access_instance_macro: Array, can_read_instance_file: Array, can_write_instance_file: Array, can_create_instance: boolean, can_delete_instance: boolean, can_read_global_file: boolean, can_write_global_file: boolean, can_manage_permission: boolean, can_install_extension: boolean, } \ No newline at end of file diff --git a/core/src/handlers/instance_setup_configs.rs b/core/src/handlers/instance_setup_configs.rs index 1174a32d..900d8629 100644 --- a/core/src/handlers/instance_setup_configs.rs +++ b/core/src/handlers/instance_setup_configs.rs @@ -25,6 +25,7 @@ pub enum HandlerGameType { MinecraftForge, MinecraftPaper, MinecraftBedrock, + MinecraftNeoforge, } impl From for GameType { @@ -35,6 +36,7 @@ impl From for GameType { HandlerGameType::MinecraftForge => Self::MinecraftJava, HandlerGameType::MinecraftPaper => Self::MinecraftJava, HandlerGameType::MinecraftBedrock => Self::MinecraftBedrock, + HandlerGameType::MinecraftNeoforge => Self::MinecraftJava, } } } @@ -48,6 +50,7 @@ impl TryFrom for FlavourKind { HandlerGameType::MinecraftFabric => Self::Fabric, HandlerGameType::MinecraftForge => Self::Forge, HandlerGameType::MinecraftPaper => Self::Paper, + HandlerGameType::MinecraftNeoforge => Self::Neoforge, HandlerGameType::MinecraftBedrock => { return Err(Error { kind: ErrorKind::BadRequest, @@ -64,6 +67,7 @@ pub async fn get_available_games() -> Json> { HandlerGameType::MinecraftFabric, HandlerGameType::MinecraftForge, HandlerGameType::MinecraftPaper, + HandlerGameType::MinecraftNeoforge, ]) } diff --git a/core/src/implementations/minecraft/configurable.rs b/core/src/implementations/minecraft/configurable.rs index 9f27a800..56fbb580 100644 --- a/core/src/implementations/minecraft/configurable.rs +++ b/core/src/implementations/minecraft/configurable.rs @@ -154,6 +154,12 @@ impl TConfigurable for MinecraftInstance { kind: ErrorKind::UnsupportedOperation, source: eyre!("Changing versions is unsupported for forge servers"), }) + }, + super::Flavour::Neoforge { .. } => { + return Err(Error { + kind: ErrorKind::UnsupportedOperation, + source: eyre!("Changing versions is unsupported for forge servers"), //TODO + }) } }; let lodestone_tmp = path_to_tmp().clone(); diff --git a/core/src/implementations/minecraft/mod.rs b/core/src/implementations/minecraft/mod.rs index 72d36a52..d422d070 100644 --- a/core/src/implementations/minecraft/mod.rs +++ b/core/src/implementations/minecraft/mod.rs @@ -10,6 +10,7 @@ pub mod server; pub mod util; mod vanilla; pub mod versions; +mod neoforge; use color_eyre::eyre::{eyre, Context, ContextCompat}; use enum_kinds::EnumKind; @@ -33,9 +34,12 @@ use tracing::error; use tokio; use ts_rs::TS; +use regex::Regex; + use crate::error::Error; use crate::event_broadcaster::EventBroadcaster; use crate::events::{Event, ProgressionEventID}; +use crate::implementations::minecraft::neoforge::{get_neoforge_minecraft_versions, NeoforgeVersion}; use crate::macro_executor::{MacroExecutor, MacroPID}; use crate::prelude::path_to_binaries; use crate::traits::t_configurable::PathBuf; @@ -92,6 +96,9 @@ pub enum Flavour { Forge { build_version: Option, }, + Neoforge { + build_version: Option, + } } impl From for Flavour { @@ -109,6 +116,9 @@ impl From for Flavour { FlavourKind::Forge => Flavour::Forge { build_version: None, }, + FlavourKind::Neoforge => Flavour::Neoforge { + build_version: None, + } } } } @@ -121,6 +131,7 @@ impl ToString for Flavour { Flavour::Paper { .. } => "paper".to_string(), Flavour::Spigot => "spigot".to_string(), Flavour::Forge { .. } => "forge".to_string(), + Flavour::Neoforge { .. } => "neoforge".to_string(), } } } @@ -133,6 +144,7 @@ impl ToString for FlavourKind { FlavourKind::Paper => "paper".to_string(), FlavourKind::Spigot => "spigot".to_string(), FlavourKind::Forge => "forge".to_string(), + FlavourKind::Neoforge => "neoforge".to_string(), } } } @@ -217,6 +229,7 @@ impl MinecraftInstance { FlavourKind::Paper => get_paper_minecraft_versions().await, FlavourKind::Spigot => todo!(), FlavourKind::Forge => get_forge_minecraft_versions().await, + FlavourKind::Neoforge => get_neoforge_minecraft_versions().await, } .context("Failed to get minecraft versions")?; @@ -463,8 +476,16 @@ impl MinecraftInstance { e })?; + let version_regex = Regex::new(r"^1\.\d+\.\d*0$").unwrap(); + + let version = if version_regex.is_match(config.version.as_str()) { + &config.version[..config.version.len() - 2] + } else { + config.version.as_str() + }; + // Step 2: Download JRE - let (url, jre_major_version) = get_jre_url(config.version.as_str()) + let (url, jre_major_version) = get_jre_url(version) .await .context("Could not get JRE URL")?; if !path_to_runtimes @@ -547,6 +568,7 @@ impl MinecraftInstance { })?; let jar_name = match flavour { Flavour::Forge { .. } => "forge-installer.jar", + Flavour::Neoforge { .. } => "neoforge-installer.jar", _ => "server.jar", }; @@ -631,6 +653,42 @@ impl MinecraftInstance { .context("Could not create user_jvm_args.txt")?; } + if let Flavour::Neoforge { .. } = flavour.clone() { + event_broadcaster.send(Event::new_progression_event_update( + progression_event_id, + "3/4: Installing Neoforge Server", + 1.0, + )); + + if !dont_spawn_terminal( + Command::new(&jre) + .arg("-jar") + .arg(&path_to_instance.join("neoforge-installer.jar")) + .arg("--installServer") + .arg(&path_to_instance) + .current_dir(&path_to_instance), + ) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .context("Failed to start neoforge-installer.jar")? + .wait() + .await + .context("neoforge-installer.jar failed")? + .success() + { + return Err(eyre!("Failed to install neoforge server").into()); + } + + tokio::fs::write( + &path_to_instance.join("user_jvm_args.txt"), + "# Generated by Lodestone\n# This file is ignored by Lodestone\n# Please set arguments using Lodestone", + ) + .await + .context("Could not create user_jvm_args.txt")?; + } + // Step 4: Finishing Up event_broadcaster.send(Event::new_progression_event_update( progression_event_id, diff --git a/core/src/implementations/minecraft/neoforge.rs b/core/src/implementations/minecraft/neoforge.rs new file mode 100644 index 00000000..95763a04 --- /dev/null +++ b/core/src/implementations/minecraft/neoforge.rs @@ -0,0 +1,233 @@ +use std::fmt; +use std::fmt::Formatter; +use std::str::FromStr; +use color_eyre::eyre::{eyre, Context}; +use enum_kinds::EnumKind; +use serde_json::Value; +use ts_rs::TS; +use crate::error::Error; +use crate::traits::t_configurable::{Deserialize, Serialize}; + +pub async fn get_neoforge_minecraft_versions() -> Result, Error> { + let versions = request_neoforge_versions().await?; + let mut minecraft_versions: Vec = versions + .iter() + .map(|version| { + format!("1.{}.{}", version.major, version.minor) + }) + .collect(); + + minecraft_versions.dedup(); + minecraft_versions.sort(); + + Ok(minecraft_versions) +} + +pub async fn get_neoforge_builds(minecraft_version: Option<&str>) -> Result, Error> { + let versions = request_neoforge_versions().await?; + let build_versions = versions + .iter() + .filter(|version| { + minecraft_version.is_none() || format!("1.{}.{}", version.major, version.minor) == minecraft_version.clone().unwrap() + }) + .cloned() + .collect::>(); + + Ok(build_versions) +} + +pub async fn get_neoforge_latest_build(minecraft_version: Option<&str>) -> Result { + let version_builds = get_neoforge_builds(minecraft_version).await?; + + let latest_version = version_builds + .iter() + .max_by_key(|v| v.patch()) + .expect("Failed to find latest Neoforge version."); + + Ok(latest_version.clone()) +} + +async fn request_neoforge_versions() -> Result, Error> { + let http = reqwest::Client::new(); + + let legacy_response: Value = serde_json::from_str( + http.get("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge") + .send() + .await + .context("Failed to get legacy neoforge versions")? + .text() + .await + .context("Failed to get legacy neoforge versions")? + .as_str() + ).context("Failed to get legacy neoforge versions")?; + + let legacy_versions = legacy_response["versions"] + .as_array() + .ok_or_else(|| eyre!("Failed to get legacy neoforge versions. Version array is not an array"))? + .iter() + .filter(|v| v.as_str().unwrap().contains("-")) + .map(|v| { + NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap() + }) + .collect::>(); + + let response: Value = serde_json::from_str( + http.get("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge") + .send() + .await + .context("Failed to get neoforge versions")? + .text() + .await + .context("Failed to get neoforge versions")? + .as_str(), + ).context("Failed to get neoforge versions")?; + + let main_versions = response["versions"] + .as_array() + .ok_or_else(|| eyre!("Failed to get neoforge versions. Versions array is not an array"))? + .iter() + .map(|v| { + NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap() + }) + .collect::>(); + + let versions = [main_versions, legacy_versions].concat(); + Ok(versions) +} + +fn split_neoforge_version(version: &str) -> Result<(String, String, (String, Option)), VersionError> { + let mut split = version.split('.'); + + let major_version = split.next().ok_or(VersionError::InvalidFormat)?.to_string(); + let minor_version = split.next().ok_or(VersionError::InvalidFormat)?.to_string(); + let patch_version = split.next().ok_or(VersionError::InvalidFormat)?.to_string(); + + let (patch, channel) = if let Some((patch, channel)) = patch_version.split_once('-') { + (patch.to_string(), Some(channel.to_string())) + } else { + (patch_version, None) + }; + + Ok((major_version, minor_version, (patch, channel))) +} + +#[derive(Debug, Serialize, Deserialize, TS, Clone)] +#[ts(export)] +pub struct NeoforgeVersion { + pub legacy: bool, + pub major: i32, + pub minor: i32, + pub patch: String, +} + +impl NeoforgeVersion { + pub fn new(major: i32, minor: i32, patch: String, legacy: bool) -> NeoforgeVersion { + NeoforgeVersion { + major, + minor, + patch, + legacy, + } + } +} + +impl FromStr for NeoforgeVersion { + type Err = VersionError; + + fn from_str(value: &str) -> Result { + let is_legacy = value.starts_with('1'); + let value = if is_legacy { + value.replace("1.", "").replace("-", ".").replace("47.", "") + } else { + value.to_string() + }; + + let (major_str, minor_str, (patch_str, _)) = split_neoforge_version(&value)?; + + let major = major_str.parse::().map_err(|_| VersionError::InvalidNumber)?; + let minor = minor_str.parse::().map_err(|_| VersionError::InvalidNumber)?; + + Ok(NeoforgeVersion::new(major, minor, patch_str, is_legacy)) + } +} + +impl PartialEq for NeoforgeVersion { + fn eq(&self, other: &Self) -> bool { + self.major == other.major && self.legacy == other.legacy && self.minor == other.minor && self.patch == other.patch + } +} + +impl NeoforgeVersion { + pub fn installer_url(&self) -> String { + if self.legacy { + return format!("https://maven.neoforged.net/releases/net/neoforged/forge/{}/forge-{}-installer.jar", self.version(), self.version()); + } + format!("https://maven.neoforged.net/releases/net/neoforged/neoforge/{}/neoforge-{}-installer.jar", self.version(), self.version()) + } + + pub fn version(&self) -> String { + if self.legacy { + return format!("1.{}.{}-47.1.{}", self.major, self.minor, self.patch()); + } + format!("{}.{}.{}", self.major, self.minor, self.patch()) + } + + pub fn patch(&self) -> i32 { + let patch_str = if self.legacy { + self.patch.replace("47.", "") + } else { + self.patch.clone() + }; + patch_str.parse::().unwrap_or(0) + } +} + +#[derive(Debug, TS)] +pub enum VersionError { + InvalidFormat, + InvalidNumber +} + +impl fmt::Display for VersionError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + VersionError::InvalidFormat => write!(f, "Invalid version format."), + VersionError::InvalidNumber => write!(f, "Invalid number in version."), + _ => write!(f, "How did we get here?") + } + } +} + +impl std::error::Error for VersionError {} +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn test_get_neoforge_minecraft_versions() { + let versions = get_neoforge_minecraft_versions().await.unwrap(); + + assert!(versions.contains(&"1.20.1".to_string())); + assert!(versions.contains(&"1.20.2".to_string())); + assert!(versions.contains(&"1.21.0".to_string())); + assert_eq!(versions.contains(&"1.20.2asd".to_string()), false); + } + + #[tokio::test] + async fn test_get_neoforge_latest_build() { + assert_eq!( + get_neoforge_latest_build(Some("1.20.2")).await.unwrap().version(), + "20.2.88".to_string() + ); + assert_eq!( + get_neoforge_latest_build(Some("1.20.1")).await.unwrap().version(), + "1.20.1-47.1.106".to_string() + ); + } + + #[tokio::test] + async fn test_get_neoforge_build_versions() { + let versions = get_neoforge_builds(Some("1.20.2")).await.unwrap(); + assert!(versions.contains(&NeoforgeVersion::from_str("20.2.88").unwrap())); + } +} \ No newline at end of file diff --git a/core/src/implementations/minecraft/server.rs b/core/src/implementations/minecraft/server.rs index 5194a990..532aa0e0 100644 --- a/core/src/implementations/minecraft/server.rs +++ b/core/src/implementations/minecraft/server.rs @@ -195,6 +195,43 @@ impl TServer for MinecraftInstance { .arg(&self.path_to_instance.join(server_jar_name)) } } + + Flavour::Neoforge { build_version } => { + let neoforge_build_version = build_version + .as_ref() + .ok_or_else(|| eyre!("Neoforge version not found"))?; + + let neoforge_args = match std::env::consts::OS { + "windows" => "win_args.txt", + _ => "unix_args.txt", + }; + + let mut full_neoforge_args = std::ffi::OsString::from("@"); + + let nf_path_buf = if neoforge_build_version.legacy { + let path = self.path_to_instance + .join("libraries") + .join("net") + .join("neoforged") + .join("forge") + .join(neoforge_build_version.version()) + .join(neoforge_args); + path.into_os_string().as_os_str().to_os_string() + } else { + let path = self.path_to_instance + .join("libraries") + .join("net") + .join("neoforged") + .join("neoforge") + .join(neoforge_build_version.version()) + .join(neoforge_args); + path.into_os_string().as_os_str().to_os_string() + }; + + full_neoforge_args.push(nf_path_buf); + server_start_command.arg(full_neoforge_args) + } + _ => server_start_command .arg("-jar") .arg(&self.path_to_instance.join("server.jar")), diff --git a/core/src/implementations/minecraft/util.rs b/core/src/implementations/minecraft/util.rs index 052c4f0a..06d8dc33 100644 --- a/core/src/implementations/minecraft/util.rs +++ b/core/src/implementations/minecraft/util.rs @@ -2,12 +2,14 @@ use color_eyre::eyre::{eyre, Context, ContextCompat}; use indexmap::IndexMap; use serde_json::{self, Value}; use std::{collections::BTreeMap, path::Path, str::FromStr}; +use semver::Op; use tokio::io::AsyncBufReadExt; use super::{ FabricInstallerVersion, FabricLoaderVersion, Flavour, ForgeBuildVersion, PaperBuildVersion, }; use crate::error::Error; +use crate::implementations::minecraft::neoforge::{get_neoforge_latest_build, NeoforgeVersion}; pub async fn read_properties_from_path( path_to_properties: &Path, @@ -59,11 +61,26 @@ pub async fn get_server_jar_url(version: &str, flavour: &Flavour) -> Option<(Str installer_version, } => get_fabric_jar_url(version, loader_version, installer_version).await, Flavour::Paper { build_version } => get_paper_jar_url(version, build_version).await, - Flavour::Spigot => todo!(), + Flavour::Neoforge { build_version } => get_neoforge_jar_url(version, build_version).await, Flavour::Forge { build_version } => get_forge_jar_url(version, build_version).await.ok(), + Flavour::Spigot => todo!(), } } +pub async fn get_neoforge_jar_url(version: &str, neoforge_build_version: &Option) -> Option<(String, Flavour)> { + let latest_build = get_neoforge_latest_build(Some(version)).await.ok()?; + + let build = neoforge_build_version + .as_ref() + .unwrap_or(&latest_build); + + Some(( + build.installer_url(), + Flavour::Neoforge { build_version: Some(build.clone())} + )) + +} + pub async fn get_vanilla_jar_url(version: &str) -> Option<(String, Flavour)> { let client = reqwest::Client::new(); let response_text = client @@ -442,11 +459,13 @@ pub async fn name_to_uuid(name: impl AsRef) -> Option { #[cfg(test)] mod tests { + use std::str::FromStr; use crate::minecraft::{ util::{get_forge_jar_url, get_server_jar_url}, FabricInstallerVersion, FabricLoaderVersion, Flavour, ForgeBuildVersion, PaperBuildVersion, }; use tokio; + use crate::implementations::minecraft::neoforge::NeoforgeVersion; #[tokio::test] async fn test_get_vanilla_jar_url() { @@ -495,6 +514,44 @@ mod tests { .is_some()); } + #[tokio::test] + async fn test_get_neoforge_jar_url() { + assert_eq!( + super::get_neoforge_jar_url( + "1.20.2", + &Some(NeoforgeVersion::from_str("20.2.88")).unwrap().ok()).await, + Some(( + "https://maven.neoforged.net/releases/net/neoforged/neoforge/20.2.88/neoforge-20.2.88-installer.jar".to_string(), + Flavour::Neoforge { + build_version: Some(super::NeoforgeVersion::from_str("20.2.88").unwrap()) + } + )) + ); + + assert_eq!( + super::get_neoforge_jar_url("1.21.0", &None).await, + Some(("https://maven.neoforged.net/releases/net/neoforged/neoforge/21.0.167/neoforge-21.0.167-installer.jar".to_string(), + Flavour::Neoforge { build_version: Some(super::NeoforgeVersion::from_str("21.0.167").unwrap()) } + )) + ); + + assert_eq!( + super::get_neoforge_jar_url("1.20.2", &None).await, + Some(( + "https://maven.neoforged.net/releases/net/neoforged/neoforge/20.2.88/neoforge-20.2.88-installer.jar".to_string(), + Flavour::Neoforge { build_version: Some(super::NeoforgeVersion::from_str("20.2.88").unwrap()) } + )) + ); + + assert_eq!( + super::get_neoforge_jar_url("1.20.1", &None).await, + Some(( + "https://maven.neoforged.net/releases/net/neoforged/forge/1.20.1-47.1.106/forge-1.20.1-47.1.106-installer.jar".to_string(), + Flavour::Neoforge { build_version: Some(super::NeoforgeVersion::from_str("1.20.1-47.1.106").unwrap()) } + )) + ); + } + #[tokio::test] async fn test_get_paper_jar_url() { assert_eq!(super::get_paper_jar_url("1.19.3", &Some(PaperBuildVersion(308))).await, Some(( diff --git a/core/src/implementations/minecraft/versions.rs b/core/src/implementations/minecraft/versions.rs index 92780b85..ce8ca6bc 100644 --- a/core/src/implementations/minecraft/versions.rs +++ b/core/src/implementations/minecraft/versions.rs @@ -4,6 +4,7 @@ use serde_json::Value; use ts_rs::TS; use crate::error::Error; +use crate::implementations::minecraft::neoforge::get_neoforge_minecraft_versions; #[derive(Serialize, Deserialize, Debug, TS)] #[ts(export)] @@ -157,6 +158,15 @@ pub async fn get_paper_versions() -> Result { group_minecraft_versions(&versions).await } +pub async fn get_neoforge_versions() -> Result { + let versions_vec = get_neoforge_minecraft_versions().await?; + let versions = versions_vec + .iter() + .map(String::as_str) + .collect::>(); + group_minecraft_versions(&versions).await +} + pub async fn get_forge_versions() -> Result { let http = reqwest::Client::new(); @@ -193,6 +203,12 @@ mod tests { rt.block_on(get_paper_versions()).unwrap(); } + #[test] + fn test_neoforge_versions() { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(get_neoforge_versions()).unwrap(); + } + #[test] fn test_forge_versions() { let rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/core/src/traits/t_configurable/mod.rs b/core/src/traits/t_configurable/mod.rs index 0d042e93..a0db91ee 100644 --- a/core/src/traits/t_configurable/mod.rs +++ b/core/src/traits/t_configurable/mod.rs @@ -28,6 +28,7 @@ pub enum MinecraftVariant { Fabric, Paper, Spigot, + Neoforge, Other { name: String }, } @@ -72,6 +73,9 @@ impl From for Game { Flavour::Forge { .. } => Self::MinecraftJava { variant: MinecraftVariant::Forge, }, + Flavour::Neoforge { .. } => Self::MinecraftJava { + variant: MinecraftVariant::Neoforge, + } } } } diff --git a/core/test.db b/core/test.db index 80f65ff1efe3f6266b47761d68b47129d302fb1f..2388a7bfd3974257c5043d8b12c684037856c1ae 100644 GIT binary patch delta 117 zcmZojXh@hKE$GO=z`zW|Fkn1U$C%M^W5N=CHUpf7{J^qOSTzrC@&N-Q>c_pr8 HK%y7`Im8+_ delta 120 zcmZojXh@hKEy&Kmz`zW|Fu*!d$C#0QW5N=CHb#Ca27al{f(ioslcnVQ1r3eOj7<%V zEG!HyjLc1p4NNBQlD86L`u2yV=a2w{XI@%9FVGZ5{`U<0?}54=@=pw45#(oNR^@cg N$xO{FaV-N9#Q>T^90>pb diff --git a/dashboard/src/bindings/MinecraftVariant.ts b/dashboard/src/bindings/MinecraftVariant.ts index e0e8a9fd..96be219d 100644 --- a/dashboard/src/bindings/MinecraftVariant.ts +++ b/dashboard/src/bindings/MinecraftVariant.ts @@ -1,9 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type MinecraftVariant = - | { type: 'Vanilla' } - | { type: 'Forge' } - | { type: 'Fabric' } - | { type: 'Paper' } - | { type: 'Spigot' } - | { type: 'Other'; name: string }; +export type MinecraftVariant = { "type": "Vanilla" } | { "type": "Forge" } | { "type": "Fabric" } | { "type": "Paper" } | { "type": "Spigot" } | { "type": "Neoforge" } | { "type": "Other", name: string, }; \ No newline at end of file From 15020e6eab63508b9859f1d5a79d5c52c2f72ac3 Mon Sep 17 00:00:00 2001 From: bsuu Date: Wed, 21 Aug 2024 18:11:16 +0200 Subject: [PATCH 2/4] dashboard update --- dashboard/public/assets/minecraft-neoforge.png | Bin 0 -> 1097 bytes dashboard/src/bindings/HandlerGameType.ts | 2 +- dashboard/src/data/GameTypeMappings.tsx | 10 ++++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 dashboard/public/assets/minecraft-neoforge.png diff --git a/dashboard/public/assets/minecraft-neoforge.png b/dashboard/public/assets/minecraft-neoforge.png new file mode 100644 index 0000000000000000000000000000000000000000..727fa7e966b0ad821165d8a7f5bfdd52e326709a GIT binary patch literal 1097 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU=i?iaSW-L^JcES$CN^e3<0}fkT0hsRLrzJF^SY&)I(cI=d$AZkF-tHEUvWa_bAfKf1Z#HQ)UGDQU*W#%bRk zoO-(8xOKrU>leE;Hp$!1p80)N^#ycG?-+hKyZUz8$D~GK2-l#p#C!Fq(uzFRAMYF+9yUetiCwpdAE)Kooc3yh@t$tba&D?jtReXIH z)Bbyg+SBajtey3bcg=Lom2f`pk{|rSksaz^B(L8&&$uV@{^97K?`F&{TK`sl`(}P; zVaMm!uKg*HzieZiJ^$s^M{AcYo>#ANZ&k&czcXfH1l}FP4FX$2eP`Xwxi4v)oi)dJ zv*^jL1yPe`MjD^C*t#Y3X8N+tr<<}mn@SB>8c~8%=ck0k)|uYdZaj`L6bzsHdE=Zc zXLKiart;|Qy_uO7_&C@(>%y63mlvmf`+Du5YuI=7`*V8dA3wGD`$zpW+U$vnByQg?WI$BiGU_BZ!S`>0%- z;d$jn>cd<;P@D;D<&`bV%ijIRg#8J!X_yn-Im(jY!O$C zZlYhOc-43QrvL5p=1uu&cNs0|Go->ickVo}RQUg2nCrH|IoB Fabric: () => '/assets/minecraft-fabric.png', Forge: () => '/assets/minecraft-forge.png', Paper: () => '/assets/minecraft-paper.png', + Neoforge: () => '/assets/minecraft-neoforge.png', }, () => unknown_icon ) @@ -55,6 +56,7 @@ export const game_to_game_title = (game: Game) => Fabric: () => 'Fabric (Minecraft)', Paper: () => 'Paper (Minecraft)', Spigot: () => 'Spigot (Minecraft)', + Neoforge: () => 'Neoforge (Minecraft)', Other: ({ name }) => `${name} (Minecraft)`, }), Generic: ({ game_name }) => `${game_name} (Generic)`, @@ -72,6 +74,8 @@ export const game_to_description = (game: Game) => 'High-performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.', Spigot: () => 'Modified Minecraft server software that supports plugins, offering enhanced performance and customization options.', + Neoforge: () => + 'Forge fork focused on enhancing modding capabilities and maintaining compatibility.', Other: ({ name }) => `Unknown Minecraft variant: ${name}`, }), Generic: ({ game_name }) => `Unknown game: ${game_name}`, @@ -102,4 +106,10 @@ export const HandlerGameType_to_Game: Record = { type: 'Paper', }, }, + MinecraftNeoforge: { + type: 'MinecraftJava', + variant: { + type: 'Neoforge' + } + } }; From d19919fc00dc2a4ef81ccc50d20f1c10a1a494e5 Mon Sep 17 00:00:00 2001 From: bsuu Date: Wed, 21 Aug 2024 18:26:19 +0200 Subject: [PATCH 3/4] =?UTF-8?q?rust-analyzer=20format=20=F0=9F=A4=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implementations/minecraft/configurable.rs | 4 +- core/src/implementations/minecraft/mod.rs | 28 +++---- .../src/implementations/minecraft/neoforge.rs | 78 ++++++++++++------- core/src/implementations/minecraft/server.rs | 6 +- core/src/implementations/minecraft/util.rs | 20 ++--- 5 files changed, 80 insertions(+), 56 deletions(-) diff --git a/core/src/implementations/minecraft/configurable.rs b/core/src/implementations/minecraft/configurable.rs index 56fbb580..3eb444d5 100644 --- a/core/src/implementations/minecraft/configurable.rs +++ b/core/src/implementations/minecraft/configurable.rs @@ -154,12 +154,12 @@ impl TConfigurable for MinecraftInstance { kind: ErrorKind::UnsupportedOperation, source: eyre!("Changing versions is unsupported for forge servers"), }) - }, + } super::Flavour::Neoforge { .. } => { return Err(Error { kind: ErrorKind::UnsupportedOperation, source: eyre!("Changing versions is unsupported for forge servers"), //TODO - }) + }); } }; let lodestone_tmp = path_to_tmp().clone(); diff --git a/core/src/implementations/minecraft/mod.rs b/core/src/implementations/minecraft/mod.rs index d422d070..9f074f94 100644 --- a/core/src/implementations/minecraft/mod.rs +++ b/core/src/implementations/minecraft/mod.rs @@ -3,6 +3,7 @@ pub mod fabric; mod forge; mod line_parser; pub mod r#macro; +mod neoforge; mod paper; pub mod player; mod players_manager; @@ -10,7 +11,6 @@ pub mod server; pub mod util; mod vanilla; pub mod versions; -mod neoforge; use color_eyre::eyre::{eyre, Context, ContextCompat}; use enum_kinds::EnumKind; @@ -39,7 +39,9 @@ use regex::Regex; use crate::error::Error; use crate::event_broadcaster::EventBroadcaster; use crate::events::{Event, ProgressionEventID}; -use crate::implementations::minecraft::neoforge::{get_neoforge_minecraft_versions, NeoforgeVersion}; +use crate::implementations::minecraft::neoforge::{ + get_neoforge_minecraft_versions, NeoforgeVersion, +}; use crate::macro_executor::{MacroExecutor, MacroPID}; use crate::prelude::path_to_binaries; use crate::traits::t_configurable::PathBuf; @@ -98,7 +100,7 @@ pub enum Flavour { }, Neoforge { build_version: Option, - } + }, } impl From for Flavour { @@ -118,7 +120,7 @@ impl From for Flavour { }, FlavourKind::Neoforge => Flavour::Neoforge { build_version: None, - } + }, } } } @@ -668,15 +670,15 @@ impl MinecraftInstance { .arg(&path_to_instance) .current_dir(&path_to_instance), ) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .context("Failed to start neoforge-installer.jar")? - .wait() - .await - .context("neoforge-installer.jar failed")? - .success() + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .context("Failed to start neoforge-installer.jar")? + .wait() + .await + .context("neoforge-installer.jar failed")? + .success() { return Err(eyre!("Failed to install neoforge server").into()); } diff --git a/core/src/implementations/minecraft/neoforge.rs b/core/src/implementations/minecraft/neoforge.rs index 95763a04..4121380e 100644 --- a/core/src/implementations/minecraft/neoforge.rs +++ b/core/src/implementations/minecraft/neoforge.rs @@ -1,20 +1,17 @@ +use crate::error::Error; +use crate::traits::t_configurable::{Deserialize, Serialize}; +use color_eyre::eyre::{eyre, Context}; +use serde_json::Value; use std::fmt; use std::fmt::Formatter; use std::str::FromStr; -use color_eyre::eyre::{eyre, Context}; -use enum_kinds::EnumKind; -use serde_json::Value; use ts_rs::TS; -use crate::error::Error; -use crate::traits::t_configurable::{Deserialize, Serialize}; pub async fn get_neoforge_minecraft_versions() -> Result, Error> { let versions = request_neoforge_versions().await?; let mut minecraft_versions: Vec = versions .iter() - .map(|version| { - format!("1.{}.{}", version.major, version.minor) - }) + .map(|version| format!("1.{}.{}", version.major, version.minor)) .collect(); minecraft_versions.dedup(); @@ -23,12 +20,16 @@ pub async fn get_neoforge_minecraft_versions() -> Result, Error> { Ok(minecraft_versions) } -pub async fn get_neoforge_builds(minecraft_version: Option<&str>) -> Result, Error> { +pub async fn get_neoforge_builds( + minecraft_version: Option<&str>, +) -> Result, Error> { let versions = request_neoforge_versions().await?; let build_versions = versions .iter() .filter(|version| { - minecraft_version.is_none() || format!("1.{}.{}", version.major, version.minor) == minecraft_version.clone().unwrap() + minecraft_version.is_none() + || format!("1.{}.{}", version.major, version.minor) + == minecraft_version.clone().unwrap() }) .cloned() .collect::>(); @@ -36,7 +37,9 @@ pub async fn get_neoforge_builds(minecraft_version: Option<&str>) -> Result) -> Result { +pub async fn get_neoforge_latest_build( + minecraft_version: Option<&str>, +) -> Result { let version_builds = get_neoforge_builds(minecraft_version).await?; let latest_version = version_builds @@ -58,17 +61,18 @@ async fn request_neoforge_versions() -> Result, Error> { .text() .await .context("Failed to get legacy neoforge versions")? - .as_str() - ).context("Failed to get legacy neoforge versions")?; + .as_str(), + ) + .context("Failed to get legacy neoforge versions")?; let legacy_versions = legacy_response["versions"] .as_array() - .ok_or_else(|| eyre!("Failed to get legacy neoforge versions. Version array is not an array"))? + .ok_or_else(|| { + eyre!("Failed to get legacy neoforge versions. Version array is not an array") + })? .iter() .filter(|v| v.as_str().unwrap().contains("-")) - .map(|v| { - NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap() - }) + .map(|v| NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap()) .collect::>(); let response: Value = serde_json::from_str( @@ -80,22 +84,23 @@ async fn request_neoforge_versions() -> Result, Error> { .await .context("Failed to get neoforge versions")? .as_str(), - ).context("Failed to get neoforge versions")?; + ) + .context("Failed to get neoforge versions")?; let main_versions = response["versions"] .as_array() .ok_or_else(|| eyre!("Failed to get neoforge versions. Versions array is not an array"))? .iter() - .map(|v| { - NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap() - }) + .map(|v| NeoforgeVersion::from_str(v.as_str().unwrap()).unwrap()) .collect::>(); let versions = [main_versions, legacy_versions].concat(); Ok(versions) } -fn split_neoforge_version(version: &str) -> Result<(String, String, (String, Option)), VersionError> { +fn split_neoforge_version( + version: &str, +) -> Result<(String, String, (String, Option)), VersionError> { let mut split = version.split('.'); let major_version = split.next().ok_or(VersionError::InvalidFormat)?.to_string(); @@ -144,8 +149,12 @@ impl FromStr for NeoforgeVersion { let (major_str, minor_str, (patch_str, _)) = split_neoforge_version(&value)?; - let major = major_str.parse::().map_err(|_| VersionError::InvalidNumber)?; - let minor = minor_str.parse::().map_err(|_| VersionError::InvalidNumber)?; + let major = major_str + .parse::() + .map_err(|_| VersionError::InvalidNumber)?; + let minor = minor_str + .parse::() + .map_err(|_| VersionError::InvalidNumber)?; Ok(NeoforgeVersion::new(major, minor, patch_str, is_legacy)) } @@ -153,7 +162,10 @@ impl FromStr for NeoforgeVersion { impl PartialEq for NeoforgeVersion { fn eq(&self, other: &Self) -> bool { - self.major == other.major && self.legacy == other.legacy && self.minor == other.minor && self.patch == other.patch + self.major == other.major + && self.legacy == other.legacy + && self.minor == other.minor + && self.patch == other.patch } } @@ -185,7 +197,7 @@ impl NeoforgeVersion { #[derive(Debug, TS)] pub enum VersionError { InvalidFormat, - InvalidNumber + InvalidNumber, } impl fmt::Display for VersionError { @@ -193,7 +205,7 @@ impl fmt::Display for VersionError { match self { VersionError::InvalidFormat => write!(f, "Invalid version format."), VersionError::InvalidNumber => write!(f, "Invalid number in version."), - _ => write!(f, "How did we get here?") + _ => write!(f, "How did we get here?"), } } } @@ -216,11 +228,17 @@ mod test { #[tokio::test] async fn test_get_neoforge_latest_build() { assert_eq!( - get_neoforge_latest_build(Some("1.20.2")).await.unwrap().version(), + get_neoforge_latest_build(Some("1.20.2")) + .await + .unwrap() + .version(), "20.2.88".to_string() ); assert_eq!( - get_neoforge_latest_build(Some("1.20.1")).await.unwrap().version(), + get_neoforge_latest_build(Some("1.20.1")) + .await + .unwrap() + .version(), "1.20.1-47.1.106".to_string() ); } @@ -230,4 +248,4 @@ mod test { let versions = get_neoforge_builds(Some("1.20.2")).await.unwrap(); assert!(versions.contains(&NeoforgeVersion::from_str("20.2.88").unwrap())); } -} \ No newline at end of file +} diff --git a/core/src/implementations/minecraft/server.rs b/core/src/implementations/minecraft/server.rs index 532aa0e0..22a44bc1 100644 --- a/core/src/implementations/minecraft/server.rs +++ b/core/src/implementations/minecraft/server.rs @@ -209,7 +209,8 @@ impl TServer for MinecraftInstance { let mut full_neoforge_args = std::ffi::OsString::from("@"); let nf_path_buf = if neoforge_build_version.legacy { - let path = self.path_to_instance + let path = self + .path_to_instance .join("libraries") .join("net") .join("neoforged") @@ -218,7 +219,8 @@ impl TServer for MinecraftInstance { .join(neoforge_args); path.into_os_string().as_os_str().to_os_string() } else { - let path = self.path_to_instance + let path = self + .path_to_instance .join("libraries") .join("net") .join("neoforged") diff --git a/core/src/implementations/minecraft/util.rs b/core/src/implementations/minecraft/util.rs index 06d8dc33..d74ee080 100644 --- a/core/src/implementations/minecraft/util.rs +++ b/core/src/implementations/minecraft/util.rs @@ -1,8 +1,8 @@ use color_eyre::eyre::{eyre, Context, ContextCompat}; use indexmap::IndexMap; +use semver::Op; use serde_json::{self, Value}; use std::{collections::BTreeMap, path::Path, str::FromStr}; -use semver::Op; use tokio::io::AsyncBufReadExt; use super::{ @@ -67,18 +67,20 @@ pub async fn get_server_jar_url(version: &str, flavour: &Flavour) -> Option<(Str } } -pub async fn get_neoforge_jar_url(version: &str, neoforge_build_version: &Option) -> Option<(String, Flavour)> { +pub async fn get_neoforge_jar_url( + version: &str, + neoforge_build_version: &Option, +) -> Option<(String, Flavour)> { let latest_build = get_neoforge_latest_build(Some(version)).await.ok()?; - let build = neoforge_build_version - .as_ref() - .unwrap_or(&latest_build); + let build = neoforge_build_version.as_ref().unwrap_or(&latest_build); Some(( build.installer_url(), - Flavour::Neoforge { build_version: Some(build.clone())} + Flavour::Neoforge { + build_version: Some(build.clone()), + }, )) - } pub async fn get_vanilla_jar_url(version: &str) -> Option<(String, Flavour)> { @@ -459,13 +461,13 @@ pub async fn name_to_uuid(name: impl AsRef) -> Option { #[cfg(test)] mod tests { - use std::str::FromStr; + use crate::implementations::minecraft::neoforge::NeoforgeVersion; use crate::minecraft::{ util::{get_forge_jar_url, get_server_jar_url}, FabricInstallerVersion, FabricLoaderVersion, Flavour, ForgeBuildVersion, PaperBuildVersion, }; + use std::str::FromStr; use tokio; - use crate::implementations::minecraft::neoforge::NeoforgeVersion; #[tokio::test] async fn test_get_vanilla_jar_url() { From 768f6ebf647f1b3f90d2a79f4c8e975c0197a87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20G=C3=B3ralczyk?= Date: Wed, 21 Aug 2024 21:42:32 +0200 Subject: [PATCH 4/4] Update HandlerGameType.ts --- dashboard/src/bindings/HandlerGameType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src/bindings/HandlerGameType.ts b/dashboard/src/bindings/HandlerGameType.ts index bf5f7177..f7c5ba87 100644 --- a/dashboard/src/bindings/HandlerGameType.ts +++ b/dashboard/src/bindings/HandlerGameType.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type HandlerGameType = "MinecraftJavaVanilla" | "MinecraftFabric" | "MinecraftForge" | "MinecraftPaper" | "MinecraftBedrock" | "MinecraftNeoforge"; \ No newline at end of file +export type HandlerGameType = "MinecraftJavaVanilla" | "MinecraftFabric" | "MinecraftForge" | "MinecraftPaper" | "MinecraftNeoforge";