Skip to content

Commit 024e9c5

Browse files
committed
fix: Boundary-aware version matching for download URLs and update assets
- Add VersionUtil.containsVersion() to prevent substring false positives (e.g. "1.21.1" no longer matches "1.21.10" or "1.21.11") - AddonMetadata.getDownloadUrls(): skip latest_release when it doesn't match current MC version, deduplicate against downloads list - GitHubReleaseAPI.findJarAsset(): select version-matching JAR from multi-asset releases, return empty instead of wrong-version fallback - AddonManager.downloadAddon(): rename file to match actual URL on fallback success, strip query strings from filenames, isolate rename errors so they don't mask successful downloads - UpdateInfo.getVersionChangeDisplay(): show only new version instead of arrow notation that doesn't render in Meteor's GUI
1 parent 9a8fe89 commit 024e9c5

5 files changed

Lines changed: 83 additions & 19 deletions

File tree

src/main/java/com/cope/meteoraddons/models/AddonMetadata.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,19 @@ public String[] getDownloadUrls() {
8181
}
8282

8383
List<String> urls = new ArrayList<>();
84+
String currentVersion = VersionUtil.getCurrentMinecraftVersion();
8485

85-
// Priority 1: Latest Release
86-
if (links.latest_release != null && !links.latest_release.isEmpty()) {
86+
// Priority 1: Latest Release (only if it matches current MC version)
87+
if (links.latest_release != null && !links.latest_release.isEmpty()
88+
&& VersionUtil.containsVersion(links.latest_release, currentVersion)) {
8789
urls.add(links.latest_release);
8890
}
8991

9092
// Priority 2: Compatible downloads from the list
9193
if (links.downloads != null && !links.downloads.isEmpty()) {
92-
String currentVersion = VersionUtil.getCurrentMinecraftVersion();
93-
9494
links.downloads.stream()
95-
.filter(url -> url != null && url.contains(currentVersion))
95+
.filter(url -> url != null && VersionUtil.containsVersion(url, currentVersion))
96+
.filter(url -> !urls.contains(url))
9697
.forEach(urls::add);
9798
}
9899

src/main/java/com/cope/meteoraddons/models/UpdateInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ public Path getLocalJarPath() {
7777
}
7878

7979
/**
80-
* Get a display string showing version change.
80+
* Get a display string showing the new version available.
8181
*/
8282
public String getVersionChangeDisplay() {
83-
if (currentVersion != null && newVersion != null && !currentVersion.equals(newVersion)) {
84-
return currentVersion + " → " + newVersion;
83+
if (newVersion != null && !newVersion.isEmpty()) {
84+
return newVersion;
8585
}
8686
return "Update available";
8787
}

src/main/java/com/cope/meteoraddons/systems/AddonManager.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import java.io.IOException;
2121
import java.lang.reflect.Type;
22+
import java.nio.file.Files;
2223
import java.nio.file.Path;
24+
import java.nio.file.StandardCopyOption;
2325
import java.util.ArrayList;
2426
import java.util.List;
2527
import java.util.stream.Collectors;
@@ -210,14 +212,28 @@ public boolean downloadAddon(OnlineAddon addon) {
210212
Path modsFolder = meteorFolder.getParent().resolve("mods");
211213

212214
String url = downloadUrls[0];
213-
String fileName = url.substring(url.lastIndexOf('/') + 1);
215+
String fileName = extractFileName(url);
214216
Path destPath = modsFolder.resolve(fileName);
215217

216218
MeteorAddonsAddon.LOG.info("Downloading addon {} to {}", addon.getName(), destPath);
217219

218220
String successUrl = HttpClient.downloadFileWithFallback(downloadUrls, destPath);
219221

220222
if (successUrl != null) {
223+
// If a fallback URL succeeded, rename the file to match the actual download
224+
if (!successUrl.equals(url)) {
225+
try {
226+
String actualFileName = extractFileName(successUrl);
227+
Path actualDestPath = modsFolder.resolve(actualFileName);
228+
if (!destPath.equals(actualDestPath)) {
229+
Files.move(destPath, actualDestPath, StandardCopyOption.REPLACE_EXISTING);
230+
MeteorAddonsAddon.LOG.info("Renamed downloaded file to match actual URL: {}", actualFileName);
231+
}
232+
} catch (Exception e) {
233+
MeteorAddonsAddon.LOG.warn("Failed to rename downloaded file, keeping original name: {}", e.getMessage());
234+
}
235+
}
236+
221237
MeteorAddonsAddon.LOG.info("Successfully downloaded addon: {}", addon.getName());
222238
installedAddonNames.add(addon.getName());
223239
save();
@@ -234,6 +250,17 @@ public boolean downloadAddon(OnlineAddon addon) {
234250
}
235251
}
236252

253+
/**
254+
* Extract the filename from a URL, stripping any query string or fragment.
255+
*/
256+
private static String extractFileName(String url) {
257+
int queryStart = url.indexOf('?');
258+
if (queryStart != -1) url = url.substring(0, queryStart);
259+
int fragmentStart = url.indexOf('#');
260+
if (fragmentStart != -1) url = url.substring(0, fragmentStart);
261+
return url.substring(url.lastIndexOf('/') + 1);
262+
}
263+
237264
public List<Addon> getOnlineAddons() {
238265
return onlineAddons;
239266
}

src/main/java/com/cope/meteoraddons/util/GitHubReleaseAPI.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,42 @@ public static Optional<ReleaseInfo> getLatestRelease(String owner, String repo)
7575
}
7676

7777
/**
78-
* Find the asset digest for a JAR file from the release.
79-
* Looks for .jar files and returns the first match's digest.
78+
* Find the JAR asset from a release that matches the current Minecraft version.
79+
* If multiple JARs exist, selects the one whose filename contains the current version
80+
* and returns empty if none match. Single-JAR releases are assumed version-agnostic.
8081
*/
8182
public static Optional<AssetInfo> findJarAsset(ReleaseInfo release) {
8283
if (release.assets == null || release.assets.isEmpty()) {
8384
return Optional.empty();
8485
}
8586

86-
return release.assets.stream()
87+
List<Asset> jars = release.assets.stream()
8788
.filter(asset -> asset.name != null && asset.name.endsWith(".jar"))
88-
.findFirst()
89-
.map(asset -> new AssetInfo(
90-
asset.name,
91-
asset.browserDownloadUrl,
92-
HashUtil.parseGitHubDigest(asset.digest),
93-
asset.size
94-
));
89+
.toList();
90+
91+
if (jars.isEmpty()) {
92+
return Optional.empty();
93+
}
94+
95+
String currentVersion = VersionUtil.getCurrentMinecraftVersion();
96+
97+
Optional<Asset> versionMatch = jars.stream()
98+
.filter(asset -> VersionUtil.containsVersion(asset.name, currentVersion))
99+
.findFirst();
100+
101+
// Multi-JAR release with no version match — no safe fallback
102+
if (versionMatch.isEmpty() && jars.size() > 1) {
103+
return Optional.empty();
104+
}
105+
106+
Asset match = versionMatch.orElse(jars.get(0));
107+
108+
return Optional.of(new AssetInfo(
109+
match.name,
110+
match.browserDownloadUrl,
111+
HashUtil.parseGitHubDigest(match.digest),
112+
match.size
113+
));
95114
}
96115

97116
/**

src/main/java/com/cope/meteoraddons/util/VersionUtil.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,21 @@ public static String getCurrentMinecraftVersion() {
1111
}
1212
return cachedVersion;
1313
}
14+
15+
/**
16+
* Performs a boundary-aware version match within a string.
17+
* Returns true only if the version occurrence is not immediately followed by another digit,
18+
* ensuring exact version segments are matched (e.g. "1.21.1" won't match inside "1.21.10").
19+
*/
20+
public static boolean containsVersion(String text, String version) {
21+
int idx = text.indexOf(version);
22+
while (idx != -1) {
23+
int end = idx + version.length();
24+
if (end >= text.length() || !Character.isDigit(text.charAt(end))) {
25+
return true;
26+
}
27+
idx = text.indexOf(version, idx + 1);
28+
}
29+
return false;
30+
}
1431
}

0 commit comments

Comments
 (0)