Add Ornithe support (#811)
* Add Ornithe support * chore: fix the remaining startup issues reltaed to arguments and manifest caching * fix: stop repairing on every launch for natives-only libraries --------- Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
This commit is contained in:
parent
fe8bb6bfcc
commit
44dfa32233
12 changed files with 382 additions and 84 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -3231,9 +3231,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "interfrost"
|
||||
version = "1.3.2"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3449eb9b8a587b832db2eb3a45776928ce0927c0efa7d0be9a7f4c619140a12a"
|
||||
checksum = "76a14453cd2d9454beb9b410dc7b845b2d62ff67a6f42460bce6b9bc6e34d3b9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ polyio = {path = "./packages/polyio"}
|
|||
|
||||
# core (extra)
|
||||
discord-rich-presence = {version = "=1.1.0"}
|
||||
interfrost = {version = "=1.3.2"}
|
||||
interfrost = {version = "=1.4.0"}
|
||||
|
||||
# logging
|
||||
sentry = {version = "=0.48.5", default-features = false, features = [
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use oneclient_net::status::{self, ServiceStatus};
|
|||
|
||||
use crate::Actions;
|
||||
use crate::components::{Button, Dropdown, Icon, IconType, TextInput, login_dialog, toggle};
|
||||
use crate::hooks::use_dispatch;
|
||||
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch};
|
||||
use crate::notifications::{
|
||||
ClusterUpdateItem, ClusterUpdateSummary, NotificationAction, NotificationActionKind,
|
||||
OptionalModsGroup,
|
||||
|
|
@ -97,6 +97,8 @@ impl Component for Debug {
|
|||
vec![CorruptionSimulator.into_element()],
|
||||
))
|
||||
.child(divider())
|
||||
.child(section("Clusters", vec![ClusterList.into_element()]))
|
||||
.child(divider())
|
||||
.child(section("SQL Console", vec![SqlConsole.into_element()]))
|
||||
.child(divider())
|
||||
.child(section(
|
||||
|
|
@ -112,6 +114,92 @@ impl Component for Debug {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct ClusterList;
|
||||
|
||||
impl Component for ClusterList {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let dispatch = use_dispatch();
|
||||
let active_id = use_active_cluster_id();
|
||||
let clusters = settled_or_loading(&use_clusters()).unwrap_or_default();
|
||||
|
||||
let mut list = rect().vertical().width(Size::fill()).spacing(8.);
|
||||
|
||||
if clusters.is_empty() {
|
||||
return list.child(
|
||||
label()
|
||||
.text("No clusters in the database.")
|
||||
.font_size(13.)
|
||||
.color(colors::fg_secondary()),
|
||||
);
|
||||
}
|
||||
|
||||
for cluster in clusters {
|
||||
let cluster_id = cluster.id;
|
||||
let launch = dispatch.clone();
|
||||
let mut active_id = active_id;
|
||||
let loader_version = cluster
|
||||
.mc_loader_version
|
||||
.as_deref()
|
||||
.map_or_else(String::new, |version| format!(" {version}"));
|
||||
|
||||
list = list.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.main_align(Alignment::SpaceBetween)
|
||||
.spacing(12.)
|
||||
.padding(Gaps::new_symmetric(10., 14.))
|
||||
.corner_radius(CornerRadius::new_all(10.))
|
||||
.background(colors::page_elevated())
|
||||
.child(
|
||||
rect()
|
||||
.vertical()
|
||||
.child(
|
||||
label()
|
||||
.text(cluster.name.clone())
|
||||
.font_size(14.)
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(format!(
|
||||
"#{cluster_id} · {} · {}{loader_version} · {:?}",
|
||||
cluster.mc_version, cluster.mc_loader, cluster.stage
|
||||
))
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.spacing(8.)
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
.on_press(move |_| launch.launch_cluster(cluster_id))
|
||||
.text("Launch"),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.secondary()
|
||||
.on_press(move |_| {
|
||||
*active_id.write() = Some(cluster_id);
|
||||
let _ = RouterContext::get()
|
||||
.push(Route::ClusterOverview { cluster_id });
|
||||
})
|
||||
.text("View"),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct ToastController;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,6 @@ pub enum ClusterError {
|
|||
#[error("cluster is in {0} stage")]
|
||||
Busy(ClusterStage),
|
||||
|
||||
#[error("invalid Minecraft version '{0}'")]
|
||||
InvalidVersion(String),
|
||||
|
||||
#[error("cluster is missing a required Java version")]
|
||||
MissingJavaVersion,
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ pub enum GameLoader {
|
|||
NeoForge = 2,
|
||||
Quilt = 3,
|
||||
Fabric = 4,
|
||||
LegacyFabric = 5,
|
||||
Ornithe = 5,
|
||||
}
|
||||
|
||||
impl GameLoader {
|
||||
|
|
@ -212,7 +212,7 @@ impl GameLoader {
|
|||
Self::NeoForge => interfrost::api::modded::CURRENT_NEOFORGE_FORMAT_VERSION,
|
||||
Self::Quilt => interfrost::api::modded::CURRENT_QUILT_FORMAT_VERSION,
|
||||
Self::Fabric => interfrost::api::modded::CURRENT_FABRIC_FORMAT_VERSION,
|
||||
Self::LegacyFabric => interfrost::api::modded::CURRENT_LEGACY_FABRIC_FORMAT_VERSION,
|
||||
Self::Ornithe => interfrost::api::modded::CURRENT_ORNITHE_FORMAT_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,12 +220,19 @@ impl GameLoader {
|
|||
match self {
|
||||
Self::Vanilla => String::from("minecraft"),
|
||||
Self::NeoForge => String::from("neo"),
|
||||
Self::Ornithe => String::from("ornithe"),
|
||||
_ => self.to_string().to_lowercase().replace(['_', '.', ' '], ""),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn modded_loaders() -> &'static [Self] {
|
||||
&[Self::Forge, Self::NeoForge, Self::Quilt, Self::Fabric]
|
||||
&[
|
||||
Self::Forge,
|
||||
Self::NeoForge,
|
||||
Self::Quilt,
|
||||
Self::Fabric,
|
||||
Self::Ornithe,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn compatible_with(self, other: Self) -> bool {
|
||||
|
|
@ -235,7 +242,7 @@ impl GameLoader {
|
|||
Self::NeoForge => other == Self::NeoForge,
|
||||
Self::Quilt => matches!(other, Self::Quilt | Self::Fabric),
|
||||
Self::Fabric => other == Self::Fabric,
|
||||
Self::LegacyFabric => other == Self::LegacyFabric,
|
||||
Self::Ornithe => other == Self::Ornithe,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,7 +253,7 @@ impl GameLoader {
|
|||
Self::NeoForge => "neoforge",
|
||||
Self::Quilt => "quilt",
|
||||
Self::Fabric => "fabric",
|
||||
Self::LegacyFabric => "legacy-fabric",
|
||||
Self::Ornithe => "ornithe",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,7 +269,7 @@ impl FromStr for GameLoader {
|
|||
"neoforge" | "neo" => Self::NeoForge,
|
||||
"quilt" | "quiltloader" => Self::Quilt,
|
||||
"fabric" | "fabricloader" => Self::Fabric,
|
||||
"legacyfabric" => Self::LegacyFabric,
|
||||
"ornithe" => Self::Ornithe,
|
||||
_ => return Err(format!("'{s}' is not a valid game loader")),
|
||||
},
|
||||
)
|
||||
|
|
@ -277,7 +284,7 @@ impl Display for GameLoader {
|
|||
Self::NeoForge => "NeoForge",
|
||||
Self::Quilt => "Quilt",
|
||||
Self::Fabric => "Fabric",
|
||||
Self::LegacyFabric => "Legacy Fabric",
|
||||
Self::Ornithe => "Fabric (Ornithe)",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -302,3 +309,19 @@ impl Resolution {
|
|||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_display_name_is_not_a_metadata_path() {
|
||||
assert_eq!(GameLoader::Ornithe.to_string(), "Fabric (Ornithe)");
|
||||
assert_eq!(GameLoader::Ornithe.get_format_name(), "ornithe");
|
||||
assert_eq!(GameLoader::Vanilla.get_format_name(), "minecraft");
|
||||
assert_eq!(GameLoader::NeoForge.get_format_name(), "neo");
|
||||
assert_eq!(GameLoader::Fabric.get_format_name(), "fabric");
|
||||
assert_eq!(GameLoader::Quilt.get_format_name(), "quilt");
|
||||
assert_eq!(GameLoader::Forge.get_format_name(), "forge");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,9 +163,7 @@ pub async fn estimate_cluster_download(
|
|||
let info = {
|
||||
let mut metadata = state.metadata.lock().await;
|
||||
let (version, _index, _updated) =
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version)
|
||||
.await
|
||||
.map_err(|_| ClusterError::InvalidVersion(cluster.mc_version.clone()))?;
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version).await?;
|
||||
let loader_version = get_loader_version(
|
||||
&mut metadata,
|
||||
&state.services.mc(),
|
||||
|
|
@ -211,9 +209,7 @@ async fn install_cluster(
|
|||
let mc_version = oneclient_common::version::normalize_mc_version_input(&cluster.mc_version);
|
||||
|
||||
let (version, _version_index, minecraft_updated) =
|
||||
resolve_minecraft_version(metadata, &state.services.mc(), &mc_version)
|
||||
.await
|
||||
.map_err(|_| ClusterError::InvalidVersion(cluster.mc_version.clone()))?;
|
||||
resolve_minecraft_version(metadata, &state.services.mc(), &mc_version).await?;
|
||||
|
||||
let loader_version = get_loader_version(
|
||||
metadata,
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ pub enum GameError {
|
|||
#[error("processor main class not found for {0}")]
|
||||
ProcessorMainClass(String),
|
||||
|
||||
#[error("invalid game version {0}")]
|
||||
InvalidVersion(String),
|
||||
|
||||
#[error("cluster {0} is already running")]
|
||||
AlreadyRunning(i64),
|
||||
|
||||
|
|
|
|||
|
|
@ -148,9 +148,7 @@ async fn start(
|
|||
let mut metadata = state.metadata.lock().await;
|
||||
|
||||
let (version, _index, updated) =
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version)
|
||||
.await
|
||||
.map_err(|_| GameError::InvalidVersion(cluster.mc_version.clone()))?;
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version).await?;
|
||||
|
||||
let loader_version = get_loader_version(
|
||||
&mut metadata,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use oneclient_cluster::ClusterError;
|
||||
use oneclient_content::packages::PackageStore;
|
||||
use oneclient_content::packages::store::artifact_absolute_path;
|
||||
use oneclient_db::dao::artifact as artifact_dao;
|
||||
|
|
@ -109,9 +108,7 @@ async fn run_verify(
|
|||
let (version_info, minecraft_updated) = {
|
||||
let mut metadata = state.metadata.lock().await;
|
||||
let (version, _index, minecraft_updated) =
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version)
|
||||
.await
|
||||
.map_err(|_| ClusterError::InvalidVersion(cluster.mc_version.clone()))?;
|
||||
resolve_minecraft_version(&mut metadata, &state.services.mc(), &mc_version).await?;
|
||||
|
||||
let loader_version = get_loader_version(
|
||||
&mut metadata,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use interfrost::api::modded::SidedDataEntry;
|
|||
use interfrost::utils::get_path_from_artifact;
|
||||
|
||||
use oneclient_common::constants::{self, DUMMY_REPLACE_NEWLINE};
|
||||
use oneclient_common::paths;
|
||||
use crate::rules::validate_rules;
|
||||
use crate::error::McError;
|
||||
use oneclient_common::Resolution;
|
||||
|
|
@ -41,13 +42,20 @@ pub fn java_arguments(
|
|||
},
|
||||
java_arch,
|
||||
)?;
|
||||
} else {
|
||||
}
|
||||
|
||||
if !parsed
|
||||
.iter()
|
||||
.any(|arg| arg.starts_with("-Djava.library.path="))
|
||||
{
|
||||
parsed.push(format!(
|
||||
"-Djava.library.path={}",
|
||||
polyio::canonicalize(natives_path)
|
||||
.map_err(|_| McError::LibraryPath(natives_path.display().to_string()))?
|
||||
.display()
|
||||
));
|
||||
}
|
||||
if !parsed.iter().any(|arg| arg == "-cp" || arg == "-classpath") {
|
||||
parsed.push("-cp".to_string());
|
||||
parsed.push(classpaths.to_string());
|
||||
}
|
||||
|
|
@ -174,8 +182,28 @@ pub fn minecraft_arguments(
|
|||
resolution: Resolution,
|
||||
java_arch: &str,
|
||||
) -> McResult<Vec<String>> {
|
||||
let mut parsed = Vec::new();
|
||||
|
||||
// a legacy version states its arguments as a string and a loader merged onto
|
||||
// one adds a list on top the game needs both, in that order
|
||||
if let Some(legacy_args) = legacy_args {
|
||||
for arg in legacy_args.split(' ') {
|
||||
parsed.push(parse_minecraft_argument(
|
||||
&arg.replace(' ', DUMMY_REPLACE_NEWLINE),
|
||||
access_token,
|
||||
username,
|
||||
uuid,
|
||||
version,
|
||||
asset_index,
|
||||
game_directory,
|
||||
assets_directory,
|
||||
version_type,
|
||||
resolution,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(args) = args {
|
||||
let mut parsed = Vec::new();
|
||||
parse_arguments(
|
||||
version_updated,
|
||||
args,
|
||||
|
|
@ -196,29 +224,9 @@ pub fn minecraft_arguments(
|
|||
},
|
||||
java_arch,
|
||||
)?;
|
||||
|
||||
Ok(parsed)
|
||||
} else if let Some(legacy_args) = legacy_args {
|
||||
let mut parsed = Vec::new();
|
||||
for arg in legacy_args.split(' ') {
|
||||
parsed.push(parse_minecraft_argument(
|
||||
&arg.replace(' ', DUMMY_REPLACE_NEWLINE),
|
||||
access_token,
|
||||
username,
|
||||
uuid,
|
||||
version,
|
||||
asset_index,
|
||||
game_directory,
|
||||
assets_directory,
|
||||
version_type,
|
||||
resolution,
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
pub fn append_profile_game_arguments(
|
||||
|
|
@ -343,15 +351,21 @@ pub fn parse_minecraft_argument(
|
|||
"${assets_root}",
|
||||
&polyio::canonicalize(assets_directory)?.display().to_string(),
|
||||
)
|
||||
.replace(
|
||||
"${game_assets}",
|
||||
&polyio::canonicalize(assets_directory)?.display().to_string(),
|
||||
)
|
||||
.replace("${game_assets}", &legacy_assets_path()?)
|
||||
.replace("${version_type}", version_type.as_str())
|
||||
.replace("${resolution_width}", &resolution.width.to_string())
|
||||
.replace("${resolution_height}", &resolution.height.to_string()))
|
||||
}
|
||||
|
||||
fn legacy_assets_path() -> McResult<String> {
|
||||
let dir = paths::legacy_assets_dir()?;
|
||||
|
||||
Ok(polyio::canonicalize(&dir)
|
||||
.unwrap_or(dir)
|
||||
.display()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn parse_java_argument(
|
||||
mut argument: String,
|
||||
natives_path: &Path,
|
||||
|
|
@ -393,6 +407,10 @@ pub fn classpaths(
|
|||
if !lib.include_in_classpath {
|
||||
continue;
|
||||
}
|
||||
// natives-only libraries (classifiers, no artifact) have no jar on disk
|
||||
if !crate::install::has_main_artifact(lib) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (artifact, version) = split_artifact_version(&lib.name);
|
||||
let ver_key = version_key(version);
|
||||
|
|
@ -516,7 +534,11 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ZGC_MIN_HEAP_MB, is_collector_flag, performance_flags, split_custom_args};
|
||||
use super::{
|
||||
Library, ZGC_MIN_HEAP_MB, classpaths, is_collector_flag, java_arguments,
|
||||
minecraft_arguments, performance_flags, split_custom_args,
|
||||
};
|
||||
use oneclient_common::Resolution;
|
||||
|
||||
#[test]
|
||||
fn blank_input_contributes_nothing() {
|
||||
|
|
@ -786,5 +808,92 @@ mod tests {
|
|||
fn a_java_7_runtime_is_left_untouched() {
|
||||
assert_eq!(flags(7, "amd64", 4096), vec!["-Xms512M"]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_natives_only_library_is_not_a_classpath_entry() {
|
||||
let libraries: Vec<Library> = serde_json::from_str(
|
||||
r#"[
|
||||
{ "name": "net.fabricmc:fabric-loader:0.19.5" },
|
||||
{ "name": "org.lwjgl.lwjgl:lwjgl:2.9.4+legacyfabric.15",
|
||||
"downloads": { "artifact": { "sha1": "a", "size": 1, "url": "https://maven.legacyfabric.net/lwjgl.jar" } } },
|
||||
{ "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4+legacyfabric.15",
|
||||
"downloads": { "classifiers": { "natives-osx": { "sha1": "b", "size": 2, "url": "https://maven.legacyfabric.net/natives-osx.jar" } } },
|
||||
"natives": { "osx": "natives-osx" } }
|
||||
]"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dir = std::env::temp_dir().join("oneclient-classpath-test");
|
||||
let client = dir.join("client.jar");
|
||||
for lib in ["net/fabricmc/fabric-loader/0.19.5/fabric-loader-0.19.5.jar",
|
||||
"org/lwjgl/lwjgl/lwjgl/2.9.4+legacyfabric.15/lwjgl-2.9.4+legacyfabric.15.jar"] {
|
||||
let path = dir.join(lib);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&path, b"").unwrap();
|
||||
}
|
||||
std::fs::write(&client, b"").unwrap();
|
||||
|
||||
let cp = classpaths(&dir, &libraries, &client, "x86", false)
|
||||
.expect("a jar that was never downloaded must not fail the launch");
|
||||
assert!(!cp.contains("lwjgl-platform"));
|
||||
assert!(cp.contains("fabric-loader") && cp.contains("lwjgl-2.9.4"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_loader_on_a_legacy_version_still_gets_a_classpath() {
|
||||
// ornithe's manifest declares two -D flags and nothing else
|
||||
// 1.8.9 has no `arguments` block to supply -cp or the natives path
|
||||
let loader_jvm: Vec<interfrost::api::minecraft::Argument> = serde_json::from_str(
|
||||
r#"["-Dfabric.fixPackageAccess=true", "-Dfabric.gameVersion=1.8.9"]"#,
|
||||
)
|
||||
.unwrap();
|
||||
let natives = std::env::temp_dir();
|
||||
|
||||
let args = java_arguments(
|
||||
false,
|
||||
Some(&loader_jvm),
|
||||
&natives,
|
||||
&natives,
|
||||
"/libs/fabric-loader.jar",
|
||||
"1.8.9",
|
||||
2048,
|
||||
String::new(),
|
||||
"arm64",
|
||||
21,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(args.contains(&"-Dfabric.fixPackageAccess=true".to_string()));
|
||||
let cp = args.iter().position(|a| a == "-cp").expect("no classpath");
|
||||
assert_eq!(args[cp + 1], "/libs/fabric-loader.jar");
|
||||
assert!(args.iter().any(|a| a.starts_with("-Djava.library.path=")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_loader_game_argument_does_not_replace_the_legacy_string() {
|
||||
let loader_game: Vec<interfrost::api::minecraft::Argument> =
|
||||
serde_json::from_str(r#"["--fabric"]"#).unwrap();
|
||||
let dir = std::env::temp_dir();
|
||||
|
||||
let args = minecraft_arguments(
|
||||
false,
|
||||
Some(&loader_game),
|
||||
Some("--username ${auth_player_name} --gameDir ${game_directory}"),
|
||||
"token",
|
||||
"player",
|
||||
uuid::Uuid::nil(),
|
||||
"1.8.9",
|
||||
"1.8",
|
||||
&dir,
|
||||
&dir,
|
||||
interfrost::api::minecraft::VersionType::Release,
|
||||
Resolution::default(),
|
||||
"arm64",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(args.first().unwrap(), "--username");
|
||||
assert_eq!(args[1], "player");
|
||||
assert_eq!(args.last().unwrap(), "--fabric");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ impl DownloadPlan {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn uses_legacy_assets(assets: &str) -> bool {
|
||||
matches!(assets, "legacy" | "pre-1.6")
|
||||
}
|
||||
|
||||
fn asset_object_path(dir: &Path, name: &str, hash: &str, legacy: bool) -> PathBuf {
|
||||
if legacy {
|
||||
dir.join(name.replace('/', std::path::MAIN_SEPARATOR_STR))
|
||||
|
|
@ -77,6 +82,12 @@ fn library_artifact_size(lib: &Library) -> u64 {
|
|||
.map_or(0, |artifact| u64::from(artifact.size))
|
||||
}
|
||||
|
||||
pub(crate) fn has_main_artifact(lib: &Library) -> bool {
|
||||
lib.downloads
|
||||
.as_ref()
|
||||
.is_none_or(|downloads| downloads.artifact.is_some())
|
||||
}
|
||||
|
||||
fn native_download<'a>(lib: &'a Library, java_arch: &str) -> Option<&'a LibraryDownload> {
|
||||
let (os_key, classifiers) = lib.natives.as_ref().and_then(|natives| {
|
||||
Some((
|
||||
|
|
@ -155,7 +166,7 @@ pub async fn plan_downloads(
|
|||
minecraft_updated: bool,
|
||||
force: bool,
|
||||
) -> McResult<DownloadPlan> {
|
||||
let legacy = version.assets == "legacy";
|
||||
let legacy = uses_legacy_assets(&version.assets);
|
||||
let asset_dir = if legacy {
|
||||
paths::legacy_assets_dir()?
|
||||
} else {
|
||||
|
|
@ -208,11 +219,12 @@ pub async fn plan_downloads(
|
|||
continue;
|
||||
};
|
||||
|
||||
let artifact = force
|
||||
|| !matches_expected_size(
|
||||
&lib_dir.join(&artifact_path),
|
||||
library_artifact_size(&lib),
|
||||
);
|
||||
let artifact = has_main_artifact(&lib)
|
||||
&& (force
|
||||
|| !matches_expected_size(
|
||||
&lib_dir.join(&artifact_path),
|
||||
library_artifact_size(&lib),
|
||||
));
|
||||
let natives = force || !natives_extracted(&natives_dir, &lib, &java_arch);
|
||||
|
||||
if !artifact && !natives {
|
||||
|
|
@ -426,7 +438,7 @@ pub async fn verify_game_files(
|
|||
java_arch: &str,
|
||||
minecraft_updated: bool,
|
||||
) -> McResult<VerifyReport> {
|
||||
let legacy = version.assets == "legacy";
|
||||
let legacy = uses_legacy_assets(&version.assets);
|
||||
let asset_dir = if legacy {
|
||||
paths::legacy_assets_dir()?
|
||||
} else {
|
||||
|
|
@ -599,7 +611,7 @@ pub async fn download_minecraft(
|
|||
download_assets(
|
||||
ctx,
|
||||
progress,
|
||||
version.assets == "legacy",
|
||||
uses_legacy_assets(&version.assets),
|
||||
assets,
|
||||
),
|
||||
download_client(ctx, progress, version, force),
|
||||
|
|
@ -782,7 +794,11 @@ pub async fn download_version_info(
|
|||
.map_err(McError::from)?,
|
||||
};
|
||||
|
||||
let legacy_args = info.minecraft_arguments.clone();
|
||||
info = interfrost::api::modded::merge_partial_version(partial, info);
|
||||
if info.minecraft_arguments.is_none() {
|
||||
info.minecraft_arguments = legacy_args;
|
||||
}
|
||||
|
||||
for lib in &mut info.libraries {
|
||||
lib.name = lib.name.replace("${interpulse.gameVersion}", &version.id);
|
||||
|
|
@ -1032,6 +1048,9 @@ pub fn natives_missing(
|
|||
);
|
||||
}
|
||||
|
||||
if !has_main_artifact(lib) {
|
||||
continue;
|
||||
}
|
||||
let Ok(rel) = interfrost::utils::get_path_from_artifact(&lib.name) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -1060,6 +1079,9 @@ pub fn libraries_missing(
|
|||
if !lib.include_in_classpath {
|
||||
continue;
|
||||
}
|
||||
if !has_main_artifact(lib) {
|
||||
continue;
|
||||
}
|
||||
let Ok(rel) = interfrost::utils::get_path_from_artifact(&lib.name) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -1098,7 +1120,7 @@ pub fn game_files_missing(
|
|||
minecraft_updated: bool,
|
||||
) -> McResult<bool> {
|
||||
let index = paths::assets_index_dir()?.join(format!("{}.json", version_info.asset_index.id));
|
||||
let objects = if version_info.assets == "legacy" {
|
||||
let objects = if uses_legacy_assets(&version_info.assets) {
|
||||
paths::legacy_assets_dir()?
|
||||
} else {
|
||||
paths::assets_object_dir()?
|
||||
|
|
@ -1424,7 +1446,8 @@ pub async fn resolve_minecraft_version(
|
|||
version_index = manifest.versions.iter().position(|it| it.id == mc_version);
|
||||
}
|
||||
|
||||
let version_index = version_index.ok_or(McError::NoMatchingVersion)?;
|
||||
let version_index =
|
||||
version_index.ok_or_else(|| McError::InvalidVersion(mc_version.to_string()))?;
|
||||
let versions = &manifest.versions;
|
||||
|
||||
Ok((
|
||||
|
|
@ -1482,6 +1505,67 @@ mod tests {
|
|||
(index, objects)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_natives_only_library_has_no_main_jar_to_fetch() {
|
||||
let natives_only = serde_json::from_str::<Library>(
|
||||
r#"{
|
||||
"name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4+legacyfabric.15",
|
||||
"downloads": { "classifiers": { "natives-osx": { "sha1": "a", "size": 1, "url": "https://maven.legacyfabric.net/natives-osx.jar" } } },
|
||||
"natives": { "osx-arm64": "natives-osx" }
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let with_artifact = serde_json::from_str::<Library>(
|
||||
r#"{
|
||||
"name": "org.ow2.asm:asm:9.10.1",
|
||||
"downloads": { "artifact": { "sha1": "b", "size": 2, "url": "https://maven.fabricmc.net/asm.jar" } }
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let coordinates_only =
|
||||
serde_json::from_str::<Library>(r#"{ "name": "net.fabricmc:fabric-loader:0.19.5" }"#)
|
||||
.unwrap();
|
||||
|
||||
assert!(!has_main_artifact(&natives_only));
|
||||
assert!(has_main_artifact(&with_artifact));
|
||||
assert!(has_main_artifact(&coordinates_only));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_versions_keep_their_assets_in_a_flat_tree() {
|
||||
assert!(uses_legacy_assets("pre-1.6"));
|
||||
assert!(uses_legacy_assets("legacy"));
|
||||
assert!(!uses_legacy_assets("1.8"));
|
||||
assert!(!uses_legacy_assets("1.14"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_manifest_without_a_wildcard_resolves_per_game_version() {
|
||||
let manifest = serde_json::from_str::<interfrost::api::modded::Manifest>(
|
||||
r#"{"gameVersions": [
|
||||
{ "id": "b1.7.3", "stable": true, "loaders": [
|
||||
{ "id": "0.19.5", "url": "https://meta.example/b1.7.3/0.19.5.json", "stable": true },
|
||||
{ "id": "0.19.4", "url": "https://meta.example/b1.7.3/0.19.4.json", "stable": false }
|
||||
]},
|
||||
{ "id": "1.8.9", "stable": true, "loaders": [
|
||||
{ "id": "0.19.5", "url": "https://meta.example/1.8.9/0.19.5.json", "stable": true }
|
||||
]}
|
||||
]}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (saw, resolved) = resolve_loader_from_manifest(&manifest, "b1.7.3", None);
|
||||
assert!(saw);
|
||||
assert_eq!(resolved.unwrap().id, "0.19.5");
|
||||
|
||||
let (saw, resolved) = resolve_loader_from_manifest(&manifest, "b1.7.3", Some("0.19.4"));
|
||||
assert!(saw);
|
||||
assert_eq!(resolved.unwrap().id, "0.19.4");
|
||||
|
||||
let (saw, resolved) = resolve_loader_from_manifest(&manifest, "1.21", None);
|
||||
assert!(!saw && resolved.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_complete_assets_tree_needs_no_repair() {
|
||||
let dir = scratch("assets-ok");
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ struct MetadataInner {
|
|||
neo: Option<ModdedManifest>,
|
||||
fabric: Option<ModdedManifest>,
|
||||
quilt: Option<ModdedManifest>,
|
||||
legacyfabric: Option<ModdedManifest>,
|
||||
ornithe: Option<ModdedManifest>,
|
||||
}
|
||||
|
||||
impl MetadataStore {
|
||||
|
|
@ -50,6 +50,10 @@ impl MetadataStore {
|
|||
self.initialize(ctx).await?;
|
||||
}
|
||||
|
||||
if self.inner.minecraft.is_none() {
|
||||
self.refetch_errored(ctx).await;
|
||||
}
|
||||
|
||||
self.get_vanilla()
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +91,7 @@ impl MetadataStore {
|
|||
GameLoader::NeoForge => self.inner.neo.as_ref(),
|
||||
GameLoader::Fabric => self.inner.fabric.as_ref(),
|
||||
GameLoader::Quilt => self.inner.quilt.as_ref(),
|
||||
GameLoader::LegacyFabric => self.inner.legacyfabric.as_ref(),
|
||||
GameLoader::Ornithe => self.inner.ornithe.as_ref(),
|
||||
GameLoader::Vanilla => None,
|
||||
}
|
||||
.ok_or_else(|| McError::FetchError)
|
||||
|
|
@ -164,25 +168,28 @@ impl MetadataStore {
|
|||
check_modded!(neo);
|
||||
check_modded!(fabric);
|
||||
check_modded!(quilt);
|
||||
check_modded!(ornithe);
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn fetch_all(&mut self, ctx: &McCtx) {
|
||||
let (minecraft, forge, neo, fabric, quilt) = tokio::join!(
|
||||
let (minecraft, forge, neo, fabric, quilt, ornithe) = tokio::join!(
|
||||
fetch_vanilla_manifest(ctx),
|
||||
fetch_modded_manifest(ctx, GameLoader::Forge),
|
||||
fetch_modded_manifest(ctx, GameLoader::NeoForge),
|
||||
fetch_modded_manifest(ctx, GameLoader::Fabric),
|
||||
fetch_modded_manifest(ctx, GameLoader::Quilt),
|
||||
fetch_modded_manifest(ctx, GameLoader::Ornithe),
|
||||
);
|
||||
|
||||
self.inner.minecraft = minecraft.ok();
|
||||
self.inner.forge = forge.ok();
|
||||
self.inner.neo = neo.ok();
|
||||
self.inner.fabric = fabric.ok();
|
||||
self.inner.quilt = quilt.ok();
|
||||
keep_fetched(&mut self.inner.minecraft, minecraft);
|
||||
keep_fetched(&mut self.inner.forge, forge);
|
||||
keep_fetched(&mut self.inner.neo, neo);
|
||||
keep_fetched(&mut self.inner.fabric, fabric);
|
||||
keep_fetched(&mut self.inner.quilt, quilt);
|
||||
keep_fetched(&mut self.inner.ornithe, ornithe);
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, ctx))]
|
||||
|
|
@ -201,12 +208,8 @@ impl MetadataStore {
|
|||
|
||||
let mut loaders = Vec::new();
|
||||
for loader in GameLoader::modded_loaders() {
|
||||
let manifest = match self.get_modded(*loader) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(McError::NotModdedManifest(_)) => {
|
||||
continue
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
let Ok(manifest) = self.get_modded(*loader) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let found = manifest.game_versions.iter().any(|entry| {
|
||||
|
|
@ -229,6 +232,12 @@ impl MetadataStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn keep_fetched<T>(slot: &mut Option<T>, fetched: McResult<T>) {
|
||||
if let Ok(data) = fetched {
|
||||
*slot = Some(data);
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn fetch_vanilla_manifest(ctx: &McCtx) -> McResult<VanillaManifest> {
|
||||
match fetch_manifest::<VanillaManifest>(ctx, GameLoader::Vanilla).await {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue