Skip to content

Commit cac3ed5

Browse files
committed
Enhance mod version compatibility checks with stricter fallback logic, preventing false positives and ensuring accurate mod selection based on available patch versions.
1 parent 1302bf2 commit cac3ed5

3 files changed

Lines changed: 186 additions & 83 deletions

File tree

modules/instance_creation.sh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,18 @@ EOF
410410
echo " → Wildcard match result: ${mod_url:-'(empty)'}"
411411
fi
412412

413-
# Try prefix matching (any version starting with major.minor)
413+
# Try limited previous patch version (more restrictive than prefix matching)
414414
if [[ -z "$mod_url" || "$mod_url" == "null" ]]; then
415-
echo " → Trying prefix matching with: $mc_major_minor"
416-
mod_url=$(printf "%s" "$resolve_data" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] | startswith($v) and (.loaders[] == "fabric")) | .files[0].url' 2>/dev/null | head -n1)
417-
echo " → Prefix match result: ${mod_url:-'(empty)'}"
415+
local mc_patch_version
416+
mc_patch_version=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.([0-9]+)' | grep -oE '[0-9]+$')
417+
if [[ -n "$mc_patch_version" && $mc_patch_version -gt 0 ]]; then
418+
# Try one patch version down (e.g., if looking for 1.21.6, try 1.21.5)
419+
local prev_patch=$((mc_patch_version - 1))
420+
local mc_prev_version="$mc_major_minor.$prev_patch"
421+
echo " → Trying limited backwards compatibility with: $mc_prev_version"
422+
mod_url=$(printf "%s" "$resolve_data" | jq -r --arg v "$mc_prev_version" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[0].url' 2>/dev/null | head -n1)
423+
echo " → Limited backwards compatibility result: ${mod_url:-'(empty)'}"
424+
fi
418425
fi
419426
fi
420427

modules/mod_management.sh

Lines changed: 128 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -77,41 +77,69 @@ check_modrinth_mod() {
7777

7878
# STAGE 2: Try major.minor version match if exact match failed
7979
# Example: "1.21.3" -> try "1.21", "1.21.x", "1.21.0"
80+
# BUT ONLY if no specific patch version exists that's higher than what we're looking for
8081
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
8182
local mc_major_minor
8283
mc_major_minor=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+') # Extract "1.21" from "1.21.3"
8384

84-
# Try exact major.minor (e.g., "1.21")
85-
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
86-
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
87-
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
88-
fi
85+
# Before trying major.minor match, check if this version is higher than existing patch versions
86+
# This prevents matching 1.21 when looking for 1.21.6 if the highest patch version is only 1.21.5
87+
local mc_patch_version
88+
mc_patch_version=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.([0-9]+)' | grep -oE '[0-9]+$')
89+
local should_try_fallback=true
8990

90-
# Try wildcard version format (e.g., "1.21.x")
91-
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
92-
local mc_major_minor_x="$mc_major_minor.x"
93-
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_x" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
94-
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
95-
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_x" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
91+
# If we have a patch version (e.g. 1.21.6), check if it's higher than any available versions
92+
if [[ -n "$mc_patch_version" ]]; then
93+
# Check if there's a standalone major.minor version (e.g., "1.21" without patch)
94+
local has_standalone_major_minor=$(printf "%s" "$version_json" | jq -r --arg major_minor "$mc_major_minor" '
95+
.[] | select(.game_versions[] == $major_minor and (.loaders[] == "fabric")) | .version_number' 2>/dev/null | head -n1)
96+
97+
# Get the highest patch version available for this major.minor series
98+
local highest_patch=$(printf "%s" "$version_json" | jq -r --arg major_minor "$mc_major_minor" '
99+
[.[] | select(.game_versions[] | test("^" + $major_minor + "\\.[0-9]+$") and (.loaders[] == "fabric")) |
100+
.game_versions[] | select(test("^" + $major_minor + "\\.[0-9]+$")) |
101+
split(".")[2] | tonumber] | if length > 0 then max else empty end' 2>/dev/null)
102+
103+
# Don't try fallback if:
104+
# 1. There's a standalone major.minor version (e.g., "1.21") and we're requesting a patch version, OR
105+
# 2. There are patch versions and our requested patch is higher than the highest available
106+
if [[ -n "$has_standalone_major_minor" && "$has_standalone_major_minor" != "null" ]] ||
107+
[[ -n "$highest_patch" && "$highest_patch" != "null" && "$mc_patch_version" -gt "$highest_patch" ]]; then
108+
should_try_fallback=false
96109
fi
97110
fi
98111

99-
# Try zero-padded version format (e.g., "1.21.0")
100-
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
101-
local mc_major_minor_0="$mc_major_minor.0"
102-
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_0" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
112+
# Only try major.minor fallback if it's safe to do so
113+
if [[ "$should_try_fallback" == true ]]; then
114+
# Try exact major.minor (e.g., "1.21")
115+
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
103116
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
104-
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_0" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
117+
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
105118
fi
106-
fi
107-
108-
# Try prefix matching (any version starting with major.minor)
109-
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
110-
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] | startswith($v) and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
111-
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
112-
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor" '.[] | select(.game_versions[] | startswith($v) and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
119+
120+
# Try wildcard version format (e.g., "1.21.x")
121+
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
122+
local mc_major_minor_x="$mc_major_minor.x"
123+
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_x" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
124+
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
125+
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_x" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
126+
fi
127+
fi
128+
129+
# Try zero-padded version format (e.g., "1.21.0")
130+
if [[ -z "$file_url" || "$file_url" == "null" ]]; then
131+
local mc_major_minor_0="$mc_major_minor.0"
132+
file_url=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_0" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .files[] | select(.primary == true) | .url' 2>/dev/null | head -n1)
133+
if [[ -n "$file_url" && "$file_url" != "null" ]]; then
134+
dep_ids=$(printf "%s" "$version_json" | jq -r --arg v "$mc_major_minor_0" '.[] | select(.game_versions[] == $v and (.loaders[] == "fabric")) | .dependencies[]? | select(.dependency_type=="required") | .project_id' 2>/dev/null | tr '\n' ' ')
135+
fi
113136
fi
114137
fi
138+
139+
# DISABLED: Limited prefix matching - this was allowing false positives
140+
# We've disabled this section to prevent matching lower patch versions
141+
# when a higher patch version is requested that doesn't exist
142+
# (e.g., preventing 1.21.5 from matching when 1.21.6 is requested)
115143
fi
116144

117145
# STAGE 3: Advanced pattern matching with comprehensive version range support
@@ -123,43 +151,72 @@ check_modrinth_mod() {
123151
local mc_major_minor_x="$mc_major_minor.x"
124152
local mc_major_minor_0="$mc_major_minor.0"
125153

126-
# Simplified and corrected jq filter that handles multiple version pattern types
127-
# Fixed: game_versions is always at release level in Modrinth API, not file level
128-
local jq_filter='
129-
.[] as $release
130-
| select($release.loaders[] == "fabric")
131-
| select(
132-
$release.game_versions[]
133-
| test("^" + $mc_major_minor + "\\..*$") or
134-
. == $mc_version or
135-
. == $mc_major_minor or
136-
. == $mc_major_minor_x or
137-
. == $mc_major_minor_0
138-
)
139-
| $release.files[]
140-
| select(.primary == true)
141-
| {
142-
url,
143-
dependencies: ($release.dependencies // [] | map(select(.dependency_type == "required") | .project_id))
144-
}
145-
| @base64
146-
'
154+
# Apply the same fallback safety check as in STAGE 2
155+
local mc_patch_version
156+
mc_patch_version=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.([0-9]+)' | grep -oE '[0-9]+$')
157+
local should_try_stage3_fallback=true
147158

148-
# Execute the corrected jq filter with all version variants
149-
local jq_result
150-
jq_result=$(printf "%s" "$version_json" | jq -r \
151-
--arg mc_version "$MC_VERSION" \
152-
--arg mc_major_minor "$mc_major_minor" \
153-
--arg mc_major_minor_x "$mc_major_minor_x" \
154-
--arg mc_major_minor_0 "$mc_major_minor_0" \
155-
"$jq_filter" 2>/dev/null | head -n1)
159+
# If we have a patch version, check if it's higher than any available versions
160+
if [[ -n "$mc_patch_version" ]]; then
161+
# Check if there's a standalone major.minor version (e.g., "1.21" without patch)
162+
local has_standalone_major_minor=$(printf "%s" "$version_json" | jq -r --arg major_minor "$mc_major_minor" '
163+
.[] | select(.game_versions[] == $major_minor and (.loaders[] == "fabric")) | .version_number' 2>/dev/null | head -n1)
164+
165+
# Get the highest patch version available for this major.minor series
166+
local highest_patch=$(printf "%s" "$version_json" | jq -r --arg major_minor "$mc_major_minor" '
167+
[.[] | select(.game_versions[] | test("^" + $major_minor + "\\.[0-9]+$") and (.loaders[] == "fabric")) |
168+
.game_versions[] | select(test("^" + $major_minor + "\\.[0-9]+$")) |
169+
split(".")[2] | tonumber] | if length > 0 then max else empty end' 2>/dev/null)
170+
171+
# Don't try fallback if:
172+
# 1. There's a standalone major.minor version (e.g., "1.21") and we're requesting a patch version, OR
173+
# 2. There are patch versions and our requested patch is higher than the highest available
174+
if [[ -n "$has_standalone_major_minor" && "$has_standalone_major_minor" != "null" ]] ||
175+
[[ -n "$highest_patch" && "$highest_patch" != "null" && "$mc_patch_version" -gt "$highest_patch" ]]; then
176+
should_try_stage3_fallback=false
177+
fi
178+
fi
179+
180+
# Only proceed with STAGE 3 if fallback is safe
181+
if [[ "$should_try_stage3_fallback" == true ]]; then
182+
# Simplified and corrected jq filter with stricter version matching
183+
# Fixed: game_versions is always at release level in Modrinth API, not file level
184+
# Made version matching more strict to avoid false positives
185+
local jq_filter='
186+
.[] as $release
187+
| select($release.loaders[] == "fabric")
188+
| select(
189+
$release.game_versions[]
190+
| (. == $mc_version or
191+
. == $mc_major_minor or
192+
. == $mc_major_minor_x or
193+
. == $mc_major_minor_0)
194+
)
195+
| $release.files[]
196+
| select(.primary == true)
197+
| {
198+
url,
199+
dependencies: ($release.dependencies // [] | map(select(.dependency_type == "required") | .project_id))
200+
}
201+
| @base64
202+
'
203+
204+
# Execute the corrected jq filter with all version variants
205+
local jq_result
206+
jq_result=$(printf "%s" "$version_json" | jq -r \
207+
--arg mc_version "$MC_VERSION" \
208+
--arg mc_major_minor "$mc_major_minor" \
209+
--arg mc_major_minor_x "$mc_major_minor_x" \
210+
--arg mc_major_minor_0 "$mc_major_minor_0" \
211+
"$jq_filter" 2>/dev/null | head -n1)
156212

157-
# Decode the base64-encoded result and extract URL and dependencies
158-
if [[ -n "$jq_result" && "$jq_result" != "null" ]]; then
159-
local decoded
160-
if decoded=$(echo "$jq_result" | base64 --decode 2>/dev/null); then
161-
file_url=$(echo "$decoded" | jq -r '.url' 2>/dev/null)
162-
dep_ids=$(echo "$decoded" | jq -r '.dependencies[]?' 2>/dev/null | tr '\n' ' ')
213+
# Decode the base64-encoded result and extract URL and dependencies
214+
if [[ -n "$jq_result" && "$jq_result" != "null" ]]; then
215+
local decoded
216+
if decoded=$(echo "$jq_result" | base64 --decode 2>/dev/null); then
217+
file_url=$(echo "$decoded" | jq -r '.url' 2>/dev/null)
218+
dep_ids=$(echo "$decoded" | jq -r '.dependencies[]?' 2>/dev/null | tr '\n' ' ')
219+
fi
163220
fi
164221
fi
165222
fi
@@ -672,12 +729,12 @@ resolve_modrinth_dependencies_api() {
672729
local mc_major_minor
673730
mc_major_minor=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+') # Extract "1.21" from "1.21.3"
674731

675-
# Simple jq filter to get dependencies from any compatible fabric version
732+
# Simple jq filter to get dependencies from compatible fabric versions with strict matching
676733
# Use temporary file to avoid command line length limits
677734
dependencies=$(jq -r "
678735
.[]
679736
| select(.loaders[]? == \"fabric\")
680-
| select(.game_versions[]? | test(\"$mc_major_minor\"))
737+
| select(.game_versions[]? | (. == \"$MC_VERSION\" or . == \"$mc_major_minor\" or . == \"${mc_major_minor}.x\" or . == \"${mc_major_minor}.0\"))
681738
| .dependencies[]?
682739
| select(.dependency_type == \"required\")
683740
| .project_id
@@ -785,8 +842,8 @@ resolve_curseforge_dependencies_api() {
785842
local mc_major_minor
786843
mc_major_minor=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+')
787844

788-
# Extract file ID from the most recent compatible file
789-
local file_id=$(jq -r --arg v "$MC_VERSION" --arg mmv "$mc_major_minor" '.data[] | select(.gameVersions[] == $v or .gameVersions[] == $mmv or (.gameVersions[] | startswith($mmv))) | .id' "$files_temp" 2>/dev/null | head -n1)
845+
# Extract file ID from the most recent compatible file with strict version matching
846+
local file_id=$(jq -r --arg v "$MC_VERSION" --arg mmv "$mc_major_minor" '.data[] | select(.gameVersions[] == $v or .gameVersions[] == $mmv or .gameVersions[] == ($mmv + ".x") or .gameVersions[] == ($mmv + ".0")) | .id' "$files_temp" 2>/dev/null | head -n1)
790847

791848
if [[ -n "$file_id" && "$file_id" != "null" ]]; then
792849
# Get dependencies for this specific file
@@ -1086,9 +1143,16 @@ get_curseforge_download_url() {
10861143
download_url=$(jq -r --arg v "$mc_major_minor_x" '.data[]? | select(.gameVersions[]? == $v) | .downloadUrl' "$temp_file" 2>/dev/null | head -n1)
10871144
fi
10881145

1089-
# Try prefix matching (any version starting with major.minor)
1146+
# Try limited previous patch version (more restrictive than prefix matching)
10901147
if [[ -z "$download_url" || "$download_url" == "null" ]]; then
1091-
download_url=$(jq -r --arg v "$mc_major_minor" '.data[]? | select(.gameVersions[]? | startswith($v)) | .downloadUrl' "$temp_file" 2>/dev/null | head -n1)
1148+
local mc_patch_version
1149+
mc_patch_version=$(echo "$MC_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.([0-9]+)' | grep -oE '[0-9]+$')
1150+
if [[ -n "$mc_patch_version" && $mc_patch_version -gt 0 ]]; then
1151+
# Try one patch version down (e.g., if looking for 1.21.6, try 1.21.5)
1152+
local prev_patch=$((mc_patch_version - 1))
1153+
local mc_prev_version="$mc_major_minor.$prev_patch"
1154+
download_url=$(jq -r --arg v "$mc_prev_version" '.data[]? | select(.gameVersions[]? == $v) | .downloadUrl' "$temp_file" 2>/dev/null | head -n1)
1155+
fi
10921156
fi
10931157

10941158
# If still no URL found, try the latest file

0 commit comments

Comments
 (0)