mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2026-04-23 19:17:17 -04:00
Feat[downloader]: add external mirror support
This commit is contained in:
@@ -27,6 +27,7 @@ import net.kdt.pojavlaunch.extra.ExtraListener;
|
||||
import net.kdt.pojavlaunch.fragments.MainMenuFragment;
|
||||
import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment;
|
||||
import net.kdt.pojavlaunch.fragments.SelectAuthFragment;
|
||||
import net.kdt.pojavlaunch.mirrors.DownloadMirror;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.IconCacheJanitor;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
@@ -141,6 +142,7 @@ public class LauncherActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public void onDownloadFailed(Throwable th) {
|
||||
if(DownloadMirror.checkForTamperedException(LauncherActivity.this, th)) return;
|
||||
if(th != null) Tools.showError(LauncherActivity.this, R.string.mc_download_failed, th);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.mirrors.DownloadMirror;
|
||||
import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener;
|
||||
import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy;
|
||||
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
|
||||
@@ -143,6 +144,7 @@ public abstract class ModVersionListFragment<T> extends Fragment implements Runn
|
||||
getTaskProxy().detachListener();
|
||||
setTaskProxy(null);
|
||||
mExpandableListView.setEnabled(true);
|
||||
if(DownloadMirror.checkForTamperedException(context, e)) return;
|
||||
Tools.showError(context, e);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package net.kdt.pojavlaunch.mirrors;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
public class DownloadMirror {
|
||||
public static final int DOWNLOAD_CLASS_LIBRARIES = 0;
|
||||
public static final int DOWNLOAD_CLASS_METADATA = 1;
|
||||
public static final int DOWNLOAD_CLASS_ASSETS = 2;
|
||||
|
||||
private static final String[] MIRROR_BMCLAPI = {
|
||||
"https://bmclapi2.bangbang93.com/maven",
|
||||
"https://bmclapi2.bangbang93.com",
|
||||
"https://bmclapi2.bangbang93.com/assets"
|
||||
};
|
||||
|
||||
private static final String[] MIRROR_MCBBS = {
|
||||
"https://download.mcbbs.net/maven",
|
||||
"https://download.mcbbs.net",
|
||||
"https://download.mcbbs.net/assets"
|
||||
};
|
||||
|
||||
/**
|
||||
* Download a file with the current mirror. If the file is missing on the mirror,
|
||||
* fall back to the official source.
|
||||
* @param downloadClass Class of the download. Can either be DOWNLOAD_CLASS_LIBRARIES,
|
||||
* DOWNLOAD_CLASS_METADATA or DOWNLOAD_CLASS_ASSETS
|
||||
* @param urlInput The original (Mojang) URL for the download
|
||||
* @param outputFile The output file for the download
|
||||
* @param buffer The shared buffer
|
||||
* @param monitor The download monitor.
|
||||
*/
|
||||
public static void downloadFileMirrored(int downloadClass, String urlInput, File outputFile,
|
||||
@Nullable byte[] buffer, Tools.DownloaderFeedback monitor) throws IOException {
|
||||
try {
|
||||
DownloadUtils.downloadFileMonitored(getMirrorMapping(downloadClass, urlInput),
|
||||
outputFile, buffer, monitor);
|
||||
return;
|
||||
}catch (FileNotFoundException e) {
|
||||
Log.w("DownloadMirror", "Cannot find the file on the mirror", e);
|
||||
Log.i("DownloadMirror", "Failling back to default source");
|
||||
}
|
||||
DownloadUtils.downloadFileMonitored(urlInput, outputFile, buffer, monitor);
|
||||
}
|
||||
|
||||
public static boolean isMirrored() {
|
||||
return !LauncherPreferences.PREF_DOWNLOAD_SOURCE.equals("default");
|
||||
}
|
||||
|
||||
public static boolean checkForTamperedException(Context context, Throwable e) {
|
||||
if(e instanceof MirrorTamperedException){
|
||||
showMirrorTamperedDialog(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void showMirrorTamperedDialog(Context ctx) {
|
||||
Tools.runOnUiThread(()->{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(R.string.dl_tampered_manifest_title);
|
||||
builder.setMessage(Html.fromHtml(ctx.getString(R.string.dl_tampered_manifest)));
|
||||
builder.setPositiveButton(R.string.dl_switch_to_official_site,(d,w)->{
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putString("downloadSource", "default").apply();
|
||||
LauncherPreferences.PREF_DOWNLOAD_SOURCE = "default";
|
||||
|
||||
});
|
||||
builder.setNegativeButton(R.string.dl_turn_off_manifest_checks,(d,w)->{
|
||||
LauncherPreferences.DEFAULT_PREF.edit().putBoolean("verifyManifest", false).apply();
|
||||
LauncherPreferences.PREF_VERIFY_MANIFEST = false;
|
||||
});
|
||||
builder.setNeutralButton(android.R.string.cancel, (d,w)->{});
|
||||
builder.show();
|
||||
});
|
||||
}
|
||||
|
||||
private static String[] getMirrorSettings() {
|
||||
switch (LauncherPreferences.PREF_DOWNLOAD_SOURCE) {
|
||||
case "mcbbs": return MIRROR_MCBBS;
|
||||
case "bmclapi": return MIRROR_BMCLAPI;
|
||||
case "default":
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMirrorMapping(int downloadClass, String mojangUrl) throws MalformedURLException{
|
||||
String[] mirrorSettings = getMirrorSettings();
|
||||
if(mirrorSettings == null) return mojangUrl;
|
||||
int urlTail = getBaseUrlTail(mojangUrl);
|
||||
String baseUrl = mojangUrl.substring(0, urlTail);
|
||||
String path = mojangUrl.substring(urlTail);
|
||||
switch(downloadClass) {
|
||||
case DOWNLOAD_CLASS_ASSETS:
|
||||
case DOWNLOAD_CLASS_METADATA:
|
||||
baseUrl = mirrorSettings[downloadClass];
|
||||
break;
|
||||
case DOWNLOAD_CLASS_LIBRARIES:
|
||||
if(!baseUrl.endsWith("libraries.minecraft.net")) break;
|
||||
baseUrl = mirrorSettings[downloadClass];
|
||||
break;
|
||||
}
|
||||
return baseUrl + path;
|
||||
}
|
||||
|
||||
private static int getBaseUrlTail(String wholeUrl) throws MalformedURLException{
|
||||
int protocolNameEnd = wholeUrl.indexOf("://");
|
||||
if(protocolNameEnd == -1) throw new MalformedURLException("No protocol");
|
||||
protocolNameEnd += 3;
|
||||
return wholeUrl.indexOf('/', protocolNameEnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.kdt.pojavlaunch.mirrors;
|
||||
|
||||
public class MirrorTamperedException extends Exception{
|
||||
}
|
||||
@@ -61,6 +61,9 @@ public class LauncherPreferences {
|
||||
public static float PREF_DEADZONE_SCALE = 1f;
|
||||
public static boolean PREF_BIG_CORE_AFFINITY = false;
|
||||
public static boolean PREF_ZINK_PREFER_SYSTEM_DRIVER = false;
|
||||
|
||||
public static boolean PREF_VERIFY_MANIFEST = true;
|
||||
public static String PREF_DOWNLOAD_SOURCE = "default";
|
||||
|
||||
|
||||
|
||||
@@ -104,6 +107,8 @@ public class LauncherPreferences {
|
||||
PREF_DEADZONE_SCALE = DEFAULT_PREF.getInt("gamepad_deadzone_scale", 100)/100f;
|
||||
PREF_BIG_CORE_AFFINITY = DEFAULT_PREF.getBoolean("bigCoreAffinity", false);
|
||||
PREF_ZINK_PREFER_SYSTEM_DRIVER = DEFAULT_PREF.getBoolean("zinkPreferSystemDriver", false);
|
||||
PREF_DOWNLOAD_SOURCE = DEFAULT_PREF.getString("downloadSource", "default");
|
||||
PREF_VERIFY_MANIFEST = DEFAULT_PREF.getBoolean("verifyManifest", true);
|
||||
|
||||
String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
|
||||
for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
|
||||
import static net.kdt.pojavlaunch.Tools.BYTE_TO_MB;
|
||||
import static net.kdt.pojavlaunch.utils.DownloadUtils.downloadFileMonitored;
|
||||
import static net.kdt.pojavlaunch.mirrors.DownloadMirror.downloadFileMirrored;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
@@ -19,9 +19,10 @@ import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.extra.ExtraConstants;
|
||||
import net.kdt.pojavlaunch.extra.ExtraCore;
|
||||
import net.kdt.pojavlaunch.mirrors.DownloadMirror;
|
||||
import net.kdt.pojavlaunch.mirrors.MirrorTamperedException;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
import net.kdt.pojavlaunch.value.DependentLibrary;
|
||||
import net.kdt.pojavlaunch.value.MinecraftClientInfo;
|
||||
import net.kdt.pojavlaunch.value.MinecraftLibraryArtifact;
|
||||
@@ -127,7 +128,8 @@ public class AsyncMinecraftDownloader {
|
||||
if (!outLib.exists()) {
|
||||
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_downloading, verInfo.logging.client.file.id);
|
||||
JMinecraftVersionList.Version finalVerInfo = verInfo;
|
||||
downloadFileMonitored(
|
||||
downloadFileMirrored(
|
||||
DownloadMirror.DOWNLOAD_CLASS_METADATA,
|
||||
verInfo.logging.client.file.url, outLib, getByteBuffer(),
|
||||
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
|
||||
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, finalVerInfo.logging.client.file.id, curr/BYTE_TO_MB, max/BYTE_TO_MB)
|
||||
@@ -185,6 +187,7 @@ public class AsyncMinecraftDownloader {
|
||||
}
|
||||
}
|
||||
} catch (DownloaderException e) {
|
||||
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
Log.e("AsyncMcDownloader", e.toString(),e );
|
||||
@@ -214,7 +217,7 @@ public class AsyncMinecraftDownloader {
|
||||
}
|
||||
|
||||
public void verifyAndDownloadMainJar(String url, String sha1, File destination) throws Exception{
|
||||
while(!destination.exists() || (destination.exists() && !Tools.compareSHA1(destination, sha1))) downloadFileMonitored(
|
||||
while(!destination.exists() || (destination.exists() && !Tools.compareSHA1(destination, sha1))) downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_LIBRARIES,
|
||||
url,
|
||||
destination, getByteBuffer(),
|
||||
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
|
||||
@@ -289,7 +292,7 @@ public class AsyncMinecraftDownloader {
|
||||
public void downloadAsset(JAssetInfo asset, File objectsDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(objectsDir, assetPath);
|
||||
downloadFileMonitored(MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
|
||||
downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_ASSETS, MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
|
||||
new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
@@ -303,7 +306,7 @@ public class AsyncMinecraftDownloader {
|
||||
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir, AtomicInteger downloadCounter) throws IOException {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(resDir,"/"+assetName);
|
||||
downloadFileMonitored(MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
|
||||
downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_ASSETS, MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
|
||||
new Tools.DownloaderFeedback() {
|
||||
int prevCurr;
|
||||
@Override
|
||||
@@ -336,7 +339,7 @@ public class AsyncMinecraftDownloader {
|
||||
timesChecked++;
|
||||
if(timesChecked > 5) throw new RuntimeException("Library download failed after 5 retries");
|
||||
|
||||
downloadFileMonitored(libPathURL, outLib, getByteBuffer(),
|
||||
downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_LIBRARIES, libPathURL, outLib, getByteBuffer(),
|
||||
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
|
||||
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, outLib.getName(), curr/BYTE_TO_MB, max/BYTE_TO_MB)
|
||||
);
|
||||
@@ -362,30 +365,44 @@ public class AsyncMinecraftDownloader {
|
||||
public JAssets downloadIndex(JMinecraftVersionList.Version version, File output) throws IOException {
|
||||
if (!output.exists()) {
|
||||
output.getParentFile().mkdirs();
|
||||
DownloadUtils.downloadFile(version.assetIndex != null
|
||||
downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_METADATA, version.assetIndex != null
|
||||
? version.assetIndex.url
|
||||
: "https://s3.amazonaws.com/Minecraft.Download/indexes/" + version.assets + ".json", output);
|
||||
: "https://s3.amazonaws.com/Minecraft.Download/indexes/" + version.assets + ".json", output, null,
|
||||
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
|
||||
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, output.getName(), curr/BYTE_TO_MB, max/BYTE_TO_MB)
|
||||
);
|
||||
}
|
||||
|
||||
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
|
||||
}
|
||||
|
||||
public void downloadVersionJson(String versionName, File verJsonDir, JMinecraftVersionList.Version verInfo) throws IOException {
|
||||
public void downloadVersionJson(String versionName, File verJsonDir, JMinecraftVersionList.Version verInfo) throws IOException, DownloaderException {
|
||||
if(!LauncherPreferences.PREF_CHECK_LIBRARY_SHA) Log.w("Chk","Checker is off");
|
||||
|
||||
boolean isManifestGood = verJsonDir.exists()
|
||||
&& (!LauncherPreferences.PREF_CHECK_LIBRARY_SHA
|
||||
|| verInfo.sha1 == null
|
||||
|| Tools.compareSHA1(verJsonDir, verInfo.sha1));
|
||||
|
||||
if(!isManifestGood) {
|
||||
boolean isManifestGood = verifyManifest(verJsonDir, verInfo);
|
||||
byte retryCount = 0;
|
||||
while(!isManifestGood && retryCount < 5) {
|
||||
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_downloading, versionName + ".json");
|
||||
verJsonDir.delete();
|
||||
downloadFileMonitored(verInfo.url, verJsonDir, getByteBuffer(),
|
||||
downloadFileMirrored(DownloadMirror.DOWNLOAD_CLASS_METADATA, verInfo.url, verJsonDir, getByteBuffer(),
|
||||
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
|
||||
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, versionName + ".json", curr/BYTE_TO_MB, max/BYTE_TO_MB)
|
||||
);
|
||||
isManifestGood = verifyManifest(verJsonDir, verInfo);
|
||||
retryCount++;
|
||||
// Always do the first verification. But skip all the errors from further ones.
|
||||
if(!LauncherPreferences.PREF_VERIFY_MANIFEST) return;
|
||||
}
|
||||
if(!isManifestGood) {
|
||||
if(DownloadMirror.isMirrored()) throw new DownloaderException(new MirrorTamperedException());
|
||||
else throw new DownloaderException(new IOException("Manifest check failed after 5 tries"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyManifest(File verJsonDir, JMinecraftVersionList.Version verInfo) {
|
||||
return verJsonDir.exists()
|
||||
&& (!LauncherPreferences.PREF_CHECK_LIBRARY_SHA
|
||||
|| verInfo.sha1 == null
|
||||
|| Tools.compareSHA1(verJsonDir, verInfo.sha1));
|
||||
}
|
||||
|
||||
public static String normalizeVersionId(String versionString) {
|
||||
@@ -432,5 +449,4 @@ public class AsyncMinecraftDownloader {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,5 +40,15 @@
|
||||
<item>vulkan_zink</item> <!-- virglrenderer with OpenGL ES 3 -->
|
||||
<item>opengles3_desktopgl_angle_vulkan</item>
|
||||
</string-array>
|
||||
<string-array name="download_source_names">
|
||||
<item>@string/global_default</item>
|
||||
<item>BMCLAPI</item>
|
||||
<item>MCBBS</item>
|
||||
</string-array>
|
||||
<string-array name="download_source_values">
|
||||
<item>default</item>
|
||||
<item>bmclapi</item>
|
||||
<item>mcbbs</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -338,4 +338,16 @@
|
||||
<string name="notif_error_occured">An error has occurred</string>
|
||||
<string name="notif_error_occured_desc">Click to see more details</string>
|
||||
<string name="fabric_dl_only_stable">Show only stable versions</string>
|
||||
<string name="dl_tampered_manifest_title">File verification warning</string>
|
||||
<string name="dl_tampered_manifest"><![CDATA[The game version manifest on the mirror does not match the official Mojang version manifest, which means it may have been tampered with.<br/>
|
||||
<b>This is not safe. Your personal information may be at risk if you continue.</b><br/>
|
||||
If you still want to use the mirror, press \"Turn off manifest checks\" and start the download again.<br/>
|
||||
If you want to use the official download source, press \"Switch to official site\" and start the download again.<br/>]]>
|
||||
</string>
|
||||
<string name="dl_turn_off_manifest_checks">Turn off manifest checks</string>
|
||||
<string name="dl_switch_to_official_site">Switch to official site</string>
|
||||
<string name="preference_download_source_title">Game download source</string>
|
||||
<string name="preference_download_source_description">Select a download mirror instead of using the official download server</string>
|
||||
<string name="preference_verify_manifest_title">Verify game version manifest</string>
|
||||
<string name="preference_verify_manifest_description">When enabled, the launcher will check the game version manifest along with the libraries.</string>
|
||||
</resources>
|
||||
|
||||
@@ -16,6 +16,20 @@
|
||||
android:key="arc_capes"
|
||||
android:summary="@string/arc_capes_desc"
|
||||
android:title="@string/arc_capes_title" />
|
||||
<androidx.preference.ListPreference
|
||||
android:defaultValue="default"
|
||||
android:key="downloadSource"
|
||||
android:entries="@array/download_source_names"
|
||||
android:entryValues="@array/download_source_values"
|
||||
android:title="@string/preference_download_source_title"
|
||||
android:summary="@string/preference_download_source_description"
|
||||
app2:useSimpleSummaryProvider="true"/>
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="verifyManifest"
|
||||
android:title="@string/preference_verify_manifest_title"
|
||||
android:summary="@string/preference_verify_manifest_description"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="zinkPreferSystemDriver"
|
||||
|
||||
Reference in New Issue
Block a user