From 41ce177483e759b147c0f98c79a83a043c09ca6a Mon Sep 17 00:00:00 2001 From: Julian Chang Date: Wed, 16 Sep 2026 18:02:21 -0400 Subject: [PATCH] Fix version-tag matching and bundle filtering --- .../src/packages/activity.rs | 51 +++++++++++++++++-- .../src/packages/dependencies.rs | 39 +++++++++++++- .../oneclient_core/src/game/shared_dir.rs | 1 + 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/packages/oneclient_content/src/packages/activity.rs b/packages/oneclient_content/src/packages/activity.rs index f789bc73..0497af22 100644 --- a/packages/oneclient_content/src/packages/activity.rs +++ b/packages/oneclient_content/src/packages/activity.rs @@ -7,10 +7,12 @@ use std::collections::{HashMap, HashSet}; use oneclient_common::domain::{ContentType, ProviderId}; use oneclient_db::dao::applied_migration as migration_dao; use oneclient_db::dao::artifact as artifact_dao; +use oneclient_db::dao::bundle as bundle_catalog_dao; use oneclient_db::dao::cluster_bundle as bundle_dao; use crate::ctx::ContentCtx; use crate::error::ContentResult; +use crate::packages::dependencies::base_game_version; use crate::packages::store::PackageStore; use crate::packages::types::LinkedArtifactInfo; @@ -96,6 +98,7 @@ fn group_duplicates( pub async fn disable_foreign_game_versions( cluster_id: i64, mc_version: &str, + mc_loader: i64, ctx: &ContentCtx, ) -> ContentResult> { if mc_version.is_empty() { @@ -105,9 +108,21 @@ pub async fn disable_foreign_game_versions( let linked = PackageStore::list_linked_artifacts(cluster_id, ctx).await?; // bundle installs skip the compatibility check so launch must trust them too + let live_bundles: HashSet = + bundle_catalog_dao::list_visible_for_version_loader(&ctx.db, mc_version, mc_loader) + .await? + .into_iter() + .filter_map(|row| row.name) + .collect(); + let from_bundle: HashSet = bundle_dao::list_bundle_tracked(&ctx.db, cluster_id) .await? .into_iter() + .filter(|row| { + row.bundle_name + .as_deref() + .is_some_and(|name| live_bundles.contains(name)) + }) .map(|row| row.hash) .collect(); @@ -175,11 +190,13 @@ fn built_for_another_game_version(stated: &[String], mc_version: &str) -> bool { } fn covers_game_version(stated: &[String], mc_version: &str) -> bool { + let wanted = base_game_version(mc_version); stated.is_empty() || stated.iter().any(|version| { - version == mc_version - || mc_version - .strip_prefix(version.as_str()) + let built_for = base_game_version(version); + built_for == wanted + || wanted + .strip_prefix(built_for) .is_some_and(|rest| rest.starts_with('.')) }) } @@ -295,6 +312,34 @@ mod tests { ); } + #[test] + fn a_pre_release_tag_is_the_same_game_version() { + let foreign = |rows: &[&str], mc_version: &str| { + built_for_another_game_version( + &rows.iter().map(|r| (*r).to_string()).collect::>(), + mc_version, + ) + }; + + assert!( + !foreign(&["[\"26.3-rc-1\"]"], "26.3"), + "a jar built against the release candidate runs on the release" + ); + assert!(!foreign(&["[\"26.3-snapshot-7\",\"26.3-pre-2\"]"], "26.3")); + assert!( + !foreign(&["[\"26.3\"]"], "26.3-snapshot-10"), + "and a snapshot cluster keeps the release build" + ); + assert!( + !foreign(&["[\"26.3-rc-1\"]"], "26.3.1"), + "the component prefix still applies once the tag is off" + ); + assert!( + foreign(&["[\"26.2-rc-1\"]"], "26.3"), + "stripping the tag must not blur two game versions together" + ); + } + #[test] fn only_projects_with_several_copies_are_reconciled() { let groups = group_duplicates(&[ diff --git a/packages/oneclient_content/src/packages/dependencies.rs b/packages/oneclient_content/src/packages/dependencies.rs index 44bd8c3d..1a9af701 100644 --- a/packages/oneclient_content/src/packages/dependencies.rs +++ b/packages/oneclient_content/src/packages/dependencies.rs @@ -193,9 +193,18 @@ fn choose_version( .cloned() } +#[must_use] +pub(crate) fn base_game_version(version: &str) -> &str { + version + .split_once('-') + .filter(|(base, _)| base.contains('.')) + .map_or(version, |(base, _)| base) +} + #[must_use] pub(crate) fn supports_game_version(game_versions: &[String], mc_version: &str) -> bool { - game_versions.is_empty() || game_versions.iter().any(|v| v == mc_version) + let wanted = base_game_version(mc_version); + game_versions.is_empty() || game_versions.iter().any(|v| base_game_version(v) == wanted) } fn fits_cluster(version: &VersionSummary, cluster: &ClusterRow, loader: GameLoader) -> bool { @@ -230,6 +239,34 @@ mod tests { use super::*; + #[test] + fn a_version_is_offered_only_for_the_game_version_it_names() { + let offered = |stated: &[&str], mc_version: &str| { + supports_game_version( + &stated.iter().map(|v| (*v).to_string()).collect::>(), + mc_version, + ) + }; + + assert!(offered(&[], "26.3"), "an undescribed version is offered"); + assert!(offered(&["26.3"], "26.3")); + assert!( + offered(&["26.3-rc-1"], "26.3"), + "a pre-release tag names the same game version" + ); + assert!(offered(&["26.3"], "26.3-snapshot-10")); + assert!( + !offered(&["26.1"], "26.1.2"), + "strict where the launch-time check is lenient: #830 stopped 26.1 \ + builds being offered to a 26.1.2 cluster" + ); + assert!(!offered(&["1.21.11"], "26.1.2")); + assert!( + !offered(&["rd-132211"], "rd-160052"), + "pre-classic names the build after the dash, so there is no tag to strip" + ); + } + fn summary( version_id: &str, release_type: ReleaseType, diff --git a/packages/oneclient_core/src/game/shared_dir.rs b/packages/oneclient_core/src/game/shared_dir.rs index 2d496e26..66178f09 100644 --- a/packages/oneclient_core/src/game/shared_dir.rs +++ b/packages/oneclient_core/src/game/shared_dir.rs @@ -139,6 +139,7 @@ pub async fn materialize_content( match oneclient_content::packages::disable_foreign_game_versions( cluster.id, &cluster.mc_version, + cluster.mc_loader as i64, &services.content(), ) .await