Skip to content

Commit

Permalink
Pushed the final changes for 2.7
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
HRudyPlayZ committed Dec 2, 2023
1 parent 87175b1 commit 5d92b01
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 80 deletions.
19 changes: 10 additions & 9 deletions src/main/java/com/hrudyplayz/mcinstanceloader/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -19,17 +20,17 @@ 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;

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";
Expand All @@ -50,20 +51,20 @@ 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;

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.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -359,7 +359,7 @@ public boolean getModrinthData() {
break;
}
}
if (file.length() < 1) return false;
if (file.isEmpty()) return false;

String[] splitted;

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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();
}
}

Expand Down Expand Up @@ -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);
}

Expand Down
142 changes: 86 additions & 56 deletions src/main/java/com/hrudyplayz/mcinstanceloader/utils/WebHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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")) {
Expand Down Expand Up @@ -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;
}
}
6 changes: 3 additions & 3 deletions versionData.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5d92b01

Please sign in to comment.