mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-15 07:39:00 -04:00
CurseForge modpack download (WIP) + more generalization
This commit is contained in:
parent
b430edbc0b
commit
19a781ad18
@ -987,6 +987,12 @@ public final class Tools {
|
||||
return prefixedName.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length());
|
||||
}
|
||||
|
||||
public static String getFileName(String pathOrUrl) {
|
||||
int lastSlashIndex = pathOrUrl.lastIndexOf('/');
|
||||
if(lastSlashIndex == -1) return null;
|
||||
return pathOrUrl.substring(lastSlashIndex);
|
||||
}
|
||||
|
||||
public static String getSelectedRuntime(MinecraftProfile minecraftProfile) {
|
||||
String runtime = LauncherPreferences.PREF_DEFAULT_RUNTIME;
|
||||
String profileRuntime = getRuntimeName(minecraftProfile.javaDir);
|
||||
|
@ -5,14 +5,16 @@ import android.util.Log;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.kdt.mcgui.ProgressLayout;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.CurseManifest;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModrinthIndex;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
|
||||
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
|
||||
import net.kdt.pojavlaunch.utils.ZipUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -31,6 +33,8 @@ public class CurseforgeApi implements ModpackApi{
|
||||
// https://api.curseforge.com/v1/categories?gameId=432 and search for "Mods" (case-sensitive)
|
||||
private static final int CURSEFORGE_MOD_CLASS_ID = 6;
|
||||
private static final int CURSEFORGE_PAGINATION_SIZE = 50;
|
||||
private static final int CURSEFORGE_PAGINATION_END_REACHED = -1;
|
||||
private static final int CURSEFORGE_PAGINATION_ERROR = -2;
|
||||
|
||||
private final ApiHandler mApiHandler = new ApiHandler("https://api.curseforge.com/v1", "$2a$10$Vxkj4kH1Ekf8EsS4Mx8b2eVTHsht107Lk2erVEUtnbqvojsLy.jYq");
|
||||
|
||||
@ -71,10 +75,11 @@ public class CurseforgeApi implements ModpackApi{
|
||||
public ModDetail getModDetails(ModItem item) {
|
||||
ArrayList<JsonObject> allModDetails = new ArrayList<>();
|
||||
int index = 0;
|
||||
while(index != -1 && index != -2) {
|
||||
while(index != CURSEFORGE_PAGINATION_END_REACHED &&
|
||||
index != CURSEFORGE_PAGINATION_ERROR) {
|
||||
index = getPaginatedDetails(allModDetails, index, item.id);
|
||||
}
|
||||
if(index == -2) return null;
|
||||
if(index == CURSEFORGE_PAGINATION_ERROR) return null;
|
||||
int length = allModDetails.size();
|
||||
String[] versionNames = new String[length];
|
||||
String[] mcVersionNames = new String[length];
|
||||
@ -99,8 +104,8 @@ public class CurseforgeApi implements ModpackApi{
|
||||
|
||||
@Override
|
||||
public void installMod(ModDetail modDetail, int selectedVersion) {
|
||||
String versionUrl = modDetail.versionUrls[selectedVersion];
|
||||
File modpackFile = new File(Tools.DIR_CACHE, modDetail.id+".zip");
|
||||
//TODO considering only modpacks for now
|
||||
ModpackInstaller.installModpack(modDetail, selectedVersion, this::installCurseforgeZip);
|
||||
}
|
||||
|
||||
|
||||
@ -109,16 +114,16 @@ public class CurseforgeApi implements ModpackApi{
|
||||
params.put("index", index);
|
||||
params.put("pageSize", CURSEFORGE_PAGINATION_SIZE);
|
||||
JsonObject response = mApiHandler.get("mods/"+modId+"/files", params, JsonObject.class);
|
||||
if(response == null) return -2;
|
||||
if(response == null) return CURSEFORGE_PAGINATION_ERROR;
|
||||
JsonArray data = response.getAsJsonArray("data");
|
||||
if(data == null) return -2;
|
||||
if(data == null) return CURSEFORGE_PAGINATION_ERROR;
|
||||
for(int i = 0; i < data.size(); i++) {
|
||||
JsonObject fileInfo = data.get(i).getAsJsonObject();
|
||||
if(fileInfo.get("isServerPack").getAsBoolean()) continue;
|
||||
objectList.add(fileInfo);
|
||||
}
|
||||
if(data.size() < CURSEFORGE_PAGINATION_SIZE) {
|
||||
return -1; // we read the remainder! yay!
|
||||
return CURSEFORGE_PAGINATION_END_REACHED; // we read the remainder! yay!
|
||||
}
|
||||
return index + data.size();
|
||||
}
|
||||
@ -128,7 +133,72 @@ public class CurseforgeApi implements ModpackApi{
|
||||
CurseManifest curseManifest = Tools.GLOBAL_GSON.fromJson(
|
||||
Tools.read(ZipUtils.getEntryStream(modpackZipFile, "manifest.json")),
|
||||
CurseManifest.class);
|
||||
}
|
||||
if(!verifyManifest(curseManifest)) {
|
||||
Log.i("CurseforgeApi","manifest verification failed");
|
||||
return null;
|
||||
}
|
||||
ModDownloader modDownloader = new ModDownloader(new File(instanceDestination,"mods"), true);
|
||||
int fileCount = curseManifest.files.length;
|
||||
for(int i = 0; i < fileCount; i++) {
|
||||
CurseManifest.CurseFile curseFile = curseManifest.files[i];
|
||||
String downloadUrl = getDownloadUrl(curseFile.projectID, curseFile.fileID);
|
||||
if(downloadUrl == null && curseFile.required) throw new IOException("Failed to obtain download URL for "+curseFile.projectID+" "+curseFile.fileID);
|
||||
else if(downloadUrl == null) continue;
|
||||
modDownloader.submitDownload(Tools.getFileName(downloadUrl), downloadUrl);
|
||||
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, (int) Math.max((float)i/fileCount*100,0), R.string.modpack_download_getting_links, i, fileCount);
|
||||
}
|
||||
modDownloader.awaitFinish((c,m)->{ // insert joke about semen
|
||||
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, (int) Math.max((float)c/m*100,0), R.string.modpack_download_downloading_mods_fc, c, m);
|
||||
});
|
||||
String overridesDir = "overrides";
|
||||
if(curseManifest.overrides != null) overridesDir = curseManifest.overrides;
|
||||
ZipUtils.zipExtract(modpackZipFile, overridesDir, instanceDestination);
|
||||
return createInfo(curseManifest.minecraft);
|
||||
}
|
||||
}
|
||||
|
||||
private ModLoaderInfo createInfo(CurseManifest.CurseMinecraft minecraft) {
|
||||
CurseManifest.CurseModLoader primaryModLoader = null;
|
||||
for(CurseManifest.CurseModLoader modLoader : minecraft.modLoaders) {
|
||||
if(modLoader.primary) {
|
||||
primaryModLoader = modLoader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(primaryModLoader == null) primaryModLoader = minecraft.modLoaders[0];
|
||||
String modLoaderId = primaryModLoader.id;
|
||||
int dashIndex = modLoaderId.indexOf('-');
|
||||
String modLoaderName = modLoaderId.substring(0, dashIndex);
|
||||
String modLoaderVersion = modLoaderId.substring(dashIndex+1);
|
||||
int modLoaderTypeInt = -1;
|
||||
switch (modLoaderName) {
|
||||
case "forge":
|
||||
modLoaderTypeInt = ModLoaderInfo.MOD_LOADER_FORGE;
|
||||
break;
|
||||
case "fabric":
|
||||
modLoaderTypeInt = ModLoaderInfo.MOD_LOADER_FABRIC;
|
||||
break;
|
||||
//TODO: Quilt is also Forge? How does that work?
|
||||
}
|
||||
if(modLoaderTypeInt == -1) return null;
|
||||
return new ModLoaderInfo(modLoaderTypeInt, modLoaderVersion, minecraft.version);
|
||||
}
|
||||
|
||||
private String getDownloadUrl(long projectID, long fileID) {
|
||||
JsonObject response = mApiHandler.get("mods/"+projectID+"/files/"+fileID+"/download-url", JsonObject.class);
|
||||
if(response == null) return null;
|
||||
JsonElement data = response.get("data");
|
||||
if(data == null || data.isJsonNull()) return null;
|
||||
return data.getAsString();
|
||||
}
|
||||
|
||||
private boolean verifyManifest(CurseManifest manifest) {
|
||||
if(!"minecraftModpack".equals(manifest.manifestType)) return false;
|
||||
if(manifest.manifestVersion != 1) return false;
|
||||
if(manifest.minecraft == null) return false;
|
||||
if(manifest.minecraft.version == null) return false;
|
||||
if(manifest.minecraft.modLoaders == null) return false;
|
||||
if(manifest.minecraft.modLoaders.length < 1) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,28 @@ public class ModDownloader {
|
||||
private final AtomicLong mDownloadSize = new AtomicLong(0);
|
||||
private final Object mExceptionSyncPoint = new Object();
|
||||
private final File mDestinationDirectory;
|
||||
private final boolean mUseFileCount;
|
||||
private IOException mFirstIOException;
|
||||
private long mTotalSize;
|
||||
|
||||
public ModDownloader(File destinationDirectory) {
|
||||
this(destinationDirectory, false);
|
||||
}
|
||||
|
||||
public ModDownloader(File destinationDirectory, boolean useFileCount) {
|
||||
this.mDestinationDirectory = destinationDirectory;
|
||||
this.mUseFileCount = useFileCount;
|
||||
}
|
||||
|
||||
public void submitDownload(int fileSize, String relativePath, String... url) {
|
||||
mTotalSize += fileSize;
|
||||
if(mUseFileCount) mTotalSize += 1;
|
||||
else mTotalSize += fileSize;
|
||||
mDownloadPool.execute(new DownloadTask(url, new File(mDestinationDirectory, relativePath)));
|
||||
}
|
||||
|
||||
public void submitDownload(String relativePath, String... url) {
|
||||
if(!mUseFileCount) throw new RuntimeException("This method can only be used in a file-counting ModDownloader");
|
||||
mTotalSize += 1;
|
||||
mDownloadPool.execute(new DownloadTask(url, new File(mDestinationDirectory, relativePath)));
|
||||
}
|
||||
|
||||
@ -95,6 +108,7 @@ public class ModDownloader {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
try {
|
||||
DownloadUtils.downloadFileMonitored(sourceUrl, mDestination, getThreadLocalBuffer(), this);
|
||||
if(mUseFileCount) mDownloadSize.addAndGet(1);
|
||||
return null;
|
||||
} catch (InterruptedIOException e) {
|
||||
throw new InterruptedException();
|
||||
@ -102,14 +116,17 @@ public class ModDownloader {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
if(!mUseFileCount) {
|
||||
mDownloadSize.addAndGet(-last);
|
||||
last = 0;
|
||||
}
|
||||
}
|
||||
return exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
if(mUseFileCount) return;
|
||||
mDownloadSize.addAndGet(curr - last);
|
||||
last = curr;
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import net.kdt.pojavlaunch.JavaGUILauncherActivity;
|
||||
|
||||
public class ModLoaderInfo {
|
||||
public static final int MOD_LOADER_FORGE = 0;
|
||||
public static final int MOD_LOADER_FABRIC = 1;
|
||||
@ -21,7 +26,7 @@ public class ModLoaderInfo {
|
||||
case MOD_LOADER_FABRIC:
|
||||
return "fabric-loader-"+modLoaderVersion+"-"+minecraftVersion;
|
||||
case MOD_LOADER_QUILT:
|
||||
// TODO
|
||||
throw new RuntimeException("Quilt is gay af");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
|
||||
import com.kdt.mcgui.ProgressLayout;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.ModIconCache;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
|
||||
import net.kdt.pojavlaunch.progresskeeper.DownloaderProgressWrapper;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ModpackInstaller {
|
||||
|
||||
public static void installModpack(ModDetail modDetail, int selectedVersion, InstallFunction installFunction) {
|
||||
String versionUrl = modDetail.versionUrls[selectedVersion];
|
||||
String modpackName = modDetail.title.toLowerCase(Locale.ROOT).trim().replace(" ", "_" );
|
||||
|
||||
// Build a new minecraft instance, folder first
|
||||
|
||||
// Get the modpack file
|
||||
File modpackFile = new File(Tools.DIR_CACHE, modpackName + ".cf"); // Cache File
|
||||
ModLoaderInfo modLoaderInfo;
|
||||
try {
|
||||
byte[] downloadBuffer = new byte[8192];
|
||||
DownloadUtils.downloadFileMonitored(versionUrl, modpackFile, downloadBuffer,
|
||||
new DownloaderProgressWrapper(R.string.modpack_download_downloading_metadata,
|
||||
ProgressLayout.INSTALL_MODPACK));
|
||||
// Install the modpack
|
||||
modLoaderInfo = installFunction.installModpack(modpackFile, new File(Tools.DIR_GAME_HOME, "custom_instances/"+modpackName));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
modpackFile.delete();
|
||||
ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK);
|
||||
}
|
||||
if(modLoaderInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
MinecraftProfile profile = new MinecraftProfile();
|
||||
profile.gameDir = "./custom_instances/" + modpackName;
|
||||
profile.name = modDetail.title;
|
||||
profile.lastVersionId = modLoaderInfo.getVersionId();
|
||||
profile.icon = ModIconCache.getBase64Image(modDetail.getIconCacheTag());
|
||||
|
||||
|
||||
LauncherProfiles.mainProfileJson.profiles.put(modpackName, profile);
|
||||
LauncherProfiles.update();
|
||||
}
|
||||
|
||||
interface InstallFunction {
|
||||
ModLoaderInfo installModpack(File modpackFile, File instanceDestination) throws IOException;
|
||||
}
|
||||
}
|
@ -6,22 +6,17 @@ import com.kdt.mcgui.ProgressLayout;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.ModIconCache;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModrinthIndex;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
|
||||
import net.kdt.pojavlaunch.progresskeeper.DownloaderProgressWrapper;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
import net.kdt.pojavlaunch.utils.ZipUtils;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@ -93,41 +88,7 @@ public class ModrinthApi implements ModpackApi{
|
||||
@Override
|
||||
public void installMod(ModDetail modDetail, int selectedVersion) {
|
||||
//TODO considering only modpacks for now
|
||||
String versionUrl = modDetail.versionUrls[selectedVersion];
|
||||
String modpackName = modDetail.title.toLowerCase(Locale.ROOT).trim().replace(" ", "_" );
|
||||
|
||||
// Build a new minecraft instance, folder first
|
||||
|
||||
// Get the mrpack
|
||||
File modpackFile = new File(Tools.DIR_CACHE, modpackName + ".mrpack");
|
||||
ModLoaderInfo modLoaderInfo;
|
||||
try {
|
||||
byte[] downloadBuffer = new byte[8192];
|
||||
DownloadUtils.downloadFileMonitored(versionUrl, modpackFile, downloadBuffer,
|
||||
new DownloaderProgressWrapper(R.string.modpack_download_downloading_metadata,
|
||||
ProgressLayout.INSTALL_MODPACK));
|
||||
ModrinthIndex modrinthIndex = installMrpack(modpackFile, new File(Tools.DIR_GAME_HOME, "custom_instances/"+modpackName));
|
||||
modLoaderInfo = createInfo(modrinthIndex);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
modpackFile.delete();
|
||||
ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK);
|
||||
}
|
||||
if(modLoaderInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
MinecraftProfile profile = new MinecraftProfile();
|
||||
profile.gameDir = "./custom_instances/" + modpackName;
|
||||
profile.name = modDetail.title;
|
||||
profile.lastVersionId = modLoaderInfo.getVersionId();
|
||||
profile.icon = ModIconCache.getBase64Image(modDetail.getIconCacheTag());
|
||||
|
||||
|
||||
LauncherProfiles.mainProfileJson.profiles.put(modpackName, profile);
|
||||
LauncherProfiles.update();
|
||||
ModpackInstaller.installModpack(modDetail, selectedVersion, this::installMrpack);
|
||||
}
|
||||
|
||||
private static ModLoaderInfo createInfo(ModrinthIndex modrinthIndex) {
|
||||
@ -143,13 +104,12 @@ public class ModrinthApi implements ModpackApi{
|
||||
return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_FABRIC, modLoaderVersion, mcVersion);
|
||||
}
|
||||
if((modLoaderVersion = dependencies.get("quilt-loader")) != null) {
|
||||
throw new RuntimeException("Quilt is gay af");
|
||||
//return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_QUILT, modLoaderVersion, mcVersion);
|
||||
return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_QUILT, modLoaderVersion, mcVersion);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ModrinthIndex installMrpack(File mrpackFile, File instanceDestination) throws IOException {
|
||||
private ModLoaderInfo installMrpack(File mrpackFile, File instanceDestination) throws IOException {
|
||||
try (ZipFile modpackZipFile = new ZipFile(mrpackFile)){
|
||||
ModrinthIndex modrinthIndex = Tools.GLOBAL_GSON.fromJson(
|
||||
Tools.read(ZipUtils.getEntryStream(modpackZipFile, "modrinth.index.json")),
|
||||
@ -164,7 +124,7 @@ public class ModrinthApi implements ModpackApi{
|
||||
ZipUtils.zipExtract(modpackZipFile, "overrides/", instanceDestination);
|
||||
ProgressLayout.setProgress(ProgressLayout.INSTALL_MODPACK, 50, R.string.modpack_download_applying_overrides, 2, 2);
|
||||
ZipUtils.zipExtract(modpackZipFile, "client-overrides/", instanceDestination);
|
||||
return modrinthIndex;
|
||||
return createInfo(modrinthIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ public class CurseManifest {
|
||||
public static class CurseFile {
|
||||
public long projectID;
|
||||
public long fileID;
|
||||
public boolean required;
|
||||
}
|
||||
public static class CurseMinecraft {
|
||||
public String version;
|
||||
|
@ -416,5 +416,7 @@
|
||||
|
||||
<string name="modpack_download_downloading_metadata">Downloading modpack metadata (%.2f MB / %.2f MB)</string>
|
||||
<string name="modpack_download_downloading_mods">Downloading mods (%.2f MB / %.2f MB)</string>
|
||||
<string name="modpack_download_getting_links">Downloading mod links (%d/%d)</string>
|
||||
<string name="modpack_download_downloading_mods_fc">Downloading mods (File %d out of %d)</string>
|
||||
<string name="modpack_download_applying_overrides">Applying overrides (%d/%d)</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user