From d52559ab930abaefc9a5adedd7edb5c5bfc337d1 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Tue, 21 Nov 2023 18:18:04 +0100 Subject: [PATCH] Feat[modpacks]: verify mod sha1 --- .../modpacks/api/CurseforgeApi.java | 21 ++++++++++- .../modpacks/api/ModDownloader.java | 35 +++++++++++++++---- .../modloaders/modpacks/api/ModrinthApi.java | 2 +- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java index 5f22e82a8..733c713c6 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java @@ -2,6 +2,8 @@ package net.kdt.pojavlaunch.modloaders.modpacks.api; import android.util.Log; +import androidx.annotation.Nullable; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -181,7 +183,7 @@ public class CurseforgeApi implements ModpackApi{ if(url == null && curseFile.required) throw new IOException("Failed to obtain download URL for "+curseFile.projectID+" "+curseFile.fileID); else if(url == null) return null; - return new ModDownloader.FileInfo(url, FileUtils.getFileName(url)); + return new ModDownloader.FileInfo(url, FileUtils.getFileName(url), getDownloadSha1(curseFile.projectID, curseFile.fileID)); }); } modDownloader.awaitFinish((c,m)-> @@ -240,6 +242,23 @@ public class CurseforgeApi implements ModpackApi{ return null; } + private @Nullable String getDownloadSha1(long projectID, long fileID) { + // Try the api endpoint, die in the other case + JsonObject response = mApiHandler.get("mods/"+projectID+"/files/"+fileID, JsonObject.class); + if (response == null || response.get("data").isJsonNull()) return null; + + JsonArray hashes = response.get("data").getAsJsonObject().getAsJsonArray("hashes"); + for (JsonElement jsonElement : hashes) { + // The sha1 = 1; md5 = 2; + if(jsonElement.getAsJsonObject().get("algo").getAsInt() == ALGO_SHA_1){ + return jsonElement.getAsJsonObject().get("value").getAsString(); + } + } + + // No hashes available + return null; + } + private boolean verifyManifest(CurseManifest manifest) { if(!"minecraftModpack".equals(manifest.manifestType)) return false; if(manifest.manifestVersion != 1) return false; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModDownloader.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModDownloader.java index 09ddd7c6e..9d1de62ad 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModDownloader.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModDownloader.java @@ -1,5 +1,7 @@ package net.kdt.pojavlaunch.modloaders.modpacks.api; +import androidx.annotation.Nullable; + import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.utils.DownloadUtils; @@ -34,10 +36,10 @@ public class ModDownloader { this.mUseFileCount = useFileCount; } - public void submitDownload(int fileSize, String relativePath, String... url) { + public void submitDownload(int fileSize, String relativePath, @Nullable String downloadHash, String... url) { if(mUseFileCount) mTotalSize += 1; else mTotalSize += fileSize; - mDownloadPool.execute(new DownloadTask(url, new File(mDestinationDirectory, relativePath))); + mDownloadPool.execute(new DownloadTask(url, new File(mDestinationDirectory, relativePath), downloadHash)); } public void submitDownload(FileInfoProvider infoProvider) { @@ -93,7 +95,7 @@ public class ModDownloader { FileInfo fileInfo = mFileInfoProvider.getFileInfo(); if(fileInfo == null) return; new DownloadTask(new String[]{fileInfo.url}, - new File(mDestinationDirectory, fileInfo.relativePath)).run(); + new File(mDestinationDirectory, fileInfo.relativePath), fileInfo.sha1).run(); }catch (IOException e) { downloadFailed(e); } @@ -103,12 +105,14 @@ public class ModDownloader { class DownloadTask implements Runnable, Tools.DownloaderFeedback { private final String[] mDownloadUrls; private final File mDestination; + private final String mSha1; private int last = 0; public DownloadTask(String[] downloadurls, - File downloadDestination) { + File downloadDestination, String downloadHash) { this.mDownloadUrls = downloadurls; this.mDestination = downloadDestination; + this.mSha1 = downloadHash; } @Override @@ -116,8 +120,23 @@ public class ModDownloader { IOException exception = null; for(String sourceUrl : mDownloadUrls) { try { - exception = tryDownload(sourceUrl); - if(exception == null) return; + int i=0; + while (i < 5){ + exception = tryDownload(sourceUrl); + if(exception == null) { + if(mSha1 != null && !Tools.compareSHA1(mDestination, mSha1)){ + // Remove the target file and retry + i++; + mDestination.delete(); + continue; + } + // No Sha or it matched + break; + } + // If there is an exception, try another source + break; + } + }catch (InterruptedException e) { return; } @@ -159,10 +178,12 @@ public class ModDownloader { public static class FileInfo { public final String url; public final String relativePath; + public final String sha1; - public FileInfo(String url, String relativePath) { + public FileInfo(String url, String relativePath, @Nullable String sha1) { this.url = url; this.relativePath = relativePath; + this.sha1 = sha1; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java index 0cd8eceef..e79b5ad0f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java @@ -125,7 +125,7 @@ public class ModrinthApi implements ModpackApi{ ModDownloader modDownloader = new ModDownloader(instanceDestination); for(ModrinthIndex.ModrinthIndexFile indexFile : modrinthIndex.files) { - modDownloader.submitDownload(indexFile.fileSize, indexFile.path, indexFile.downloads); + modDownloader.submitDownload(indexFile.fileSize, indexFile.path, indexFile.hashes.sha1, indexFile.downloads); } modDownloader.awaitFinish(new DownloaderProgressWrapper(R.string.modpack_download_downloading_mods, ProgressLayout.INSTALL_MODPACK)); ProgressLayout.setProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.modpack_download_applying_overrides, 1, 2);