From 5d92b01870c0211f3032cc0d983238db9db0fcfe Mon Sep 17 00:00:00 2001 From: HRudyPlayZ Date: Sat, 2 Dec 2023 19:48:31 +0100 Subject: [PATCH] Pushed the final changes for 2.7 - Added support for HTTP proxies, this might fix issues with VPNs getting ignored (courtesy of @GlodBlock) - Added an option to ignore the HTTPS certificate check, for launchers which may break the certificates list somehow (courtesy of @DrParadox7) - Reworked the retry system for failed downloads, now allows a configurable amount of retries. - A few changes and cleanups in the codebase - Bumped version number to 2.7 --- .../hrudyplayz/mcinstanceloader/Config.java | 19 +-- .../mcinstanceloader/ModProperties.java | 2 +- .../resources/ResourceObject.java | 22 +-- .../mcinstanceloader/utils/WebHelper.java | 142 +++++++++++------- versionData.txt | 6 +- 5 files changed, 111 insertions(+), 80 deletions(-) diff --git a/src/main/java/com/hrudyplayz/mcinstanceloader/Config.java b/src/main/java/com/hrudyplayz/mcinstanceloader/Config.java index bfff537..ed4d6c4 100644 --- a/src/main/java/com/hrudyplayz/mcinstanceloader/Config.java +++ b/src/main/java/com/hrudyplayz/mcinstanceloader/Config.java @@ -6,6 +6,7 @@ import com.hrudyplayz.mcinstanceloader.utils.LogHelper; + public class Config { // This class will handle every setting and config the user can set to change the mod's behavior. @@ -19,9 +20,10 @@ public class Config { public static boolean disableCache; public static boolean disableAutomaticZipCreation; public static int connectionTimeout; - public static boolean useHttpProxy; - public static String proxyHttpHost; - public static int proxyHttpPort; + public static String httpProxyHost; + public static int httpProxyPort; + public static boolean allowHTTPSCertificateCheckBypass; + public static int maxAmountOfDownloadRetries; public static String curseforgeURL; public static String curseforgeAPIKey; @@ -29,7 +31,6 @@ public class Config { public static int closeGameTimer; public static int amountOfDisplayedErrors; public static String[] successMessage; - public static boolean allowSSLCertificateBypass; public static String CATEGORY_BEHAVIOR = "Behavior"; public static String CATEGORY_GUI = "GUI"; @@ -50,12 +51,12 @@ public static void createConfigFile() { skipFileDisabling = config.getBoolean("Skip file disabling", CATEGORY_BEHAVIOR, false, "Whether to skip the step that disables the pack.mcinstance file and deletes the temp folder. Useful for pack devs."); deleteInsteadOfRenaming = config.getBoolean("Delete MCInstance directly", CATEGORY_BEHAVIOR, false, "Wheter to delete the pack.mcinstance file instead of renaming it."); disableStopModRepostsCheck = config.getBoolean("Disable StopModReposts check", CATEGORY_BEHAVIOR, false, "Whether to disable the StopModReposts check, used to prevent the use of malware sites. It's recommended to keep it enabled."); - allowSSLCertificateBypass = config.getBoolean("Allow SSL Certificate bypass", CATEGORY_BEHAVIOR, false, "Whether to allow the bypass of SSL Certificates if this cause problems. This may fix download issues in some launchers."); disableCache = config.getBoolean("Disable the cache system", CATEGORY_BEHAVIOR, false, "Whether to disable the cache system, forcing every resource to be downloaded regardless of the cached value."); - useHttpProxy = config.getBoolean("Use proxy", CATEGORY_BEHAVIOR, false, "This will enable proxy when downing files."); - proxyHttpHost = config.getString("Proxy host address", CATEGORY_BEHAVIOR, "127.0.0.1", "The proxy host address."); - proxyHttpPort = config.getInt("Proxy host port", CATEGORY_BEHAVIOR, 0, 0, 65535, "The proxy host port."); + httpProxyHost = config.getString("Web connection proxy host", CATEGORY_BEHAVIOR, "", "The HTTP proxy ip address, use this if you use a VPN/Proxy. If you don't know what this is or don't want to use a proxy just leave it blank."); + httpProxyPort = config.getInt("Web connection proxy port", CATEGORY_BEHAVIOR, 8080, 0, 65535, "The internet port to use, if using an HTTP proxy."); + allowHTTPSCertificateCheckBypass = config.getBoolean("Allow HTTPS certificate check bypass", CATEGORY_BEHAVIOR, false, "Whether to allow to bypass the certificate check for HTTPS requests and allow invalid certificates. Required for certain launchers to work properly on some links but can represent a security risk. Only use if necessary."); + maxAmountOfDownloadRetries = config.getInt("Maximum amount of download retries", CATEGORY_BEHAVIOR, 2, 0, 99, "The maximum amount of times to attempt to retry a failed download."); disableAutomaticZipCreation = config.getBoolean("Disable the automatic zipping system for the pack folder", CATEGORY_BEHAVIOR, false, "Whether to disable the automatic creation of the pack.mcinstance file from the pack folder."); if (deleteInsteadOfRenaming && !disableAutomaticZipCreation) disableAutomaticZipCreation = true; @@ -63,7 +64,7 @@ public static void createConfigFile() { connectionTimeout = config.getInt("Web connection timeout", CATEGORY_BEHAVIOR, 100, 0, Integer.MAX_VALUE, "The amount of seconds the mod will wait to receive a response for downloads. Zero for no timeout at all (not recommended)."); curseforgeURL = config.getString("CFCore proxy URL", CATEGORY_BEHAVIOR, "http://api-pocket.com/", "The URL to use for the CFCore API. Any proxy works. Leave blank to use the official CFCore one. Defaults to a simple proxy."); - if (curseforgeURL.trim().length() == 0) curseforgeURL = "https://api.curseforge.com/"; + if (curseforgeURL.trim().isEmpty()) curseforgeURL = "https://api.curseforge.com/"; curseforgeAPIKey = config.getString("CFCore API key", CATEGORY_BEHAVIOR, "", "The API key to use if you use the official Curseforge API."); diff --git a/src/main/java/com/hrudyplayz/mcinstanceloader/ModProperties.java b/src/main/java/com/hrudyplayz/mcinstanceloader/ModProperties.java index 6ae5e6c..d6ae125 100644 --- a/src/main/java/com/hrudyplayz/mcinstanceloader/ModProperties.java +++ b/src/main/java/com/hrudyplayz/mcinstanceloader/ModProperties.java @@ -10,7 +10,7 @@ public class ModProperties { // General values public static final String MODID = "mcinstanceloader"; public static final String NAME = "MCInstance Loader"; - public static final String VERSION = "2.6"; + public static final String VERSION = "2.7"; public static final String MC_VERSION = "1.7.10"; public static final String URL = "https://github.com/HRudyPlayZ/MCInstanceLoader/"; diff --git a/src/main/java/com/hrudyplayz/mcinstanceloader/resources/ResourceObject.java b/src/main/java/com/hrudyplayz/mcinstanceloader/resources/ResourceObject.java index 7fcb7e4..5bc0f66 100644 --- a/src/main/java/com/hrudyplayz/mcinstanceloader/resources/ResourceObject.java +++ b/src/main/java/com/hrudyplayz/mcinstanceloader/resources/ResourceObject.java @@ -295,7 +295,7 @@ private boolean getCurseforgeData() { url = splitted[1]; url = url.substring(0, url.indexOf("\"")); } - if (url.length() >= 1) this.url = url; + if (!url.isEmpty()) this.url = url; } // Source file name @@ -306,7 +306,7 @@ private boolean getCurseforgeData() { fileName = splitted[1]; fileName = fileName.substring(0, fileName.indexOf("\"")); } - if (fileName.length() >= 1) this.sourceFileName = fileName; + if (!fileName.isEmpty()) this.sourceFileName = fileName; } // SHA1 hash @@ -316,7 +316,7 @@ private boolean getCurseforgeData() { if (splitted.length >= 2) { sha1 = splitted[0].substring(splitted[0].lastIndexOf("\"") + 1); } - if (sha1.length() >= 1) this.SHA1 = sha1; + if (!sha1.isEmpty()) this.SHA1 = sha1; } // MD5 hash @@ -326,10 +326,10 @@ private boolean getCurseforgeData() { if (splitted.length >= 2) { md5 = splitted[0].substring(splitted[0].lastIndexOf("\"") + 1); } - if (md5.length() >= 1) this.MD5 = md5; + if (!md5.isEmpty()) this.MD5 = md5; } - return this.url.length() > 0 || this.sourceFileName != null; + return !this.url.isEmpty() || this.sourceFileName != null; } return false; @@ -359,7 +359,7 @@ public boolean getModrinthData() { break; } } - if (file.length() < 1) return false; + if (file.isEmpty()) return false; String[] splitted; @@ -371,7 +371,7 @@ public boolean getModrinthData() { url = splitted[1]; url = url.substring(0, url.indexOf("\"")); } - if (url.length() >= 1) this.url = url; + if (!url.isEmpty()) this.url = url; } // SHA1 hash @@ -382,7 +382,7 @@ public boolean getModrinthData() { sha1 = splitted[1]; sha1 = sha1.substring(0, sha1.indexOf("\"")); } - if (sha1.length() >= 1) this.SHA1 = sha1; + if (!sha1.isEmpty()) this.SHA1 = sha1; } // SHA512 hash @@ -393,10 +393,10 @@ public boolean getModrinthData() { sha512 = splitted[1]; sha512 = sha512.substring(0, sha512.indexOf("\"")); } - if (sha512.length() >= 1) this.SHA512 = sha512; + if (!sha512.isEmpty()) this.SHA512 = sha512; } - return this.url.length() > 0; + return !this.url.isEmpty(); } } @@ -464,7 +464,7 @@ else if (this.type.equals("modrinth")) { // If it didn't download it from before (so either it's not of curseforge type, the resource didn't have a sourceFileName specified, or the direct download failed), // it will download it here. Anything other than the curseforge or modrinth type will directly go here. - if (this.follows.length <= 0) return WebHelper.downloadFile(this.url, this.destination); + if (this.follows.length == 0) return WebHelper.downloadFile(this.url, this.destination); else return WebHelper.downloadFile(this.url, this.destination, this.follows); } diff --git a/src/main/java/com/hrudyplayz/mcinstanceloader/utils/WebHelper.java b/src/main/java/com/hrudyplayz/mcinstanceloader/utils/WebHelper.java index 2d01ada..78485d2 100644 --- a/src/main/java/com/hrudyplayz/mcinstanceloader/utils/WebHelper.java +++ b/src/main/java/com/hrudyplayz/mcinstanceloader/utils/WebHelper.java @@ -5,20 +5,16 @@ import java.net.*; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import javax.net.ssl.*; import org.apache.commons.io.FileUtils; import com.hrudyplayz.mcinstanceloader.Config; import com.hrudyplayz.mcinstanceloader.Main; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; /** An helper class to download files from the internet. @@ -32,7 +28,7 @@ public class WebHelper { public static String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) twitch-desktop-electron-platform/1.0.0 Chrome/73.0.3683.121 Electron/5.0.12 Safari/537.36 desklight/8.51.0"; public static String REFERER = "https://www.google.com"; - public static Boolean checkSSLCertificates = true; + private static final SSLSocketFactory DEFAULT_HTTPS_CERTIFICATES = HttpsURLConnection.getDefaultSSLSocketFactory(); /** Downloads a specific file from an internet address and saves it to a given location. @@ -42,10 +38,13 @@ public class WebHelper { @return Whether the operation succeeded or not. */ public static boolean downloadFile(String fileURL, String savePath) { - return downloadFile(fileURL, savePath, false); + return downloadFile(fileURL, savePath, 0); } + private static boolean downloadFile(String fileURL, String savePath, int retryCount) { + // Resets the SSL certificate ignore status, for the upcoming HttpURLConnection + if (retryCount == 0) toggleHTTPSCertificateChecks(true); - private static boolean downloadFile(String fileURL, String savePath, boolean doneIOException) { + // URL Object used for the URLConnection URL url; try { url = new URL(fileURL); @@ -56,53 +55,85 @@ private static boolean downloadFile(String fileURL, String savePath, boolean don } try { + // URLConnection object HttpURLConnection connection; - if (Config.useHttpProxy) { - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(Config.proxyHttpHost, Config.proxyHttpPort)); + if (!Config.httpProxyHost.isEmpty()) { + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(Config.httpProxyHost, Config.httpProxyPort)); connection = (HttpURLConnection) url.openConnection(proxy); - } else { - connection = (HttpURLConnection) url.openConnection(); } + else connection = (HttpURLConnection) url.openConnection(); + + // GET Method that follows redirects connection.setRequestMethod("GET"); connection.setInstanceFollowRedirects(true); - + + // Timeouts connection.setReadTimeout(Config.connectionTimeout * 1000); connection.setConnectTimeout(Config.connectionTimeout * 1000); + // Request properties connection.setRequestProperty("User-Agent", USER_AGENT); connection.setRequestProperty("Referer", REFERER); - if (Config.curseforgeAPIKey.length() > 0) connection.setRequestProperty("x-api-key", Config.curseforgeAPIKey); + if (!Config.curseforgeAPIKey.isEmpty()) connection.setRequestProperty("x-api-key", Config.curseforgeAPIKey); + + // If the response didn't an HTTP 200 (OK) response code, it failed. int responseCode = connection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { - Main.errorContext = "Received an HTTP " + responseCode + " error."; - return false; + if (retryCount < Config.maxAmountOfDownloadRetries) { + LogHelper.info("The website returned an HTTP " + responseCode + " error status, trying again..."); + + try { + TimeUnit.MILLISECONDS.sleep(1000); + } + catch (InterruptedException ignore) {} + + return downloadFile(fileURL, savePath, retryCount + 1); + } + else { + Main.errorContext = "Received an HTTP " + responseCode + " error."; + return false; + } } File file = new File(savePath); FileUtils.copyInputStreamToFile(connection.getInputStream(), file); - file.canRead(); - if (doneIOException) - LogHelper.info("Successfully downloaded file on the second attempt."); + + LogHelper.info("Successfully downloaded the file on attempt number " + (retryCount + 1) + "."); + return true; } - catch (IOException e) { - if (!doneIOException) { - LogHelper.info("An error occurred while downloading the file, trying again..."); + catch (SSLException e) { + if (Config.allowHTTPSCertificateCheckBypass && retryCount < Config.maxAmountOfDownloadRetries) { + LogHelper.info("An error occurred while checking the HTTPS certificate of the address, disabling the certificate check and trying again..."); + + toggleHTTPSCertificateChecks(false); + try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException ignore) {} - if (Config.allowSSLCertificateBypass && checkSSLCertificates) { - LogHelper.info("Attempting to resolve issues by bypassing SSL certificates."); - disableSSLCertificateChecking(); + return downloadFile(fileURL, savePath, retryCount + 1); + } + else { + Main.errorContext = "There was an issue writing to file."; + e.printStackTrace(); + return false; + } + } + catch (IOException e) { + if (retryCount < Config.maxAmountOfDownloadRetries) { + LogHelper.info("An error occurred while downloading the file, trying again..."); + + try { + TimeUnit.MILLISECONDS.sleep(1000); } + catch (InterruptedException ignore) {} - return downloadFile(fileURL, savePath, true); + return downloadFile(fileURL, savePath, retryCount + 1); } else { - Main.errorContext = "There was an issue writing to file."; e.printStackTrace(); return false; @@ -123,9 +154,6 @@ private static boolean downloadFile(String fileURL, String savePath, boolean don @return Whether the operation succeeded or not. */ public static boolean downloadFile(String fileURL, String savePath, String[] follows) { - // Lets you download a file from a specific URL and save it to a location. - // Will follow any button with a text present in the follows list. - int counter = 0; while (counter < follows.length) { if (!downloadFile(fileURL, Config.configFolder + "temp" + File.separator + "page.html")) { @@ -167,36 +195,38 @@ public static boolean downloadFile(String fileURL, String savePath, String[] fol return downloadFile(fileURL, savePath); } - /** - * Disables the SSL certificate checking for new instances of {@link HttpsURLConnection} This has been created to - * aid testing on a local box, not for use on production. - */ - private static void disableSSLCertificateChecking() { - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - @Override - public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { - // Not implemented - } - - @Override - public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { - // Not implemented - } - } }; + /** + Enables or disables the TLS/SSL certificate checks of future {@link HttpURLConnection} instances - try { - SSLContext sc = SSLContext.getInstance("TLS"); + @param enableChecks Whether to enable or disable the TLS/SSL certificate checks + */ + private static void toggleHTTPSCertificateChecks(boolean enableChecks) { + if (enableChecks) HttpsURLConnection.setDefaultSSLSocketFactory(DEFAULT_HTTPS_CERTIFICATES); + else { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) {} + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) {} + } + }; - sc.init(null, trustAllCerts, new java.security.SecureRandom()); + try { + SSLContext context = SSLContext.getInstance("TLS"); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - } catch (KeyManagementException | NoSuchAlgorithmException e) { - e.printStackTrace(); + context.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); + } + catch (KeyManagementException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } } - checkSSLCertificates = false; } } diff --git a/versionData.txt b/versionData.txt index 7aba590..68c946d 100644 --- a/versionData.txt +++ b/versionData.txt @@ -1,3 +1,3 @@ -version: 2.6 -fileName: mcinstanceloader-2.6.jar -url: https://github.com/HRudyPlayZ/MCInstanceLoader/releases/download/1.7.10-2.6/mcinstanceloader-2.6.jar +version: 2.7 +fileName: mcinstanceloader-2.7.jar +url: https://github.com/HRudyPlayZ/MCInstanceLoader/releases/download/1.7.10-2.7/mcinstanceloader-2.7.jar