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:
Julian Chang 2026-09-12 18:11:21 -04:00 • committed by GitHub
parent fe8bb6bfcc
commit 44dfa32233
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 382 additions and 84 deletions

4
Cargo.lock generated
View file

@ -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",

View file

@ -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 = [

View file

@ -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;

View file

@ -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,

View file

@ -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");
}
}

View file

@ -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,

View file

@ -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),

View file

@ -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,

View file

@ -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,

View file

@ -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");
}
}

View file

@ -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");

View file

@ -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 {